Implement Bower state module

This commit is contained in:
Alexander Pyatkin 2015-02-21 05:57:54 +00:00
parent c408c935a0
commit 5edb6cba11
5 changed files with 607 additions and 0 deletions

View File

@ -31,6 +31,7 @@ Full list of builtin state modules
boto_secgroup
boto_sns
boto_sqs
bower
chef
cloud
cmd

View File

@ -0,0 +1,6 @@
===============
salt.states.bower
===============
.. automodule:: salt.states.bower
:members:

273
salt/states/bower.py Normal file
View File

@ -0,0 +1,273 @@
# -*- coding: utf-8 -*-
'''
Installation of Bower Packages
==============================
These states manage the installed packages using Bower.
Note that npm, git and bower must be installed for these states to be
available, so bower states should include requisites to pkg.installed states
for the packages which provide npm and git (simply ``npm`` and ``git`` in most
cases), and npm.installed state for the package which provides bower.
Example:
.. code-block:: yaml
npm:
pkg.installed
git:
pkg.installed
bower:
npm.installed
require:
- pkg: npm
- pkg: git
underscore:
bower.installed:
- dir: /path/to/project
- require:
- npm: bower
'''
from __future__ import absolute_import
# Import salt libs
from salt.exceptions import CommandExecutionError, CommandNotFoundError
# Import 3rd-party libs
import salt.ext.six as six
def __virtual__():
'''
Only load if the bower module is available in __salt__
'''
return 'bower' if 'bower.list' in __salt__ else False
def installed(name,
dir,
pkgs=None,
user=None,
env=None):
'''
Verify that the given package is installed and is at the correct version
(if specified).
.. code-block:: yaml
underscore:
bower.installed:
- dir: /path/to/project
- user: someuser
jquery#2.0:
bower.installed:
- dir: /path/to/project
name
The package to install
dir
The target directory in which to install the package
pkgs
A list of packages to install with a single Bower invocation;
specifying this argument will ignore the ``name`` argument
user
The user to run Bower with
env
A list of environment variables to be set prior to execution. The
format is the same as the :py:func:`cmd.run <salt.states.cmd.run>`.
state function.
'''
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if pkgs is not None:
pkg_list = pkgs
else:
pkg_list = [name]
try:
installed_pkgs = __salt__['bower.list'](dir=dir, runas=user, env=env)
except (CommandNotFoundError, CommandExecutionError) as err:
ret['result'] = False
ret['comment'] = 'Error looking up {0!r}: {1}'.format(name, err)
return ret
else:
installed_pkgs = dict((p, info) for p, info in
six.iteritems(installed_pkgs))
pkgs_satisfied = []
pkgs_to_install = []
for pkg in pkg_list:
pkg_name, _, pkg_ver = pkg.partition('#')
pkg_name = pkg_name.strip()
if pkg_name not in installed_pkgs:
pkgs_to_install.append(pkg)
continue
if pkg_name in installed_pkgs:
installed_pkg = installed_pkgs[pkg_name]
installed_pkg_ver = installed_pkg.get('pkgMeta').get('version')
installed_name_ver = '{0}#{1}'.format(
pkg_name,
installed_pkg_ver)
# If given an explicit version check the installed version matches.
if pkg_ver:
if installed_pkg_ver != pkg_ver:
pkgs_to_install.append(pkg)
else:
pkgs_satisfied.append(installed_name_ver)
continue
else:
pkgs_satisfied.append(installed_name_ver)
continue
if __opts__['test']:
ret['result'] = None
comment_msg = []
if pkgs_to_install:
comment_msg.append(
'Bower package(s) {0!r} are set to be installed'.format(
', '.join(pkgs_to_install)))
ret['changes'] = {'old': [], 'new': pkgs_to_install}
if pkgs_satisfied:
comment_msg.append(
'Package(s) {0!r} satisfied by {1}'.format(
', '.join(pkg_list), ', '.join(pkgs_satisfied)))
ret['comment'] = '. '.join(comment_msg)
return ret
if not pkgs_to_install:
ret['result'] = True
ret['comment'] = ('Package(s) {0!r} satisfied by {1}'.format(
', '.join(pkg_list), ', '.join(pkgs_satisfied)))
return ret
try:
cmd_args = {
'pkg': None,
'dir': dir,
'pkgs': None,
'runas': user,
'env': env,
}
if pkgs is not None:
cmd_args['pkgs'] = pkgs
else:
cmd_args['pkg'] = pkg_name
call = __salt__['bower.install'](**cmd_args)
except (CommandNotFoundError, CommandExecutionError) as err:
ret['result'] = False
ret['comment'] = 'Error installing {0!r}: {1}'.format(
', '.join(pkg_list), err)
return ret
if call:
ret['result'] = True
ret['changes'] = {'old': [], 'new': pkgs_to_install}
ret['comment'] = 'Package(s) {0!r} successfully installed'.format(
', '.join(pkgs_to_install))
else:
ret['result'] = False
ret['comment'] = 'Could not install package(s) {0!r}'.format(
', '.join(pkg_list))
return ret
def removed(name, dir, user=None):
'''
Verify that the given package is not installed.
dir
The target directory in which to install the package
user
The user to run Bower with
'''
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
try:
installed_pkgs = __salt__['bower.list'](dir=dir, runas=user)
except (CommandExecutionError, CommandNotFoundError) as err:
ret['result'] = False
ret['comment'] = 'Error removing {0!r}: {1}'.format(name, err)
return ret
if name not in installed_pkgs:
ret['result'] = True
ret['comment'] = 'Package {0!r} is not installed'.format(name)
return ret
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Package {0!r} is set to be removed!'.format(name)
return ret
try:
if __salt__['bower.uninstall'](pkg=name, dir=dir, runas=user):
ret['result'] = True
ret['changes'] = {name: 'Removed'}
ret['comment'] = 'Package {0!r} was successfully removed'.format(
name)
else:
ret['result'] = False
ret['comment'] = 'Error removing {0!r}'.format(name)
except (CommandExecutionError, CommandNotFoundError) as err:
ret['result'] = False
ret['comment'] = 'Error removing {0!r}: {1}'.format(name, err)
return ret
def bootstrap(name, user=None):
'''
Bootstraps a frontend distribution.
Will execute 'bower install' on the specified directory.
user
The user to run Bower with
'''
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Directory {0!r} is set to be bootstrapped'.format(
name)
return ret
try:
call = __salt__['bower.install'](pkg=None, dir=name, runas=user)
except (CommandNotFoundError, CommandExecutionError) as err:
ret['result'] = False
ret['comment'] = 'Error bootstrapping {0!r}: {1}'.format(name, err)
return ret
if not call:
ret['result'] = True
ret['comment'] = 'Directory is already bootstrapped'
return ret
ret['result'] = True
ret['changes'] = {name: 'Bootstrapped'}
ret['comment'] = 'Directory was successfully bootstrapped'
return ret

View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Alexander Pyatkin <asp@thexyz.net`
'''
# Import Python libs
from __future__ import absolute_import
import json
# Import Salt Testing libs
from salttesting import skipIf
from salttesting.helpers import destructiveTest, ensure_in_syspath
ensure_in_syspath('../../')
# Import salt libs
import integration
import salt.utils
@skipIf(salt.utils.which('bower') is None, 'bower not installed')
class BowerStateTest(integration.ModuleCase,
integration.SaltReturnAssertsMixIn):
@destructiveTest
def test_bower_installed_removed(self):
'''
Basic test to determine if Bower package was successfully installed and
removed.
'''
ret = self.run_state('file.directory', name='/salt_test_bower_1',
makedirs=True)
self.assertSaltTrueReturn(ret)
ret = self.run_state('bower.installed', name='underscore',
dir='/salt_test_bower_1')
self.assertSaltTrueReturn(ret)
ret = self.run_state('bower.removed', name='underscore',
dir='/salt_test_bower_1')
self.assertSaltTrueReturn(ret)
ret = self.run_state('file.absent', name='/salt_test_bower_1')
self.assertSaltTrueReturn(ret)
@destructiveTest
def test_bower_installed_pkgs(self):
'''
Basic test to determine if Bower package successfully installs multiple
packages.
'''
ret = self.run_state('file.directory', name='/salt_test_bower_2',
makedirs=True)
self.assertSaltTrueReturn(ret)
ret = self.run_state('bower.installed', name='test',
dir='/salt_test_bower_2',
pkgs=['numeral', 'underscore'])
self.assertSaltTrueReturn(ret)
ret = self.run_state('file.absent', name='/salt_test_bower_2')
self.assertSaltTrueReturn(ret)
@destructiveTest
def test_bower_installed_from_file(self):
ret = self.run_state('file.directory', name='/salt_test_bower_3',
makedirs=True)
self.assertSaltTrueReturn(ret)
bower_json = json.dumps({
'name': 'salt_test_bower_3',
'dependencies': {
'numeral': '~1.5.3',
'underscore': '~1.7.0'
}
})
ret = self.run_state('file.managed',
name='/salt_test_bower_3/bower.json',
contents=bower_json)
self.assertSaltTrueReturn(ret)
ret = self.run_state('bower.bootstrap', name='/salt_test_bower_3')
self.assertSaltTrueReturn(ret)
ret = self.run_state('file.absent', name='/salt_test_bower_3')
self.assertSaltTrueReturn(ret)
if __name__ == '__main__':
from integration import run_tests
run_tests(BowerStateTest)

View File

@ -0,0 +1,246 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Alexander Pyatkin <asp@thexyz.net>`
'''
# Import Python Libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
# Import Salt Libs
from salt.states import bower
from salt.exceptions import CommandExecutionError
# Globals
bower.__salt__ = {}
bower.__opts__ = {'test': False}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BowerTestCase(TestCase):
'''
Test cases for salt.states.bower
'''
def test_removed_not_installed(self):
'''
Test if it returns True when specified package is not installed
'''
mock = MagicMock(return_value={'underscore': {}})
with patch.dict(bower.__salt__, {'bower.list': mock}):
ret = bower.removed('jquery', '/path/to/project')
expected = {'name': 'jquery',
'result': True,
'comment': 'Package \'jquery\' is not installed',
'changes': {}}
self.assertEqual(ret, expected)
def test_removed_with_error(self):
'''
Test if returns False when list packages fails
'''
mock = MagicMock(side_effect=CommandExecutionError)
with patch.dict(bower.__salt__, {'bower.list': mock}):
ret = bower.removed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': False,
'comment': 'Error removing \'underscore\': ',
'changes': {}}
self.assertEqual(ret, expected)
def test_removed_existing(self):
'''
Test if it returns True when specified package is installed and
uninstall succeeds
'''
mock_list = MagicMock(return_value={'underscore': {}})
mock_uninstall = MagicMock(return_value=True)
with patch.dict(bower.__salt__, {'bower.list': mock_list,
'bower.uninstall': mock_uninstall}):
ret = bower.removed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': True,
'comment':
'Package \'underscore\' was successfully removed',
'changes': {'underscore': 'Removed'}}
self.assertEqual(ret, expected)
def test_removed_existing_with_error(self):
'''
Test if it returns False when specified package is installed and
uninstall fails
'''
mock_list = MagicMock(return_value={'underscore': {}})
mock_uninstall = MagicMock(side_effect=CommandExecutionError)
with patch.dict(bower.__salt__, {'bower.list': mock_list,
'bower.uninstall': mock_uninstall}):
ret = bower.removed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': False,
'comment':
'Error removing \'underscore\': ',
'changes': {}}
self.assertEqual(ret, expected)
def test_bootstrap_with_error(self):
'''
Test if it return False when install packages fails
'''
mock = MagicMock(side_effect=CommandExecutionError)
with patch.dict(bower.__salt__, {'bower.install': mock}):
ret = bower.bootstrap('/path/to/project')
expected = {'name': '/path/to/project',
'result': False,
'comment':
'Error bootstrapping \'/path/to/project\': ',
'changes': {}}
self.assertEqual(ret, expected)
def test_bootstrap_not_needed(self):
'''
Test if it returns True when there is nothing to install
'''
mock = MagicMock(return_value=False)
with patch.dict(bower.__salt__, {'bower.install': mock}):
ret = bower.bootstrap('/path/to/project')
expected = {'name': '/path/to/project',
'result': True,
'comment':
'Directory is already bootstrapped',
'changes': {}}
self.assertEqual(ret, expected)
def test_bootstrap_success(self):
'''
Test if it returns True when install packages succeeds
'''
mock = MagicMock(return_value=True)
with patch.dict(bower.__salt__, {'bower.install': mock}):
ret = bower.bootstrap('/path/to/project')
expected = {'name': '/path/to/project',
'result': True,
'comment':
'Directory was successfully bootstrapped',
'changes': {'/path/to/project': 'Bootstrapped'}}
self.assertEqual(ret, expected)
def test_installed_with_error(self):
'''
Test if it returns False when list packages fails
'''
mock = MagicMock(side_effect=CommandExecutionError)
with patch.dict(bower.__salt__, {'bower.list': mock}):
ret = bower.installed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': False,
'comment': 'Error looking up \'underscore\': ',
'changes': {}}
self.assertEqual(ret, expected)
def test_installed_not_needed(self):
'''
Test if it returns True when there is nothing to install
'''
mock = MagicMock(return_value={
'underscore': {
'pkgMeta': {'version': '1.7.0'}},
'jquery': {
'pkgMeta': {'version': '2.0.0'}}})
with patch.dict(bower.__salt__, {'bower.list': mock}):
ret = bower.installed('test', '/path/to/project',
['underscore', 'jquery#2.0.0'])
expected = {'name': 'test',
'result': True,
'comment':
('Package(s) \'underscore, jquery#2.0.0\''
' satisfied by underscore#1.7.0, jquery#2.0.0'),
'changes': {}}
self.assertEqual(ret, expected)
def test_installed_new_with_exc(self):
'''
Test if it returns False when install packages fails (exception)
'''
mock_list = MagicMock(return_value={})
mock_install = MagicMock(side_effect=CommandExecutionError)
with patch.dict(bower.__salt__, {'bower.list': mock_list,
'bower.install': mock_install}):
ret = bower.installed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': False,
'comment': 'Error installing \'underscore\': ',
'changes': {}}
self.assertEqual(ret, expected)
def test_installed_new_with_error(self):
'''
Test if returns False when install packages fails (bower error)
'''
mock_list = MagicMock(return_value={})
mock_install = MagicMock(return_value=False)
with patch.dict(bower.__salt__, {'bower.list': mock_list,
'bower.install': mock_install}):
ret = bower.installed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': False,
'comment':
'Could not install package(s) \'underscore\'',
'changes': {}}
self.assertEqual(ret, expected)
def test_installed_success(self):
'''
Test if it returns True when install succeeds
'''
mock_list = MagicMock(return_value={})
mock_install = MagicMock(return_value=True)
with patch.dict(bower.__salt__, {'bower.list': mock_list,
'bower.install': mock_install}):
ret = bower.installed('underscore', '/path/to/project')
expected = {'name': 'underscore',
'result': True,
'comment':
'Package(s) \'underscore\' successfully installed',
'changes': {'new': ['underscore'], 'old': []}}
self.assertEqual(ret, expected)
if __name__ == '__main__':
from integration import run_tests
run_tests(BowerTestCase, needs_daemon=False)