diff --git a/fuel_plugin_builder/fuel_plugin_builder/__init__.py b/fuel_plugin_builder/fuel_plugin_builder/__init__.py new file mode 100644 index 0000000..ce4c008 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2014 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. diff --git a/fuel_plugin_builder/fuel_plugin_builder/actions/__init__.py b/fuel_plugin_builder/fuel_plugin_builder/actions/__init__.py new file mode 100644 index 0000000..e9c31bf --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/actions/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2014 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 fuel_plugin_builder.actions.base import BaseAction +from fuel_plugin_builder.actions.create import CreatePlugin +from fuel_plugin_builder.actions.build import BuildPlugin diff --git a/fuel_plugin_builder/fuel_plugin_builder/actions/base.py b/fuel_plugin_builder/fuel_plugin_builder/actions/base.py new file mode 100644 index 0000000..5a822c5 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/actions/base.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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 BaseAction(object): + + @abc.abstractmethod + def check(self): + """Check if it's possible to perform an action. + """ + + @abc.abstractmethod + def run(self): + """Run an action. + """ diff --git a/fuel_plugin_builder/fuel_plugin_builder/actions/build.py b/fuel_plugin_builder/fuel_plugin_builder/actions/build.py new file mode 100644 index 0000000..46ac7ff --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/actions/build.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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 +import logging + +from fuel_plugin_builder import utils +from fuel_plugin_builder import errors +from fuel_plugin_builder.actions import BaseAction + + +logger = logging.getLogger(__name__) + + +class BuildPlugin(BaseAction): + + requires = ['rpm', 'createrepo', 'dpkg-scanpackages'] + + def __init__(self, plugin_path): + self.plugin_path = plugin_path + self.pre_build_hook_path = os.path.join(plugin_path, 'pre_build_hook') + + self.centos_repo_path = os.path.join(plugin_path, 'repos/centos') + self.ubuntu_repo_path = os.path.join(plugin_path, 'repos/ubuntu') + + def run(self): + if utils.which(self.pre_build_hook_path): + utils.exec_cmd(self.pre_build_hook_path) + + utils.exec_cmd( + 'createrepo -o {0} {1}'.format( + os.path.join(self.centos_repo_path, 'x86_64'), + os.path.join(self.centos_repo_path, 'x86_64', 'Packages'))) + utils.exec_cmd( + 'dpkg-scanpackages {0} | gzip -c9 > {1}'.format( + self.ubuntu_repo_path, + os.path.join(self.ubuntu_repo_path, 'Packages.gz'))) + + def check(self): + not_found = filter(lambda r: not utils.which(r), self.requires) + + if not_found: + raise errors.FuelCannotFindCommandError( + 'Cannot find commands "{0}", ' + 'install required commands and try again'.format( + ','.join(not_found))) diff --git a/fuel_plugin_builder/fuel_plugin_builder/actions/create.py b/fuel_plugin_builder/fuel_plugin_builder/actions/create.py new file mode 100644 index 0000000..df42d1f --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/actions/create.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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 + +from fuel_plugin_builder.actions import BaseAction + +logger = logging.getLogger(__name__) + + +class CreatePlugin(BaseAction): + + def __init__(self, plugin_name): + self.plugin_name = plugin_name + + + def check(self): + pass + + def run(self): + pass diff --git a/fuel_plugin_builder/fuel_plugin_builder/cli.py b/fuel_plugin_builder/fuel_plugin_builder/cli.py new file mode 100644 index 0000000..caf8547 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/cli.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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 sys +import argparse + +from fuel_plugin_builder import actions +from fuel_plugin_builder import messages +from fuel_plugin_builder import errors + +from fuel_plugin_builder.logger import configure_logger +logger = configure_logger() + + + +def handle_exception(exc): + logger.exception(exc) + + if isinstance(exc, errors.FuelCannotFindCommandError): + print(messages.header) + print(messages.install_required_packages) + + sys.exit(-1) + + +def parse_args(): + """Parse arguments and return them + """ + parser = argparse.ArgumentParser( + description='fpb is a fuel plugin builder which ' + 'helps you create plugin for Fuel') + + group = parser.add_mutually_exclusive_group(required=True) + + group.add_argument( + '--create', help='create a plugin skeleton', + nargs=1, metavar='plugin_name') + group.add_argument( + '--build', help='build a plugin', + nargs=1, metavar='path_to_directory') + + return parser.parse_args() + + +def perform_action(args): + """Performs an action + + :param args: argparse object + """ + + if args.create: + plugin_name = args.create[0] + logger.debug('Start plugin creation "%s"', args.create) + action = actions.CreatePlugin(plugin_name) + elif args.build: + plugin_path = args.build[0] + logger.debug('Start plugin building "%s"', args.build) + action = actions.BuildPlugin(plugin_path) + + action.check() + action.run() + + +def main(): + """Entry point + """ + try: + perform_action(parse_args()) + except Exception as exc: + handle_exception(exc) diff --git a/fuel_plugin_builder/fuel_plugin_builder/errors.py b/fuel_plugin_builder/fuel_plugin_builder/errors.py new file mode 100644 index 0000000..d659014 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/errors.py @@ -0,0 +1,25 @@ +# Copyright 2014 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. + + +class FuelPluginException(Exception): + pass + + +class FuelCannotFindCommandError(Exception): + pass + + +class ExecutedErrorNonZeroExitCode(Exception): + pass diff --git a/fuel_plugin_builder/fuel_plugin_builder/logger.py b/fuel_plugin_builder/fuel_plugin_builder/logger.py new file mode 100644 index 0000000..289d009 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/logger.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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 sys + + +def configure_logger(): + logger = logging.getLogger('fuel_plugin_builder') + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter( + '%(asctime)s %(levelname)s %(process)d (%(module)s) %(message)s', + "%Y-%m-%d %H:%M:%S") + + stream_handler = logging.StreamHandler() + stream_handler.setLevel(logging.DEBUG) + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + + return logger diff --git a/fuel_plugin_builder/fuel_plugin_builder/messages.py b/fuel_plugin_builder/fuel_plugin_builder/messages.py new file mode 100644 index 0000000..768d674 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/messages.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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. + + +header = '=' * 50 + + +install_required_packages = """ +Was not able to find required packages, try to run: + + # sudo apt-get install createrepo rpm dpkg-dev +""" diff --git a/fuel_plugin_builder/fuel_plugin_builder/utils.py b/fuel_plugin_builder/fuel_plugin_builder/utils.py new file mode 100644 index 0000000..a8cdb22 --- /dev/null +++ b/fuel_plugin_builder/fuel_plugin_builder/utils.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014 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 +import logging + +import subprocess + +from fuel_plugin_builder import errors + +logger = logging.getLogger(__name__) + + +def is_executable(file_path): + """Checks if file executable + + :param str file_path: path to the file + :returns: True if file is executable, False if is not + """ + return os.path.isfile(file_path) and os.access(file_path, os.X_OK) + + +def which(cmd): + """Checks if file executable + + :param str cmd: the name of the command or path + + :returns: None if there is no such command, + if there is such command returns + the path to the command + """ + + fpath, fname = os.path.split(cmd) + if fpath: + if is_executable(cmd): + return cmd + + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, cmd) + if is_executable(exe_file): + return exe_file + + return None + + +def exec_cmd(cmd): + """Execute command with logging. + Ouput of stdout and stderr will be written + in log. + + :param cmd: shell command + """ + logger.debug(u'Execute command "{0}"'.format(cmd)) + child = subprocess.Popen( + cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True) + + logger.debug(u'Stdout and stderr of command "{0}":'.format(cmd)) + for line in child.stdout: + logger.debug(line.rstrip()) + + + child.wait() + exit_code = child.returncode + + if exit_code != 0: + raise errors.ExecutedErrorNonZeroExitCode( + u'Shell command executed with "{0}" ' + 'exit code: {1} '.format(exit_code, cmd)) + + logger.debug(u'Command "{0}" successfully executed'.format(cmd)) diff --git a/fuel_plugin_builder/requirements.txt b/fuel_plugin_builder/requirements.txt new file mode 100644 index 0000000..f6ce0bd --- /dev/null +++ b/fuel_plugin_builder/requirements.txt @@ -0,0 +1,2 @@ +argparse==1.2.1 +six==1.5.2 diff --git a/fuel_plugin_builder/setup.py b/fuel_plugin_builder/setup.py new file mode 100644 index 0000000..f7e16d7 --- /dev/null +++ b/fuel_plugin_builder/setup.py @@ -0,0 +1,49 @@ +# Copyright 2014 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 +import re + +from setuptools import find_packages +from setuptools import setup + + +def find_requires(): + dir_path = os.path.dirname(os.path.realpath(__file__)) + requirements = [] + with open(u'{0}/requirements.txt'.format(dir_path), 'r') as reqs: + requirements = reqs.readlines() + return requirements + + +setup( + name='fuel_plugin_builder', + version='0.1.0', + description='Helps to create and build fuel plugins', + long_description="""Helps to create and build fuel plugins""", + classifiers=[ + "Programming Language :: Python", + "Topic :: System :: Software Distribution"], + author='Mirantis Inc.', + author_email='product@mirantis.com', + url='http://mirantis.com', + keywords='fuel plugins plugin', + packages=find_packages(), + zip_safe=False, + install_requires=find_requires(), + include_package_data=True, + package_data={'': ['templates/*']}, + entry_points={ + 'console_scripts': [ + 'fpb = fuel_plugin_builder.cli:main']}) diff --git a/fuel_plugin_builder/test-requirements.txt b/fuel_plugin_builder/test-requirements.txt new file mode 100644 index 0000000..e40ab3c --- /dev/null +++ b/fuel_plugin_builder/test-requirements.txt @@ -0,0 +1,6 @@ +-r requirements.txt +hacking==0.7 +mock==1.0 +nose==1.1.2 +nose2==0.4.1 +nose-timer==0.2.0 diff --git a/fuel_plugin_builder/tox.ini b/fuel_plugin_builder/tox.ini new file mode 100644 index 0000000..2d4cc4e --- /dev/null +++ b/fuel_plugin_builder/tox.ini @@ -0,0 +1,38 @@ +[tox] +minversion = 1.6 +skipsdist = True +envlist = py26,py27,pep8 + +[testenv] +usedevelop = True +install_command = pip install {packages} +setenv = VIRTUAL_ENV={envdir} +deps = -r{toxinidir}/test-requirements.txt +commands = + nosetests {posargs:fuel_plugin} + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv:pep8] +deps = hacking==0.7 +usedevelop = False +commands = + flake8 {posargs:.} + +[testenv:venv] +commands = {posargs:} + +[testenv:devenv] +envdir = devenv +usedevelop = True + +[flake8] +ignore = H302,H802 +exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs +show-pep8 = True +show-source = True +count = True + +[hacking] +import_exceptions = testtools.matchers