From 22519ee19c8bf3367e72fd2e753bdc3d6e9bd6fb Mon Sep 17 00:00:00 2001 From: Wouter De Troyer Date: Thu, 9 Nov 2023 12:15:03 +0100 Subject: [PATCH 1/5] add support for the settings api call --- dynatrace/environment_v2/schemas.py | 1 + dynatrace/environment_v2/settings.py | 89 ++++++++++++++++++++++++++++ dynatrace/main.py | 3 + 3 files changed, 93 insertions(+) create mode 100644 dynatrace/environment_v2/settings.py diff --git a/dynatrace/environment_v2/schemas.py b/dynatrace/environment_v2/schemas.py index b126d78..fd254c5 100644 --- a/dynatrace/environment_v2/schemas.py +++ b/dynatrace/environment_v2/schemas.py @@ -49,3 +49,4 @@ def _create_from_raw_data(self, raw_element: Dict[str, Any]): def to_json(self) -> Dict[str, Any]: return {"name": self.name, "id": self.id} + diff --git a/dynatrace/environment_v2/settings.py b/dynatrace/environment_v2/settings.py new file mode 100644 index 0000000..f21bafd --- /dev/null +++ b/dynatrace/environment_v2/settings.py @@ -0,0 +1,89 @@ +from datetime import datetime +from dynatrace.dynatrace_object import DynatraceObject +from typing import List, Optional, Union, Dict, Any + +from dynatrace.http_client import HttpClient +from dynatrace.pagination import PaginatedList +import json +import logging +from http.client import HTTPConnection # py3 + + +class SettingService: + ENDPOINT = "/api/v2/settings/objects" + + def __init__(self, http_client: HttpClient): + self.__http_client = http_client + + def list(self,schema_id: Optional[str] = None, + scope: Optional[str] = None,external_ids: Optional[str] = None, + fields: Optional[str] = None, + filter:Optional[str] = None, sort:Optional[str] = None, page_size:Optional[str] = None) -> PaginatedList["DynatraceObject"]: + """Lists settings + + :return: a list of settings with details + """ + params = { + "schemaIds": schema_id, + "scope": scope, + "fields": fields, + "externalIds": external_ids, + "filter": filter, + "sort": sort, + "pageSize": page_size, + } + return PaginatedList(Settings, self.__http_client, target_url=self.ENDPOINT, list_item="items", target_params=params) + + def post(self,external_id,object_id,schema_id,schema_version,scope, value,validate_only): + + params = { + "validate_only": validate_only, + } + body =[ { + "externalId" : external_id, + "objectId": object_id, + "schemaId": schema_id, + "schemaVersion": schema_version, + "scope": scope, + "value" : value + + }] + + response = self.__http_client.make_request(self.ENDPOINT,params=body, method="POST",query_params=params).json() + + + + def get(self, object_id: str): + """Gets parameters of specified settings object + + :param object_id: the ID of the network zone + :return: a Settings object + """ + response = self.__http_client.make_request(f"{self.ENDPOINT}/{object_id}").json() + return Settings(raw_element=response) + + def update(self, object_id: str, ): + """Updates an existing network zone or creates a new one + + :param networkzone_id: the ID of the network zone, if none exists, will create + :param alternate_zones: optional list of text body of alternative network zones + :param description: optional text body for short description of network zone + :return: HTTP response + """ + return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", params=params, method="PUT") + + def delete(self, object_id: str): + """Deletes the specified object + + :param object_id: the ID of the network zone + :return: HTTP response + """ + return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", method="DELETE") + +class Settings(DynatraceObject): + value = None + def _create_from_raw_data(self, raw_element: Dict[str, Any]): + # Mandatory + self.objectId: str = raw_element["objectId"] + def to_json(self): + return self.value \ No newline at end of file diff --git a/dynatrace/main.py b/dynatrace/main.py index 29d213f..1dd0476 100644 --- a/dynatrace/main.py +++ b/dynatrace/main.py @@ -56,6 +56,8 @@ from dynatrace.environment_v2.problems import ProblemService from dynatrace.environment_v2.service_level_objectives import SloService from dynatrace.environment_v2.logs import LogService +from dynatrace.environment_v2.settings import SettingService + from dynatrace.http_client import HttpClient @@ -118,6 +120,7 @@ def __init__( self.oneagents_config_environment: OneAgentEnvironmentWideConfigService = OneAgentEnvironmentWideConfigService(self.__http_client) self.oneagents_config_host: OneAgentOnAHostConfigService = OneAgentOnAHostConfigService(self.__http_client) self.oneagents_config_hostgroup: OneAgentInAHostGroupService = OneAgentInAHostGroupService(self.__http_client) + self.settings : SettingService = SettingService(self.__http_client) self.plugins: PluginService = PluginService(self.__http_client) self.problems: ProblemService = ProblemService(self.__http_client) self.slos: SloService = SloService(self.__http_client) From 12b1c4e7d14645a12939a0ea513fb7770262dd9c Mon Sep 17 00:00:00 2001 From: Wouter Evolane Date: Wed, 15 Nov 2023 11:54:52 +0100 Subject: [PATCH 2/5] bugfix: added params to settings put --- dynatrace/environment_v2/settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dynatrace/environment_v2/settings.py b/dynatrace/environment_v2/settings.py index f21bafd..25abc9a 100644 --- a/dynatrace/environment_v2/settings.py +++ b/dynatrace/environment_v2/settings.py @@ -62,7 +62,7 @@ def get(self, object_id: str): response = self.__http_client.make_request(f"{self.ENDPOINT}/{object_id}").json() return Settings(raw_element=response) - def update(self, object_id: str, ): + def update(self, object_id: str, value): """Updates an existing network zone or creates a new one :param networkzone_id: the ID of the network zone, if none exists, will create @@ -70,7 +70,7 @@ def update(self, object_id: str, ): :param description: optional text body for short description of network zone :return: HTTP response """ - return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", params=params, method="PUT") + return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", params=value, method="PUT") def delete(self, object_id: str): """Deletes the specified object @@ -85,5 +85,6 @@ class Settings(DynatraceObject): def _create_from_raw_data(self, raw_element: Dict[str, Any]): # Mandatory self.objectId: str = raw_element["objectId"] + self.value: str = raw_element["value"] def to_json(self): return self.value \ No newline at end of file From 6a6e905aeeef09892340f9c71c8831c3190c008e Mon Sep 17 00:00:00 2001 From: Wouter Evolane Date: Tue, 28 Nov 2023 14:12:08 +0100 Subject: [PATCH 3/5] * bugfix: add response to post return * add unittests * add mock data --- dynatrace/environment_v2/settings.py | 2 +- test/environment_v2/test_settings.py | 37 +++++++++++++++++++ ...i_v2_settings_objects_804a7afa1d2a354.json | 31 ++++++++++++++++ ...zMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t.json | 24 ++++++++++++ ...i_v2_settings_objects_a47ba18512efbe8.json | 6 +++ ...ings_objects_unittest_6ea3a764200250e.json | 4 ++ 6 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 test/environment_v2/test_settings.py create mode 100644 test/mock_data/GET_api_v2_settings_objects_804a7afa1d2a354.json create mode 100644 test/mock_data/GET_api_v2_settings_objects_vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t.json create mode 100644 test/mock_data/POST_api_v2_settings_objects_a47ba18512efbe8.json create mode 100644 test/mock_data/PUT_api_v2_settings_objects_unittest_6ea3a764200250e.json diff --git a/dynatrace/environment_v2/settings.py b/dynatrace/environment_v2/settings.py index 25abc9a..e95d825 100644 --- a/dynatrace/environment_v2/settings.py +++ b/dynatrace/environment_v2/settings.py @@ -50,7 +50,7 @@ def post(self,external_id,object_id,schema_id,schema_version,scope, value,valida }] response = self.__http_client.make_request(self.ENDPOINT,params=body, method="POST",query_params=params).json() - + return response def get(self, object_id: str): diff --git a/test/environment_v2/test_settings.py b/test/environment_v2/test_settings.py new file mode 100644 index 0000000..d695c1b --- /dev/null +++ b/test/environment_v2/test_settings.py @@ -0,0 +1,37 @@ +from datetime import datetime + +import dynatrace.environment_v2.settings as st +from dynatrace import Dynatrace +from dynatrace.pagination import PaginatedList +payload = {"additionalInformation": [], + "contactDetails":[{"email": 'unittest@contoso.com', "integrationType": "EMAIL"}], + "description": 'unittest', + "identifier":'unittest', + "links": [], + "name": 'unittest', + "responsibilities": {"development": False, + "infrastructure": False, + "lineOfBusiness": True, + "operations": False, + "security": False}, + "supplementaryIdentifiers": [] } +def test_list(dt: Dynatrace): + settings = dt.settings.list(schema_id="builtin:ownership.teams") + assert isinstance(settings, PaginatedList) + assert len(list(settings)) == 1 + assert all(isinstance(s, st.Settings) for s in settings) + +def test_get(dt: Dynatrace): + setting = dt.settings.get(object_id="vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t") + assert isinstance(setting, st.Settings) + +def test_post(dt: Dynatrace): + + response = dt.settings.post(external_id='unittest',object_id='unittest',schema_id="builtin:ownership.teams",schema_version="1.0.6",scope="environment", value=payload,validate_only=False) + assert response[0].get("code") == 200 + assert response[0].get("code") is not None + +def test_put(dt: Dynatrace): + payload["identifier"] = "unittestupdate" + response = dt.settings.update("unittest",payload) + print(response) \ No newline at end of file diff --git a/test/mock_data/GET_api_v2_settings_objects_804a7afa1d2a354.json b/test/mock_data/GET_api_v2_settings_objects_804a7afa1d2a354.json new file mode 100644 index 0000000..a9548af --- /dev/null +++ b/test/mock_data/GET_api_v2_settings_objects_804a7afa1d2a354.json @@ -0,0 +1,31 @@ +{ + "items": [ + + { + "objectId": "vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t", + "value": { + "name": "user", + "description": "user", + "identifier": "user", + "supplementaryIdentifiers": [], + "responsibilities": { + "development": false, + "security": false, + "operations": false, + "infrastructure": true, + "lineOfBusiness": false + }, + "contactDetails": [ + { + "integrationType": "EMAIL", + "email": "test@contoso.com" + } + ], + "links": [], + "additionalInformation": [] + } + } + ], + "totalCount": 1, + "pageSize": 500 +} \ No newline at end of file diff --git a/test/mock_data/GET_api_v2_settings_objects_vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t.json b/test/mock_data/GET_api_v2_settings_objects_vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t.json new file mode 100644 index 0000000..ce6aebb --- /dev/null +++ b/test/mock_data/GET_api_v2_settings_objects_vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t.json @@ -0,0 +1,24 @@ +{ + "objectId": "vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t", + "value": { + "name": "user", + "description": "user", + "identifier": "user", + "supplementaryIdentifiers": [], + "responsibilities": { + "development": false, + "security": false, + "operations": false, + "infrastructure": true, + "lineOfBusiness": false + }, + "contactDetails": [ + { + "integrationType": "EMAIL", + "email": "test@contoso.com" + } + ], + "links": [], + "additionalInformation": [] + } +} \ No newline at end of file diff --git a/test/mock_data/POST_api_v2_settings_objects_a47ba18512efbe8.json b/test/mock_data/POST_api_v2_settings_objects_a47ba18512efbe8.json new file mode 100644 index 0000000..eea4685 --- /dev/null +++ b/test/mock_data/POST_api_v2_settings_objects_a47ba18512efbe8.json @@ -0,0 +1,6 @@ +[ + { + "code": 200, + "objectId": "vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJDVhNjk1MzViLWQ3NDYtMzVmYi05MDdjLTM2ODNkMmMyNWQzOb7vVN4V2t6t" + } +] \ No newline at end of file diff --git a/test/mock_data/PUT_api_v2_settings_objects_unittest_6ea3a764200250e.json b/test/mock_data/PUT_api_v2_settings_objects_unittest_6ea3a764200250e.json new file mode 100644 index 0000000..d8fcd3c --- /dev/null +++ b/test/mock_data/PUT_api_v2_settings_objects_unittest_6ea3a764200250e.json @@ -0,0 +1,4 @@ +{ + "code": 200, + "objectId": "vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJDVhNjk1MzViLWQ3NDYtMzVmYi05MDdjLTM2ODNkMmMyNWQzOb7vVN4V2t6t" +} \ No newline at end of file From 9127ea5141f09a0f17fb2761173417e48b81ee22 Mon Sep 17 00:00:00 2001 From: Wouter Evolane Date: Tue, 28 Nov 2023 14:29:11 +0100 Subject: [PATCH 4/5] updated doc --- dynatrace/environment_v2/settings.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/dynatrace/environment_v2/settings.py b/dynatrace/environment_v2/settings.py index e95d825..e043f2d 100644 --- a/dynatrace/environment_v2/settings.py +++ b/dynatrace/environment_v2/settings.py @@ -35,7 +35,17 @@ def list(self,schema_id: Optional[str] = None, return PaginatedList(Settings, self.__http_client, target_url=self.ENDPOINT, list_item="items", target_params=params) def post(self,external_id,object_id,schema_id,schema_version,scope, value,validate_only): - + """Creates a new settings object + + :param external_id: External identifier for the object being created + :param object_id: The ID of the settings object that should be replaced. Only applicable if an external identifier + :param object_id: the ID of the object + :param schema_id: The schema on which the object is based + :param schema_version: The version of the schema on which the object is based. + :param scope The scope that the object targets. For more details, please see Dynatrace Documentation. + :param value The value of the setting. + :return: a Settings object + """ params = { "validate_only": validate_only, } @@ -56,26 +66,23 @@ def post(self,external_id,object_id,schema_id,schema_version,scope, value,valida def get(self, object_id: str): """Gets parameters of specified settings object - :param object_id: the ID of the network zone + :param object_id: the ID of the object :return: a Settings object """ response = self.__http_client.make_request(f"{self.ENDPOINT}/{object_id}").json() return Settings(raw_element=response) def update(self, object_id: str, value): - """Updates an existing network zone or creates a new one + """Updates an existing settings object + :param object_id: the ID of the object - :param networkzone_id: the ID of the network zone, if none exists, will create - :param alternate_zones: optional list of text body of alternative network zones - :param description: optional text body for short description of network zone - :return: HTTP response """ return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", params=value, method="PUT") def delete(self, object_id: str): """Deletes the specified object - :param object_id: the ID of the network zone + :param object_id: the ID of the object :return: HTTP response """ return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", method="DELETE") From a9b6ae3d06af726310bfc10f4dea510ac2082015 Mon Sep 17 00:00:00 2001 From: Wouter Evolane Date: Wed, 29 Nov 2023 08:35:37 +0100 Subject: [PATCH 5/5] * removed unused imports * renamed methods * removed unnecessary methods * updated tests for method rename --- dynatrace/environment_v2/settings.py | 24 +++++++++--------------- test/environment_v2/test_settings.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/dynatrace/environment_v2/settings.py b/dynatrace/environment_v2/settings.py index e043f2d..bdd78e4 100644 --- a/dynatrace/environment_v2/settings.py +++ b/dynatrace/environment_v2/settings.py @@ -1,12 +1,8 @@ -from datetime import datetime -from dynatrace.dynatrace_object import DynatraceObject -from typing import List, Optional, Union, Dict, Any +from typing import Optional, Dict, Any +from dynatrace.dynatrace_object import DynatraceObject from dynatrace.http_client import HttpClient from dynatrace.pagination import PaginatedList -import json -import logging -from http.client import HTTPConnection # py3 class SettingService: @@ -15,7 +11,7 @@ class SettingService: def __init__(self, http_client: HttpClient): self.__http_client = http_client - def list(self,schema_id: Optional[str] = None, + def list_objects(self,schema_id: Optional[str] = None, scope: Optional[str] = None,external_ids: Optional[str] = None, fields: Optional[str] = None, filter:Optional[str] = None, sort:Optional[str] = None, page_size:Optional[str] = None) -> PaginatedList["DynatraceObject"]: @@ -34,7 +30,7 @@ def list(self,schema_id: Optional[str] = None, } return PaginatedList(Settings, self.__http_client, target_url=self.ENDPOINT, list_item="items", target_params=params) - def post(self,external_id,object_id,schema_id,schema_version,scope, value,validate_only): + def create_object(self,external_id,object_id,schema_id,schema_version,scope, value,validate_only): """Creates a new settings object :param external_id: External identifier for the object being created @@ -42,7 +38,7 @@ def post(self,external_id,object_id,schema_id,schema_version,scope, value,valida :param object_id: the ID of the object :param schema_id: The schema on which the object is based :param schema_version: The version of the schema on which the object is based. - :param scope The scope that the object targets. For more details, please see Dynatrace Documentation. + :param scope The scope that the object targets. For more details, please see Dynatrace Documentation. :param value The value of the setting. :return: a Settings object """ @@ -63,7 +59,7 @@ def post(self,external_id,object_id,schema_id,schema_version,scope, value,valida return response - def get(self, object_id: str): + def get_object(self, object_id: str): """Gets parameters of specified settings object :param object_id: the ID of the object @@ -72,14 +68,15 @@ def get(self, object_id: str): response = self.__http_client.make_request(f"{self.ENDPOINT}/{object_id}").json() return Settings(raw_element=response) - def update(self, object_id: str, value): + def update_object(self, object_id: str, value): """Updates an existing settings object + :param object_id: the ID of the object """ return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", params=value, method="PUT") - def delete(self, object_id: str): + def delete_object(self, object_id: str): """Deletes the specified object :param object_id: the ID of the object @@ -88,10 +85,7 @@ def delete(self, object_id: str): return self.__http_client.make_request(path=f"{self.ENDPOINT}/{object_id}", method="DELETE") class Settings(DynatraceObject): - value = None def _create_from_raw_data(self, raw_element: Dict[str, Any]): # Mandatory self.objectId: str = raw_element["objectId"] self.value: str = raw_element["value"] - def to_json(self): - return self.value \ No newline at end of file diff --git a/test/environment_v2/test_settings.py b/test/environment_v2/test_settings.py index d695c1b..7f8f00a 100644 --- a/test/environment_v2/test_settings.py +++ b/test/environment_v2/test_settings.py @@ -15,23 +15,23 @@ "operations": False, "security": False}, "supplementaryIdentifiers": [] } -def test_list(dt: Dynatrace): - settings = dt.settings.list(schema_id="builtin:ownership.teams") +def test_list_objects(dt: Dynatrace): + settings = dt.settings.list_objects(schema_id="builtin:ownership.teams") assert isinstance(settings, PaginatedList) assert len(list(settings)) == 1 assert all(isinstance(s, st.Settings) for s in settings) -def test_get(dt: Dynatrace): - setting = dt.settings.get(object_id="vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t") +def test_get_object(dt: Dynatrace): + setting = dt.settings.get_object(object_id="vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJGVjN2UyNTdhLWM5MTktM2YzMC05NWFiLTliMzNkMmQwZGRkY77vVN4V2t6t") assert isinstance(setting, st.Settings) -def test_post(dt: Dynatrace): +def test_post_object(dt: Dynatrace): - response = dt.settings.post(external_id='unittest',object_id='unittest',schema_id="builtin:ownership.teams",schema_version="1.0.6",scope="environment", value=payload,validate_only=False) + response = dt.settings.create_object(external_id='unittest',object_id='unittest',schema_id="builtin:ownership.teams",schema_version="1.0.6",scope="environment", value=payload,validate_only=False) assert response[0].get("code") == 200 assert response[0].get("code") is not None -def test_put(dt: Dynatrace): +def test_put_object(dt: Dynatrace): payload["identifier"] = "unittestupdate" - response = dt.settings.update("unittest",payload) + response = dt.settings.update_object("unittest",payload) print(response) \ No newline at end of file