From b5e0bc5b5870659b153f44056512795c784729c8 Mon Sep 17 00:00:00 2001 From: Nir Cohen Date: Wed, 5 Oct 2016 09:31:34 +0300 Subject: [PATCH] Prepare for 0.2.0 release --- .travis.yml | 12 ++++- README.md | 8 +-- README.rst | 11 +--- dev-requirements.txt | 6 +-- packer.py | 116 ++++++++++++++++++++++++++++++------------- setup.py | 2 +- tester.py | 26 ---------- tests/test_packer.py | 6 +-- tox.ini | 25 ++++++++-- 9 files changed, 125 insertions(+), 87 deletions(-) delete mode 100644 tester.py diff --git a/.travis.yml b/.travis.yml index 9b16a78..8b98415 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,22 @@ sudo: false language: python python: - "2.7" +matrix: + include: + - python: 3.5 + env: + - TOX_ENV=py35 + - python: 3.4 + env: + - TOX_ENV=py34 env: - TOX_ENV=flake8 - TOX_ENV=py27 - TOX_ENV=py26 install: - pip install tox + - pip install codecov script: - tox -e $TOX_ENV - +after_success: + - codecov \ No newline at end of file diff --git a/README.md b/README.md index 8f078d2..d269659 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,15 @@ The `output_file` parameter will write the output of the `fix` function to a fil ### [Packer.inspect()](https://www.packer.io/docs/command-line/inspect.html) -A `-machine-readable` (mrf) argument is provided. +A `-machine-readable` argument is provided. -If the `mrf` argument is set to `True`, the output will be parsed and an object containing the parsed output will be exposed as a dictionary containing the components: +If the `machine_readable` argument is set to `True`, the output will be parsed and an object containing the parsed output will be exposed as a dictionary containing the components: ```python ... p = packer.Packer(packerfile, ...) -result = p.inspect(mrf=True) +result = p.inspect(machine_reable=True) print(result.parsed_output) # print(result.stdout) can also be used here @@ -95,7 +95,7 @@ print(result.parsed_output) ] ``` -If the `mrf` argument is set to `False`, the output will not be parsed but rather returned as is: +If the `machine_readable` argument is set to `False`, the output will not be parsed but rather returned as is: ```python ... diff --git a/README.rst b/README.rst index f738792..0a7616b 100644 --- a/README.rst +++ b/README.rst @@ -3,13 +3,4 @@ python-packer A Python interface for executing `Packer `_. -- Master Branch |Build Status| -- PyPI |PyPI| -- Version |PypI| - -.. |Build Status| image:: https://travis-ci.org/nir0s/python-packer.svg?branch=master - :target: https://travis-ci.org/nir0s/python-packer -.. |PyPI| image:: http://img.shields.io/pypi/dm/python-packer.svg - :target: http://img.shields.io/pypi/dm/python-packer.svg -.. |PypI| image:: http://img.shields.io/pypi/v/python-packer.svg - :target: http://img.shields.io/pypi/v/python-packer.svg +See `Official Github Repo `_. \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt index a08abb2..cffeec6 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,2 @@ -coverage==3.7.1 -nose -nose-cov -testtools \ No newline at end of file +pytest +pytest-cov \ No newline at end of file diff --git a/packer.py b/packer.py index a50758f..0152673 100644 --- a/packer.py +++ b/packer.py @@ -1,3 +1,17 @@ +# Copyright 2015,2016 Nir Cohen +# +# 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 sh import os import json @@ -7,25 +21,36 @@ class Packer(object): - """A packer client + """A Hashicorp packer client """ - def __init__(self, packerfile, exc=None, only=None, vars=None, - var_file=None, exec_path=DEFAULT_PACKER_PATH, out_iter=None, - err_iter=None): - """ + def __init__(self, + packerfile, + exc=None, + only=None, + vars=None, + var_file=None, + exec_path=DEFAULT_PACKER_PATH, + out_iter=None, + err_iter=None, + validate=False): + """Initialize a packer instance + :param string packerfile: Path to Packer template file :param list exc: List of builders to exclude :param list only: List of builders to include :param dict vars: key=value pairs of template variables :param string var_file: Path to variables file :param string exec_path: Path to Packer executable + :param bool validate: Whether to validate the packerfile on init """ + # TODO: redo type validation. This is nasty self.packerfile = self._validate_argtype(packerfile, str) - self.var_file = var_file - if not os.path.isfile(self.packerfile): + if not os.path.isfile(packerfile): raise OSError('packerfile not found at path: {0}'.format( - self.packerfile)) + packerfile)) + self.validate(syntax_only=True) + self.var_file = var_file self.exc = self._validate_argtype(exc or [], list) self.only = self._validate_argtype(only or [], list) self.vars = self._validate_argtype(vars or {}, dict) @@ -41,10 +66,12 @@ def __init__(self, packerfile, exc=None, only=None, vars=None, self.packer = sh.Command(exec_path) self.packer = self.packer.bake(**kwargs) - def build( - self, parallel=True, debug=False, force=False, - machine_readable=False): - """Executes a `packer build` + def build(self, + parallel=True, + debug=False, + force=False, + machine_readable=False): + """Return the result of a `packer build` execution :param bool parallel: Run builders in parallel :param bool debug: Run in debug mode @@ -63,7 +90,10 @@ def build( return self.packer_cmd() def fix(self, to_file=None): - """Implements the `packer fix` function + """Return a fixed packerfile as provided by the `packer fix` command + + v0.2.0: `result.fixed` is deprecated and will be removed sometime in + the future. `result` will instead return the fixed packerfile. :param string to_file: File to output fixed template to """ @@ -73,13 +103,19 @@ def fix(self, to_file=None): result = self.packer_cmd() if to_file: - with open(to_file, 'w') as f: - f.write(result.stdout) - result.fixed = json.loads(result.stdout) + with open(to_file, 'w') as fixed_packerfile: + fixed_packerfile.write(result.stdout) + result = json.loads(result.stdout) + # Deprecated in v0.2.0 + result.fixed = result return result - def inspect(self, mrf=True): - """Inspects a Packer Templates file (`packer inspect -machine-readable`) + def inspect(self, machine_readable=True, mrf=True): + """Return the result of a packerfile inspection + + v0.2.0: `mrf` is deprecated and will be removed sometime in the + future. It is replaced with `machine_readable` Same goes for + `result.parsed_output` which will be put in `result`. To return the output in a readable form, the `-machine-readable` flag is appended automatically, afterwhich the output is parsed and returned @@ -106,22 +142,29 @@ def inspect(self, mrf=True): } ] - :param bool mrf: output in machine-readable form. + DEPRECATED: param bool mrf: output in machine-readable form. + :param bool machine_readable: output in machine-readable form. """ self.packer_cmd = self.packer.inspect - self._add_opt('-machine-readable' if mrf else None) + self._add_opt('-machine-readable' if mrf or machine_readable else None) self._add_opt(self.packerfile) result = self.packer_cmd() - if mrf: + if machine_readable or mrf: + if mrf: + print( + '`mrf` is deprecated starting with v0.2.0. Please use ' + '`machine_readable` instead.') + # `parsed_output` is deprecated in 0.2.0v result.parsed_output = self._parse_inspection_output(result.stdout) + result = result.parsed_output else: result.parsed_output = None return result def push(self, create=True, token=False): - """Implmenets the `packer push` function + """Return the result of a `packer push` execution UNTESTED! Must be used alongside an Atlas account """ @@ -134,9 +177,10 @@ def push(self, create=True, token=False): return self.packer_cmd() def validate(self, syntax_only=False): - """Validates a Packer Template file (`packer validate`) + """Return the result of a packerfile validation If the validation failed, an `sh` exception will be raised. + :param bool syntax_only: Whether to validate the syntax only without validating the configuration itself. """ @@ -152,6 +196,7 @@ def validate(self, syntax_only=False): try: validation = self.packer_cmd() validation.succeeded = validation.exit_code == 0 + validation.failed = not validation.succeeded validation.error = None except Exception as ex: validation = ValidationObject() @@ -161,10 +206,10 @@ def validate(self, syntax_only=False): return validation def version(self): - """Returns Packer's version number (`packer version`) + """Return Packer's version number As of v0.7.5, the format shows when running `packer version` - is: Packer vX.Y.Z. This method will only returns the number, without + is: Packer vX.Y.Z. This method will only return the number, without the `packer v` prefix so that you don't have to parse the version yourself. """ @@ -174,14 +219,16 @@ def _add_opt(self, option): if option: self.packer_cmd = self.packer_cmd.bake(option) - def _validate_argtype(self, arg, argtype): - if not isinstance(arg, argtype): + def _validate_argtype(self, argument, required_argtype): + """Return an argument if it passed type validation + """ + if not isinstance(argument, required_argtype): raise PackerException('{0} argument must be of type {1}'.format( - arg, argtype)) - return arg + argument, required_argtype)) + return argument def _append_base_arguments(self): - """Appends base arguments to packer commands. + """Append base arguments to packer commands. -except, -only, -var and -var-file are appeneded to almost all subcommands in packer. As such this can be called to add @@ -199,12 +246,13 @@ def _append_base_arguments(self): if self.var_file: self._add_opt('-var-file={0}'.format(self.var_file)) - def _join_comma(self, lst): - """Returns a comma delimited string from a list""" - return str(','.join(lst)) + def _join_comma(self, a_list): + """Return a comma delimited string from a list + """ + return str(','.join(a_list)) def _parse_inspection_output(self, output): - """Parses the machine-readable output `packer inspect` provides. + """Return a dictionary containing the parts of an inspected packerfile See the inspect method for more info. This has been tested vs. Packer v0.7.5 diff --git a/setup.py b/setup.py index 161d075..e722733 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ def read(*parts): author_email='nir36g@gmail.com', license='LICENSE', platforms='All', - description='A Python interface for Hashicorp\'s Packer', + description="A Python interface for Hashicorp's Packer", long_description=read('README.rst'), py_modules=['packer'], install_requires=["sh==1.11"], diff --git a/tester.py b/tester.py deleted file mode 100644 index b537f08..0000000 --- a/tester.py +++ /dev/null @@ -1,26 +0,0 @@ -import packer - -packerfile = 'packer/tests/resources/packerfile.json' -# exc = ['z', 't'] -exc = [] -# only = ['x', 'y'] -only = [] -# vars = {"variable1": "y", "variable2": "value"} -vars = {} -vars_file = '/x' - -p = packer.Installer('packer_executables/', 'packer_0.7.5_linux_amd64.zip') -# If we installed packer using the provided installer, it will return -# packer's executable path. We can use it below: -# packer_exec = p.install() -packer_exec = 'packer' -p = packer.Packer(packerfile, exc=exc, only=only, vars=vars, - exec_path=packer_exec) -# print(p.version()) -# validation = p.validate(syntax_only=True) -# print(validation.succeeded) -# print(validation.error) -# result = p.inspect(mrf=True) -# print result.parsed_output -# print(p.fix('TEST.json').fixed) -# print(p.build()) diff --git a/tests/test_packer.py b/tests/test_packer.py index 424e01d..92c271a 100644 --- a/tests/test_packer.py +++ b/tests/test_packer.py @@ -1,7 +1,7 @@ +import os + import packer -import testtools -import os PACKER_PATH = '/usr/bin/packer' TEST_RESOURCES_DIR = 'tests/resources' @@ -9,7 +9,7 @@ TEST_BAD_PACKERFILE = os.path.join(TEST_RESOURCES_DIR, 'badpackerfile.json') -class TestBase(testtools.TestCase): +class TestBase(): def test_build(self): p = packer.Packer(TEST_PACKERFILE) diff --git a/tox.ini b/tox.ini index 0ed516a..2d615c4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,33 @@ # content of: tox.ini , put in same dir as setup.py [tox] -envlist=flake8,py27,py26 +envlist = flake8,py26,py27,py34,py35 +skip_missing_interpreters = true -[testenv:py26] +[testenv] deps = -rdev-requirements.txt -commands=nosetests --with-cov --cov-report term-missing --cov packer tests -v + codecov +passenv = CI TRAVIS TRAVIS_* +commands=pytest --cov-report term-missing --cov packer.py tests -v + +[testenv:py26] +basepython = python2.6 [testenv:py27] +basepython = python2.7 + +[testenv:py34] +basepython = python3.4 + +[testenv:py35] +basepython = python3.5 + +[testenv:pywin] deps = -rdev-requirements.txt -commands=nosetests --with-cov --cov-report term-missing --cov packer tests -v +commands=pytest --cov-report term-missing --cov packer.py tests -v +basepython = {env:PYTHON:}\python.exe +passenv=ProgramFiles APPVEYOR LOGNAME USER LNAME USERNAME HOME USERPROFILE [testenv:flake8] deps =