# Copyright 2020 Red Hat, 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 validations_libs.logger import getLogger import validations_libs.exceptions as exceptions import os import yaml from collections import OrderedDict LOG = getLogger(__name__ + ".validation") class Validation: """An object for encapsulating a validation Each validation is an `Ansible` playbook. Each playbook have some ``metadata``. Here is what a minimal validation would look like: .. code-block:: yaml - hosts: webserver vars: metadata: name: Hello World description: This validation prints Hello World! roles: - hello_world As shown here, the validation playbook requires three top-level directives: ``hosts``, ``vars -> metadata`` and ``roles`` ``hosts`` specify which nodes to run the validation on. The ``vars`` section serves for storing variables that are going to be available to the `Ansible` playbook. The validations API uses the ``metadata`` section to read validation's name and description. These values are then reported by the API. The validations can be grouped together by specifying a ``groups``, a ``categories`` and a ``products`` metadata. ``groups`` are the deployment stage the validations should run on, ``categories`` are the technical classification for the validations and ``products`` are the specific validations which should be executed against a specific product. Groups, Categories and Products function similar to tags and a validation can thus be part of many groups and many categories. Here is an example: .. code-block:: yaml - hosts: webserver vars: metadata: name: Hello World description: This validation prints Hello World! groups: - pre-deployment - hardware categories: - os - networking - storage - security products: - product1 - product2 roles: - hello_world """ _col_keys = ['ID', 'Name', 'Description', 'Groups', 'Categories', 'Products'] def __init__(self, validation_path): self.dict = self._get_content(validation_path) if not self.has_metadata_dict: raise exceptions.ValidationParsingException( "No metadata found in validation {}".format(validation_path)) self.id = os.path.splitext(os.path.basename(validation_path))[0] self.path = validation_path self.metadata = {'id': self.id, 'path': self.path} self.metadata.update(self.dict['vars'].get('metadata')) def _get_content(self, val_path): try: with open(val_path, 'r') as val_playbook: return yaml.safe_load(val_playbook)[0] except (IOError, KeyError) as ex: raise exceptions.ValidationParsingException( "Error while parsing validation playbook: {}".format(ex)) @property def has_vars_dict(self): """Check the presence of the vars dictionary .. code-block:: yaml - hosts: webserver vars: <==== metadata: name: hello world description: this validation prints hello world! groups: - pre-deployment - hardware categories: - os - networking - storage - security products: - product1 - product2 roles: - hello_world :return: `true` if `vars` is found, `false` if not. :rtype: `boolean` """ return 'vars' in self.dict.keys() @property def has_metadata_dict(self): """Check the presence of the metadata dictionary .. code-block:: yaml - hosts: webserver vars: metadata: <==== name: hello world description: this validation prints hello world! groups: - pre-deployment - hardware categories: - os - networking - storage - security products: - product1 - product2 roles: - hello_world :return: `true` if `vars` and metadata are found, `false` if not. :rtype: `boolean` """ return self.has_vars_dict and 'metadata' in self.dict['vars'].keys() @property def get_metadata(self): """Get the metadata of a validation :return: The validation metadata :rtype: `dict` :Example: >>> pl = '/foo/bar/val1.yaml' >>> val = Validation(pl) >>> print(val.get_metadata) {'description': 'Val1 desc.', 'groups': ['group1', 'group2'], 'categories': ['category1', 'category2'], 'products': ['product1', 'product2'], 'id': 'val1', 'name': 'The validation val1\'s name', 'path': '/tmp/foo/val1.yaml'} """ return self.metadata @property def get_vars(self): """Get only the variables of a validation :return: All the variables belonging to a validation apart from metadata :rtype: `dict` :Example: >>> pl = '/foo/bar/val.yaml' >>> val = Validation(pl) >>> print(val.get_vars) {'var_name1': 'value1', 'var_name2': 'value2'} """ validation_vars = self.dict['vars'].copy() validation_vars.pop('metadata') return validation_vars @property def get_data(self): """Get the full contents of a validation playbook :return: The full content of the playbook :rtype: `dict` :Example: >>> pl = '/foo/bar/val.yaml' >>> val = Validation(pl) >>> print(val.get_data) {'gather_facts': True, 'hosts': 'all', 'roles': ['val_role'], 'vars': {'metadata': {'description': 'description of val ', 'groups': ['group1', 'group2'], 'categories': ['category1', 'category2'], 'products': ['product1', 'product2'], 'name': 'validation one'}, 'var_name1': 'value1'}} """ return self.dict @property def groups(self): """Get the validation list of groups :return: A list of groups for the validation :rtype: `list` :Example: >>> pl = '/foo/bar/val.yaml' >>> val = Validation(pl) >>> print(val.groups) ['group1', 'group2'] """ return self.dict['vars']['metadata'].get('groups', []) @property def categories(self): """Get the validation list of categories :return: A list of categories for the validation :rtype: `list` :Example: >>> pl = '/foo/bar/val.yaml' >>> val = Validation(pl) >>> print(val.categories) ['category1', 'category2'] """ return self.dict['vars']['metadata'].get('categories', []) @property def products(self): """Get the validation list of products :return: A list of products for the validation :rtype: `list` :Example: >>> pl = '/foo/bar/val.yaml' >>> val = Validation(pl) >>> print(val.products) ['product1', 'product2'] """ return self.dict['vars']['metadata'].get('products', []) @property def get_id(self): """Get the validation id :return: The validation id :rtype: `string` :Example: >>> pl = '/foo/bar/check-cpu.yaml' >>> val = Validation(pl) >>> print(val.id) 'check-cpu' """ return self.id @property def get_ordered_dict(self): """Get the full ordered content of a validation :return: An `OrderedDict` with the full data of a validation :rtype: `OrderedDict` """ data = OrderedDict(self.dict) return data @property def get_formated_data(self): """Get basic information from a validation for output display :return: Basic information of a validation including the `Description`, the list of 'Categories', the list of `Groups`, the `ID` and the `Name`. :rtype: `dict` :Example: >>> pl = '/foo/bar/val.yaml' >>> val = Validation(pl) >>> print(val.get_formated_data) {'Categories': ['category1', 'category2'], 'Products': ['product1', 'product2'], 'Description': 'description of val', 'Groups': ['group1', 'group2'], 'ID': 'val', 'Name': 'validation one', 'path': '/tmp/foo/'} """ data = {} for key in self.metadata: if key == 'id': data[key.upper()] = self.metadata.get(key) else: data[key.capitalize()] = self.metadata.get(key) return data