murano-pkg-check-0.3.0/0000775000567000056710000000000013044647631016016 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/doc/0000775000567000056710000000000013044647631016563 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/doc/source/0000775000567000056710000000000013044647631020063 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/doc/source/readme.rst0000664000567000056710000000003613044647404022047 0ustar jenkinsjenkins00000000000000.. include:: ../../README.rst murano-pkg-check-0.3.0/doc/source/usage.rst0000664000567000056710000000013313044647404021714 0ustar jenkinsjenkins00000000000000======== Usage ======== To use murano-pkg-check in a project:: import muranopkgcheck murano-pkg-check-0.3.0/doc/source/contributing.rst0000664000567000056710000000011313044647404023315 0ustar jenkinsjenkins00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rst murano-pkg-check-0.3.0/doc/source/conf.py0000775000567000056710000000464013044647404021367 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- # 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 os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'oslosphinx' ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'murano-pkg-check' copyright = u'2016, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} murano-pkg-check-0.3.0/doc/source/installation.rst0000664000567000056710000000033113044647404023311 0ustar jenkinsjenkins00000000000000============ Installation ============ At the command line:: $ pip install murano-pkg-check Or, if you have virtualenvwrapper installed:: $ mkvirtualenv murano-pkg-check $ pip install murano-pkg-check murano-pkg-check-0.3.0/doc/source/index.rst0000664000567000056710000000101513044647404021717 0ustar jenkinsjenkins00000000000000.. murano-pkg-check documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to murano-pkg-check's documentation! ======================================================== Contents: .. toctree:: :maxdepth: 2 readme installation errors usage contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` murano-pkg-check-0.3.0/doc/source/errors.rst0000664000567000056710000000011313044647404022122 0ustar jenkinsjenkins00000000000000============== List of errors ============== .. include:: _errors_list.rst murano-pkg-check-0.3.0/README.rst0000664000567000056710000000226413044647404017507 0ustar jenkinsjenkins00000000000000======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/murano-pkg-check.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on =============================== murano-pkg-check =============================== Murano package validator tool After checking out tool from repository easiest method to run tool is to run .. code-block:: shell tox -e venv -- murano-pkg-check -h This command will display help for murano-pkg-validator If you installed it from PYPI you can use: .. code-block:: shell murano-pkg-check -h To run validator in directory apache-app just run: .. code-block:: shell murano-pkg-check apache-app It will print all errors there are on package. .. code-block:: shell murano-pkg-check --discovery murano-apps Will search for all packages under directory `murano-apps`. It will print all errors and warnings for all packages found. * Free software: Apache license * Documentation: http://docs.openstack.org/developer/murano-pkg-check * Source: http://git.openstack.org/cgit/openstack/murano-pkg-check * Bugs: http://bugs.launchpad.net/murano murano-pkg-check-0.3.0/ChangeLog0000664000567000056710000000361713044647630017576 0ustar jenkinsjenkins00000000000000CHANGES ======= 0.3.0 ----- * Make test debug working * Add show version for murano-pkg-check * Correct the package name * Fix typos in cover.sh * Replace six.iteritems() with .items() * Show team and repo badges on README * Fix coverage job gate * Using assertIsNone() instead of assertEqual(None, ...) * Change link to bugs space * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Enable release notes translation * Updated from global requirements 0.2.0 ----- * Improve class and method regexps * Readme has now some running examples * Updated from global requirements * Catch block validation * update homepage with developer documentation page * Making Default optional in Match strcuture * Allow methods to be empty and error when not dict * Change Error to Warning for missing name of namespace * Improving tests for metadata in Murano Classes * Fix i18n set up * Fix bug with loading buffer in ZipLoader in py3 * Updated from global requirements * Add tools/tox_install.sh script 0.1.1 ----- * Introduce option only_errors * Add possibility to load package from open zip file * Clean imports in code * TrivialFix: Remove logging import unused * Added functional test base * Added docs autogeneration for error list 0.1.0 ----- * Bunch of bugfixes * Added errors registration * Add i18n support * Improve logging system * Improving tests coverage * Report yaml parsing errors instead of ignoring * Added tools/cover.sh * Improve package version validation * Allow method name to start with "." * Fix error report in version * Accept null description in manifest * Bunch of fixes for code_structure, base, manifest: * Adding Package Validator * Adding UI Validator * Adding MuranoPL validator * Improving Manifest require check * Code Structure checker added with tests * Adding ManifestValidator * Added core and CLI * Commit project structure * Added .gitreview murano-pkg-check-0.3.0/CONTRIBUTING.rst0000664000567000056710000000122413044647404020454 0ustar jenkinsjenkins00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/murano-pkg-check murano-pkg-check-0.3.0/babel.cfg0000664000567000056710000000002113044647404017533 0ustar jenkinsjenkins00000000000000[python: **.py] murano-pkg-check-0.3.0/.testr.conf0000664000567000056710000000047713044647404020112 0ustar jenkinsjenkins00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list murano-pkg-check-0.3.0/murano_pkg_check.egg-info/0000775000567000056710000000000013044647631023007 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/murano_pkg_check.egg-info/pbr.json0000664000567000056710000000005613044647630024465 0ustar jenkinsjenkins00000000000000{"is_release": true, "git_version": "47f7379"}murano-pkg-check-0.3.0/murano_pkg_check.egg-info/SOURCES.txt0000664000567000056710000000437413044647631024703 0ustar jenkinsjenkins00000000000000.coveragerc .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst babel.cfg requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/contributing.rst doc/source/errors.rst doc/source/index.rst doc/source/installation.rst doc/source/readme.rst doc/source/usage.rst murano_pkg_check.egg-info/PKG-INFO murano_pkg_check.egg-info/SOURCES.txt murano_pkg_check.egg-info/dependency_links.txt murano_pkg_check.egg-info/entry_points.txt murano_pkg_check.egg-info/not-zip-safe murano_pkg_check.egg-info/pbr.json murano_pkg_check.egg-info/requires.txt murano_pkg_check.egg-info/top_level.txt muranopkgcheck/__init__.py muranopkgcheck/consts.py muranopkgcheck/error.py muranopkgcheck/i18n.py muranopkgcheck/log.py muranopkgcheck/manager.py muranopkgcheck/pkg_loader.py muranopkgcheck/plugin.py muranopkgcheck/yaml_loader.py muranopkgcheck/checkers/__init__.py muranopkgcheck/checkers/code_structure.py muranopkgcheck/checkers/yaql_checker.py muranopkgcheck/cmd/__init__.py muranopkgcheck/cmd/run.py muranopkgcheck/tests/__init__.py muranopkgcheck/tests/base.py muranopkgcheck/tests/test_code_structure_checker.py muranopkgcheck/tests/test_error.py muranopkgcheck/tests/test_log.py muranopkgcheck/tests/test_manager.py muranopkgcheck/tests/test_manifest_validator.py muranopkgcheck/tests/test_muranopl_validator.py muranopkgcheck/tests/test_package.py muranopkgcheck/tests/test_pkg_loader.py muranopkgcheck/tests/test_plugin.py muranopkgcheck/tests/test_ui_validator.py muranopkgcheck/tests/test_validator_helpers.py muranopkgcheck/tests/test_yaml_validator.py muranopkgcheck/tests/functional/__init__.py muranopkgcheck/tests/functional/test_cases.py muranopkgcheck/tests/functional/cases/case.yaml muranopkgcheck/validators/__init__.py muranopkgcheck/validators/base.py muranopkgcheck/validators/manifest.py muranopkgcheck/validators/muranopl.py muranopkgcheck/validators/package.py muranopkgcheck/validators/ui.py releasenotes/notes/.placeholder releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po tools/cover.sh tools/gen_errors.py tools/tox_install.shmurano-pkg-check-0.3.0/murano_pkg_check.egg-info/top_level.txt0000664000567000056710000000001713044647630025536 0ustar jenkinsjenkins00000000000000muranopkgcheck murano-pkg-check-0.3.0/murano_pkg_check.egg-info/PKG-INFO0000664000567000056710000000446113044647630024110 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: murano-pkg-check Version: 0.3.0 Summary: Murano package validator tool Home-page: http://docs.openstack.org/developer/murano/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/murano-pkg-check.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on =============================== murano-pkg-check =============================== Murano package validator tool After checking out tool from repository easiest method to run tool is to run .. code-block:: shell tox -e venv -- murano-pkg-check -h This command will display help for murano-pkg-validator If you installed it from PYPI you can use: .. code-block:: shell murano-pkg-check -h To run validator in directory apache-app just run: .. code-block:: shell murano-pkg-check apache-app It will print all errors there are on package. .. code-block:: shell murano-pkg-check --discovery murano-apps Will search for all packages under directory `murano-apps`. It will print all errors and warnings for all packages found. * Free software: Apache license * Documentation: http://docs.openstack.org/developer/murano-pkg-check * Source: http://git.openstack.org/cgit/openstack/murano-pkg-check * Bugs: http://bugs.launchpad.net/murano Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 murano-pkg-check-0.3.0/murano_pkg_check.egg-info/not-zip-safe0000664000567000056710000000000113044647621025234 0ustar jenkinsjenkins00000000000000 murano-pkg-check-0.3.0/murano_pkg_check.egg-info/entry_points.txt0000664000567000056710000000010213044647630026275 0ustar jenkinsjenkins00000000000000[console_scripts] murano-pkg-check = muranopkgcheck.cmd.run:main murano-pkg-check-0.3.0/murano_pkg_check.egg-info/dependency_links.txt0000664000567000056710000000000113044647630027054 0ustar jenkinsjenkins00000000000000 murano-pkg-check-0.3.0/murano_pkg_check.egg-info/requires.txt0000664000567000056710000000015213044647630025404 0ustar jenkinsjenkins00000000000000pbr>=1.8 PyYAML>=3.10.0 yaql>=1.1.0 six>=1.9.0 stevedore>=1.17.1 semantic-version>=2.3.1 oslo.i18n>=2.1.0 murano-pkg-check-0.3.0/.coveragerc0000664000567000056710000000016213044647404020134 0ustar jenkinsjenkins00000000000000[run] branch = True source = muranopkgcheck omit = .tox/* muranopkgcheck/tests/* [report] ignore_errors = True murano-pkg-check-0.3.0/MANIFEST.in0000664000567000056710000000013613044647404017552 0ustar jenkinsjenkins00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pyc murano-pkg-check-0.3.0/PKG-INFO0000664000567000056710000000446113044647631017120 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: murano-pkg-check Version: 0.3.0 Summary: Murano package validator tool Home-page: http://docs.openstack.org/developer/murano/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/murano-pkg-check.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on =============================== murano-pkg-check =============================== Murano package validator tool After checking out tool from repository easiest method to run tool is to run .. code-block:: shell tox -e venv -- murano-pkg-check -h This command will display help for murano-pkg-validator If you installed it from PYPI you can use: .. code-block:: shell murano-pkg-check -h To run validator in directory apache-app just run: .. code-block:: shell murano-pkg-check apache-app It will print all errors there are on package. .. code-block:: shell murano-pkg-check --discovery murano-apps Will search for all packages under directory `murano-apps`. It will print all errors and warnings for all packages found. * Free software: Apache license * Documentation: http://docs.openstack.org/developer/murano-pkg-check * Source: http://git.openstack.org/cgit/openstack/murano-pkg-check * Bugs: http://bugs.launchpad.net/murano Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 murano-pkg-check-0.3.0/tools/0000775000567000056710000000000013044647631017156 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/tools/tox_install.sh0000775000567000056710000000342413044647404022056 0ustar jenkinsjenkins00000000000000#!/usr/bin/env bash # Client constraint file contains this client version pin that is in conflict # with installing the client from source. We should replace the version pin in # the constraints file before applying it for from-source installation. ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner BRANCH_NAME=master NAME=murano-pkg-check requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?) set -e CONSTRAINTS_FILE=$1 shift install_cmd="pip install" mydir=$(mktemp -dt "$NAME-tox_install-XXXXXXX") trap "rm -rf $mydir" EXIT localfile=$mydir/upper-constraints.txt if [[ $CONSTRAINTS_FILE != http* ]]; then CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE fi curl $CONSTRAINTS_FILE -k -o $localfile install_cmd="$install_cmd -c$localfile" if [ $requirements_installed -eq 0 ]; then echo "ALREADY INSTALLED" > /tmp/tox_install.txt echo "Requirements already installed; using existing package" elif [ -x "$ZUUL_CLONER" ]; then echo "ZUUL CLONER" > /tmp/tox_install.txt pushd $mydir $ZUUL_CLONER --cache-dir \ /opt/git \ --branch $BRANCH_NAME \ git://git.openstack.org \ openstack/requirements cd openstack/requirements $install_cmd -e . popd else echo "PIP HARDCODE" > /tmp/tox_install.txt if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" fi $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} fi # This is the main purpose of the script: Allow local installation of # the current repo. It is listed in constraints file and thus any # install will be constrained and we need to unconstrain it. edit-constraints $localfile -- $NAME "-e file://$PWD#egg=$NAME" $install_cmd -U $* exit $? murano-pkg-check-0.3.0/tools/cover.sh0000775000567000056710000000531313044647404020633 0ustar jenkinsjenkins00000000000000#!/bin/bash # # Copyright 2015: Mirantis Inc. # All Rights Reserved. # # 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. ALLOWED_EXTRA_MISSING=4 show_diff () { head -1 $1 diff -U 0 $1 $2 | sed 1,2d } # Stash uncommitted changes, checkout master and save coverage report uncommitted=$(git status --porcelain | grep -v "^??") [[ -n $uncommitted ]] && git stash > /dev/null git checkout HEAD^ baseline_report=$(mktemp -t murano_coverageXXXXXXX) find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --testr-args="$*" coverage report > $baseline_report baseline_missing=$(awk 'END { print $3 }' $baseline_report) # Checkout back and unstash uncommitted changes (if any) git checkout - [[ -n $uncommitted ]] && git stash pop > /dev/null # Generate and save coverage report current_report=$(mktemp -t murano_coverageXXXXXXX) find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --testr-args="$*" coverage report > $current_report current_missing=$(awk 'END { print $3 }' $current_report) baseline_percentage=$(awk 'END { print $6 }' $baseline_report) current_percentage=$(awk 'END { print $6 }' $current_report) # Show coverage details allowed_missing=$((baseline_missing+ALLOWED_EXTRA_MISSING)) echo "Baseline report: $(cat ${baseline_report})" echo "Proposed change report: $(cat ${current_report})" echo "" echo "" echo "Allowed to introduce missing lines : ${ALLOWED_EXTRA_MISSING}" echo "Missing lines in master : ${baseline_missing}" echo "Missing lines in proposed change : ${current_missing}" echo "Current percentage : ${baseline_percentage}" echo "Proposed change percentage : ${current_percentage}" if [[ $allowed_missing -gt $current_missing ]]; then if [[ $baseline_missing -lt $current_missing ]]; then show_diff $baseline_report $current_report echo "I believe you can cover all your code with 100% coverage!" else echo "Thank you! You are awesome! Keep writing unit tests! :)" fi exit_code=0 else show_diff $baseline_report $current_report echo "Please write more unit tests, we should keep our test coverage :( " exit_code=1 fi rm $baseline_report $current_report exit $exit_code murano-pkg-check-0.3.0/tools/gen_errors.py0000664000567000056710000000274513044647404021703 0ustar jenkinsjenkins00000000000000# Copyright 2016: Mirantis Inc. # All Rights Reserved. # # 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 jinja2 import six from muranopkgcheck import error TEMPLATE = """ ============ ================================================================ CODE DESCRITION ============ ================================================================ {% for err in errors %}{{ err.code }} {{err.description}} {% endfor %} ============ ================================================================ """ def main(): from muranopkgcheck import validators print('Validators:') for v in validators.VALIDATORS: print(v.__name__) errors = sorted(six.itervalues(error.errors), key=lambda item: item['code']) t = jinja2.Template(TEMPLATE) r = t.render(errors=errors) print(r) with open('doc/source/_errors_list.rst', 'w') as f: f.write(r) if __name__ == '__main__': main() murano-pkg-check-0.3.0/tox.ini0000664000567000056710000000223513044647404017331 0ustar jenkinsjenkins00000000000000[tox] minversion = 2.0 envlist = py34,py27,pep8 skipsdist = True [testenv] usedevelop = True whitelist_externals = bash find install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py test --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 {posargs} [testenv:venv] commands = {posargs} [testenv:cover] commands = {toxinidir}/tools/cover.sh {posargs} [testenv:docs] commands = python tools/gen_errors.py python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:debug] commands = find . -type f -name "*.pyc" -delete oslo_debug_helper -t muranopkgcheck/tests {posargs} [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build murano-pkg-check-0.3.0/setup.cfg0000664000567000056710000000267013044647631017644 0ustar jenkinsjenkins00000000000000[metadata] name = murano-pkg-check summary = Murano package validator tool description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://docs.openstack.org/developer/murano/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 [files] packages = muranopkgcheck [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = muranopkgcheck/locale domain = muranopkgcheck [update_catalog] domain = murano-pkg-check output_dir = muranopkgcheck/locale input_file = muranopkgcheck/locale/muranopkgcheck.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = muranopkgcheck/locale/muranopkgcheck.pot [entry_points] console_scripts = murano-pkg-check = muranopkgcheck.cmd.run:main [build_releasenotes] all_files = 1 build-dir = releasenotes/build source-dir = releasenotes/source [global] setup-hooks = pbr.hooks.setup_hook murano-pkg-check-0.3.0/setup.py0000664000567000056710000000200413044647404017522 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=1.8'], pbr=True) murano-pkg-check-0.3.0/AUTHORS0000664000567000056710000000067113044647630017071 0ustar jenkinsjenkins00000000000000Aleksandr Kholkin Andreas Jaeger Cao Xuan Hoang Flavio Percoco Krzysztof Szukiełojć Lucky samadhiya Nguyen Hung Phuong gecong1973 sslypushenko zhangyanxian zhurong murano-pkg-check-0.3.0/LICENSE0000664000567000056710000002363713044647404017034 0ustar jenkinsjenkins00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. murano-pkg-check-0.3.0/HACKING.rst0000664000567000056710000000025013044647404017607 0ustar jenkinsjenkins00000000000000murano-pkg-check Style Commandments =============================================== Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ murano-pkg-check-0.3.0/releasenotes/0000775000567000056710000000000013044647631020507 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/0000775000567000056710000000000013044647631022007 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/_templates/0000775000567000056710000000000013044647631024144 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/_templates/.placeholder0000664000567000056710000000000013044647404026413 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/conf.py0000664000567000056710000002153313044647404023310 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- # 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. # Glance Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'oslosphinx', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'muranopkgcheck Release Notes' copyright = u'2016, OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'GlanceReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'GlanceReleaseNotes.tex', u'Glance Release Notes Documentation', u'Glance Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'glancereleasenotes', u'Glance Release Notes Documentation', [u'Glance Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'GlanceReleaseNotes', u'Glance Release Notes Documentation', u'Glance Developers', 'GlanceReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] murano-pkg-check-0.3.0/releasenotes/source/_static/0000775000567000056710000000000013044647631023435 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/_static/.placeholder0000664000567000056710000000000013044647404025704 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/unreleased.rst0000664000567000056710000000016013044647404024663 0ustar jenkinsjenkins00000000000000============================== Current Series Release Notes ============================== .. release-notes:: murano-pkg-check-0.3.0/releasenotes/source/index.rst0000664000567000056710000000024513044647404023647 0ustar jenkinsjenkins00000000000000============================================ muranopkgcheck Release Notes ============================================ .. toctree:: :maxdepth: 1 unreleased murano-pkg-check-0.3.0/releasenotes/source/locale/0000775000567000056710000000000013044647631023246 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/locale/fr/0000775000567000056710000000000013044647631023655 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000567000056710000000000013044647631025442 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000567000056710000000124213044647404030470 0ustar jenkinsjenkins00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: muranopkgcheck Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-10-07 14:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 05:31+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "muranopkgcheck Release Notes" msgstr "Note de release de muranopkgcheck" murano-pkg-check-0.3.0/releasenotes/notes/0000775000567000056710000000000013044647631021637 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/releasenotes/notes/.placeholder0000664000567000056710000000000013044647404024106 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/0000775000567000056710000000000013044647631021017 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/cmd/0000775000567000056710000000000013044647631021562 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/cmd/run.py0000664000567000056710000001041613044647404022740 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import argparse import os import sys import six import muranopkgcheck from muranopkgcheck import log from muranopkgcheck import manager LOG = log.getLogger(__name__) def parse_cli_args(args=None): usage_string = 'murano-pkg-checker [options] ' parser = argparse.ArgumentParser( description='murano-pkg-checker arguments', formatter_class=argparse.HelpFormatter, usage=usage_string ) parser.add_argument('--select', dest='select', required=False, type=str, help='select errors and warnings (e.g. E001,W002)') parser.add_argument('--ignore', dest='ignore', required=False, type=str, help='skip errors and warnings (e.g. E042,W007)') parser.add_argument('--verbose', '-v', dest='verbose', default=0, action='count', help='Verbosity level. -v for ERROR. -vv for INFO') parser.add_argument('--debug', dest='debug', action='store_true', help='Set logging level to DEBUG') parser.add_argument('--discover', dest='discover', action='store_true', help='Run discovery packages') parser.add_argument('path', type=str, help='Path to package or catalog') parser.add_argument('--version', action='version', version=muranopkgcheck.__version__, help="Show program's version number and exit.") return parser.parse_args(args=args) def setup_logs(args): if args.verbose == 0: log.setup(level=log.CRITICAL) elif args.verbose == 1: log.setup(level=log.ERROR) else: log.setup(level=log.INFO) if args.debug: log.setup(level=log.DEBUG) def run(args, pkg_path=None, quiet_load=False): m = manager.Manager(pkg_path or args.path, quiet_load=quiet_load) m.load_plugins() if args.select: select = args.select.split(',') else: select = None if args.ignore: ignore = args.ignore.split(',') else: ignore = None errors = m.validate(select=select, ignore=ignore) fmt = manager.PlainTextFormatter() return fmt.format(errors) def discover(args): errors = [] for dirpath, dirnames, filenames in os.walk(args.path): items = dirnames for item in items: if item.startswith('.'): continue try: path = os.path.join(dirpath, item) pkg_errors = run(args, path, quiet_load=True) LOG.info("Package {} discovered".format(path)) if pkg_errors: errors.append("Errors in package {}\n{}\n" "".format(path, pkg_errors)) except ValueError: pass return '\n'.join(errors) def main(): args = parse_cli_args() setup_logs(args) try: if args.discover: errors = discover(args) else: errors = run(args) except ValueError as e: LOG.error(six.text_type(e)) print(six.text_type(e)) return 2 except Exception as e: LOG.critical(six.text_type(e), exc_info=sys.exc_info()) return 3 if errors: print(errors) return 1 else: print('No errors found!') if __name__ == '__main__': sys.exit(main()) murano-pkg-check-0.3.0/muranopkgcheck/cmd/__init__.py0000664000567000056710000000000013044647404023657 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/validators/0000775000567000056710000000000013044647631023167 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/validators/ui.py0000664000567000056710000001140313044647404024153 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import six from muranopkgcheck import error from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base UI_VERSION = frozenset(('1.0', '1', '2', '2.0', '2.1', '2.2', '2.3')) FIELDS_TYPE = frozenset(('string', 'boolean', 'text', 'integer', 'password', 'clusterip', 'floatingip', 'domain', 'databaselist', 'table', 'flavor', 'keypair', 'image', 'azone', 'psqlDatabase', 'network', 'choice')) BOOL_FIELDS = frozenset(('required', 'hidden')) STR_FIELDS = frozenset(('name', 'label', 'description', 'descriptionTitle', 'regexpValidator', 'helpText')) INT_FIELDS = frozenset(('minLength', 'maxLength', 'minValue', 'maxValue')) error.register.E081(description='Value should be boolean') error.register.E083(description='Wrong name in UI file') error.register.E084(description='Application is not a dict') error.register.E100(description='Not valid FQN or known type') error.register.W082(description='Incorrect version of UI file') error.register.W100(description='Not known type. Probably typo') class UiValidator(base.YamlValidator): def __init__(self, loaded_package): super(UiValidator, self).__init__(loaded_package, 'UI/.*\.yaml$') self.add_checker(self._valid_forms, 'Forms', False) self.add_checker(self._null_checker, 'Templates', False) self.add_checker(self._valid_application, 'Application', False) self.add_checker(self._valid_version, 'Version', False) def _valid_application(self, application): if not isinstance(application, dict): yield error.report.E084(_('Application is not a dict'), application) return for name, value in application.items(): if not self._check_name(name): if name != '?': yield error.report.E083(_('Wrong name in UI file "{}"') .format(name), name) def _valid_version(self, version): if str(version) not in UI_VERSION: yield error.report.W082(_('Incorrect version of UI file "{}"') .format(version), version) def _valid_forms(self, forms): for named_form in forms: for name, form in named_form.items(): yield self._valid_form(form['fields']) yield self._valid_keywords(form.keys(), ('fields', 'validators')) def _valid_form(self, form): for named_params in form: for key, value in named_params.items(): if key in STR_FIELDS: if not isinstance(value, six.string_types): yield error.report.E040(_('Value of {} should be ' 'string not "{}"') .format(key, value), key) elif key in BOOL_FIELDS: if not isinstance(value, bool): yield error.report.E081(_('Value of {} should be ' 'boolean not "{}"') .format(key, value), key) elif key in INT_FIELDS: if not isinstance(value, int): yield error.report.E082(_('Value of {} should be ' 'int not "{}"') .format(key, value), key) elif key == 'type': yield self._valid_field_type(value) def _valid_field_type(self, fqn, can_be_list=True): if isinstance(fqn, list): for elem in fqn: yield self._valid_field_type(elem, False) elif self._check_fqn_name(fqn): if '.' not in fqn and fqn not in FIELDS_TYPE: yield error.report.W100('"{0}" is not known type. ' 'Probably a typo'.format(fqn), fqn) else: yield error.report.E100('"{0}" is not valid FQN or known type' .format(fqn), fqn) murano-pkg-check-0.3.0/muranopkgcheck/validators/base.py0000664000567000056710000001227013044647404024453 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import abc import itertools import re import six from muranopkgcheck import error from muranopkgcheck.i18n import _ FQN_REGEX = re.compile('^([a-zA-Z_$][\w$]*\.)*[a-zA-Z_$][\w$]*$') NAME_REGEX = re.compile('^[A-Za-z_][\w]*$') error.register.E002(description='Yaml Error') error.register.E005(description='YAML multi document is not allowed') error.register.E020(description='Missing required key') error.register.E021(description='Unknown keyword') error.register.E040(description='Value should be string') error.register.W010(description='Unknown keyword') @six.add_metaclass(abc.ABCMeta) class BaseValidator(object): def __init__(self, loaded_package, _filter='.*'): self._loaded_pkg = loaded_package self._filter = _filter @abc.abstractmethod def run(self): pass def _valid_string(self, value): if not isinstance(value, six.string_types): yield error.report.E040(_('Value is not a string "{}"' '').format(value), value) def _check_name(self, name): if isinstance(name, six.string_types) and NAME_REGEX.match(name): return True return False def _check_fqn_name(self, fqn): if isinstance(fqn, six.string_types) and FQN_REGEX.match(fqn): return True return False def _check_ns_fqn_name(self, ns_fqn): if isinstance(ns_fqn, six.string_types): if ':' in ns_fqn: ns, fqn = ns_fqn.split(':', 1) if NAME_REGEX.match(ns) and FQN_REGEX.match(fqn): return True elif FQN_REGEX.match(ns_fqn): return True return False class YamlValidator(BaseValidator): def __init__(self, loaded_package, _filter='.*', allows_multi=False): super(YamlValidator, self).__init__(loaded_package, _filter) self._checkers = {} self._allows_multi = allows_multi def add_checker(self, function, key=None, required=True): checkers = self._checkers.setdefault(key, {'checkers': [], 'required': False}) checkers['checkers'].append(function) if key is None: checkers['required'] = False elif required: checkers['required'] = True def run(self): chain_of_suits = [] for filename in self._loaded_pkg.search_for(self._filter): file_ = self._loaded_pkg.read(filename) chain_of_suits.append(self._run_single(file_)) return itertools.chain(*chain_of_suits) def _run_single(self, file_): reports_chain = [] def run_helper(name, checkers, data): for checker in checkers: result = checker(data) if result: reports_chain.append(result) try: multi_documents = file_.yaml() except Exception as e: reports_chain.append([ error.report.E002('Yaml Error: {0}'.format(e), e)]) else: if multi_documents is None: multi_documents = [{}] if len(multi_documents) > 1 and not self._allows_multi: reports_chain.append([ error.report.E005(_('Multi document is not allowed in {}') .format(file_._path))]) for ast in multi_documents: file_check = self._checkers.get(None) if file_check: run_helper(None, file_check['checkers'], ast) for key, value in ast.items(): checkers = self._checkers.get(key) if checkers: run_helper(key, checkers['checkers'], ast[key]) else: reports_chain.append(self._unknown_keyword(key, value)) missing = set(key for key, value in self._checkers.items() if value['required']) - set(ast.keys()) for m in missing: reports_chain.append([ error.report.E020(_('Missing required key "{}"') .format(m), m)]) return itertools.chain(*reports_chain) def _valid_keywords(self, present, known): unknown = set(present) - set(known) for u in unknown: yield error.report.E021(_('Unknown keyword "{}"').format(u), u) def _unknown_keyword(self, key, value): yield error.report.W010(_('Unknown keyword "{}"').format(key), key) def _null_checker(self, value): pass murano-pkg-check-0.3.0/muranopkgcheck/validators/manifest.py0000664000567000056710000001531613044647404025353 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import os.path import semantic_version import six from muranopkgcheck import consts from muranopkgcheck import error from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base from muranopkgcheck import yaml_loader error.register.E030(description='Not supported format') error.register.E050(description='File is present in Manifest, ' 'but not in filesystem') error.register.E070(description='Tags should be a list') error.register.E071(description='Type is invalid') error.register.E072(description='UI is not a string') error.register.E073(description='Invalid FullName') error.register.E074(description='Logo is not a string') error.register.W020(description='File is not present in Manifest, ' 'but it is in filesystem') error.register.W030(description='Not supported format version') error.register.W073(description='There is no UI file') error.register.W074(description='There is no Logo file') class ManifestValidator(base.YamlValidator): def __init__(self, loaded_package): super(ManifestValidator, self).__init__(loaded_package, 'manifest.yaml$') self.add_checker(self._valid_format, 'Format', False) self.add_checker(self._valid_string, 'Author', False) self.add_checker(self._valid_version, 'Version', False) self.add_checker(self._valid_fullname, 'FullName') self.add_checker(self._valid_string, 'Name', False) self.add_checker(self._valid_classes, 'Classes', False) self.add_checker(self._valid_tags, 'Tags', False) self.add_checker(self._valid_require, 'Require', False) self.add_checker(self._valid_type, 'Type') self.add_checker(self._valid_description, 'Description') self.add_checker(self._valid_ui, 'UI', False) self.add_checker(self._valid_logo, 'Logo', False) self.add_checker(self._valid_logo_ui_existance) def _valid_description(self, desc): if not isinstance(desc, six.string_types) and\ not isinstance(desc, yaml_loader.YamlNull): yield error.report.E030('Value is not valid string "{0}"' .format(desc), desc) def _valid_format(self, value): format_ = str(value).split('/', 1) if len(format_) > 1: if format_[0] != 'MuranoPL': yield error.report.W030(_('Not supported format "{}"' '').format(value), value) return ver = format_[-1] if str(ver) not in ['1.0', '1.1', '1.2', '1.3', '1.4']: yield error.report.W030(_('Not supported format version "{}"' '').format(value), value) def _valid_fullname(self, fullname): if not self._check_fqn_name(fullname): yield error.report.E073(_('Invalid FullName "{}"') .format(fullname), fullname) def _valid_tags(self, value): if not isinstance(value, list): yield error.report.E070(_('Tags should be a list'), value) def _valid_require(self, value): if not isinstance(value, dict): yield error.report.E005(_('Require is not a dict type'), value) return for fqn, ver in value.items(): if not self._check_fqn_name(fqn): yield error.report.E005(_('Require key is not valid FQN "{}"' '').format(fqn), fqn) def _valid_type(self, value): if value not in ('Application', 'Library'): yield error.report.E071(_('Type is invalid "{}"').format(value), value) def _valid_version(self, version): try: semantic_version.Version.coerce(str(version)) except ValueError: yield error.report.E071(_('Version format should be compatible ' 'with SemVer not "{}"' '').format(version), version) def _valid_logo_ui_existance(self, ast): if 'Logo' not in ast: yield self._valid_logo('logo.png') if 'UI' not in ast: yield self._valid_ui('ui.yaml') def _valid_ui(self, value): if isinstance(value, six.string_types): pkg_type = self._loaded_pkg.read( consts.MANIFEST_PATH).yaml()[0]['Type'] if pkg_type == 'Library': return if not self._loaded_pkg.exists(os.path.join('UI', value)): yield error.report.W073(_('There is no UI file "{}"' '').format(value), value) else: yield error.report.E072(_('UI is not a string'), value) def _valid_logo(self, value): if isinstance(value, six.string_types): pkg_type = self._loaded_pkg.read( consts.MANIFEST_PATH).yaml()[0]['Type'] if pkg_type == 'Library': return if not self._loaded_pkg.exists(value): yield error.report.W074(_('There is no Logo file "{}"' '').format(value), value) else: yield error.report.E074(_('Logo is not a string'), value) def _valid_classes(self, value): if not isinstance(value, dict): yield error.report.E074(_('Classes section should be a dict'), value) return files = set(value.values()) existing_files = set(self._loaded_pkg.search_for('.*', 'Classes')) for fname in files - existing_files: yield error.report.E050(_('File "{}" is present in Manifest, ' 'but not in filesystem' '').format(fname), fname) for fname in existing_files - files: yield error.report.W020(_('File "{}" is not present in Manifest, ' 'but it is in filesystem' '').format(fname), fname) murano-pkg-check-0.3.0/muranopkgcheck/validators/package.py0000664000567000056710000000356213044647404025140 0ustar jenkinsjenkins00000000000000# Copyright (c) 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 muranopkgcheck import error from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base KNOWN_FILES_DIR = frozenset(['manifest.yaml', 'UI', 'LICENSE', 'Classes', 'images.lst', 'README.rst']) REQUIRED_FILES_DIR = frozenset(['manifest.yaml', 'LICENSE']) error.register.W120(description='Unknown file in the package') error.register.W121(description='Missing file in the package') class PackageValidator(base.BaseValidator): def __init__(self, loaded_package): super(PackageValidator, self).__init__(loaded_package, '') def run(self): yield self._known_directories() def _known_directories(self): files = set(self._loaded_pkg.search_for('^[^/]+$')) try: logo_file = next(self._loaded_pkg.search_for('^manifest.yaml$'))\ .yaml()[0]['Logo'] except Exception: logo_file = 'logo.png' for file_ in files - KNOWN_FILES_DIR - {logo_file}: yield error.report.W120(_('Unknown "{}" in the package') .format(file_), file_) for file_ in REQUIRED_FILES_DIR - files: yield error.report.W121(_('Missing "{}" in the package') .format(file_), file_) murano-pkg-check-0.3.0/muranopkgcheck/validators/muranopl.py0000664000567000056710000003377713044647404025415 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import re import six from muranopkgcheck.checkers import code_structure from muranopkgcheck.checkers import yaql_checker from muranopkgcheck import error from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base from muranopkgcheck import yaml_loader SUPPORTED_FORMATS = frozenset(['1.0', '1.1', '1.2', '1.3', '1.4']) METHOD_KEYWORDS = frozenset(['Body', 'Arguments', 'Usage', 'Scope', 'Meta']) METHOD_ARGUMENTS_KEYWORDS = frozenset(['Contract', 'Usage', 'Meta', 'Default']) PROPERTIES_KEYWORDS = frozenset(['Contract', 'Usage', 'Default', 'Meta']) PROPERTIES_USAGE_VALUES = frozenset(['In', 'Out', 'InOut', 'Const', 'Static', 'Runtime', 'Config']) APPLIES_VALUES = frozenset(['Package', 'Type', 'Method', 'Property', 'Argument', 'All']) CLASSNAME_REGEX = re.compile(r'^[a-zA-Z_]\w*(\.?\w+)*$') METHOD_NAME_REGEX = re.compile(r'^[a-zA-Z_]\w*$') SPECIAL_METHODS = frozenset(['.init', '.destroy']) error.register.E011(description='Invalid class name') error.register.E025(description='Wrong namespace or FNQ of extended class') error.register.E026(description='Properties should be a dict') error.register.E027(description='Cardinality is not One or Many') error.register.E028(description='Applies has one of not possible values') error.register.E042(description='Not allowed usage') error.register.E044(description='Wrong type of namespace') error.register.E045(description='Body is not a list or scalar/yaql expression') error.register.E046(description='Method is not a dict or list') error.register.E047(description='Missing Contract in property') error.register.E048(description='It is not safe to define methods arguments ' 'as a dict with several keys') error.register.E052(description='Arguments usage is available since 1.4') error.register.E053(description='Usage is invalid value ') error.register.E054(description='Invalid name of method "{}"') error.register.E060(description='Wrong namespace fqn') error.register.W045(description='Unsupported usage type') error.register.W011(description='Invalid class name') error.register.W048(description='Contract is not valid yaql') error.register.W060(description='Wrong namespace fqn') class MuranoPLValidator(base.YamlValidator): def __init__(self, loaded_package): super(MuranoPLValidator, self).__init__(loaded_package, 'Classes/.*\.yaml$', True) self.yaql_checker = yaql_checker.YaqlChecker() self.code_structure = code_structure.CheckCodeStructure() self.add_checker(self._null_checker, 'Meta', False) self.add_checker(self._null_checker, 'Usage', False) self.add_checker(self._valid_inherited, 'Inherited', False) self.add_checker(self._valid_cardinality, 'Cardinality', False) self.add_checker(self._valid_applies, 'Applies', False) self.add_checker(self._valid_name, 'Name', False) self.add_checker(self._valid_extends, 'Extends', False) self.add_checker(self._valid_methods, 'Methods', False) self.add_checker(self._valid_import, 'Import', False) self.add_checker(self._valid_namespaces, 'Namespaces', False) self.add_checker(self._valid_properties, 'Properties', False) def _valid_inherited(self, inherited): if not isinstance(inherited, bool): yield error.report.E027(_('Inherited is not bool "{0}"') .format(inherited), inherited) def _valid_cardinality(self, cardinality): if cardinality not in ('One', 'Many'): yield error.report.E027(_('Wrong Cardinality "{0}"') .format(cardinality), cardinality) def _valid_applies(self, applies, allow_list=True): if allow_list and isinstance(applies, list): for apl in applies: yield self._valid_applies(apl, False) else: if not isinstance(applies, six.string_types) or \ applies not in APPLIES_VALUES: yield error.report.E028( _('Wrong Applies "{0}"').format(applies), applies) def _valid_import(self, import_, can_be_list=True): if can_be_list and isinstance(import_, list): for imp in import_: yield self._valid_import(imp, False) elif not self._check_ns_fqn_name(import_): yield error.report.E025(_('Wrong namespace or FNQ of extended ' 'class "{0}"').format(import_), import_) def _valid_name(self, value): if not isinstance(value, six.string_types): yield error.report.E011(_('Invalid class name "{}". ' 'Class name should be a string') .format(value), value) elif (value.startswith('__') or not CLASSNAME_REGEX.match(value)): yield error.report.E011(_('Invalid class name "{}"').format(value), value) else: # NOTE (kzaitsev): allow short uppercase names like Q, IP, CDN if (not value[0].isupper() or (len(value) > 3 and value == value.upper())): yield error.report.W011(_( 'Class name "{}" not in CamelCase').format(value), value) def _valid_extends(self, value, can_be_list=True): if can_be_list and isinstance(value, list): for cls in value: yield self._valid_extends(cls, False) elif isinstance(value, six.string_types): if not self._check_ns_fqn_name(value): yield error.report.E025(_('Wrong FNQ of extended class "{}"' '').format(value), value) else: yield error.report.E025("Wrong type of Extends field", value) def _valid_contract(self, contract): if isinstance(contract, list): if len(contract) > 1: if len(contract) < 3: if isinstance(contract[1], int): return elif len(contract) < 4: if isinstance(contract[1], int) and \ isinstance(contract[2], int): return for con in contract: yield self._valid_contract(con) elif len(contract) == 1: yield self._valid_contract(contract[0]) elif isinstance(contract, dict): if not contract: return for c_key, c_value in contract.items(): yield self._valid_string(c_key) yield self._valid_contract(c_value) elif isinstance(contract, six.string_types): if not self.yaql_checker(contract) or \ not contract.startswith('$.') and contract != '$': yield error.report.W048(_('Contract is not valid yaql "{}"' '').format(contract), contract) else: yield error.report.W048(_('Contract is not valid yaql "{}"' '').format(contract), contract) def _valid_properties(self, properties): if not isinstance(properties, dict): yield error.report.E026(_('Properties should be a dict'), properties) return for property_name, property_data in properties.items(): usage = property_data.get('Usage') if usage: if usage not in PROPERTIES_USAGE_VALUES: yield error.report.E042(_('Not allowed usage "{}"' '').format(usage), usage) contract = property_data.get('Contract') if contract is not None: yield self._valid_contract(contract) else: yield error.report.E047(_('Missing Contract in property "{}"') .format(property_name), property_name) yield self._valid_keywords(property_data.keys(), PROPERTIES_KEYWORDS) def _valid_namespaces(self, value): if not isinstance(value, dict): yield error.report.E044(_('Wrong type of namespace'), value) return for name, fqn in value.items(): if not self._check_fqn_name(fqn): yield error.report.W060(_('Wrong namespace fqn ' '"{}"').format(fqn), fqn) if not self._check_name(name) and name != '=': yield error.report.E060(_('Wrong name for namespace ' '"{}"').format(fqn), fqn) def _valid_methods(self, value): if not isinstance(value, dict): if not isinstance(value, yaml_loader.YamlNull): yield error.report.E046(_('Methods are not a dict'), value) return for method_name, method_data in value.items(): if not isinstance(method_data, dict): if method_data: yield error.report.E046(_('Method is not a dict'), method_name) return if not(method_name in SPECIAL_METHODS or METHOD_NAME_REGEX.match(method_name)): yield error.report.E054(_('Invalid name of method "{}"') .format(method_name), method_name) scope = method_data.get('Scope') if scope: yield self._valid_scope(scope) usage = method_data.get('Usage') if usage: yield self._valid_method_usage(usage) arguments = method_data.get('Arguments') if arguments: yield self._valid_arguments(arguments) body = method_data.get('Body') if body: yield self._valid_body(body) yield self._valid_keywords(method_data.keys(), METHOD_KEYWORDS) def _valid_body(self, body): if not isinstance(body, (list, six.string_types, dict)): yield error.report.E045(_('Body is not a list or scalar/yaql ' 'expression'), body) else: yield self.code_structure.codeblock(body) def _valid_scope(self, scope): if self._loaded_pkg.format_version >= '1.4': if scope is not None and scope not in ('Public', 'Session'): yield error.report.E044(_('Wrong Scope "{}"').format(scope), scope) else: yield error.report.E044(_('Scope is not supported version ' 'earlier than 1.3"'), scope) def _valid_method_usage(self, usage): if usage == 'Action': if self._loaded_pkg.format_version >= '1.4': yield error.report.W045(_('Usage "{}" is deprecated since 1.4' '').format(usage), usage) elif usage in frozenset(['Static', 'Extension']): if self._loaded_pkg.format_version <= '1.3': yield error.report.W045(_('Usage "{}" is available from 1.3') .format(usage), usage) elif usage != 'Runtime': yield error.report.W045(_('Unsupported usage type "{}" ') .format(usage), usage) def _valid_arguments(self, arguments): if isinstance(arguments, dict) and len(arguments) > 1: yield error.report.E048(_('It is not safe to define methods ' 'arguments as a dict with several keys'), arguments) return elif not isinstance(arguments, (list, dict)): yield error.report.E046(_('Methods arguments should be a list or ' 'dict'), arguments) return if isinstance(arguments, dict): arguments = [arguments] for argument in arguments: if not isinstance(argument, dict) or len(argument) != 1: yield error.report.E046(_('Methods single argument should be ' 'a one key dict'), argument) else: name = next(six.iterkeys(argument)) if not self._check_name(name): yield error.report.E054(_('Invalid name of argument "{}"') .format(name), name) val = next(six.itervalues(argument)) contract = val.get('Contract') if contract: yield self._valid_contract(contract) usage = val.get('Usage') if usage: yield self._valid_argument_usage(usage) yield self._valid_keywords(val, METHOD_ARGUMENTS_KEYWORDS) def _valid_argument_usage(self, usage): if self._loaded_pkg.format_version < '1.4': yield error.report.E052(_('Arguments usage is available ' 'since 1.4'), usage) if usage not in frozenset(['Standard', 'VarArgs', 'KwArgs']): yield error.report.E053(_('Usage is invalid value "{}"') .format(usage), usage) murano-pkg-check-0.3.0/muranopkgcheck/validators/__init__.py0000664000567000056710000000163713044647404025305 0ustar jenkinsjenkins00000000000000# Copyright (c) 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 muranopkgcheck.validators import manifest from muranopkgcheck.validators import muranopl from muranopkgcheck.validators import package from muranopkgcheck.validators import ui VALIDATORS = [ manifest.ManifestValidator, muranopl.MuranoPLValidator, ui.UiValidator, package.PackageValidator, ] murano-pkg-check-0.3.0/muranopkgcheck/manager.py0000664000567000056710000001150713044647404023005 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import abc import itertools import pprint import sys import types import six import stevedore from muranopkgcheck import error from muranopkgcheck.i18n import _ from muranopkgcheck.i18n import _LE from muranopkgcheck import log from muranopkgcheck import pkg_loader from muranopkgcheck.validators import VALIDATORS LOG = log.getLogger(__name__) error.register.E000(description='Check failed') @six.add_metaclass(abc.ABCMeta) class Formatter(object): @abc.abstractmethod def format(self, error): pass # pragma: no cover class PlainTextFormatter(Formatter): def format(self, errors): lines = [] for e in errors: if e.filename: lines.append('{filename}:{line}:{column}: {code} {message}' ''.format(**e.to_dict())) else: lines.append('{code} {message}' ''.format(**e.to_dict())) return '\n'.join(lines) class Manager(object): def __init__(self, pkg_path, quiet_load=False, loader=None): if loader: self.pkg = loader(pkg_path) else: self.pkg = pkg_loader.load_package(pkg_path, quiet=quiet_load) self.validators = list(VALIDATORS) self.plugins = None def _to_list(self, error_chain, select=None, ignore=None): errors = [] while True: try: e = next(error_chain) except StopIteration: break except Exception: exc_info = sys.exc_info() tb = exc_info[2] while tb.tb_next: tb = tb.tb_next validator_class = tb.tb_frame.f_locals.get('self') check_name = tb.tb_frame.f_code.co_name check_locals = tb.tb_frame.f_locals.copy() check_locals.pop('self', None) if validator_class: msg = (_('Checker {} from {} failed!' '').format(check_name, validator_class.__class__.__name__)) else: msg = (_('Checker {} failed!' '').format(check_name)) LOG.error('{} {}\n{}'.format(msg, _('Checker locals:'), pprint.pformat(check_locals)), exc_info=exc_info) e = error.report.E000( msg + _(' See more information in logs.')) if isinstance(e, types.GeneratorType): errors.extend(self._to_list(e, select, ignore)) else: if ((select and e.code not in select) or (ignore and e.code in ignore)): LOG.debug('Skipped: {code} {message}' ''.format(**e.to_dict())) continue LOG.debug('Reported: {code} {message}' ''.format(**e.to_dict())) errors.append(e) return sorted(errors, key=lambda err: err.code) def load_plugins(self): if self.plugins is not None: return self.plugins = stevedore.ExtensionManager( 'muranopkgcheck.plugins', invoke_on_load=True, propagate_map_exceptions=True, on_load_failure_callback=self.failure_hook) plugin_validators = list(itertools.chain( *(p.obj.validators() for p in self.plugins) )) self.validators += plugin_validators @staticmethod def failure_hook(_, ep, err): LOG.error(_LE('Could not load {plugin}: {error}' '').format(plugin=ep.name, error=err)) raise err def validate(self, validators=None, select=None, ignore=None, only_errors=False): validators = validators or self.validators report_chains = [] for validator in validators: v = validator(self.pkg) report_chains.append(v.run()) issues = self._to_list(itertools.chain(*report_chains), select, ignore) if only_errors: return [err for err in issues if err.is_error()] else: return issues murano-pkg-check-0.3.0/muranopkgcheck/plugin.py0000664000567000056710000000153513044647404022671 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import abc import six @six.add_metaclass(abc.ABCMeta) class Plugin(object): @abc.abstractmethod def validators(self): pass # pragma: no cover @abc.abstractmethod def errors(self): pass # pragma: no cover murano-pkg-check-0.3.0/muranopkgcheck/error.py0000664000567000056710000000546113044647404022526 0ustar jenkinsjenkins00000000000000# Copyright (c) 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 muranopkgcheck.i18n import _LE errors = dict() class CheckError(Exception): def __init__(self, code, message, filename=None, line=0, column=0, source=None): self.code = code self.message = message self.filename = filename self.line = line self.column = column self.source = source def to_dict(self): fields = ('code', 'message', 'filename', 'line', 'column', 'source') serialized = {} for f in fields: serialized[f] = self.__getattribute__(f) return serialized def is_warning(self): return self.code.split(':')[-1].startswith('W') def is_error(self): return self.code.split(':')[-1].startswith('E') def __repr__(self): return 'CheckError({0})'.format(self.message) class Report(object): def __init__(self, errors, prefix=None): self.prefix = prefix self.errors = errors def __getattr__(self, code): code = ':'.join((self.prefix, code)) if self.prefix else code def _report(message, yaml_obj=None, filename=None): meta = getattr(yaml_obj, '__yaml_meta__', None) kwargs = {} if meta is not None: kwargs['line'] = meta.line + 1 kwargs['column'] = meta.column + 1 kwargs['source'] = meta.get_snippet() kwargs['filename'] = filename or meta.name return CheckError(code=code, message=message, **kwargs) if code not in self.errors: raise ValueError(_LE('Error {} was not registered').format(code)) return _report class Register(object): def __init__(self, errors, prefix=None): self.prefix = prefix self.errors = errors def __getattr__(self, code): code = ':'.join((self.prefix, code)) if self.prefix else code if code in self.errors: raise ValueError(_LE('Error {} is already registered') .format(code)) def _register(**kwargs): props = kwargs.copy() props['code'] = code self.errors[code] = props return _register report = Report(errors) register = Register(errors) murano-pkg-check-0.3.0/muranopkgcheck/log.py0000664000567000056710000000311013044647404022143 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import logging import six CRITICAL = logging.CRITICAL ERROR = logging.ERROR WARNING = logging.WARNING INFO = logging.INFO DEBUG = logging.DEBUG LOG_FORMAT = "%(name)s:%(lineno)d %(levelname)s %(message)s" DEFAULT_LEVEL = logging.DEBUG _loggers = {} _logging = None def setup(external_logging=None, log_format=LOG_FORMAT, level=DEFAULT_LEVEL): if external_logging: global _logging _logging = external_logging return console_log_handler = logging.StreamHandler() console_log_handler.setFormatter(logging.Formatter(log_format)) global _loggers for logger in six.itervalues(_loggers): logger.setLevel(level) for h in logger.handlers: logger.removeHandler(h) logger.addHandler(console_log_handler) def getLogger(name): global _logging, _loggers if _logging: return _logging.getLogger(name) if name not in _loggers: _loggers[name] = logging.getLogger(name) return _loggers[name] murano-pkg-check-0.3.0/muranopkgcheck/yaml_loader.py0000664000567000056710000000504213044647404023660 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import six import yaml __all__ = ['YamlLoader'] class YamlMetadata(object): def __init__(self, mark): self.mark = mark @property def line(self): return self.mark.line @property def column(self): return self.mark.column def get_snippet(self, indent=4, max_length=75): return self.mark.get_snippet(indent, max_length) class YamlObject(object): def __init__(self, value=None): self.value = value class YamlMapping(YamlObject, dict): pass class YamlSequence(YamlObject, list): pass class YamlString(YamlObject, six.text_type): pass class YamlNull(YamlObject): def __str__(self): return 'null' def __bool__(self): return False __nonzero__ = __bool__ BaseLoader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader) class YamlLoader(BaseLoader): def construct_yaml_seq(self, node): data = YamlSequence() yield data data.extend(self.construct_sequence(node)) data.__yaml_meta__ = node.start_mark def construct_yaml_str(self, node): value = super(YamlLoader, self).construct_yaml_str(node) value = YamlString(value) value.__yaml_meta__ = node.start_mark return value def construct_yaml_map(self, node): data = YamlMapping() yield data value = self.construct_mapping(node) data.update(value) data.__yaml_meta__ = node.start_mark def construct_yaml_null(self, node): value = YamlNull(node) value.__yaml_meta__ = node.start_mark return value YamlLoader.add_constructor( u'tag:yaml.org,2002:seq', YamlLoader.construct_yaml_seq) YamlLoader.add_constructor( u'tag:yaml.org,2002:str', YamlLoader.construct_yaml_str) YamlLoader.add_constructor( u'tag:yaml.org,2002:map', YamlLoader.construct_yaml_map) YamlLoader.add_constructor( u'tag:yaml.org,2002:null', YamlLoader.construct_yaml_null) murano-pkg-check-0.3.0/muranopkgcheck/pkg_loader.py0000664000567000056710000001375013044647404023504 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import abc import os import re import sys import zipfile import six import yaml from muranopkgcheck import consts from muranopkgcheck.i18n import _ from muranopkgcheck import log from muranopkgcheck import yaml_loader LOG = log.getLogger(__name__) class FileWrapper(object): def __init__(self, pkg, path): self._path = path with pkg.open_file(path) as file_: self._raw = file_.read() self._name = file_.name self._yaml = None self._pkg = pkg def raw(self): return self._raw def yaml(self): if self._yaml is None: sio = six.BytesIO(self.raw()) setattr(sio, 'name', self._name) self._yaml = list(yaml.load_all(sio, yaml_loader.YamlLoader)) return self._yaml @six.add_metaclass(abc.ABCMeta) class BaseLoader(object): def __init__(self, path): self.path = path self._cached_files = dict() self.format = consts.DEFAULT_FORMAT self.format_version = consts.DEFAULT_FORMAT_VERSION @classmethod @abc.abstractmethod def _try_load(cls, path): pass # pragma: no cover @classmethod def try_load(cls, path): loader = cls._try_load(path) if loader is not None and loader.exists(consts.MANIFEST_PATH): try: manifest = loader.read(consts.MANIFEST_PATH).yaml()[0] if 'FullName' not in manifest: LOG.warning('Package does not look like Murano package', exc_info=sys.exc_info()) return loader.try_set_format(manifest) except yaml.YAMLError: LOG.warning('Unable to parse Manifest yaml', exc_info=sys.exc_info()) return return loader @abc.abstractmethod def list_files(self, subdir=None): pass # pragma: no cover @abc.abstractmethod def open_file(self, path, mode='r'): pass # pragma: no cover @abc.abstractmethod def exists(self, name): pass # pragma: no cover def search_for(self, regex='.*', subdir=None): r = re.compile(regex) return (f for f in self.list_files(subdir) if r.match(f)) def read(self, path): if path in self._cached_files: return self._cached_files[path] self._cached_files[path] = FileWrapper(self, path) return self._cached_files[path] def try_set_format(self, manifest): if manifest and 'Format' in manifest: if '/' in six.text_type(manifest['Format']): fmt, version = manifest['Format'].split('/', 1) self.format = fmt self.format_version = version else: self.format_version = six.text_type(manifest['Format']) class DirectoryLoader(BaseLoader): @classmethod def _try_load(cls, path): if os.path.isdir(path): return cls(path) return None def open_file(self, path, mode='r'): return open(os.path.join(self.path, path), mode) def list_files(self, subdir=None): path = self.path if subdir is not None: path = os.path.join(path, subdir) files = [] for dirpath, dirnames, filenames in os.walk(path): files.extend( os.path.relpath( os.path.join(dirpath, filename), self.path) for filename in filenames) if subdir is None: return files subdir_len = len(subdir) return [file_[subdir_len:].lstrip('/') for file_ in files] def exists(self, name): return os.path.exists(os.path.join(self.path, name)) class ZipLoader(BaseLoader): def __init__(self, path): super(ZipLoader, self).__init__(path) if hasattr(self.path, 'read'): self._zipfile = zipfile.ZipFile(six.BytesIO(self.path.read())) else: self._zipfile = zipfile.ZipFile(self.path) @classmethod def _try_load(cls, path): try: return cls(path) except (IOError, zipfile.BadZipfile): return None def open_file(self, name, mode='r'): return self._zipfile.open(name, mode) def list_files(self, subdir=None): files = [file_ for file_ in self._zipfile.namelist() if not file_.endswith('/')] if subdir is None: return files subdir_len = len(subdir) return [file_[subdir_len:].strip('/') for file_ in files if file_.startswith(subdir)] def exists(self, name): try: self._zipfile.getinfo(name) return True except KeyError: pass if not name.endswith('/'): try: self._zipfile.getinfo(name + '/') return True except KeyError: pass return False PACKAGE_LOADERS = [DirectoryLoader, ZipLoader] def load_package(path, quiet=False): for loader_cls in PACKAGE_LOADERS: loader = loader_cls.try_load(path) if loader is not None: return loader else: if not quiet: LOG.debug("{} failed to load '{}'" "".format(loader_cls.__name__, path)) else: raise ValueError(_('Can not load package: "{}"').format(path)) murano-pkg-check-0.3.0/muranopkgcheck/checkers/0000775000567000056710000000000013044647631022606 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/checkers/code_structure.py0000664000567000056710000001553613044647404026222 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import re import six from muranopkgcheck.checkers import yaql_checker from muranopkgcheck import error ASSIGMENT_KEY = re.compile('^\$.?[\w]') def check_req(check, required=True): return locals() error.register.E203(description='Value should be string type') error.register.E200(description='No value should be here') error.register.E204(description='Wrong code structure/assigment') error.register.E202(description='Not a valid yaql expression') error.register.W202(description='Not a valid yaql expression') error.register.E201(description='Not a valid variable name') CODE_STRUCTURE = { 'Try': { 'keywords': { 'Try': check_req('codeblock'), 'Catch': check_req('catchblock'), 'Else': check_req('codeblock', False), 'Finally': check_req('codeblock', False)}}, 'Parallel': { 'keywords': { 'Limit': check_req('codeblock', False), 'Parallel': check_req('codeblock')}, }, 'Repeat': { 'keywords': { 'Repeat': check_req('number'), 'Do': check_req('codeblock')}}, 'If': { 'keywords': { 'If': check_req('predicate'), 'Then': check_req('codeblock'), 'Else': check_req('codeblock', False)} }, 'Break': { 'keywords': { 'Break': check_req('empty')} }, 'Return': { 'Return': check_req('expression'), }, 'While': { 'keywords': { 'While': check_req('predicate'), 'Do': check_req('codeblock')} }, 'For': { 'keywords': { 'For': check_req('string'), 'In': check_req('expression'), 'Do': check_req('codeblock')} }, 'Match': { 'keywords': { 'Match': check_req(('expression', 'codeblock')), 'Value': check_req('expression'), 'Default': check_req('codeblock', False), } }, 'Switch': { 'keywords': { 'Switch': check_req(('predicate', 'codeblock')), 'Default': check_req('codeblock')} }, 'Throw': { 'keywords': { 'Throw': check_req('string'), 'Message': check_req('string')} }, 'Continue': { 'keywords': { 'Continue': check_req('empty'), } }, 'Rethrow': { 'keywords': { 'Rethrow': check_req('empty'), } }, } class CheckCodeStructure(object): def __init__(self): self._check_mappings = { 'codeblock': self.codeblock, 'catchblock': self.catchblock, 'predicate': self.yaql, 'empty': self.empty, 'expression': self.yaql, 'string': self.string, 'number': self.yaql, } self._yaql_checker = yaql_checker.YaqlChecker() def string(self, value): if not isinstance(value, six.string_types): yield error.report.E203('Value of "{0}" should be a string' ''.format(value), value) def empty(self, value): if value: yield error.report.E200('Statement should be empty, not a ' '"{0}"'.format(value), value) def yaql(self, value): if not self._yaql_checker(value): if isinstance(value, bool): return yield error.report.W202('"{0}" is not valid yaql expression' ''.format(value), value) def catchblock(self, catchblock): if isinstance(catchblock, list): for block in catchblock: yield self._single_catchblock(block) else: yield self._single_catchblock(catchblock) def _single_catchblock(self, catchblock): do = catchblock.get('Do') if not do: yield error.report.E204('Catch is missing "Do" block', catchblock) else: yield self.codeblock(do) yield self.string(catchblock.get('With', '')) yield self.string(catchblock.get('As', '')) def codeblock(self, codeblocks): if isinstance(codeblocks, list): for block in codeblocks: yield self._single_block(block) else: yield self._single_block(codeblocks) def _check_assigment(self, block): key = next(iter(block)) if not isinstance(key, six.string_types) or\ not ASSIGMENT_KEY.match(key): yield error.report.E201('"{0}" is not valid variable name' ''.format(key), key) def _single_block(self, block): if isinstance(block, dict): yield self._check_structure(block) elif isinstance(block, six.string_types): yield self.yaql(block) def _run_check(self, check, value): yield self._check_mappings[check](value) def _check_structure(self, block): for key, value in CODE_STRUCTURE.items(): if key in block: break else: if len(block.keys()) == 1: yield self._check_assigment(block) else: yield error.report.E204('Wrong code structure/assigment. ' 'Probably a typo', block) return keywords = value.get('keywords', {}) kset = set(keywords.keys()) block_keys_set = set(block.keys()) for missing in (kset - block_keys_set): if keywords[missing]['required']: yield error.report.E204('Missing keyword "{0}" for "{1}" ' 'code structure' .format(missing, key), block) for unknown in (block_keys_set - kset - {key}): yield error.report.E201('Unknown keyword "{0}" in "{1}"' .format(unknown, key), unknown) for ckey, cvalue in keywords.items(): check = cvalue['check'] data = block.get(ckey) if not data: continue if isinstance(check, tuple): for left, right in data.items(): yield self._run_check(check[0], left) yield self._run_check(check[1], right) else: yield self._run_check(check, data) murano-pkg-check-0.3.0/muranopkgcheck/checkers/yaql_checker.py0000664000567000056710000000404013044647404025606 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import yaql ITERATORS_LIMIT = 100 EXPRESSION_MEMORY_QUOTA = 512 * 1024 ENGINE_10_OPTIONS = { 'yaql.limitIterators': ITERATORS_LIMIT, 'yaql.memoryQuota': EXPRESSION_MEMORY_QUOTA, 'yaql.convertSetsToLists': True, 'yaql.convertTuplesToLists': True, 'yaql.iterableDicts': True } ENGINE_12_OPTIONS = { 'yaql.limitIterators': ITERATORS_LIMIT, 'yaql.memoryQuota': EXPRESSION_MEMORY_QUOTA, 'yaql.convertSetsToLists': True, 'yaql.convertTuplesToLists': True } def _add_operators(engine_factory): engine_factory.insert_operator( '>', True, 'is', yaql.factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, False) engine_factory.insert_operator( None, True, ':', yaql.factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True) engine_factory.insert_operator( ':', True, ':', yaql.factory.OperatorType.PREFIX_UNARY, False) engine_factory.operators.insert(0, ()) def _create_engine(): engine_factory = yaql.factory.YaqlFactory() _add_operators(engine_factory=engine_factory) options = ENGINE_12_OPTIONS return engine_factory.create(options=options) class YaqlChecker(object): def __init__(self): self._engine = _create_engine() def __call__(self, data): try: self._engine(data) except yaql.utils.exceptions.YaqlParsingException: return False except TypeError: return False return True murano-pkg-check-0.3.0/muranopkgcheck/checkers/__init__.py0000664000567000056710000000000013044647404024703 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/consts.py0000664000567000056710000000130113044647404022673 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. MANIFEST_PATH = 'manifest.yaml' DEFAULT_FORMAT = 'MuranoPL' DEFAULT_FORMAT_VERSION = '1.0' murano-pkg-check-0.3.0/muranopkgcheck/tests/0000775000567000056710000000000013044647631022161 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/tests/test_pkg_loader.py0000664000567000056710000002315613044647404025706 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock import zipfile import six import yaml import yaml.error from muranopkgcheck import consts from muranopkgcheck import pkg_loader from muranopkgcheck.tests import base class FileWrapperTest(base.TestCase): @mock.patch('muranopkgcheck.pkg_loader.yaml') def test_file_wrapper(self, m_yaml): m_yaml.load_all.side_effect = yaml.load_all fake_pkg = mock.Mock() fake_pkg.open_file.side_effect = \ lambda f: mock.mock_open(read_data=six.b('text'))() f = pkg_loader.FileWrapper(fake_pkg, 'fake_path') self.assertEqual(six.b('text'), f.raw()) self.assertEqual(['text'], f.yaml()) m_yaml.load_all.assert_called() self.assertEqual(['text'], f.yaml()) m_yaml.load_all.reset_mock() m_yaml.load_all.assert_not_called() fake_pkg.open_file.side_effect = \ lambda f: mock.mock_open(read_data=six.b('!@#$%'))() f = pkg_loader.FileWrapper(fake_pkg, six.b('fake_path')) self.assertEqual(six.b('!@#$%'), f.raw()) self.assertRaises(yaml.error.YAMLError, f.yaml) class FakeLoader(pkg_loader.BaseLoader): @classmethod def _try_load(cls, path): return cls(path) def open_file(self, path, mode='r'): pass def exists(self, name): if name == consts.MANIFEST_PATH: return True def list_files(self, subdir=None): if subdir is None: return ['1.yaml', '2.sh', 'sub/3.yaml'] else: return ['3.yaml'] class BaseLoaderTest(base.TestCase): @mock.patch.object(FakeLoader, 'read') @mock.patch.object(FakeLoader, '_try_load') @mock.patch.object(FakeLoader, 'try_set_format') def test_try_load(self, m_format, m_load, m_read): m_read.return_value.yaml.return_value = [{'FullName': 'fake'}] m_load.return_value = FakeLoader('fake') fake_pkg = FakeLoader.try_load('fake') self.assertEqual(m_load.return_value, fake_pkg) m_load.assert_called_once_with('fake') m_format.assert_called_once_with({'FullName': 'fake'}) m_format.reset_mock() m_read.return_value.yaml.return_value = [{}] self.assertIsNone(FakeLoader.try_load('fake')) m_format.assert_not_called() m_format.reset_mock() m_read.return_value.yaml.side_effect = yaml.error.YAMLError() self.assertIsNone(FakeLoader.try_load('fake')) m_format.assert_not_called() @mock.patch.object(FakeLoader, '_try_load') def test_try_set_version(self, m_load): m_file_wrapper = mock.Mock() m_file = m_file_wrapper.return_value m_file.yaml.return_value = [{'Format': 'Fake/42', 'FullName': 'fake'}] with mock.patch('muranopkgcheck.pkg_loader.FileWrapper', m_file_wrapper): m_load.return_value = FakeLoader('fake') loader = FakeLoader.try_load('fake') self.assertEqual('Fake', loader.format) self.assertEqual('42', loader.format_version) m_file.yaml.assert_called_once_with() m_load.return_value = FakeLoader('fake') m_file.yaml.return_value = [{'Format': '4.2', 'FullName': 'fake'}] loader = FakeLoader.try_load('fake') self.assertEqual(consts.DEFAULT_FORMAT, loader.format) self.assertEqual('4.2', loader.format_version) m_load.return_value = FakeLoader('fake') m_file.yaml.return_value = [{'FullName': 'fake'}] loader = FakeLoader.try_load('fake') self.assertEqual(consts.DEFAULT_FORMAT, loader.format) self.assertEqual(consts.DEFAULT_FORMAT_VERSION, loader.format_version) def test_search_for(self): fake = FakeLoader('fake') self.assertEqual(['1.yaml', 'sub/3.yaml'], list(fake.search_for('.*\.yaml$'))) self.assertEqual(['3.yaml'], list(fake.search_for('.*\.yaml$', subdir='sub'))) def test_read(self): fake = FakeLoader('fake') m_file_wrapper = mock.Mock() m_file = m_file_wrapper.return_value with mock.patch('muranopkgcheck.pkg_loader.FileWrapper', m_file_wrapper): loaded = fake.read('fake') self.assertEqual(m_file, loaded) # check that cache works loaded = fake.read('fake') self.assertEqual(m_file, loaded) class DirectoryLoaderTest(base.TestCase): @mock.patch.object(pkg_loader.DirectoryLoader, 'read') @mock.patch.object(pkg_loader.DirectoryLoader, 'try_set_format') @mock.patch.object(pkg_loader.DirectoryLoader, 'exists') def _load_fake_pkg(self, m_exists, m_try_set_format, m_read): with mock.patch('muranopkgcheck.pkg_loader.os.path.isdir') as m_isdir: m_read.return_value.yaml.return_value = [{'FullName': 'fake'}] m_exists.return_value = True m_isdir.return_value = True loader = pkg_loader.DirectoryLoader.try_load('fake') m_try_set_format.assert_called_once_with({'FullName': 'fake'}) return loader def test_try_load(self): # NOTE(sslypushenko) Using mock.patch here as decorator breaks pdb pkg = self._load_fake_pkg() self.assertEqual('fake', pkg.path) with mock.patch('muranopkgcheck.pkg_loader.os.path.isdir') as m_isdir: m_isdir.return_value = False pkg = pkg_loader.DirectoryLoader.try_load('fake') self.assertIsNone(pkg) def test_list_files(self): # NOTE(sslypushenko) Using mock.patch here as decorator breaks pdb pkg = self._load_fake_pkg() with mock.patch('muranopkgcheck.pkg_loader.os.walk') as m_walk: m_walk.return_value = (item for item in [ ('fake', ['subdir'], ['1', '2']), ('fake/subdir', [], ['3', '4']), ]) self.assertEqual(['1', '2', 'subdir/3', 'subdir/4'], pkg.list_files()) m_walk.return_value = (item for item in [ ('fake/subdir', [], ['3', '4']), ]) self.assertEqual(['3', '4'], pkg.list_files(subdir='subdir')) def test_exist(self): # NOTE(sslypushenko) Using mock.patch here as decorator breaks pdb pkg = self._load_fake_pkg() with mock.patch('muranopkgcheck.pkg_loader' '.os.path.exists') as m_exists: m_exists.return_value = True self.assertTrue(pkg.exists('1.yaml')) m_exists.return_value = False self.assertFalse(pkg.exists('1.yaml')) class ZipLoaderTest(base.TestCase): @mock.patch.object(pkg_loader.ZipLoader, 'read') @mock.patch.object(pkg_loader.ZipLoader, 'try_set_format') @mock.patch.object(pkg_loader.ZipLoader, 'exists') @mock.patch('muranopkgcheck.pkg_loader.zipfile') def _load_fake_pkg(self, m_zip, m_exists, m_try_set_format, m_read): m_zip_file = m_zip.ZipFile.return_value m_read.return_value.yaml.return_value = [{'FullName': 'fake'}] m_exists.return_value = True loader = pkg_loader.ZipLoader.try_load('fake') m_try_set_format.assert_called_once_with({'FullName': 'fake'}) m_zip.ZipFile.assert_called_once_with('fake') return loader, m_zip_file def test_try_load(self): pkg, _ = self._load_fake_pkg() self.assertEqual('fake', pkg.path) with mock.patch('muranopkgcheck.pkg_loader.zipfile.ZipFile') as m_zip: m_zip.side_effect = zipfile.BadZipfile pkg = pkg_loader.ZipLoader.try_load('fake') self.assertIsNone(pkg) @mock.patch.object(pkg_loader.ZipLoader, 'read') @mock.patch.object(pkg_loader.ZipLoader, 'try_set_format') @mock.patch.object(pkg_loader.ZipLoader, 'exists') @mock.patch('muranopkgcheck.pkg_loader.zipfile') def test_try_load_from_bytesio(self, m_zip, m_exists, m_try_set_format, m_read): m_read.return_value.yaml.return_value = [{'FullName': 'fake'}] m_exists.return_value = True m_content = six.b('fake') loader = pkg_loader.ZipLoader.try_load(six.BytesIO(m_content)) self.assertIsNotNone(loader) m_try_set_format.assert_called_once_with({'FullName': 'fake'}) m_zip.ZipFile.assert_called() def test_list_files(self): pkg, m_zip = self._load_fake_pkg() m_zip.namelist.return_value = ['1', '2', 'sub/', 'sub/3', 'sub/4'] self.assertEqual(['1', '2', 'sub/3', 'sub/4'], pkg.list_files()) self.assertEqual(['3', '4'], pkg.list_files(subdir='sub')) def test_exist(self): pkg, m_zip = self._load_fake_pkg() self.assertTrue(pkg.exists('1.yaml')) m_zip.getinfo.side_effect = KeyError self.assertFalse(pkg.exists('1.yaml')) m_zip.getinfo.side_effect = [KeyError, None] self.assertTrue(pkg.exists('sub')) m_zip.getinfo.side_effect = [KeyError, KeyError] self.assertFalse(pkg.exists('sub')) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_manager.py0000664000567000056710000000721413044647404025206 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck import error from muranopkgcheck import manager from muranopkgcheck.tests import base class PlainTextFormatterTest(base.TestCase): def test_format(self): class FakeYamlMeta(object): def __init__(self): self.line = 0 self.column = 0 self.name = 'fake' def get_snippet(self): return 'fake' class FakeYamlNode(str): def __init__(self, value): super(FakeYamlNode, self).__init__() self.value = value self.__yaml_meta__ = FakeYamlMeta() formatter = manager.PlainTextFormatter() fake_yaml_node = FakeYamlNode('Fake!!!') errors = [error.report.E042('Fake!!!', fake_yaml_node)] self.assertEqual('fake:1:1: E042 Fake!!!', formatter.format(errors)) class ManagerTest(base.TestCase): def _assert_errors(self, expected, actual): expected_errors = list(expected) for e in actual: if e in expected_errors: expected_errors.remove(e) else: self.fail('Unexpected error {}'.format(e)) self.assertEqual([], expected_errors, 'Expected errors left') @mock.patch('muranopkgcheck.manager.pkg_loader') @mock.patch('muranopkgcheck.manager.error.report') def test_validate(self, m_error, m_pkg_loader): fake_error = error.CheckError('W007', 'Fake') m_error.W007.return_value = fake_error fake_E000_error = error.CheckError('E000', 'Fake') m_error.E000.return_value = fake_E000_error def error_generator(): yield fake_error def broken_checker(): a = 0 if 1 / a: yield fake_error def broken_method_checker(self): a = 0 if 1 / a: yield fake_error mgr = manager.Manager('fake') m_pkg_loader.load_package.assert_called_once_with('fake', quiet=False) MockValidator = mock.Mock() m_validator = MockValidator.return_value def prepare_errors(): return iter((fake_error, error_generator(), broken_checker(), broken_method_checker(m_validator))) m_validator.run.return_value = prepare_errors() errors = mgr.validate(validators=[MockValidator]) self._assert_errors( [fake_E000_error, fake_E000_error, fake_error, fake_error], errors) m_validator.run.return_value = prepare_errors() errors = mgr.validate(validators=[MockValidator], only_errors=True) self._assert_errors([fake_E000_error, fake_E000_error], errors) m_validator.run.return_value = prepare_errors() errors = mgr.validate(validators=[MockValidator], select=['W007']) self._assert_errors([fake_error, fake_error], errors) m_validator.run.return_value = prepare_errors() errors = mgr.validate(validators=[MockValidator], ignore=['E000']) self._assert_errors([fake_error, fake_error], errors) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_muranopl_validator.py0000664000567000056710000004605413044647404027503 0ustar jenkinsjenkins00000000000000# Copyright (c) 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 copy import deepcopy import mock from muranopkgcheck.tests import test_validator_helpers as helpers from muranopkgcheck.validators import muranopl from muranopkgcheck import yaml_loader MURANOPL_BASE = { 'Name': 'Instance', 'Namespaces': { '=': 'org.openstack.test', 'res': 'io.murano.resources', 'std': 'io.murano'}, 'Extends': 'res:LinuxMuranoInstance', 'Properties': { 'ports': { 'Contract': '$.class(NeutronPort).notNull()', 'Default': []}}, 'Methods': { 'foo': { 'Scope': 'Public', 'Arguments': [{ 'arg1': {'Contract': '$.string()', 'Usage': 'Standard'}}], 'Body': [ {'Do': [ '$port.deploy()', {'$template': { 'resources': { '$.name': { 'properties': { 'networks': [ {'port': '$port.getRef()'}]}}}}}, {'$arg1': '$arg1.mergeWith($template)'}], 'For': 'port', 'In': '$.ports'}, {'$sth': 'new(res:Neutron)'}, {'Return': '$arg1'}]}}, } class MuranoPlTests(helpers.BaseValidatorTestClass): def setUp(self): super(MuranoPlTests, self).setUp() self.loaded_package = mock.Mock() self.loaded_package.format_version = '1.4' self.mpl_validator = muranopl.MuranoPLValidator(self.loaded_package) def test_import(self): self.g = self.mpl_validator._valid_import(['aaa.bbb', 'ccc.ddd', 'fff', 'w_ww']) def test_import_error(self): self.g = self.mpl_validator._valid_import(['aaa.bbb', 'ccc.ddd', 'fff', 'w_ww#']) self.assertIn('Wrong namespace or FNQ of extended class "w_ww#"', next(self.g).message) def test_correct_name_single(self): self.g = self.mpl_validator._valid_name('A') self.assertEqual(0, len([e for e in self.g])) def test_correct_name_upper(self): self.g = self.mpl_validator._valid_name('ABC') self.assertEqual(0, len([e for e in self.g])) def test_dot_in_name(self): self.g = self.mpl_validator._valid_name('.') self.assertIn('Invalid class name "."', next(self.g).message) def test_startswith_number_in_name(self): self.g = self.mpl_validator._valid_name('1A') self.assertIn('Invalid class name "1A"', next(self.g).message) def test_dot_in_name_startswith_dot(self): self.g = self.mpl_validator._valid_name('.A') self.assertIn('Invalid class name ".A"', next(self.g).message) def test_dot_in_name_endswith_dot(self): self.g = self.mpl_validator._valid_name('A.') self.assertIn('Invalid class name "A."', next(self.g).message) def test_dot_in_name_double_dot(self): self.g = self.mpl_validator._valid_name('A..B') self.assertIn('Invalid class name "A..B"', next(self.g).message) def test_double_underscored_name(self): self.g = self.mpl_validator._valid_name('__Instance') self.assertIn('Invalid class name "__Instance"', next(self.g).message) def test_not_camel_case_name(self): self.g = self.mpl_validator._valid_name('notcamelcase') self.assertIn('Class name "notcamelcase" not in CamelCase', next(self.g).message) def test_not_camel_case_name_upper(self): self.g = self.mpl_validator._valid_name('ABCD') self.assertIn('Class name "ABCD" not in CamelCase', next(self.g).message) def test_not_camel_case_name_first_lower(self): self.g = self.mpl_validator._valid_name('almostCamel') self.assertIn('Class name "almostCamel" not in CamelCase', next(self.g).message) def test_whitespace_in_name(self): name = 'white space' self.g = self.mpl_validator._valid_name(name) self.assertIn('Invalid class name "white space"', next(self.g).message) def test_name_not_a_string(self): name = 42 self.g = self.mpl_validator._valid_name(name) self.assertIn('Class name should be a string', next(self.g).message) def test_properties_list(self): self.g = self.mpl_validator._valid_properties([]) self.assertIn('Properties should be a dict', next(self.g).message) def test_properties_usage(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Usage'] = 'OutIn' self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Not allowed usage "OutIn"', next(self.g).message) def test_wrong_type_namespace(self): self.g = self.mpl_validator._valid_namespaces([1, 2, 3]) self.assertIn('Wrong type of namespace', next(self.g).message) def test_namespace_with_double_equal(self): self.g = self.mpl_validator._valid_namespaces({'==': 'std.io'}) self.assertIn('Wrong name for namespace', next(self.g).message) def test_namespace_with_wrong_fqn(self): self.g = self.mpl_validator._valid_namespaces({'=': 'io.murano@'}) self.assertIn('Wrong namespace fqn "io.murano@"', next(self.g).message) def test_wrong_method_scope(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Scope'] = 'Wrong' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Wrong Scope "Wrong"', next(self.g).message) def test_methods_list(self): self.g = self.mpl_validator._valid_methods([]) self.assertIn('Methods are not a dict', next(self.g).message) def test_methods_null(self): self.g = self.mpl_validator._valid_methods(yaml_loader.YamlNull()) def test_dict_in_body(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Body'] = {'$a': 'b'} self.g = self.mpl_validator._valid_methods(m_dict) def test_error_in_method_scalar_body(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Body'] = '$.deploy(' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('"$.deploy(" is not valid yaql expression', next(self.g).message) def test_method_body_is_return(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Body'] = {'Return': '3'} self.g = self.mpl_validator._valid_methods(m_dict) def test_error_in_method_for_loop_in(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Body'][0]['In'] =\ '$.deploy(' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('"$.deploy(" is not valid yaql expression', next(self.g).message) def test_error_in_method_for_loop_body(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Body'][0]['Do'][1] =\ '$.deploy(' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('"$.deploy(" is not valid yaql expression', next(self.g).message) def test_missing_contract_in_properties(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) del p_dict['ports']['Contract'] self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Missing Contract in property "ports"', next(self.g).message) def test_contract_is_not_yaql(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = '$.deploy(' self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Contract is not valid yaql "$.deploy("', next(self.g).message) def test_contract_is_yaql_without_dollar(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = 'deploy()' self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Contract is not valid yaql "deploy()"', next(self.g).message) def test_contract_is_yaql_without_dot(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = '$deploy()' self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Contract is not valid yaql "$deploy()"', next(self.g).message) def test_contract_is_a_dict(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = { '$.int()': '$.string()', '$.string()': ['$.ports()'] } self.g = self.mpl_validator._valid_properties(p_dict) def test_contract_is_a_dict_with_two_levels(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = { '$.int()': '$.string()', '$.string()': {'$.int()': '$.ports()', '$.int()': { '$': [], '$.int()': '$.string()'}} } self.g = self.mpl_validator._valid_properties(p_dict) def test_contract_is_a_dict_with_list(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = { '$.int()': [], '$.int()': {} } self.g = self.mpl_validator._valid_properties(p_dict) def test_contract_list_with_numbers(self): self.g = self.mpl_validator._valid_contract(['$.string()', 1, 3]) def test_contract_is_a_list_with_dict(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = [{ '$.string()': '$.string()', '$.int()': [{'$.int()': '$.string()', '$.int()': []}]}] self.g = self.mpl_validator._valid_properties(p_dict) def test_contract_is_a_list_two_elements(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = ['$.string()', '$.int()'] self.g = self.mpl_validator._valid_properties(p_dict) def test_contract_is_a_number(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = 1 self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Contract is not valid yaql "1"', next(self.g).message) def test_contract_list_with_min_length(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = ['$.int()', 1] self.g = self.mpl_validator._valid_properties(p_dict) def test_contract_a_list_with_invalid_yaql(self): p_dict = deepcopy(MURANOPL_BASE['Properties']) p_dict['ports']['Contract'] = ['$.string('] self.g = self.mpl_validator._valid_properties(p_dict) self.assertIn('Contract is not valid yaql "$.string("', next(self.g).message) def test_extends_is_not_a_valid_list(self): p_dict = deepcopy(MURANOPL_BASE['Extends']) p_dict = ['abc:def', 1] self.g = self.mpl_validator._valid_extends(p_dict) self.assertIn('Wrong type of Extends field', next(self.g).message) def test_extends_fqn_wrong(self): p_dict = deepcopy(MURANOPL_BASE['Extends']) p_dict = ['abc:def.afdsa.das#'] self.g = self.mpl_validator._valid_extends(p_dict) self.assertIn('Wrong FNQ of extended class "abc:def.afdsa.das#"', next(self.g).message) def test_extends_is_not_valid(self): p_dict = deepcopy(MURANOPL_BASE['Extends']) p_dict = 4 self.g = self.mpl_validator._valid_extends(p_dict) self.assertIn('Wrong type of Extends field', next(self.g).message) def test_method_valid_name(self): self.g = self.mpl_validator._valid_methods({'foo': {}}) self.assertEqual(0, len([e for e in self.g])) def test_method_invalid_name(self): m_dict = {'foo#': {}} self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Invalid name of method "foo#"', next(self.g).message) def test_method_invalid_name_dot(self): m_dict = {'.foo': {}} self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Invalid name of method ".foo"', next(self.g).message) def test_method_valid_special_name(self): for name in muranopl.SPECIAL_METHODS: m_dict = {name: {}} self.g = self.mpl_validator._valid_methods(m_dict) def test_method_invalid_name_number(self): m_dict = {'1abc': {}} self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Invalid name of method "1abc"', next(self.g).message) def test_method_unknown_keyword(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) del m_dict['foo']['Body'] m_dict['foo']['Body2'] = [] self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Unknown keyword "Body2"', next(self.g).message) def test_body_number(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Body'] = 1 self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Body is not a list or scalar/yaql expression', next(self.g).message) def test_dot_function(self): m_dict = {'.init': {}} self.g = self.mpl_validator._valid_methods(m_dict) def test_method_is_a_list(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo'] = [1, 2] self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Method is not a dict', next(self.g).message) def test_method_scope_in_1_2(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) del m_dict['foo']['Arguments'][0]['arg1']['Usage'] self.loaded_package.format_version = '1.2' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Scope is not supported version earlier than 1.3', next(self.g).message) def test_method_usage_action_in_1_4(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Usage'] = 'Action' self.loaded_package.format_version = '1.4' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Usage "Action" is deprecated since 1.4', next(self.g).message) def test_method_usage_action(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Usage'] = 'Action' self.loaded_package.format_version = '1.4' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Usage "Action" is deprecated since 1.4', next(self.g).message) def test_method_wrong_usage_action(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) m_dict['foo']['Usage'] = 'Runtimed' self.loaded_package.format_version = '1.4' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Unsupported usage type "Runtimed"', next(self.g).message) def test_method_wrong_usage_static_in_1_3(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) del m_dict['foo']['Scope'] del m_dict['foo']['Arguments'][0]['arg1']['Usage'] m_dict['foo']['Usage'] = 'Static' self.loaded_package.format_version = '1.3' self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Usage "Static" is available from 1.3', next(self.g).message) def test_method_arguments_invalid(self): self.g = self.mpl_validator._valid_arguments('foobar') self.assertIn('Methods arguments should be a list or dict', next(self.g).message) def test_method_arguments_are_dict(self): self.g = self.mpl_validator._valid_arguments({'a': {'Contract': '$'}}) def test_method_arguments_are_dict_two_keys(self): self.g = self.mpl_validator._valid_arguments({'a': 'b', 'c': 'd'}) self.assertIn('It is not safe to define methods arguments as a dict ' 'with several keys', next(self.g).message) def test_method_arguments_element_is_two_key(self): arguments = [ {'a': {'Contract': '$.string()'}, 'b': {'Contract': '$.string()'}}] self.g = self.mpl_validator._valid_arguments(arguments) self.assertIn('Methods single argument should be a one key dict', next(self.g).message) def test_method_arguments_usage_1_3(self): self.loaded_package.format_version = '1.3' self.g = self.mpl_validator._valid_argument_usage('Standard') self.assertIn('Arguments usage is available since 1.4', next(self.g).message) def test_wrong_method_arguments_usage(self): self.g = self.mpl_validator._valid_argument_usage('Standard1') self.assertIn('Usage is invalid value "Standard1"', next(self.g).message) def test_method_arguments_invalid_name(self): self.g = self.mpl_validator._valid_arguments( [{'a#': {'Contract': '$.int()'}}]) self.assertIn('Invalid name of argument "a#"', next(self.g).message) def test_cardinality(self): self.g = self.mpl_validator._valid_cardinality('One') def test_wrong_cardinality(self): self.g = self.mpl_validator._valid_cardinality('ManyToMany') self.assertIn('Wrong Cardinality "ManyToMany"', next(self.g).message) def test_inherited(self): self.g = self.mpl_validator._valid_inherited(True) def test_wrong_inherited(self): self.g = self.mpl_validator._valid_inherited(0) self.assertIn('Inherited is not bool "0"', next(self.g).message) def test_applies(self): self.g = self.mpl_validator._valid_applies('Property') def test_wrong_applies(self): self.g = self.mpl_validator._valid_applies('all') self.assertIn('Wrong Applies "all"', next(self.g).message) def test_applies_dict(self): self.g = self.mpl_validator._valid_applies({}) self.assertIn('Wrong Applies "{}"', next(self.g).message) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_manifest_validator.py0000664000567000056710000001470513044647404027452 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck.tests import test_validator_helpers as helpers from muranopkgcheck.validators import manifest from muranopkgcheck import yaml_loader class ManifestValidatorTests(helpers.BaseValidatorTestClass): def setUp(self): super(ManifestValidatorTests, self).setUp() self._oe_patcher = mock.patch('os.path.exists') self.exists = self._oe_patcher.start() self.exists.return_value = [True, True] self.loaded_package = mock.MagicMock() # self.loaded_package = mock.Mock() # self.loaded_package.read.return_value.yaml.return_value = [ # {'Type': 'Application'}] self.mv = manifest.ManifestValidator(self.loaded_package) def test_format_as_number(self): self.g = self.mv._valid_format(1.3) def test_description(self): self.g = self.mv._valid_description(yaml_loader.YamlNull()) def test_description_string(self): self.g = self.mv._valid_description("lalal") def test_description_number(self): self.g = self.mv._valid_description(1.3) self.assertIn('Value is not valid string "1.3"', next(self.g).message) def test_wrong_format(self): self.g = self.mv._valid_format('0.9') self.assertIn('Not supported format version "0.9"', next(self.g).message) def test_valid_string(self): self.g = self.mv._valid_string([]) self.assertIn('Value is not a string "[]"', next(self.g).message) def test_heat_format(self): self.g = self.mv._valid_format('Heat/1.0') self.assertIn('Not supported format "Heat/1.0"', next(self.g).message) def test_heat_format_invalid_version_also(self): self.g = self.mv._valid_format('Heat/0.1.0') self.assertIn('Not supported format "Heat/0.1.0"', next(self.g).message) def test_unsupported_format(self): self.g = self.mv._valid_format('Heat.HOT') self.assertIn('Not supported format version "Heat.HOT"', next(self.g).message) def test_type(self): self.g = self.mv._valid_type('Application') def test_wrong_type(self): self.g = self.mv._valid_type('Shared Library') self.assertIn('Type is invalid "Shared Library"', next(self.g).message) def test_incorrect_package_version(self): self.g = self.mv._valid_version('a1.3') self.assertIn('Version format should be compatible with SemVer ' 'not "a1.3"', next(self.g).message) def test_wrong_require_type(self): self.g = self.mv._valid_require([1, 2, 3]) self.assertIn('Require is not a dict type', next(self.g).message) def test_wrong_require_fqn(self): self.g = self.mv._valid_require({'io.murano!': '1.3.2'}) self.assertIn('Require key is not valid FQN "io.murano!"', next(self.g).message) def test_require(self): self.g = self.mv._valid_require({'aaa.bbb': '>= 1.0.0'}) def test_not_existing_file(self): data = {'org.openstack.Flow': 'FlowClassifier.yaml', 'org.openstack.Instance': 'Instance.yaml'} self.loaded_package.search_for.return_value = ['FlowClassifier.yaml'] self.g = self.mv._valid_classes(data) self.assertIn('File "Instance.yaml" is present in Manifest, ' 'but not in filesystem', next(self.g).message) def test_extra_file_in_directory(self): data = {'org.openstack.Instance': 'Instance.yaml'} self.loaded_package.search_for.return_value = ['FlowClassifier.yaml', 'Instance.yaml'] self.g = self.mv._valid_classes(data) self.assertIn('File "FlowClassifier.yaml" is not present in Manifest, ' 'but it is in filesystem', next(self.g).message) def test_classess_list(self): data = [{'org.openstack.Instance': 'Instance.yaml'}] self.loaded_package.search_for.return_value = ['FlowClassifier.yaml', 'Instance.yaml'] self.g = self.mv._valid_classes(data) self.assertIn('Classes section should be a dict', next(self.g).message) def test_missing_ui_file(self): self.loaded_package.exists.return_value = False self.g = self.mv._valid_ui('ui.yaml') self.assertIn('There is no UI file "ui.yaml"', next(self.g).message) def test_missing_logo_file(self): self.loaded_package.exists.return_value = False self.g = self.mv._valid_logo('logo.png') self.assertIn('There is no Logo file "logo.png"', next(self.g).message) def test_wrong_logo_type(self): self.g = self.mv._valid_logo([1, 2, 3]) self.assertIn('Logo is not a string', next(self.g).message) def test_wrong_ui_type(self): self.g = self.mv._valid_ui([1, 2, 3]) self.assertIn('UI is not a string', next(self.g).message) def test_tags(self): self.g = self.mv._valid_tags(['whatever']) def test_tags_false(self): self.g = self.mv._valid_tags('whatever') self.assertIn('Tags should be a list', next(self.g).message) def test_logo_ui_existance(self): self.g = self.mv._valid_logo_ui_existance({'Logo': 0, 'UI': 0}) def test_logo_ui_existance_false(self): self.loaded_package.exists.return_value = True self.g = self.mv._valid_logo_ui_existance({}) def test_fullname_wrong(self): self.g = self.mv._valid_fullname('aaa.bbb.ccc#') self.assertIn('Invalid FullName "aaa.bbb.ccc#"', next(self.g).message) def test_fullname(self): self.g = self.mv._valid_fullname('invalid.fullname.!@#@') self.assertIn('Invalid FullName "invalid.fullname.!@#@"', next(self.g).message) murano-pkg-check-0.3.0/muranopkgcheck/tests/functional/0000775000567000056710000000000013044647631024323 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/tests/functional/cases/0000775000567000056710000000000013044647631025421 5ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/tests/functional/cases/case.yaml0000664000567000056710000000200413044647404027212 0ustar jenkinsjenkins00000000000000- case_no_errors - pkg: manifest.yaml: format: yaml content: Type: Library FullName: org.Test Description: Very important package Name: Test Classes: org.Test: Test.yaml LICENSE: format: raw content: LICENSE logo.png: format: raw content: logo Classes/Test.yaml: format: yaml content: Namespaces: =: org.Test Name: Test expected: [] --- - case_no_files - pkg: manifest.yaml: format: yaml content: Type: Application FullName: org.Test Description: Very important package Name: Test Classes: org.Test: Test.yaml expected: - code: E050 msg: File "Test.yaml" is present in Manifest, but not in filesystem - code: W073 msg: There is no UI file "ui.yaml" - code: W074 msg: There is no Logo file "logo.png" - code: W121 msg: Missing "LICENSE" in the packagemurano-pkg-check-0.3.0/muranopkgcheck/tests/functional/test_cases.py0000664000567000056710000000546213044647404027037 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import io import os import oslotest.base import six import testscenarios import yaml from muranopkgcheck import consts from muranopkgcheck import manager from muranopkgcheck import pkg_loader class DictLoader(pkg_loader.BaseLoader): @classmethod def _try_load(cls, pkg): if consts.MANIFEST_PATH in pkg: return cls(pkg) return None def __init__(self, pkg): super(DictLoader, self).__init__('') self.pkg = pkg def open_file(self, path, mode='r'): if self.pkg[path]['format'] == 'raw': sio = io.BytesIO(six.b(self.pkg[path]['content'])) setattr(sio, 'name', path) elif self.pkg[path]['format'] == 'yaml': content = yaml.safe_dump(self.pkg[path]['content']) sio = io.BytesIO(six.b(content)) setattr(sio, 'name', path) else: raise ValueError('Unknown type of content') return sio def list_files(self, subdir=None): files = self.pkg.keys() if subdir is None: return files subdir_len = len(subdir) return [file_[subdir_len:].lstrip('/') for file_ in files if file_.startswith(subdir)] def exists(self, name): return name in self.pkg class DictFormatter(manager.Formatter): def format(self, error): return sorted([{'code': e.code, 'msg': e.message} for e in error], key=lambda item: item['code']) def load_cases(): cases_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cases') cases_files = [os.path.join(cases_path, f)for f in os.listdir(cases_path) if os.path.isfile(os.path.join(cases_path, f))] cases = [] for cases_file in cases_files: with open(cases_file) as f: cases.extend(list(yaml.load_all(f))) return cases cases = load_cases() class TestCase(testscenarios.WithScenarios, oslotest.base.BaseTestCase): """Test case base class for all unit tests.""" scenarios = cases def test_foo(self): m = manager.Manager(self.pkg, loader=DictLoader) errors = m.validate() fmt = DictFormatter() self.assertEqual(self.expected, fmt.format(errors)) murano-pkg-check-0.3.0/muranopkgcheck/tests/functional/__init__.py0000664000567000056710000000000013044647404026420 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/tests/test_ui_validator.py0000664000567000056710000000533513044647404026260 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck.tests import test_validator_helpers as helpers from muranopkgcheck.validators import ui class UiValidatorTest(helpers.BaseValidatorTestClass): def setUp(self): super(UiValidatorTest, self).setUp() self.package = mock.Mock() self.ui_validator = ui.UiValidator(self.package) def test_forms(self): forms = [ {'name1': { 'fields': [{ 'name': 'whatever', 'type': 'integer', 'label': 'sth', 'description': 'something' }]}}] self.g = self.ui_validator._valid_forms(forms) def test_forms_required_bool(self): forms = [ {'name1': { 'fields': [{ 'name': 'whatever', 'type': 'integer', 'label': 'sth', 'description': 'something', 'required': 2, }]}}] self.g = self.ui_validator._valid_forms(forms) self.assertIn('Value of', next(self.g).message) def test_forms_hidden_bool(self): forms = [ {'name1': { 'fields': [{ 'name': 'whatever', 'type': 'integer', 'label': 'sth', 'description': 'something', 'hidden': 2, }]}}] self.g = self.ui_validator._valid_forms(forms) self.assertIn('Value of', next(self.g).message) def test_version(self): self.g = self.ui_validator._valid_version('0.9') self.assertIn('Incorrect version of UI file "0.9"', next(self.g).message) def test_aplication_list(self): self.g = self.ui_validator._valid_application([]) self.assertIn('Application is not a dict', next(self.g).message) def test_aplication_wrong_name(self): self.g = self.ui_validator._valid_application({ '??': 'fasdf.dfas.dfd'}) self.assertIn('Wrong name in UI file "??"', next(self.g).message) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_code_structure_checker.py0000664000567000056710000001302513044647404030307 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import six from muranopkgcheck.checkers import code_structure from muranopkgcheck.tests import test_validator_helpers as helpers class CodeStructureTest(helpers.BaseValidatorTestClass): def setUp(self): super(CodeStructureTest, self).setUp() self._checker = code_structure.CheckCodeStructure() def test_simple(self): SIMPLE_BODY = '$.deploy()' self.g = self._checker.codeblock(SIMPLE_BODY) def test_double_assigment(self): SIMPLE_BODY = [{ '$a': '$.deploy()', '$b': '$.string()'}] self.g = self._checker.codeblock(SIMPLE_BODY) self.assertIn('Wrong code structure/assigment. Probably a typo', next(self.g).message) def test_multiline(self): MULTILINE_BODY = [ '$.deploy()', {'$res': 'new(YaqlStuff)'}, '$.call($res)', ] self.g = self._checker.codeblock(MULTILINE_BODY) def test_bad_assigment(self): MULTILINE_BODY = [ '$.deploy()', {1: 'new(YaqlStuff)'}, '$.call($res)', ] self.g = self._checker.codeblock(MULTILINE_BODY) self.assertIn('"1" is not valid variable name', next(self.g).message) def test_bad_assigment_with_double_dollar(self): MULTILINE_BODY = [ '$.deploy()', {'$$': 'new(YaqlStuff)'}, '$.call($res)', ] self.g = self._checker.codeblock(MULTILINE_BODY) self.assertIn('"$$" is not valid variable name', next(self.g).message) def test_bad_assigment_case2(self): MULTILINE_BODY = [ '$.deploy()', {'res': 'new(YaqlStuff)'}, '$.call($res)', ] self.g = self._checker.codeblock(MULTILINE_BODY) p = next(self.g) self.assertIn('"res" is not valid variable name', p.message) def test_if(self): MULTILINE_BODY = [ {'If': '$.deploy()', 'Then': [ '$.w()', {'$abc': '$a'}]} ] self.g = self._checker.codeblock(MULTILINE_BODY) def test_while_missing_do(self): MULTILINE_BODY = [ {'While': '$.deploy()'} ] self.g = self._checker.codeblock(MULTILINE_BODY) p = next(self.g) self.assertIn('Missing keyword "Do" for "While" code structure', p.message) def test_while_unknown_does(self): MULTILINE_BODY = [ {'While': '$.deploy()', 'Does': ['$.a()', '$.b()']} ] self.g = self._checker.codeblock(MULTILINE_BODY) p1 = next(self.g) p2 = next(self.g) six.assertCountEqual(self, [ 'Unknown keyword "Does" in "While"', 'Missing keyword "Do" for "While" code structure'], [p1.message, p2.message]) def test_empty_return(self): MULTILINE_BODY = [ {'Return': ''} ] self.g = self._checker.codeblock(MULTILINE_BODY) def test_switch(self): MULTILINE_BODY = [ {'Switch': { '$.black()': '$.single()', '$.blue()': [ '$.b()', {'$w': 3}]}, 'Default': '$.a()'} ] self.g = self._checker.codeblock(MULTILINE_BODY) def test_error_under_while_in_if(self): MULTILINE_BODY = [ {'If': '1', 'Then': {'While': '$.deploy()', 'Do': [ {'www': '$.a()'}, '$.b()']}} ] self.g = self._checker.codeblock(MULTILINE_BODY) self.assertIn('"www" is not valid variable name', next(self.g).message) def test_minimal_try_block(self): MULTILINE_BODY = [ {'Try': [ '$port.deploy()'], 'Catch': {}}] self.g = self._checker.codeblock(MULTILINE_BODY) def test_try_not_string(self): MULTILINE_BODY = [ {'Try': ['$port.deploy()'], 'Catch': { 'With': 'exceptionName', 'As': 213, 'Do': ['$.string()']}} ] self.g = self._checker.codeblock(MULTILINE_BODY) self.assertIn('Value of "213" should be a string', next(self.g).message) def test_try_only_do(self): MULTILINE_BODY = [ {'Try': ['$port.deploy()'], 'Catch': [{ 'Do': ['$.string()']}]} ] self.g = self._checker.codeblock(MULTILINE_BODY) def test_yaql_accept_bool(self): self.g = self._checker.yaql(True) def test_not_empty(self): MULTILINE_BODY = [ '$.deploy()', {'$d': 'new(YaqlStuff)'}, '$.call($res)', {'Break': 'a'}, ] self.g = self._checker.codeblock(MULTILINE_BODY) self.assertIn('Statement should be empty, not a "a"', next(self.g).message) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_error.py0000664000567000056710000000361013044647404024721 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck import error from muranopkgcheck.tests import base class LogTest(base.TestCase): @mock.patch('muranopkgcheck.error.CheckError') def test_errors(self, m_error): errors = dict() register = error.Register(errors, prefix='PRE') report = error.Report(errors, prefix='PRE') register.F042(description='Fake error') self.assertRaises(ValueError, register.__getattr__, code='F042') self.assertEqual({'PRE:F042': {'code': 'PRE:F042', 'description': 'Fake error'}}, errors) yaml_obj = mock.MagicMock(__yaml_meta__=mock.Mock(line=1, column=1)) type(yaml_obj.__yaml_meta__).name = mock.PropertyMock( return_value='fake.yaml') yaml_obj.__yaml_meta__.get_snippet.return_value = 'fake_code' report.F042('It is an error!', yaml_obj) m_error.assert_called_once_with( code='PRE:F042', column=2, line=2, filename='fake.yaml', message='It is an error!', source='fake_code') self.assertRaises(ValueError, report.__getattr__, code='FAKE') murano-pkg-check-0.3.0/muranopkgcheck/tests/base.py0000664000567000056710000000140113044647404023437 0ustar jenkinsjenkins00000000000000# Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" murano-pkg-check-0.3.0/muranopkgcheck/tests/test_validator_helpers.py0000664000567000056710000000240113044647404027274 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import types import unittest class BaseValidatorTestClass(unittest.TestCase): def setUp(self): self._g = [] def tearDown(self): problems = [p for p in self.g] for p in problems: print('Left errors:', p) self.assertEqual(len(problems), 0) def _linear(self, error_chain): for e in error_chain: if isinstance(e, types.GeneratorType): for w in self._linear(e): yield w else: yield e def get_g(self): return self._g def set_g(self, value): self._g = self._linear(value) g = property(get_g, set_g) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_package.py0000664000567000056710000000276413044647404025174 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck.tests import test_validator_helpers as helpers from muranopkgcheck.validators import package class PackageValidatorTests(helpers.BaseValidatorTestClass): def setUp(self): super(PackageValidatorTests, self).setUp() self.loaded_package = mock.Mock() self.pv = package.PackageValidator(self.loaded_package) def test_known_files(self): self.loaded_package.search_for.return_value = [ 'manifest.yaml', 'LICENSE', 'logo'] self.g = self.pv.run() self.assertIn('Unknown "logo" in the package', next(self.g).message) def test_known_files_missing_req(self): self.loaded_package.search_for.return_value = [ 'manifest.yaml', 'logo.png'] self.g = self.pv.run() self.assertIn('Missing "LICENSE" in the package', next(self.g).message) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_yaml_validator.py0000664000567000056710000000452013044647404026600 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock import unittest from muranopkgcheck.validators import base class YamlValidatorTest(unittest.TestCase): def setUp(self): self.pkg = mock.Mock() self.pkg.search_for.return_value = ['sth'] self.fmock = mock.Mock() self.document = mock.Mock() self.pkg.read.return_value = self.fmock self.v = base.YamlValidator(self.pkg, '***') def test_checker_with_ast(self): c = mock.Mock() c.return_value = 'ok' self.fmock.yaml.return_value = [{}] self.v.add_checker(c) self.v.run() c.assert_called_once_with({}) self.pkg.search_for.assert_called_once_with('***') def test_run_single_with_key_checker(self): c = mock.Mock() c.return_value = 'ok' self.fmock.yaml.return_value = [{'key': 'whatever'}] self.v.add_checker(c, 'key') self.v.run() c.assert_called_once_with('whatever') self.pkg.search_for.assert_called_once_with('***') def test_two_keys_unknown_key(self): c = mock.Mock() c.return_value = None self.fmock.yaml.return_value = [{'key': 'whatever', 'unknown': ''}] self.v.add_checker(c, 'key') errors = self.v.run() c.assert_called_once_with('whatever') self.pkg.search_for.assert_called_once_with('***') self.assertIn('Unknown keyword "unknown"', next(errors).message) def test_missing_required_key(self): c = mock.Mock() self.fmock.yaml.return_value = [{}] self.v.add_checker(c, 'key') errors = self.v.run() self.pkg.search_for.assert_called_once_with('***') self.assertIn('Missing required key "key"', next(errors).message) murano-pkg-check-0.3.0/muranopkgcheck/tests/test_plugin.py0000664000567000056710000000355513044647404025076 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck import manager from muranopkgcheck import plugin from muranopkgcheck.tests import test_validator_helpers as helpers from muranopkgcheck import validators class FakePlugin(plugin.Plugin): def validators(self): pass def errors(self): pass class PluginTest(helpers.BaseValidatorTestClass): def test_plugin(self): fake = FakePlugin() self.assertIsNotNone(fake) @mock.patch('muranopkgcheck.manager.pkg_loader') @mock.patch('muranopkgcheck.manager.stevedore') def test_load_plugins(self, m_stevedore, m_pkg_loader): fake_validator = mock.Mock() fake_plugin = mock.Mock() fake_plugin.obj.validators.return_value = [fake_validator] m_stevedore.ExtensionManager.return_value = [fake_plugin] fake_manager = manager.Manager('fake') m_pkg_loader.load_package.assert_called_once_with('fake', quiet=False) fake_manager.load_plugins() m_stevedore.ExtensionManager.assert_called() self.assertEqual(validators.VALIDATORS + [fake_validator], fake_manager.validators) m_stevedore.ExtensionManager.reset_mock() fake_manager.load_plugins() m_stevedore.ExtensionManager.assert_not_called() murano-pkg-check-0.3.0/muranopkgcheck/tests/__init__.py0000664000567000056710000000000013044647404024256 0ustar jenkinsjenkins00000000000000murano-pkg-check-0.3.0/muranopkgcheck/tests/test_log.py0000664000567000056710000000426313044647404024356 0ustar jenkinsjenkins00000000000000# Copyright (c) 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. import mock from muranopkgcheck import log from muranopkgcheck.tests import base class LogTest(base.TestCase): @mock.patch('muranopkgcheck.log.logging') def test_get_logger(self, m_log): fake_logger = log.getLogger('fake') m_log.getLogger.assert_called_once_with('fake') self.assertEqual(fake_logger, log._loggers['fake']) self.assertEqual(fake_logger, m_log.getLogger.return_value) m_log.reset_mock() fake_logger = log.getLogger('fake') m_log.assert_not_called() self.assertEqual(fake_logger, log._loggers['fake']) @mock.patch('muranopkgcheck.log.logging') def test_setup(self, m_log): fake_handler = m_log.StreamHandler.return_value fake_formatter = m_log.Formatter.return_value fake_logger = mock.Mock() fake_old_handler = mock.Mock() fake_logger.handlers = [fake_old_handler] log._loggers['fake'] = fake_logger log.setup(log_format='fake_format', level=42) m_log.StreamHandler.assert_called_once_with() fake_handler.setFormatter.assert_called_once_with(fake_formatter) fake_logger.setLevel.assert_called_once_with(42) fake_logger.removeHandler.assert_called_once_with(fake_old_handler) fake_logger.addHandler.assert_called_once_with(fake_handler) def test_setup_ext_logging(self): fake_logging = mock.Mock() log.setup(external_logging=fake_logging) self.assertEqual(log._logging, fake_logging) log.getLogger('fake_name') fake_logging.getLogger.assert_called_once_with('fake_name') murano-pkg-check-0.3.0/muranopkgcheck/i18n.py0000664000567000056710000000220413044647404022144 0ustar jenkinsjenkins00000000000000 # 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. """oslo.i18n integration module. See http://docs.openstack.org/developer/oslo.i18n/usage.html """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='muranopkgcheck') # The primary translation function using the well-known name "_" _ = _translators.primary # Translators for log levels. # # The abbreviated names are meant to reflect the usual use of a short # name like '_'. The "L" is for "log" and the other letter comes from # the level. _LI = _translators.log_info _LW = _translators.log_warning _LE = _translators.log_error _LC = _translators.log_critical murano-pkg-check-0.3.0/muranopkgcheck/__init__.py0000664000567000056710000000124013044647404023123 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- # 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 pbr.version __version__ = pbr.version.VersionInfo( 'murano-pkg-check').version_string() murano-pkg-check-0.3.0/test-requirements.txt0000664000567000056710000000104413044647404022254 0ustar jenkinsjenkins00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking<0.12,>=0.11.0 # Apache-2.0 coverage>=4.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD sphinx!=1.3b1,<1.4,>=1.2.1 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT # releasenotes reno>=1.8.0 # Apache-2.0 murano-pkg-check-0.3.0/requirements.txt0000664000567000056710000000061313044647404021300 0ustar jenkinsjenkins00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.8 # Apache-2.0 PyYAML>=3.10.0 # MIT yaql>=1.1.0 # Apache 2.0 License six>=1.9.0 # MIT stevedore>=1.17.1 # Apache-2.0 semantic-version>=2.3.1 # BSD oslo.i18n>=2.1.0 # Apache-2.0