mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Implement Bower state module
This commit is contained in:
parent
c408c935a0
commit
5edb6cba11
@ -31,6 +31,7 @@ Full list of builtin state modules
|
||||
boto_secgroup
|
||||
boto_sns
|
||||
boto_sqs
|
||||
bower
|
||||
chef
|
||||
cloud
|
||||
cmd
|
||||
|
6
doc/ref/states/all/salt.states.bower.rst
Normal file
6
doc/ref/states/all/salt.states.bower.rst
Normal file
@ -0,0 +1,6 @@
|
||||
===============
|
||||
salt.states.bower
|
||||
===============
|
||||
|
||||
.. automodule:: salt.states.bower
|
||||
:members:
|
273
salt/states/bower.py
Normal file
273
salt/states/bower.py
Normal 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
|
81
tests/integration/states/bower.py
Normal file
81
tests/integration/states/bower.py
Normal 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)
|
246
tests/unit/states/bower_test.py
Normal file
246
tests/unit/states/bower_test.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user