From 0e1773b28c47f9e48e04b49ddc7a6ba83d32ad9b Mon Sep 17 00:00:00 2001 From: Kurtis Velarde Date: Fri, 8 Jun 2012 14:32:07 -0700 Subject: [PATCH 01/10] First monit module commit --- salt/modules/monit.py | 86 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 salt/modules/monit.py diff --git a/salt/modules/monit.py b/salt/modules/monit.py new file mode 100644 index 0000000000..a8f2d10291 --- /dev/null +++ b/salt/modules/monit.py @@ -0,0 +1,86 @@ +''' +Salt module to manage monit +''' + +def version(): + ''' + List monit version + + Cli Example:: + + salt '*' monit.version + ''' + + cmd = 'monit -V' + res = __salt__['cmd.run'](cmd) + return res.split("\n")[0] + + +def status(): + ''' + Monit status + + CLI Example:: + + salt '*' monit.status + ''' + cmd = 'monit status' + res = __salt__['cmd.run'](cmd) + return res.split("\n") + + +def start(): + ''' + Starts monit + + CLI Example:: + + salt '*' monit.start + *Note need to add check to insure its running* + `ps ax | grep monit | grep -v grep or something` + ''' + cmd = 'monit' + res = __salt__['cmd.run'](cmd) + return "Monit started" + + +def stop(): + ''' + Stop monit + + CLI Example:: + + salt '*' monit.stop + *Note Needs check as above* + ''' + def _is_bsd(): + return True if __grains__['os'] == 'FreeBSD' else False + + if _is_bsd(): + cmd = "/usr/local/etc/rc.d/monit stop" + else: + cmd = "/etc/init.d/monit stop" + res = __salt__['cmd.run'](cmd) + return "Monit Stopped" + + +def monitor_all(): + ''' + Initializing all monit modules. + ''' + cmd = 'monit monitor all' + res = __salt__['cmd.run'](cmd) + if res: + return "All Services initaialized" + return "Issue starting monitoring on all services" + + +def unmonitor_all(): + ''' + unmonitor all services. + ''' + cmd = 'monit unmonitor all' + res = __salt__['cmd.run'](cmd) + if res: + return "All Services unmonitored" + return "Issue unmonitoring all services" From 28353efe2802059c1da8b1c81b157dc6e773032e Mon Sep 17 00:00:00 2001 From: Kurtis Velarde Date: Sun, 10 Jun 2012 21:06:23 -0700 Subject: [PATCH 02/10] Check to see if we are going donw the right path --- salt/modules/monit.py | 86 +++++++++++-------------------------------- 1 file changed, 22 insertions(+), 64 deletions(-) diff --git a/salt/modules/monit.py b/salt/modules/monit.py index a8f2d10291..4eb8c53632 100644 --- a/salt/modules/monit.py +++ b/salt/modules/monit.py @@ -1,86 +1,44 @@ ''' -Salt module to manage monit +Monit service module. This module will create a monit type +service watcher. ''' -def version(): +import os + +def start(name): ''' - List monit version - - Cli Example:: - - salt '*' monit.version + + CLI Example:: + salt '*' monit.start ''' + cmd = "monit start {0}".format(name) - cmd = 'monit -V' - res = __salt__['cmd.run'](cmd) - return res.split("\n")[0] + return not __salt__['cmd.retcode'](cmd) -def status(): +def stop(name): ''' - Monit status + Stops service via monit CLI Example:: - salt '*' monit.status + salt '*' monit.stop ''' - cmd = 'monit status' - res = __salt__['cmd.run'](cmd) - return res.split("\n") + cmd = "monit stop {0}".format(name) -def start(): + return not __salt__['cmd.retcode'](cmd) + + +def restart(name): ''' - Starts monit + Restart service via monit CLI Example:: - salt '*' monit.start - *Note need to add check to insure its running* - `ps ax | grep monit | grep -v grep or something` + salt '*' monit.restart ''' - cmd = 'monit' - res = __salt__['cmd.run'](cmd) - return "Monit started" + cmd = "monit restart {0}".format(name) + return not __salt__['cmd.retcode'](cmd) -def stop(): - ''' - Stop monit - - CLI Example:: - - salt '*' monit.stop - *Note Needs check as above* - ''' - def _is_bsd(): - return True if __grains__['os'] == 'FreeBSD' else False - - if _is_bsd(): - cmd = "/usr/local/etc/rc.d/monit stop" - else: - cmd = "/etc/init.d/monit stop" - res = __salt__['cmd.run'](cmd) - return "Monit Stopped" - - -def monitor_all(): - ''' - Initializing all monit modules. - ''' - cmd = 'monit monitor all' - res = __salt__['cmd.run'](cmd) - if res: - return "All Services initaialized" - return "Issue starting monitoring on all services" - - -def unmonitor_all(): - ''' - unmonitor all services. - ''' - cmd = 'monit unmonitor all' - res = __salt__['cmd.run'](cmd) - if res: - return "All Services unmonitored" - return "Issue unmonitoring all services" From 01b74fc04cbf6a9f0ed1e24a70d5594b9fedc3cc Mon Sep 17 00:00:00 2001 From: Dan Colish Date: Sat, 30 Jun 2012 13:28:15 -0700 Subject: [PATCH 03/10] Add unittests for salt.utils.verify --- tests/unit/utils/verify_test.py | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/unit/utils/verify_test.py diff --git a/tests/unit/utils/verify_test.py b/tests/unit/utils/verify_test.py new file mode 100644 index 0000000000..3fa6d7e939 --- /dev/null +++ b/tests/unit/utils/verify_test.py @@ -0,0 +1,48 @@ +import logging +import os +import stat +import tempfile + +from saltunittest import TestCase + +from salt.utils.verify import ( + check_user, + verify_env, + verify_socket, + zmq_version, +) + + +class TestVerify(TestCase): + + def setUp(self): + self.logger = logging.getLogger(__name__) + + def test_zmq_verify(self): + self.assertTrue(zmq_version()) + + def test_zmq_verify_insuficient(self): + import zmq + zmq.__version__ = '2.1.0' + self.assertFalse(zmq_version()) + + def test_user(self): + self.assertTrue(check_user(os.getlogin(), self.logger)) + + def test_no_user(self): + self.assertFalse(check_user('nouser', self.logger)) + + def test_verify_env(self): + root_dir = tempfile.mkdtemp() + var_dir = os.path.join(root_dir, 'var', 'log', 'salt') + verify_env([var_dir], os.getlogin()) + self.assertTrue(os.path.exists(var_dir)) + dir_stat = os.stat(var_dir) + self.assertEqual(dir_stat.st_uid, os.getuid()) + self.assertEqual(dir_stat.st_gid, os.getgid()) + self.assertEqual(dir_stat.st_mode & stat.S_IRWXU, stat.S_IRWXU) + self.assertEqual(dir_stat.st_mode & stat.S_IRWXG, 0) + self.assertEqual(dir_stat.st_mode & stat.S_IRWXO, 0) + + def test_verify_socket(self): + self.assertTrue(verify_socket('', 18000, 18001)) From 53ea031a72ac95e8004755bfa8141054601d3992 Mon Sep 17 00:00:00 2001 From: Eric O'Connell Date: Sat, 30 Jun 2012 13:31:38 -0700 Subject: [PATCH 04/10] Create skeletal HACKING file --- HACKING | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 HACKING diff --git a/HACKING b/HACKING new file mode 100644 index 0000000000..bc70d8d5ce --- /dev/null +++ b/HACKING @@ -0,0 +1,26 @@ +Hacking on Salt +=============== + +Contributing +------------ + +TODO + + +Running Tests +------------- + +To run the full suite, you'll need mock: + +`pip install mock` + +On Python < 2.7, unittest compatibility is provided by unittest2 + +`pip install unittest2` + + +Community +--------- + +The #salt irc channel is on irc.freenode.net + From c5cdb366dc018fd5c83cc7eaff706a28c9613346 Mon Sep 17 00:00:00 2001 From: Dan Colish Date: Sat, 30 Jun 2012 13:54:23 -0700 Subject: [PATCH 05/10] Fix test.integration.states.host. Make master_config and minion_config a property to keep semantics consistent --- tests/integration/__init__.py | 2 ++ tests/integration/modules/grains.py | 4 ++-- tests/integration/states/host.py | 22 ++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 328ce2ee67..2abb12cadd 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -183,6 +183,7 @@ class ModuleCase(TestCase): ''' return self.run_function('state.single', [function], **kwargs) + @property def minion_opts(self): ''' Return the options used for the minion @@ -194,6 +195,7 @@ class ModuleCase(TestCase): ) ) + @property def master_opts(self): ''' Return the options used for the minion diff --git a/tests/integration/modules/grains.py b/tests/integration/modules/grains.py index 2dfbfc30ef..353c576180 100644 --- a/tests/integration/modules/grains.py +++ b/tests/integration/modules/grains.py @@ -18,14 +18,14 @@ class TestModulesGrains(integration.ModuleCase): ''' grains.items ''' - opts = self.minion_opts() + opts = self.minion_opts self.assertEqual(self.run_function('grains.items')['test_grain'], opts['grains']['test_grain']) def test_item(self): ''' grains.item ''' - opts = self.minion_opts() + opts = self.minion_opts self.assertEqual(self.run_function('grains.item', ['test_grain']), opts['grains']['test_grain']) def test_ls(self): diff --git a/tests/integration/states/host.py b/tests/integration/states/host.py index 81c4886d34..12d62153da 100644 --- a/tests/integration/states/host.py +++ b/tests/integration/states/host.py @@ -13,21 +13,19 @@ HFILE = os.path.join(integration.TMP, 'hosts') class HostTest(integration.ModuleCase): - def setUp(self): - shutil.copy(os.path.join( - integration.INTEGRATION_TEST_DIR, 'files', 'hosts'), - self.master_opts['hosts.file']) - shutil.copy(os.path.join( - integration.INTEGRATION_TEST_DIR, 'files', 'hosts'), - self.minion_opts['hosts.file']) - - def tearDown(self): - os.remove(self.master_opts['hosts.file']) - os.remove(self.minion_opts['hosts.file']) - ''' Validate the host state ''' + + def setUp(self): + shutil.copyfile(os.path.join(integration.FILES, 'hosts'), HFILE) + super(HostTest, self).setUp() + + def tearDown(self): + if os.path.exists(HFILE): + os.remove(HFILE) + super(HostTest, self).tearDown() + def test_present(self): ''' host.present From 1b1431ba6632bc8701ff933eb8f5a924ed89a706 Mon Sep 17 00:00:00 2001 From: Dan Colish Date: Sat, 30 Jun 2012 14:11:37 -0700 Subject: [PATCH 06/10] Skip verify_env test on windows. --- tests/unit/utils/verify_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/utils/verify_test.py b/tests/unit/utils/verify_test.py index 3fa6d7e939..3e22b45ef4 100644 --- a/tests/unit/utils/verify_test.py +++ b/tests/unit/utils/verify_test.py @@ -1,9 +1,10 @@ import logging import os +import sys import stat import tempfile -from saltunittest import TestCase +from saltunittest import skipIf, TestCase from salt.utils.verify import ( check_user, @@ -32,6 +33,7 @@ class TestVerify(TestCase): def test_no_user(self): self.assertFalse(check_user('nouser', self.logger)) + @skipIf(sys.platform.startswith('win'), 'No verify_env Windows') def test_verify_env(self): root_dir = tempfile.mkdtemp() var_dir = os.path.join(root_dir, 'var', 'log', 'salt') From d327e6bdf4173e63b1168eb2052cd5368a3b0ee4 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sat, 30 Jun 2012 14:24:22 -0700 Subject: [PATCH 07/10] Adding tests for Django module commands --- tests/integration/modules/django.py | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/integration/modules/django.py diff --git a/tests/integration/modules/django.py b/tests/integration/modules/django.py new file mode 100644 index 0000000000..9f814bc69b --- /dev/null +++ b/tests/integration/modules/django.py @@ -0,0 +1,92 @@ +''' +Test the django module +''' +# Import python libs +import sys + +# Import Salt libs +from saltunittest import TestLoader, TextTestRunner +import integration +from integration import TestDaemon +from salt.modules import django +django.__salt__ = {} + +from mock import MagicMock, patch + + +class DjangoModuleTest(integration.ModuleCase): + ''' + Test the django module + ''' + + def test_command(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.command('settings.py', 'runserver') + mock.assert_called_once_with('django-admin.py runserver --settings=settings.py') + + def test_command_with_args(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.command('settings.py', 'runserver', None, None, 'noinput', 'somethingelse') + mock.assert_called_once_with('django-admin.py runserver --settings=settings.py --noinput --somethingelse') + + def test_command_with_kwargs(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.command('settings.py', 'runserver', None, None, database='something') + mock.assert_called_once_with('django-admin.py runserver --settings=settings.py --database=something') + + def test_command_with_kwargs_ignore_dunder(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.command('settings.py', 'runserver', None, None, __ignore='something') + mock.assert_called_once_with('django-admin.py runserver --settings=settings.py') + + def test_syncdb(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.syncdb('settings.py') + mock.assert_called_once_with('django-admin.py syncdb --settings=settings.py --noinput') + + def test_syncdb_migrate(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.syncdb('settings.py', migrate=True) + mock.assert_called_once_with('django-admin.py syncdb --settings=settings.py --migrate --noinput') + + def test_createsuperuser(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.createsuperuser('settings.py', 'testuser', 'user@example.com') + mock.assert_called_once_with('django-admin.py createsuperuser --settings=settings.py --noinput --username=testuser --email=user@example.com') + + def test_loaddata(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.loaddata('settings.py', 'app1,app2') + mock.assert_called_once_with('django-admin.py loaddata --settings=settings.py app1 app2') + + def test_collectstatic(self): + mock = MagicMock() + with patch.dict(django.__salt__, + {'cmd.run': mock}): + django.collectstatic('settings.py', None, True, 'something', True, True, True, True) + mock.assert_called_once_with('django-admin.py collectstatic --settings=settings.py --no-post-process --dry-run --clear --link --no-default-ignore --ignore=something') + + +if __name__ == '__main__': + loader = TestLoader() + tests = loader.loadTestsFromTestCase(DjangoModuleTest) + print('Setting up Salt daemons to execute tests') + with TestDaemon(): + runner = TextTestRunner(verbosity=1).run(tests) + sys.exit(runner.wasSuccessful()) From 40781ea0ae2407bda518b412755cdcd2971dfcad Mon Sep 17 00:00:00 2001 From: Alec Koumjian Date: Sat, 30 Jun 2012 14:25:14 -0700 Subject: [PATCH 08/10] Fix for minion KeyError 1523 --- tests/integration/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 328ce2ee67..514ec9859b 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -168,7 +168,7 @@ class ModuleCase(TestCase): Run a single salt function and condition the return down to match the behavior of the raw function call ''' - orig = self.client.cmd('minion', function, arg, kwarg=kwargs) + orig = self.client.cmd('minion', function, arg, timeout=5, kwarg=kwargs) return orig['minion'] def state_result(self, ret): From aea679801f860dcc453c088409f7f0ecb90c80fa Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sat, 30 Jun 2012 14:25:25 -0700 Subject: [PATCH 09/10] Django commands should all internally call the generic "command" function --- salt/modules/django.py | 68 +++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/salt/modules/django.py b/salt/modules/django.py index 196ba8dea7..6bffe53fcf 100644 --- a/salt/modules/django.py +++ b/salt/modules/django.py @@ -4,6 +4,7 @@ Manage Django sites import os + def _get_django_admin(bin_env): ''' Return the django admin @@ -11,7 +12,7 @@ def _get_django_admin(bin_env): if not bin_env: da = 'django-admin.py' else: - # try to get pip bin from env + # try to get django-admin.py bin from env if os.path.exists(os.path.join(bin_env, 'bin', 'django-admin.py')): da = os.path.join(bin_env, 'bin', 'django-admin.py') else: @@ -39,7 +40,6 @@ def command(settings_module, for key, value in kwargs.items(): if not key.startswith('__'): cmd = '{0} --{1}={2}'.format(cmd, key, value) - return __salt__['cmd.run'](cmd) @@ -60,17 +60,20 @@ def syncdb(settings_module, salt '*' django.syncdb settings.py ''' - da = _get_django_admin(bin_env) - cmd = '{0} syncdb --settings={1}'.format(da, settings_module) + args = [] + kwargs = {} if migrate: - cmd = '{0} --migrate'.format(cmd) + args.append('migrate') if database: - cmd = '{0} --database={1}'.format(cmd, database) - if pythonpath: - cmd = '{0} --pythonpath={1}'.format(cmd, pythonpath) + kwargs['database'] = database if noinput: - cmd = '{0} --noinput'.format(cmd) - return __salt__['cmd.run'](cmd) + args.append('noinput') + + return command(settings_module, + 'syncdb', + bin_env, + pythonpath, + *args, **kwargs) def createsuperuser(settings_module, @@ -88,14 +91,18 @@ def createsuperuser(settings_module, salt '*' django.createsuperuser settings.py user user@example.com ''' - da = _get_django_admin(bin_env) - cmd = "{0} createsuperuser --settings={1} --noinput --email='{2}' --username={3}".format( - da, settings_module, email, username) + args = ['noinput'] + kwargs = dict( + email=email, + username=username, + ) if database: - cmd = '{0} --database={1}'.format(cmd, database) - if pythonpath: - cmd = '{0} --pythonpath={1}'.format(cmd, pythonpath) - return __salt__['cmd.run'](cmd) + kwargs['database'] = database + return command(settings_module, + 'createsuperuser', + bin_env, + pythonpath, + *args, **kwargs) def loaddata(settings_module, @@ -138,25 +145,26 @@ def collectstatic(settings_module, that can easily be served in production. CLI Example:: - + salt '*' django.collectstatic settings.py ''' - da = _get_django_admin(bin_env) - cmd = '{0} collectstatic --settings={1} --noinput'.format( - da, settings_module) + args = [] + kwargs = {} if no_post_process: - cmd = '{0} --no-post-process'.format(cmd) + args.append('no-post-process') if ignore: - cmd = '{0} --ignore='.format(cmd, ignore) + kwargs['ignore'] = ignore if dry_run: - cmd = '{0} --dry-run'.format(cmd) + args.append('dry-run') if clear: - cmd = '{0} --clear'.format(cmd) + args.append('clear') if link: - cmd = '{0} --link'.format(cmd) + args.append('link') if no_default_ignore: - cmd = '{0} --no-default-ignore'.format(cmd) - if pythonpath: - cmd = '{0} --pythonpath={1}'.format(cmd, pythonpath) + args.append('no-default-ignore') - return __salt__['cmd.run'](cmd) + return command(settings_module, + 'collectstatic', + bin_env, + pythonpath, + *args, **kwargs) From edcbb727413a2c904cc3fb3eac6be5262afd30d9 Mon Sep 17 00:00:00 2001 From: Seth House Date: Sat, 30 Jun 2012 14:36:43 -0700 Subject: [PATCH 10/10] Updated the marketing blurb with something less buzzword-heavy --- doc/index.rst | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index a4005d8e87..416e7f84db 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,12 +15,19 @@ Get started with Salt .. _`presentations and interviews on Salt`: http://saltstack.org/presentations/ -Salt is a **remote execution** and **configuration management** tool. +Salt is an open source tool to manage your infrastructure. Easy enough to get +running in minutes and fast enough to manage tens of thousands of servers (and +still get a response back in *seconds*). -Salt is designed to be secure using **AES encryption** and **public-key -authentication**; incredibly scalable using an advanced **ZeroMQ** topology; -fast and efficient using **msgpack**; and extensible using small and simple -**Python** modules. +Execute arbitrary shell commands or choose from dozens of pre-built modules of +common (or complex) commands. Target individual servers or groups of servers +based on name, defined roles, or a variety of system information such as +hardware, software, operating system, current version, current environment, and +many more. + +Bring your servers up to a known configured state by writing simple lists of +items and defining attributes on those lists—no need to learn yet another +language. Read the :doc:`Salt overview ` for a more thorough description.