diff --git a/.gitignore b/.gitignore index a5103ff..b43a539 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__/ # Distribution / packaging .Python +.idea env/ build/ develop-eggs/ diff --git a/examples/data/cma_payload.json b/examples/data/cma_payload.json new file mode 100644 index 0000000..7609140 --- /dev/null +++ b/examples/data/cma_payload.json @@ -0,0 +1,75 @@ +{ + "params": { + "mlRole": "cma_role", + "mlUser": "cma-user", + "mlUserPassword": "password", + "mlForest": "cma_forest", + "mlSchemaDatabase": "cma_schema_database", + "mlDatabase": "cma_database", + "mlAppServer": "cma_appserver", + "mlGroup": "cma_group", + "mlAuth": "digest", + "mlPort": "8005" + + }, + "config": [{ + "role": [{ + "role-name": "%%mlRole%%", + "description": "A role that allows the user to read and write to rest", + "role": [ + "rest-reader", + "rest-extension-user", + "rest-writer" + ], + "privilege": [{ + "privilege-name": "xdmp:eval", + "action": "http://marklogic.com/xdmp/privileges/xdmp-eval", + "kind": "execute" + }] + } + ], + "user": [{ + "user-name": "%%mlUser%%", + "description": "A user with the rest role with eval", + "password": "%%mlUserPassword%%", + "role": ["%%mlRole%%"] + }], + "forest":[ + {"forest-name":"%%mlForest%%"} + ], + "database": [ + { + "database-name": "%%mlSchemaDatabase%%" + }, + { + "database-name": "%%mlDatabase%%", + "range-element-index": [], + "triggers-database": "%%mlSchemaDatabase%%", + "triple-index": true, + "collection-lexicon": true, + "uri-lexicon": true, + "forest":["%%mlForest%%"] + } + ], + "group":[ + { + "group-name":"%%mlGroup%%" + } + ], + "server": [{ + "server-name": "%%mlAppServer%%", + "server-type": "http", + "root": "/", + "group-name": "%%mlGroup%%", + "port": "%%mlPort%%", + "modules-database": "%%mlDatabase%%", + "content-database": "%%mlDatabase%%", + "authentication": "%%mlAuth%%", + "default-error-format": "json", + "error-handler": "/MarkLogic/rest-api/error-handler.xqy", + "url-rewriter": "/MarkLogic/rest-api/rewriter.xml", + "rewrite-resolves-globally": true + } + ] + }] +} \ No newline at end of file diff --git a/examples/data/cma_payload.xml b/examples/data/cma_payload.xml new file mode 100644 index 0000000..7a8d19b --- /dev/null +++ b/examples/data/cma_payload.xml @@ -0,0 +1,121 @@ + + + + + + mlAppServer + cma_appserver + + + mlAuth + digest + + + mlDatabase + cma_database + + + mlForest + cma_forest + + + mlGroup + cma_group + + + mlAppServer + cma_appserver + + + mlPort + 8005 + + + mlRole + cma_role + + + mlSchemaDatabase + cma_schema_database + + + mlUser + cma-user + + + mlUserPassword + password + + + + + %%mlRole%% + A role that allows the user to read and write to rest + + + http://marklogic.com/xdmp/privileges/xdmp-eval + execute + xdmp:eval + + + + rest-reader + rest-extension-user + rest-writer + + + + + + %%mlUser%% + A user with the rest role with eval + %%mlUserPassword%% + + %%mlRole%% + + + + + + %%mlForest%% + + + + + %%mlSchemaDatabase%% + + + %%mlDatabase%% + true + %%mlSchemaDatabase%% + true + true + + %%mlForest%% + + + + + + %%mlGroup%% + + + + + %%mlAppServer%% + http + %%mlAuth%% + %%mlDatabase%% + json + /MarkLogic/rest-api/error-handler.xqy + %%mlGroup%% + %%mlDatabase%% + %%mlPort%% + true + / + /MarkLogic/rest-api/rewriter.xml + + + + + \ No newline at end of file diff --git a/marklogic/client/cma.py b/marklogic/client/cma.py new file mode 100644 index 0000000..03e4fe3 --- /dev/null +++ b/marklogic/client/cma.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2015 MarkLogic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0# +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Support the CMA V3 endpoint +""" + +import json, logging +import requests +from marklogic.exceptions import * + +class CMA: + """ + CMA class provides methods to generate and apply application configuration. + Sample payloads in examples/data + """ + + _cma_version = "v3" + _min_support_version = 9.0-5 + + def __init__(self, connection, params={}, scenario=None, format="json"): + self.connection = connection + self.params = params + self.scenario = scenario + self.format = format + self.logger = logging.getLogger("marklogic.client.cma") + + @property + def scenario(self): + """ + Scenario property for generate config(ex. ha-local) + :return:scenario as string + """ + return self.__scenario + + @scenario.setter + def scenario(self, scenario): + self.__scenario = scenario + + # + @property + def params(self): + """ + Params for generate and apply config + ref: http://docs.marklogic.com/REST/configuration-management-api + :return:params as dict + """ + return self.__params + + @params.setter + def params(self, params): + if isinstance(params, dict): + self.__params = params + else: + raise Exception("Params should be a dict") + + @property + def format(self): + return self.__format + + @format.setter + def format(self, format): + self.__format = format + + def generate_config(self): + """ + Generate configuration for different scenarios + :return configuration as string(format : xml/json) or binary(format:zip) + """ + + cma_url = "http://{0}:{1}/{2}/{3}" \ + .format(self.connection.host, self.connection.management_port, self.connection.root, self._cma_version) + + cma_url = (cma_url + "?format={}").format(self.format) + + if self.scenario: + cma_url = (cma_url+"&scenario={}").format(self.scenario) + if self.params: + cma_url = (cma_url+"¶ms={}").format(json.dumps(self.params)) + + print(cma_url) + + response = requests.get(cma_url, auth=self.connection.auth) + + if response.status_code == 404: + return None + elif response.status_code == 200: + return response.text + else: + raise UnexpectedAPIResponse(response.text) + + def apply_config(self, config, content_type): + """ + Applies the specified configuration on MarkLogic + :param config: configuration to be applied as string + :param content_type: application/json or application/xml + :return response object + """ + cma_url = "http://{0}:{1}/{2}/{3}" \ + .format(self.connection.host, self.connection.management_port, self.connection.root, self._cma_version) + if self.params: + cma_url = (cma_url+"¶ms={}").format(self.params) + response = requests.post(cma_url, data=config, auth=self.connection.auth, + headers={'content-type': content_type}) + if response.status_code > 299: + raise UnexpectedAPIResponse(response.text) + return response diff --git a/tests/test_cma.py b/tests/test_cma.py new file mode 100644 index 0000000..1c865e3 --- /dev/null +++ b/tests/test_cma.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2015 MarkLogic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0# +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import json +from mlconfig import MLConfig +from marklogic.client.cma import CMA +from marklogic.models import Database + + +class TestCMA(MLConfig): + + def test_generate_config(self): + cma = CMA(self.connection) + cma.format="xml" + config = cma.generate_config() + assert config is not None + assert json.dumps(config)[1] == '<' + cma.format = "json" + config = cma.generate_config() + assert config is not None + assert json.dumps(config)[1] == '{' + + def test_apply_config(self): + cma = CMA(self.connection) + config1 = { + "config": [ + { + "database": [ + { + "database-name": "CMA_Check1" + } + ] + } + ] + } + config2 = "" \ + "CMA_Check2" + cma.apply_config(json.dumps(config1), "application/json") + cma.apply_config(config2, "application/xml") + + validate_db1 = Database.lookup(self.connection, "CMA_Check1") + try: + assert validate_db1 is not None + assert 'CMA_Check1' == validate_db1.database_name() + + finally: + validate_db1.delete(connection=self.connection) + validate_db1 = Database.lookup(self.connection, "CMA_Check1") + assert validate_db1 is None + + validate_db2 = Database.lookup(self.connection, "CMA_Check2") + try: + assert validate_db2 is not None + assert 'CMA_Check2' == validate_db2.database_name() + + finally: + validate_db2.delete(connection=self.connection) + validate_db2 = Database.lookup(self.connection, "CMA_Check2") + assert validate_db2 is None \ No newline at end of file