diff --git a/poetry.lock b/poetry.lock index 1180903e..9215c715 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "certifi" -version = "2020.6.20" +version = "2020.11.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -85,14 +85,11 @@ optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" -[package.dependencies.importlib-metadata] -version = "*" -python = "<3.8" - [[package]] name = "idna" version = "2.10" @@ -108,15 +105,14 @@ description = "Read metadata from Python packages" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -marker = "python_version < \"3.8\"" + +[package.dependencies] +zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] -[package.dependencies] -zipp = ">=0.5" - [[package]] name = "mamba" version = "0.11.1" @@ -182,21 +178,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "requests" -version = "2.24.0" +version = "2.25.0" description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - [package.dependencies] certifi = ">=2017.4.17" chardet = ">=3.0.2,<4" idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] name = "requests-toolbelt" @@ -224,7 +220,6 @@ description = "TatSu takes a grammar in a variation of EBNF as input, and output category = "main" optional = false python-versions = "*" -marker = "python_version < \"3.8\"" [package.extras] future-regex = ["regex"] @@ -236,14 +231,13 @@ description = "TatSu takes a grammar in a variation of EBNF as input, and output category = "main" optional = false python-versions = ">=3.8" -marker = "python_version >= \"3.8\" and python_version < \"4.0\"" [package.extras] future-regex = ["regex"] [[package]] name = "urllib3" -version = "1.25.11" +version = "1.26.2" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -256,19 +250,18 @@ socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.3.1" +version = "3.4.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.6" -marker = "python_version < \"3.8\"" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.6" content-hash = "fb28d0db08cb771d219781fbfa925110552065fc4f3349c061fe9dc2aa894f4c" @@ -277,8 +270,8 @@ args = [ {file = "args-0.1.0.tar.gz", hash = "sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814"}, ] certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, + {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, + {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -381,8 +374,8 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, + {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, + {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, ] requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, @@ -399,10 +392,10 @@ tatsu = [ {file = "TatSu-5.5.0.zip", hash = "sha256:0adbf7189a8c4f9a882b442f7b8ed6c6ab3baae37057db0e96b6888daacffad0"}, ] urllib3 = [ - {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, - {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, ] zipp = [ - {file = "zipp-3.3.1-py3-none-any.whl", hash = "sha256:16522f69653f0d67be90e8baa4a46d66389145b734345d68a257da53df670903"}, - {file = "zipp-3.3.1.tar.gz", hash = "sha256:c1532a8030c32fd52ff6a288d855fe7adef5823ba1d26a29a68fd6314aa72baa"}, + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, ] diff --git a/sdcclient/monitor/_events_v2.py b/sdcclient/monitor/_events_v2.py index 8d071225..274e39e5 100644 --- a/sdcclient/monitor/_events_v2.py +++ b/sdcclient/monitor/_events_v2.py @@ -1,4 +1,5 @@ import json +from datetime import datetime from sdcclient._common import _SdcCommon @@ -8,7 +9,8 @@ def __init__(self, token="", sdc_url='https://app.sysdigcloud.com', ssl_verify=T super().__init__(token, sdc_url, ssl_verify, custom_headers) self.product = "SDC" - def get_events(self, name=None, category=None, direction='before', status=None, limit=100, pivot=None): + def get_events(self, name=None, category=None, direction='before', status=None, limit=100, pivot=None, from_s=None, + to_s=None): '''**Description** Returns the list of Sysdig Monitor events. @@ -19,6 +21,8 @@ def get_events(self, name=None, category=None, direction='before', status=None, - **status**: status of the event as list. Default: ['triggered', 'resolved', 'acknowledged', 'unacknowledged'] - **limit**: max number of events to retrieve. Default: 100. - **pivot**: event id to use as pivot. Default: None. + - **from_s**: the unix timestamp in milliseconds or datetime object for the beginning of the events. Default: None. + - **to_s**: the unix timestamp in milliseconds or datetime object for the end of the events. Default: None. **Success Return Value** A dictionary containing the list of events. @@ -46,6 +50,18 @@ def get_events(self, name=None, category=None, direction='before', status=None, if direction not in ["before", "after"]: return False, "Invalid direction '{}', must be either 'before' or 'after'".format(direction) + if from_s is not None and isinstance(from_s, datetime): + from_s = int(from_s.timestamp() * 1000) + if to_s is not None and isinstance(to_s, datetime): + to_s = int(to_s.timestamp() * 1000) + + if to_s is None and from_s is not None or from_s is None and to_s is not None: + return False, "only one of 'from_s' or 'to_s' has been specified, both are required when filtering by time" + + if to_s is not None and from_s is not None: + if int(to_s) < int(from_s): + return False, "'from_s' must be lower than 'to_s'" + options = { 'alertStatus': status, 'category': ','.join(category), @@ -56,6 +72,8 @@ def get_events(self, name=None, category=None, direction='before', status=None, 'limit': str(limit), 'pivot': pivot, 'filter': name, + 'from': from_s, + 'to': to_s, } params = {k: v for k, v in options.items() if v is not None} res = self.http.get(self.url + '/api/v2/events/', headers=self.hdrs, params=params, verify=self.ssl_verify) @@ -78,7 +96,7 @@ def delete_event(self, event): return [False, "Invalid event format"] res = self.http.delete(self.url + '/api/v2/events/' + str(event['id']), headers=self.hdrs, - verify=self.ssl_verify) + verify=self.ssl_verify) if not self._checkResponse(res): return [False, self.lasterr] return [True, None] @@ -112,5 +130,5 @@ def post_event(self, name, description=None, severity=None, event_filter=None, t 'event': {k: v for k, v in options.items() if v is not None} } res = self.http.post(self.url + '/api/v2/events/', headers=self.hdrs, data=json.dumps(edata), - verify=self.ssl_verify) + verify=self.ssl_verify) return self._request_result(res) diff --git a/specs/monitor/events_v2_spec.py b/specs/monitor/events_v2_spec.py index c8744af7..42dbbc2e 100644 --- a/specs/monitor/events_v2_spec.py +++ b/specs/monitor/events_v2_spec.py @@ -1,9 +1,9 @@ import os import time +from datetime import datetime, timedelta -from expects import expect, have_key, contain, have_keys, be_empty, equal, be_false -from expects.matchers.built_in import have_len -from mamba import it, before, description +from expects import expect, have_key, contain, have_keys, be_empty, equal, be_false, be_above_or_equal, have_len +from mamba import it, before, context, description from sdcclient.monitor import EventsClientV2 from specs import be_successful_api_call @@ -83,6 +83,35 @@ expect((ok, res)).to(be_successful_api_call) expect(res).to(have_key("events", have_len(1))) + with it("is able to retrieve the events from the last day"): + to_s = datetime.now() + from_s = to_s - timedelta(weeks=2) + ok, res = self.client.get_events(from_s=from_s, to_s=to_s) + + expect((ok, res)).to(be_successful_api_call) + expect(res).to(have_key("events", have_len(be_above_or_equal(1)))) + + with context("but the from and to parameters are incorrectly specified"): + with it("returns an error if any of the parameters is specified but not the other"): + t = datetime.now() - timedelta(weeks=2) + ok1, res1 = self.client.get_events(from_s=t) + ok2, res2 = self.client.get_events(to_s=t) + + expect((ok1, res1)).not_to(be_successful_api_call) + expect((ok2, res2)).not_to(be_successful_api_call) + expect(res1).to(equal("only one of 'from_s' or 'to_s' has been specified, " + "both are required when filtering by time")) + expect(res2).to(equal("only one of 'from_s' or 'to_s' has been specified, " + "both are required when filtering by time")) + + with it("returns an error if they are specified in the wrong order"): + to_s = datetime.now() + from_s = to_s - timedelta(weeks=2) + ok, res = self.client.get_events(from_s=to_s, to_s=from_s) + + expect((ok, res)).not_to(be_successful_api_call) + expect(res).to(equal("'from_s' must be lower than 'to_s'")) + with it("is able to remove the event from the feed"): time.sleep(3) # Wait for the event to appear in the feed _, res = self.client.get_events(category=["custom"])