| # Copyright 2016 Mirantis, Inc. |
| # |
| # 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. |
| from tcp_tests import logger |
| from tcp_tests.managers.execute_commands import ExecuteCommandsMixin |
| |
| LOG = logger.logger |
| |
| |
| class ReclassManager(ExecuteCommandsMixin): |
| """docstring for ReclassManager""" |
| |
| __config = None |
| __underlay = None |
| reclass_tools_cmd = ". venv-reclass-tools/bin/activate; reclass-tools " |
| tgt = "cfg01" # place where the reclass-tools installed |
| |
| def __init__(self, config, underlay): |
| self.__config = config |
| self.__underlay = underlay |
| |
| reclass_node = [node_name |
| for node_name in self.__underlay.node_names() |
| if self.tgt in node_name] |
| self.ssh = self.__underlay.remote(node_name=reclass_node[0]) |
| |
| super(ReclassManager, self).__init__(config=config, underlay=underlay) |
| |
| def check_existence(self, key): |
| """ |
| Returns True if reclass contains that key. |
| :param key: string |
| :return: boolean |
| """ |
| if key in self.ssh.check_call( |
| "{reclass_tools} get-key {key} /srv/salt/reclass/classes". |
| format(reclass_tools=self.reclass_tools_cmd, |
| key=key)): |
| LOG.warning("({}) key already exists in reclass".format(key)) |
| return True |
| return False |
| |
| def add_key(self, key, value, short_path): |
| """ |
| Shows alert if key exists |
| |
| :param key: string, parameters which will be added or updated |
| :param value: value of key |
| :param short_path: path to reclass yaml file. |
| It takes into account default path where the reclass locates. |
| May look like cluster/*/cicd/control/leader.yml |
| :return: None |
| """ |
| |
| value = str(value).replace('"', "'") |
| # let's escape $ symbol for bash-like command |
| value = str(value).replace("$", r"\$") |
| |
| # value can contain a space symbol. So value should be in quotes |
| cmd = "{reclass_tools} add-key {key} \"{value}\" \ |
| /srv/salt/reclass/classes/{path}".format( |
| reclass_tools=self.reclass_tools_cmd, |
| key=key, |
| value=value, |
| path=short_path) |
| LOG.info("Add key to reclass: \n {cmd} ".format(cmd=cmd)) |
| self.ssh.check_call(cmd) |
| |
| def get_key(self, key, file_name): |
| """Find a key in a YAML |
| |
| :param key: string, parameter to add |
| :param file_name: name of YAML file to find a key |
| :return: str, key if found |
| """ |
| LOG.info("Try to get '{key}' key from '{file}' file".format( |
| file=file_name, |
| key=key |
| )) |
| request_key = self.ssh.check_call( |
| "{reclass_tools} get-key {key} /srv/salt/reclass/*/{file_name}". |
| format(reclass_tools=self.reclass_tools_cmd, |
| key=key, |
| file_name=file_name))['stdout'] |
| |
| # Reclass-tools returns result to stdout, so we get it as |
| # ['\n', |
| # '---\n', |
| # '# Found parameters._param.jenkins_pipelines_branch in \ |
| # /srv/salt/reclass/classes/cluster/../infra/init.yml\n', |
| # 'release/proposed/2019.2.0\n', |
| # '...\n', |
| # '\n'] |
| # So we have no chance to get value without dirty code like `stdout[3]` |
| |
| LOG.info("Raw output from reclass.get_key {}".format(request_key)) |
| if len(request_key) < 4: |
| print("Can't find {key} in {file_name}. Got stdout {stdout}". |
| format(key=key, |
| file_name=file_name, |
| stdout=request_key)) |
| return None |
| value = request_key[3].strip('\n') |
| LOG.info("From reclass.get_key {}".format(value)) |
| return value |
| |
| def add_bool_key(self, key, value, short_path): |
| """ |
| Shows alert if key exists |
| |
| :param key: string, parameters which will be added or updated |
| :param value: value of key |
| :param short_path: path to reclass yaml file. |
| It takes into account default path where the reclass locates. |
| May look like cluster/*/cicd/control/leader.yml |
| :return: None |
| """ |
| self.check_existence(key) |
| self.ssh.check_call( |
| "{reclass_tools} add-bool-key {key} {value} \ |
| /srv/salt/reclass/classes/{path}".format( |
| reclass_tools=self.reclass_tools_cmd, |
| key=key, |
| value=value, |
| path=short_path |
| ), raise_on_err=False) |
| |
| def add_class(self, value, short_path): |
| """ |
| Shows warning if class exists |
| :param value: role to add to 'classes' parameter in the reclass |
| :param short_path: path to reclass yaml file. |
| It takes into account default path where the reclass locates. |
| May look like cluster/*/cicd/control/leader.yml |
| :return: None |
| """ |
| if value in self.ssh.check_call( |
| "{reclass_tools} get-key classes \ |
| /srv/salt/reclass/classes/{path}".format( |
| reclass_tools=self.reclass_tools_cmd, |
| value=value, |
| path=short_path |
| )): |
| LOG.warning("Class {} already exists in {}".format( |
| value, |
| short_path |
| )) |
| |
| self.ssh.check_call( |
| "{reclass_tools} add-key classes {value} \ |
| /srv/salt/reclass/classes/{path} --merge".format( |
| reclass_tools=self.reclass_tools_cmd, |
| value=value, |
| path=short_path |
| )) |
| |
| def delete_key(self, key, short_path): |
| """ |
| Remove key from the provided file |
| |
| :param value: string, parameter which will be deleted |
| :param short_path: string,, path to reclass yaml file. |
| It takes into account default path where the reclass locates. |
| May look like cluster/*/cicd/control/leader.yml |
| :return: None |
| """ |
| self.ssh.check_call( |
| "{reclass_tools} del-key {key} \ |
| /srv/salt/reclass/classes/{path}".format( |
| reclass_tools=self.reclass_tools_cmd, |
| key=key, |
| path=short_path |
| )) |
| |
| def merge_context(self, yaml_context, short_path): |
| """ |
| Merge |
| |
| :param yaml_context: string, yaml with extra context |
| :param short_path: string, path to reclass yaml file. |
| It takes into account default path where the reclass locates. |
| May look like cluster/*/cicd/control/leader.yml |
| :return: None |
| """ |
| tmp_file = "/tmp/extra_context.yaml" |
| with open(tmp_file, "w") as f: |
| f.write(yaml_context) |
| |
| self.ssh.upload(tmp_file, tmp_file) |
| self.ssh.check_call( |
| "{reclass_tools} merge-context {yaml} \ |
| /srv/salt/reclass/classes/{path}".format( |
| reclass_tools=self.reclass_tools_cmd, |
| yaml=tmp_file, |
| path=short_path |
| )) |
| |
| def commit(self, text_commit): |
| self.ssh.check_call( |
| "cd /srv/salt/reclass; git add -u && git commit --allow-empty " |
| "-m '{text}'".format(text=text_commit)) |