Merge branch 'addCygwin' into develop

This commit is contained in:
Gavin Swanson 2014-08-23 15:56:32 -07:00
commit d972aeff55
4 changed files with 744 additions and 0 deletions

352
salt/modules/cyg.py Executable file
View File

@ -0,0 +1,352 @@
"""
Manage cygwin packages.
Module file to accompany the cyg state.
"""
# Import python libs
import logging
import re
import os
import bz2
from urllib import urlopen
import salt.utils
from salt.exceptions import SaltInvocationError
LOG = logging.getLogger(__name__)
DEFAULT_MIRROR = "ftp://mirrors.kernel.org/sourceware/cygwin/"
DEFAULT_MIRROR_KEY = ""
# Define the module's virtual name
__virtualname__ = 'cyg'
def __virtual__():
"""Only works on Windows systems."""
if salt.utils.is_windows():
return __virtualname__
return False
__func_alias__ = {
'list_': 'list'
}
def _get_cyg_dir(cyg_arch='x86_64'):
"""Return the cygwin install directory based on the architecture."""
if cyg_arch == 'x86_64':
return 'cygwin64'
elif cyg_arch == 'x86':
return 'cygwin'
raise SaltInvocationError(
'Invalid architecture {arch}'.format(arch=cyg_arch))
def _check_cygwin_installed(cyg_arch='x86_64'):
"""
Return True or False if cygwin is installed.
Use the cygcheck executable to check install. It is installed as part of
the base package, and we use it to check packages
"""
path_to_cygcheck = os.sep.join(['C:',
_get_cyg_dir(cyg_arch),
'bin', 'cygcheck.exe'])
LOG.debug('Path to cygcheck.exe: {}'.format(path_to_cygcheck))
if not os.path.exists(path_to_cygcheck):
LOG.debug('Could not find cygcheck.exe')
return False
return True
def _get_all_packages(mirror=DEFAULT_MIRROR,
cyg_arch='x86_64'):
"""Return the list of packages based on the mirror provided."""
if 'cyg.all_packages' not in __context__:
__context__['cyg.all_packages'] = {}
if mirror not in __context__['cyg.all_packages']:
__context__['cyg.all_packages'][mirror] = []
if not len(__context__['cyg.all_packages'][mirror]):
pkg_source = '/'.join([mirror, cyg_arch, 'setup.bz2'])
file_data = urlopen(pkg_source).read()
file_lines = bz2.decompress(file_data).decode('utf_8',
errors='replace'
).splitlines()
packages = [re.search('^@ ([^ ]+)', line).group(1) for
line in file_lines if re.match('^@ [^ ]+', line)]
__context__['cyg.all_packages'][mirror] = packages
return __context__['cyg.all_packages'][mirror]
def check_valid_package(package,
cyg_arch='x86_64',
mirrors=None):
"""Check if the package is valid on the given mirrors."""
if mirrors is None:
mirrors = {DEFAULT_MIRROR: DEFAULT_MIRROR_KEY}
LOG.debug('Checking Valid Mirrors: {0}'.format(mirrors))
for mirror in mirrors:
if package in _get_all_packages(mirror, cyg_arch):
return True
return False
def _run_silent_cygwin(cyg_arch='x86_64',
args=None,
mirrors=None):
"""
Retrieve the correct setup.exe.
Run it with the correct arguments to get the bare minimum cygwin
installation up and running.
"""
cyg_cache_dir = os.sep.join(['c:', 'cygcache'])
cyg_setup = 'setup-{0}.exe'.format(cyg_arch)
cyg_setup_path = os.sep.join([cyg_cache_dir, cyg_setup])
cyg_setup_source = 'http://cygwin.com/{0}'.format(cyg_setup)
# cyg_setup_source_hash = 'http://cygwin.com/{0}.sig'.format(cyg_setup)
# until a hash gets published that we can verify the newest setup against
# just go ahead and download a new one.
if not os.path.exists(cyg_cache_dir):
os.mkdir(cyg_cache_dir)
elif os.path.exists(cyg_setup_path):
os.remove(cyg_setup_path)
file_data = urlopen(cyg_setup_source)
open(cyg_setup_path, "wb").write(file_data.read())
setup_command = cyg_setup_path
options = []
options.append('--local-package-dir {0}'.format(cyg_cache_dir))
if mirrors is None:
mirrors = {DEFAULT_MIRROR: DEFAULT_MIRROR_KEY}
for mirror, key in mirrors.items():
options.append('--site {}'.format(mirror))
if key:
options.append('--pubkey {}'.format(key))
options.append('--no-desktop')
options.append('--quiet-mode')
options.append('--disable-buggy-antivirus')
if args is not None:
for arg in args:
options.append(arg)
cmdline_args = ' '.join(options)
setup_command = ' '.join([cyg_setup_path, cmdline_args])
ret = __salt__['cmd.run_all'](
setup_command
)
if ret['retcode'] == 0:
return ret['stdout']
else:
return False
def _cygcheck(args, cyg_arch='x86_64'):
"""Run the cygcheck executable."""
bashcmd = ' '.join([
os.sep.join(['c:', _get_cyg_dir(cyg_arch), 'bin', 'bash']),
'--login', '-c'])
cygcheck = '\'cygcheck {0}\''.format(args)
cmdline = ' '.join([bashcmd, cygcheck])
ret = __salt__['cmd.run_all'](
cmdline
)
if ret['retcode'] == 0:
return ret['stdout']
else:
return False
def install(packages=None,
cyg_arch='x86_64',
mirrors=None):
"""
Install one or several packages.
packages : None
The packages to install
cyg_arch : x86_64
Specify the architecture to install the package under
Current options are x86 and x86_64
CLI Example:
.. code-block:: bash
salt '*' cyg.install dos2unix
"""
args = []
# If we want to install packages
if packages is not None:
args.append('--packages {pkgs}'.format(pkgs=packages))
# but we don't have cygwin installed yet
if not _check_cygwin_installed(cyg_arch):
# install just the base system
_run_silent_cygwin(cyg_arch=cyg_arch)
return _run_silent_cygwin(cyg_arch=cyg_arch, args=args, mirrors=mirrors)
def uninstall(packages,
cyg_arch='x86_64',
mirrors=None):
"""
Uninstall one or several packages.
packages
The packages to uninstall.
cyg_arch : x86_64
Specify the architecture to remove the package from
Current options are x86 and x86_64
CLI Example:
.. code-block:: bash
salt '*' cyg.uninstall dos2unix
"""
args = []
if packages is not None:
args.append('--remove-packages {pkgs}'.format(pkgs=packages))
LOG.debug('args: {0}'.format(args))
if not _check_cygwin_installed(cyg_arch):
LOG.debug('We\'re convinced cygwin isn\'t installed')
return True
return _run_silent_cygwin(cyg_arch=cyg_arch, args=args, mirrors=mirrors)
def update(cyg_arch='x86_64', mirrors=None):
"""
Update all packages.
cyg_arch : x86_64
Specify the cygwin architecture update
Current options are x86 and x86_64
CLI Example:
.. code-block:: bash
salt '*' cyg.update
"""
args = []
args.append('--upgrade-also')
# Can't update something that isn't installed
if not _check_cygwin_installed(cyg_arch):
LOG.debug('Cygwin ({}) not installed,\
could not update'.format(cyg_arch))
return False
return _run_silent_cygwin(cyg_arch=cyg_arch, args=args, mirrors=mirrors)
def list_(package='', cyg_arch='x86_64'):
"""
List locally installed packages.
package : ''
package name to check. else all
cyg_arch :
Cygwin architecture to use
Options are x86 and x86_64
CLI Example:
.. code-block:: bash
salt '*' cyg.list
"""
pkgs = {}
args = ' '.join(['-c', '-d', package])
stdout = _cygcheck(args, cyg_arch=cyg_arch)
lines = []
if isinstance(stdout, str):
lines = str(stdout).splitlines()
for line in lines:
match = re.match(r'^([^ ]+) *([^ ]+)', line)
if match:
pkg = match.group(1)
version = match.group(2)
pkgs[pkg] = version
return pkgs
# def sources_add(source_uri, ruby=None, runas=None):
# """
# Add a gem source.
# source_uri
# The source URI to add.
# ruby : None
# If RVM or rbenv are installed, the ruby version and gemset to use.
# runas : None
# The user to run gem as.
# CLI Example:
# .. code-block:: bash
# salt '*' gem.sources_add http://rubygems.org/
# """
# return _gem('sources --add {source_uri}'.
# format(source_uri=source_uri), ruby, runas=runas)
# def sources_remove(source_uri, ruby=None, runas=None):
# """
# Remove a gem source.
# source_uri
# The source URI to remove.
# ruby : None
# If RVM or rbenv are installed, the ruby version and gemset to use.
# runas : None
# The user to run gem as.
# CLI Example:
# .. code-block:: bash
# salt '*' gem.sources_remove http://rubygems.org/
# """
# return _gem('sources --remove {source_uri}'.
# format(source_uri=source_uri), ruby, runas=runas)
# def sources_list(ruby=None, runas=None):
# """
# List the configured gem sources.
# ruby : None
# If RVM or rbenv are installed, the ruby version and gemset to use.
# runas : None
# The user to run gem as.
# CLI Example:
# .. code-block:: bash
# salt '*' gem.sources_list
# """
# ret = _gem('sources', ruby, runas=runas)
# return [] if ret is False else ret.splitlines()[2:]

224
salt/states/cyg.py Executable file
View File

@ -0,0 +1,224 @@
"""
Installation of Cygwin packages.
A state module to manage cygwin packages. Packages can be installed
or removed.
.. code-block:: yaml
dos2unix:
cyg.installed
"""
import logging
# Import salt libs
import salt.utils
LOG = logging.getLogger(__name__)
def __virtual__():
"""Only load if cyg module is available in __salt__."""
return 'cyg.list' in __salt__
def installed(name,
cyg_arch='x86_64',
mirrors=None):
"""
Make sure that a package is installed.
name
The name of the package to install
cyg_arch : x86_64
The cygwin architecture to install the package into.
Current options are x86 and x86_64
mirrors : None
List of mirrors to check.
None will use a default mirror (kernel.org)
"""
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if cyg_arch not in ['x86', 'x86_64']:
return _fail(ret,
'The \'cyg_arch\' argument must\
be one of \'x86\' or \'x86_64\'')
LOG.debug('Installed State: Initial Mirror list: {0}'.format(mirrors))
if not __salt__['cyg.check_valid_package'](name,
cyg_arch=cyg_arch,
mirrors=mirrors):
ret['result'] = False
ret['comment'] = 'Invalid package name.'
return ret
pkgs = __salt__['cyg.list'](name, cyg_arch)
if name in pkgs:
ret['result'] = True
ret['comment'] = 'Package is already installed.'
return ret
if __opts__['test']:
ret['comment'] = 'The package {0} would\
have been installed'.format(name)
return ret
if __salt__['cyg.install'](name,
cyg_arch=cyg_arch,
mirrors=mirrors):
ret['result'] = True
ret['changes'][name] = 'Installed'
ret['comment'] = 'Package was successfully installed'
else:
ret['result'] = False
ret['comment'] = 'Could not install package.'
return ret
def removed(name, cyg_arch='x86_64', mirrors=None):
"""
Make sure that a package is not installed.
name
The name of the package to uninstall
cyg_arch : x86_64
The cygwin architecture to remove the package from.
Current options are x86 and x86_64
mirrors : None
List of mirrors to check.
None will use a default mirror (kernel.org)
"""
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if cyg_arch not in ['x86', 'x86_64']:
return _fail(ret,
'The \'cyg_arch\' argument must\
be one of \'x86\' or \'x86_64\'')
if not __salt__['cyg.check_valid_package'](name,
cyg_arch=cyg_arch,
mirrors=mirrors):
ret['result'] = False
ret['comment'] = 'Invalid package name.'
return ret
if name not in __salt__['cyg.list'](name, cyg_arch, mirrors):
ret['result'] = True
ret['comment'] = 'Package is not installed.'
return ret
if __opts__['test']:
ret['comment'] = 'The package {0} would have been removed'.format(name)
return ret
if __salt__['cyg.uninstall'](name, cyg_arch):
ret['result'] = True
ret['changes'][name] = 'Removed'
ret['comment'] = 'Package was successfully removed.'
else:
ret['result'] = False
ret['comment'] = 'Could not remove package.'
return ret
def updated(name=None, cyg_arch='x86_64', mirrors=None):
"""
Make sure all packages are up to date.
name : None
No affect, salt fails poorly without the arg available
cyg_arch : x86_64
The cygwin architecture to update.
Current options are x86 and x86_64
mirrors : None
List of mirrors to check.
None will use a default mirror (kernel.org)
"""
ret = {'name': 'cyg.updated', 'result': None, 'comment': '', 'changes': {}}
if cyg_arch not in ['x86', 'x86_64']:
return _fail(ret,
'The \'cyg_arch\' argument must\
be one of \'x86\' or \'x86_64\'')
if __opts__['test']:
ret['comment'] = 'All packages would have been updated'
return ret
if not mirrors:
LOG.warn('No mirror given, using the default.')
before = __salt__['cyg.list'](cyg_arch=cyg_arch)
if __salt__['cyg.update'](cyg_arch, mirrors=mirrors):
after = __salt__['cyg.list'](cyg_arch=cyg_arch)
differ = DictDiffer(after, before)
ret['result'] = True
if differ.same():
ret['comment'] = 'Nothing to update.'
else:
ret['changes']['added'] = list(differ.added())
ret['changes']['removed'] = list(differ.removed())
ret['changes']['changed'] = list(differ.changed())
ret['comment'] = 'All packages successfully updated.'
else:
ret['result'] = False
ret['comment'] = 'Could not update packages.'
return ret
"""
https://github.com/hughdbrown/dictdiffer
DictDiffer is licensed as MIT code
A dictionary difference calculator
Originally posted as:
http://stackoverflow.com/questions/1165352/fast-comparison-between-two-python-dictionary/1165552#1165552
"""
class DictDiffer(object):
"""
Calculate the difference between two dictionaries.
(1) items added
(2) items removed
(3) keys same in both but changed values
(4) keys same in both and unchanged values
"""
def __init__(self, current_dict, past_dict):
"""Iitialize the differ."""
self.current_dict, self.past_dict = current_dict, past_dict
self.current_keys, self.past_keys = [
set(d.keys()) for d in (current_dict, past_dict)
]
self.intersect = self.current_keys.intersection(self.past_keys)
def same(self):
"""True if the two dicts are the same."""
return self.current_dict == self.past_dict
def added(self):
"""Return a set of additions to past_dict."""
return self.current_keys - self.intersect
def removed(self):
"""Return a set of things removed from past_dict."""
return self.past_keys - self.intersect
def changed(self):
"""Return a set of the keys with changed values."""
return set(o for o in self.intersect
if self.past_dict[o] != self.current_dict[o])
def unchanged(self):
"""Return a set of the keys with unchanged values."""
return set(o for o in self.intersect
if self.past_dict[o] == self.current_dict[o])

98
tests/unit/modules/cyg_test.py Executable file
View File

@ -0,0 +1,98 @@
# # -*- coding: utf-8 -*-
# # Import Salt Testing libs
# from salttesting import skipIf, TestCase
# from salttesting.helpers import ensure_in_syspath
# from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
# ensure_in_syspath('../../')
# # Import salt libs
# import salt.modules.cyg as cyg
# cyg.__salt__ = {}
# @skipIf(NO_MOCK, NO_MOCK_REASON)
# class TestcygModule(TestCase):
# def test__get_cyg_dir(self):
# self.assertEqual(cyg._get_cyg_dir(), 'c:\\cygwin64')
# self.assertEqual(cyg._get_cyg_dir('x86_64'), 'c:\\cygwin64')
# self.assertEqual(cyg._get_cyg_dir('x86'), 'c:\\cygwin')
# def test_cyg_install(self):
# mock = MagicMock(return_value={'retcode': 0, 'stdout': ''})
# with patch.dict(cyg.__salt__,
# {'cmd.run_all': mock}):
# cyg._get_cyg_dir()
# mock.assert_called_once_with('cyg install dos2unix')
# mock = MagicMock(return_value=None)
# with patch.dict(cyg.__salt__,
# {'rvm.is_installed': MagicMock(return_value=True),
# 'rbenv.is_installed': MagicMock(return_value=False),
# 'rvm.do': mock}):
# cyg._get_cyg_dir('install dos2unix', ruby='1.9.3')
# mock.assert_called_once_with(
# '1.9.3', 'cyg install dos2unix'
# )
# mock = MagicMock(return_value=None)
# with patch.dict(cyg.__salt__,
# {'rvm.is_installed': MagicMock(return_value=False),
# 'rbenv.is_installed': MagicMock(return_value=True),
# 'rbenv.do': mock}):
# cyg._get_cyg_dir('install dos2unix')
# mock.assert_called_once_with(
# 'cyg install dos2unix'
# )
# def test_install_pre(self):
# mock = MagicMock(return_value={'retcode': 0, 'stdout': ''})
# with patch.dict(cyg.__salt__,
# {'rvm.is_installed': MagicMock(return_value=False),
# 'rbenv.is_installed': MagicMock(return_value=False),
# 'cmd.run_all': mock}):
# cyg.install('dos2unix', pre_releases=True)
# mock.assert_called_once_with(
# 'cyg install dos2unix --no-rdoc --no-ri --pre'
# )
# def test_list(self):
# output = '''
# actionmailer (2.3.14)
# actionpack (2.3.14)
# activerecord (2.3.14)
# activeresource (2.3.14)
# activesupport (3.0.5, 2.3.14)
# rake (0.9.2, 0.8.7)
# responds_to_parent (1.0.20091013)
# sass (3.1.15, 3.1.7)
# '''
# mock = MagicMock(return_value=output)
# with patch.object(cyg, '_cyg', new=mock):
# self.assertEqual(
# {'actionmailer': ['2.3.14'],
# 'actionpack': ['2.3.14'],
# 'activerecord': ['2.3.14'],
# 'activeresource': ['2.3.14'],
# 'activesupport': ['3.0.5', '2.3.14'],
# 'rake': ['0.9.2', '0.8.7'],
# 'responds_to_parent': ['1.0.20091013'],
# 'sass': ['3.1.15', '3.1.7']},
# cyg.list_())
# def test_sources_list(self):
# output = '''*** CURRENT SOURCES ***
# http://rubycygs.org/
# '''
# mock = MagicMock(return_value=output)
# with patch.object(cyg, '_cyg', new=mock):
# self.assertEqual(
# ['http://rubycygs.org/'], cyg.sources_list())
# if __name__ == '__main__':
# from integration import run_tests
# run_tests(TestcygModule, needs_daemon=False)

70
tests/unit/states/cyg_test.py Executable file
View File

@ -0,0 +1,70 @@
# # -*- coding: utf-8 -*-
# # Import Salt Testing libs
# from salttesting import skipIf, TestCase
# from salttesting.helpers import ensure_in_syspath
# from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
# ensure_in_syspath('../../')
# # Late import so mock can do its job
# import salt.states.cyg as cyg
# cyg.__salt__ = {}
# cyg.__opts__ = {'test': False}
# @skipIf(NO_MOCK, NO_MOCK_REASON)
# class TestGemState(TestCase):
# def test_installed(self):
# gems = {'foo': ['1.0'], 'bar': ['2.0']}
# gem_list = MagicMock(return_value=gems)
# gem_install_succeeds = MagicMock(return_value=True)
# gem_install_fails = MagicMock(return_value=False)
# with patch.dict(gem.__salt__, {'gem.list': gem_list}):
# with patch.dict(gem.__salt__,
# {'gem.install': gem_install_succeeds}):
# ret = gem.installed('foo')
# self.assertEqual(True, ret['result'])
# ret = gem.installed('quux')
# self.assertEqual(True, ret['result'])
# gem_install_succeeds.assert_called_once_with(
# 'quux', pre_releases=False, ruby=None, runas=None,
# version=None, rdoc=False, ri=False
# )
# with patch.dict(gem.__salt__,
# {'gem.install': gem_install_fails}):
# ret = gem.installed('quux')
# self.assertEqual(False, ret['result'])
# gem_install_fails.assert_called_once_with(
# 'quux', pre_releases=False, ruby=None, runas=None,
# version=None, rdoc=False, ri=False
# )
# def test_removed(self):
# gems = ['foo', 'bar']
# gem_list = MagicMock(return_value=gems)
# gem_uninstall_succeeds = MagicMock(return_value=True)
# gem_uninstall_fails = MagicMock(return_value=False)
# with patch.dict(gem.__salt__, {'gem.list': gem_list}):
# with patch.dict(gem.__salt__,
# {'gem.uninstall': gem_uninstall_succeeds}):
# ret = gem.removed('quux')
# self.assertEqual(True, ret['result'])
# ret = gem.removed('foo')
# self.assertEqual(True, ret['result'])
# gem_uninstall_succeeds.assert_called_once_with(
# 'foo', None, runas=None)
# with patch.dict(gem.__salt__,
# {'gem.uninstall': gem_uninstall_fails}):
# ret = gem.removed('bar')
# self.assertEqual(False, ret['result'])
# gem_uninstall_fails.assert_called_once_with(
# 'bar', None, runas=None)
# if __name__ == '__main__':
# from integration import run_tests
# run_tests(TestGemState, needs_daemon=False)