From c5e8b7e88b58fbe36cc2ad297c6ccfad27cfef48 Mon Sep 17 00:00:00 2001 From: rallytime Date: Mon, 13 Aug 2018 14:20:29 -0400 Subject: [PATCH 01/27] Add sum to README for 2018.08.13 release --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index ebe66ef..1138886 100644 --- a/README.rst +++ b/README.rst @@ -28,6 +28,7 @@ sum** of the downloaded ``bootstrap-salt.sh`` file. The SHA256 sum of the ``bootstrap-salt.sh`` file, per release, is: +- 2018.08.13: ``98284bdc2b5ebaeb619b22090374e42a68e8fdefe6bff1e73bd1760db4407ed0`` - 2018.04.25: ``e2e3397d6642ba6462174b4723f1b30d04229b75efc099a553e15ea727877dfb`` - 2017.12.13: ``c127b3aa4a8422f6b81f5b4a40d31d13cec97bf3a39bca9c11a28f24910a6895`` - 2017.08.17: ``909b4d35696b9867b34b22ef4b60edbc5a0e9f8d1ed8d05f922acb79a02e46e3`` From 550e6cfebdf9855cb99c3c00c4c6b18f8202d32e Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 09:26:59 -0500 Subject: [PATCH 02/27] add kitchen tests --- .gitignore | 4 ++ .kitchen.yml | 103 +++++++++++++++++++++++++++++++++++++++++++ Gemfile | 4 ++ tests/accept_key.sls | 4 ++ 4 files changed, 115 insertions(+) create mode 100644 .kitchen.yml create mode 100644 Gemfile create mode 100644 tests/accept_key.sls diff --git a/.gitignore b/.gitignore index 2120008..0d8af35 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ # Pycharm .idea + +# test-kitchen +.bundle +Gemfile.lock diff --git a/.kitchen.yml b/.kitchen.yml new file mode 100644 index 0000000..1b9d438 --- /dev/null +++ b/.kitchen.yml @@ -0,0 +1,103 @@ +--- +driver: + name: docker + use_sudo: false + hostname: salt + privileged: true + username: kitchen + cap_add: + - sys_admin + disable_upstart: false + +provisioner: + name: salt_solo + salt_install: bootstrap + salt_bootstrap_url: bootstrap-salt.sh + install_after_init_environment: true + log_level: info + sudo: true + require_chef: false + formula: tests + state_top: + base: + '*': + - tests.accept_key + +platforms: + - name: fedora + driver_config: + image: fedora:latest + run_command: /usr/lib/systemd/systemd + - name: centos-7 + driver_config: + run_command: /usr/lib/systemd/systemd + - name: centos-6 + driver_config: + run_command: /sbin/init + provision_command: + - yum install -y upstart + provisioner: + salt_bootstrap_options: -MPq -y -x python2.7 -X git %s + - name: ubuntu-18.04 + driver_config: + run_command: /lib/systemd/systemd + - name: ubuntu-16.04 + driver_config: + run_command: /lib/systemd/systemd + - name: ubuntu-14.04 + driver_config: + run_command: /sbin/init + provision_command: + - rm -f /sbin/initctl + - dpkg-divert --local --rename --remove /sbin/initctl + - name: debian-8 + driver_config: + run_command: /lib/systemd/systemd + provision_command: + - apt-get install -y dbus + - name: debian-9 + driver_config: + run_command: /lib/systemd/systemd + - name: arch + driver_config: + image: base/archlinux + run_command: /usr/lib/systemd/systemd + provision_command: + - pacman -Syu --noconfirm systemd + - systemctl enable sshd + - name: opensuse + driver_config: + run_command: /usr/lib/systemd/systemd + provision_command: + - systemctl enable sshd.service +suites: + - name: py2-git-2017.7 + provisioner: + salt_version: 2017.7 + salt_bootstrap_options: -MPq git %s + - name: py2-git-2018.3 + provisioner: + salt_version: 2018.3 + salt_bootstrap_options: -MPq git %s + - name: py2-git-fluorine + provisioner: + salt_version: fluorine + salt_bootstrap_options: -MPq git %s + - name: py2-git-develop + provisioner: + salt_version: develop + salt_bootstrap_options: -MPq git %s + - name: py2-stable-2017.7 + provisioner: + salt_version: 2017.7 + salt_bootstrap_options: -MP stable %s + excludes: + - centos-6 + - fedora + - name: py2-stable-2018.3 + provisioner: + salt_version: 2018.3 + salt_bootstrap_options: -MP stable %s + excludes: + - centos-6 + diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..85aa1dc --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem 'kitchen-salt', '~>0.2' +gem 'kitchen-docker', :git => 'https://github.com/test-kitchen/kitchen-docker.git' diff --git a/tests/accept_key.sls b/tests/accept_key.sls new file mode 100644 index 0000000..35b5397 --- /dev/null +++ b/tests/accept_key.sls @@ -0,0 +1,4 @@ +accept_minion_key: + salt.wheel: + - name: key.accept + - match: salt From 06ce92399d56431f2a56467d8865986fa5191518 Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 09:38:53 -0500 Subject: [PATCH 03/27] add travis check --- .travis.yml | 52 +++++++++++++++++++++++++++++++----------- tests/requirements.txt | 0 2 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 tests/requirements.txt diff --git a/.travis.yml b/.travis.yml index d6247c1..29ce78e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,43 @@ +language: ruby + +services: + - docker + +addons: + apt: + packages: + - curl + - git + - shellcheck + - python-pip + +rvm: + - ruby + +env: + matrix: + - SUITE=py2-git-2017.7 + - SUITE=py2-git-2018.3 + - SUITE=py2-git-fluorine + - SUITE=py2-git-develop + - SUITE=py2-stable-2017.7 + - SUITE=py2-stable-2018.3 + + +sudo: true +dist: trusty + before_install: - - "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then sudo pip install unittest2; fi" - - sudo pip install --use-mirrors --mirrors=http://g.pypi.python.org/ --mirrors=http://c.pypi.python.org/ --mirrors=http://pypi.crate.io/ unittest-xml-reporting - - sudo apt-get remove -y -o DPkg::Options::=--force-confold --purge libzmq3 - - sudo apt-get autoremove -y -o DPkg::Options::=--force-confold --purge - - sudo ls -lah /etc/apt/sources.list.d/ - - sudo rm -f /etc/apt/sources.list.d/travis_ci_zeromq3-source.list - - sudo pip install git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting + - pip install --user -r tests/requirements.txt script: - - BS_ECHO_DEBUG=1 sudo -E python tests/runtests.py -vv + # Check shell scripts + - shellcheck -s sh -f checkstyle bootstrap-salt.sh + # Run test-kitchen with docker driver: + - bundle exec kitchen create -c 10 "$SUITE" + - bundle exec kitchen converge "$SUITE" + - bundle exec kitchen verify "$SUITE" -notifications: - irc: - channels: "irc.freenode.org#salt-devel" - on_success: change - on_failure: change +after_script: + - bundle exec kitchen list $SUITE + - bundle exec kitchen destriy "$SUITE" diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..e69de29 From 04a056794a25d7ed5832e99396cda30283c646cb Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 09:40:21 -0500 Subject: [PATCH 04/27] add depedencies to test/requirements.txt --- tests/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/requirements.txt b/tests/requirements.txt index e69de29..3d8e2c4 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +testinfra +paramiko +pywinrm From 21d76e2e2b31ed4efdc579bfe226a2ded23b9dac Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 09:44:16 -0500 Subject: [PATCH 05/27] fix travis.yml --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29ce78e..4e2f988 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,12 @@ rvm: env: matrix: - - SUITE=py2-git-2017.7 - - SUITE=py2-git-2018.3 + - SUITE=py2-git-20177 + - SUITE=py2-git-20183 - SUITE=py2-git-fluorine - SUITE=py2-git-develop - - SUITE=py2-stable-2017.7 - - SUITE=py2-stable-2018.3 + - SUITE=py2-stable-20177 + - SUITE=py2-stable-20183 sudo: true From f554bca851d56aa4f8b7319343943d1a10ba231a Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 09:54:45 -0500 Subject: [PATCH 06/27] only post warning messages --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4e2f988..aabfae1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ script: # Check shell scripts - shellcheck -s sh -f checkstyle bootstrap-salt.sh # Run test-kitchen with docker driver: - - bundle exec kitchen create -c 10 "$SUITE" + - bundle exec kitchen create -l warn -c 10 "$SUITE" - bundle exec kitchen converge "$SUITE" - bundle exec kitchen verify "$SUITE" From 7affa159e70c34273d9025539cf5294a4e6ac0c7 Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 10:09:58 -0500 Subject: [PATCH 07/27] use platforms instead of suites --- .travis.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index aabfae1..fc5fc52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,13 +16,16 @@ rvm: env: matrix: - - SUITE=py2-git-20177 - - SUITE=py2-git-20183 - - SUITE=py2-git-fluorine - - SUITE=py2-git-develop - - SUITE=py2-stable-20177 - - SUITE=py2-stable-20183 - + - PLATFORM=fedora + - PLATFORM=centos-7 + - PLATFORM=centos-6 + - PLATFORM=ubuntu-18.04 + - PLATFORM=ubuntu-16.04 + - PLATFORM=ubuntu-14.04 + - PLATFORM=debian-8 + - PLATFORM=debian-9 + - PLATFORM=arch + - PLATFORM=opensuse sudo: true dist: trusty @@ -34,10 +37,10 @@ script: # Check shell scripts - shellcheck -s sh -f checkstyle bootstrap-salt.sh # Run test-kitchen with docker driver: - - bundle exec kitchen create -l warn -c 10 "$SUITE" - - bundle exec kitchen converge "$SUITE" - - bundle exec kitchen verify "$SUITE" + - bundle exec kitchen create -l warn -c 6 "$PLATFORM" + - bundle exec kitchen converge "$PLATFORM" + - bundle exec kitchen verify "$PLATFORM" after_script: - - bundle exec kitchen list $SUITE - - bundle exec kitchen destriy "$SUITE" + - bundle exec kitchen list $PLATFORM + - bundle exec kitchen destriy "$PLATFORM" From c3d62794a208d2c19cc1996b42fcccf7ab93bcb0 Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 10:13:36 -0500 Subject: [PATCH 08/27] remove period --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc5fc52..8bacfd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,9 +19,9 @@ env: - PLATFORM=fedora - PLATFORM=centos-7 - PLATFORM=centos-6 - - PLATFORM=ubuntu-18.04 - - PLATFORM=ubuntu-16.04 - - PLATFORM=ubuntu-14.04 + - PLATFORM=ubuntu-1804 + - PLATFORM=ubuntu-1604 + - PLATFORM=ubuntu-1404 - PLATFORM=debian-8 - PLATFORM=debian-9 - PLATFORM=arch From 9bb118c0d5ceeaca023f9ed2b75fc3d23f984490 Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 14 Aug 2018 10:20:46 -0500 Subject: [PATCH 09/27] move to pytest --- tests/README.rst | 38 -- tests/bootstrap/__init__.py | 11 - tests/bootstrap/ext/nb_popen.py | 238 ------------ tests/bootstrap/test_install.py | 633 ------------------------------- tests/bootstrap/test_lint.py | 29 -- tests/bootstrap/test_usage.py | 28 -- tests/bootstrap/unittesting.py | 206 ---------- tests/conftest.py | 29 ++ tests/ext/checkbashisms | 640 -------------------------------- tests/install-testsuite-deps.py | 99 ----- tests/integration/__init__.py | 1 + tests/runtests.ps1 | 105 ------ tests/runtests.py | 115 ------ 13 files changed, 30 insertions(+), 2142 deletions(-) delete mode 100644 tests/README.rst delete mode 100644 tests/bootstrap/__init__.py delete mode 100644 tests/bootstrap/ext/nb_popen.py delete mode 100644 tests/bootstrap/test_install.py delete mode 100644 tests/bootstrap/test_lint.py delete mode 100644 tests/bootstrap/test_usage.py delete mode 100644 tests/bootstrap/unittesting.py create mode 100644 tests/conftest.py delete mode 100755 tests/ext/checkbashisms delete mode 100755 tests/install-testsuite-deps.py create mode 100644 tests/integration/__init__.py delete mode 100644 tests/runtests.ps1 delete mode 100755 tests/runtests.py diff --git a/tests/README.rst b/tests/README.rst deleted file mode 100644 index 62106f3..0000000 --- a/tests/README.rst +++ /dev/null @@ -1,38 +0,0 @@ -Salt Bootstrap Script - Integration Testing -=========================================== - -Testing ``salt-bootstrap`` requires both Python and Perl. - -Yes, it's weird that a shell script uses either one of the above languages for testing itself, the -explanation though, is simple. - -Perl is required because we use a script, `checkbashisms`_, which does exactly what it's name says. -In our case, it tries it's best to find non ``POSIX`` compliant code in our bootstrap script since -we require the script to be able to properly execute in any ``POSIX`` compliant shell. - -Python is used in the integration tests. It greatly simplifies running these tests and can even -provide some JUnit compliant XML used to generate reports of those tests. Doing this job in shell -scripting would be too cumbersome and too complicated. - -Running the tests suite ------------------------ - -.. warning:: The test suite is **destructive**. It will install/un-install packages on your system. - You must run the suite using ``sudo`` or most / all of the tests will be skipped. - -Running the tests suite is as simple as: - -.. code:: console - - sudo python tests/runtests.py - -For additional information on the available options: - -.. code:: console - - python tests/runtests.py --help - - - -.. _`checkbashisms`: http://sourceforge.net/projects/checkbaskisms/ -.. vim: fenc=utf-8 spell spl=en cc=100 tw=99 fo=want sts=2 sw=2 et diff --git a/tests/bootstrap/__init__.py b/tests/bootstrap/__init__.py deleted file mode 100644 index ccda113..0000000 --- a/tests/bootstrap/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -''' - bootstrap - ~~~~~~~~~ - - salt-bootstrap script unittesting - - :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)` - :copyright: © 2013 by the SaltStack Team, see AUTHORS for more details. - :license: Apache 2.0, see LICENSE for more details. -''' diff --git a/tests/bootstrap/ext/nb_popen.py b/tests/bootstrap/ext/nb_popen.py deleted file mode 100644 index 6b32585..0000000 --- a/tests/bootstrap/ext/nb_popen.py +++ /dev/null @@ -1,238 +0,0 @@ -# -*- coding: utf-8 -*- -''' - :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)` - :copyright: © 2013 by the SaltStack Team, see AUTHORS for more details. - :license: Apache 2.0, see LICENSE for more details. - - - salt.utils.nb_popen - ~~~~~~~~~~~~~~~~~~~ - - Non blocking subprocess Popen. - - This functionality has been adapted to work on windows following the recipe - found on: - - http://code.activestate.com/recipes/440554/ -''' - -# Import python libs -import os -import sys -import time -import errno -import select -import logging -import tempfile -import subprocess - -if subprocess.mswindows: - from win32file import ReadFile, WriteFile - from win32pipe import PeekNamedPipe - import msvcrt -else: - import fcntl - -log = logging.getLogger(__name__) - - -class NonBlockingPopen(subprocess.Popen): - - #_stdin_logger_name_ = 'salt.utils.nb_popen.STDIN.PID-{pid}' - _stdout_logger_name_ = 'salt.utils.nb_popen.STDOUT.PID-{pid}' - _stderr_logger_name_ = 'salt.utils.nb_popen.STDERR.PID-{pid}' - - def __init__(self, *args, **kwargs): - self.stream_stds = kwargs.pop('stream_stds', False) - - # Half a megabyte in memory is more than enough to start writing to - # a temporary file. - self.max_size_in_mem = kwargs.pop('max_size_in_mem', 512000) - - # Let's configure the std{in, out,err} logging handler names - #self._stdin_logger_name_ = kwargs.pop( - # 'stdin_logger_name', self._stdin_logger_name_ - #) - self._stdout_logger_name_ = kwargs.pop( - 'stdout_logger_name', self._stdout_logger_name_ - ) - self._stderr_logger_name_ = kwargs.pop( - 'stderr_logger_name', self._stderr_logger_name_ - ) - - stderr = kwargs.get('stderr', None) - - super(NonBlockingPopen, self).__init__(*args, **kwargs) - - #self._stdin_logger = logging.getLogger( - # self._stdin_logger_name_.format(pid=self.pid) - #) - - self.stdout_buff = tempfile.SpooledTemporaryFile(self.max_size_in_mem) - self._stdout_logger = logging.getLogger( - self._stdout_logger_name_.format(pid=self.pid) - ) - - if stderr is subprocess.STDOUT: - self.stderr_buff = self.stdout_buff - self._stderr_logger = self._stdout_logger - else: - self.stderr_buff = tempfile.SpooledTemporaryFile( - self.max_size_in_mem - ) - self._stderr_logger = logging.getLogger( - self._stderr_logger_name_.format(pid=self.pid) - ) - - self._stderr_logger = logging.getLogger( - self._stderr_logger_name_.format(pid=self.pid) - ) - - log.info( - 'Running command under pid {0}: {1!r}'.format(self.pid, *args) - ) - - def recv(self, maxsize=None): - return self._recv('stdout', maxsize) - - def recv_err(self, maxsize=None): - return self._recv('stderr', maxsize) - - def send_recv(self, input='', maxsize=None): - return self.send(input), self.recv(maxsize), self.recv_err(maxsize) - - def get_conn_maxsize(self, which, maxsize): - if maxsize is None: - maxsize = 1024 - elif maxsize < 1: - maxsize = 1 - return getattr(self, which), maxsize - - def _close(self, which): - getattr(self, which).close() - setattr(self, which, None) - - if subprocess.mswindows: - def send(self, input): - if not self.stdin: - return None - - try: - x = msvcrt.get_osfhandle(self.stdin.fileno()) - (errCode, written) = WriteFile(x, input) - #self._stdin_logger.debug(input.rstrip()) - except ValueError: - return self._close('stdin') - except (subprocess.pywintypes.error, Exception) as why: - if why[0] in (109, errno.ESHUTDOWN): - return self._close('stdin') - raise - - return written - - def _recv(self, which, maxsize): - conn, maxsize = self.get_conn_maxsize(which, maxsize) - if conn is None: - return None - - try: - x = msvcrt.get_osfhandle(conn.fileno()) - (read, nAvail, nMessage) = PeekNamedPipe(x, 0) - if maxsize < nAvail: - nAvail = maxsize - if nAvail > 0: - (errCode, read) = ReadFile(x, nAvail, None) - except ValueError: - return self._close(which) - except (subprocess.pywintypes.error, Exception) as why: - if why[0] in (109, errno.ESHUTDOWN): - return self._close(which) - raise - - getattr(self, '{0}_buff'.format(which)).write(read) - getattr(self, '_{0}_logger'.format(which)).debug(read.rstrip()) - if self.stream_stds: - getattr(sys, which).write(read) - - if self.universal_newlines: - read = self._translate_newlines(read) - return read - - else: - - def send(self, input): - if not self.stdin: - return None - - if not select.select([], [self.stdin], [], 0)[1]: - return 0 - - try: - written = os.write(self.stdin.fileno(), input) - #self._stdin_logger.debug(input.rstrip()) - except OSError as why: - if why[0] == errno.EPIPE: # broken pipe - return self._close('stdin') - raise - - return written - - def _recv(self, which, maxsize): - conn, maxsize = self.get_conn_maxsize(which, maxsize) - if conn is None: - return None - - flags = fcntl.fcntl(conn, fcntl.F_GETFL) - if not conn.closed: - fcntl.fcntl(conn, fcntl.F_SETFL, flags | os.O_NONBLOCK) - - try: - if not select.select([conn], [], [], 0)[0]: - return '' - - buff = conn.read(maxsize) - if not buff: - return self._close(which) - - if self.universal_newlines: - buff = self._translate_newlines(buff) - - getattr(self, '{0}_buff'.format(which)).write(buff) - getattr(self, '_{0}_logger'.format(which)).debug(buff.rstrip()) - if self.stream_stds: - getattr(sys, which).write(buff) - - return buff - finally: - if not conn.closed: - fcntl.fcntl(conn, fcntl.F_SETFL, flags) - - def poll_and_read_until_finish(self): - silent_iterations = 0 - while self.poll() is None: - if self.stdout is not None: - silent_iterations = 0 - self.recv() - - if self.stderr is not None: - silent_iterations = 0 - self.recv_err() - - silent_iterations += 1 - - if silent_iterations > 100: - silent_iterations = 0 - (stdoutdata, stderrdata) = self.communicate() - if stdoutdata: - log.debug(stdoutdata) - if stderrdata: - log.error(stderrdata) - time.sleep(0.01) - - def communicate(self, input=None): - super(NonBlockingPopen, self).communicate(input) - self.stdout_buff.flush() - self.stdout_buff.seek(0) - self.stderr_buff.flush() - self.stderr_buff.seek(0) - return self.stdout_buff.read(), self.stderr_buff.read() diff --git a/tests/bootstrap/test_install.py b/tests/bootstrap/test_install.py deleted file mode 100644 index b06d4cf..0000000 --- a/tests/bootstrap/test_install.py +++ /dev/null @@ -1,633 +0,0 @@ -# -*- coding: utf-8 -*- -''' - bootstrap.test_install - ~~~~~~~~~~~~~~~~~~~~~~ - - Run installation tests. - - :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)` - :copyright: © 2013 by the SaltStack Team, see AUTHORS for more details. - :license: Apache 2.0, see LICENSE for more details. -''' - -import os -import re -import glob -import shutil -from bootstrap.unittesting import * - -CURRENT_SALT_STABLE_VERSION = os.environ.get('CURRENT_SALT_STABLE_VERSION', 'v2014.1.10') - - -CLEANUP_COMMANDS_BY_OS_FAMILY = { - 'Arch': [ - 'pacman -Qs python2-crypto && pacman -Rsc --noconfirm python2-crypto && exit $? || exit 0', - 'pacman -Qs python2-distribute && pacman -Rsc --noconfirm python2-distribute && exit $? || exit 0', - 'pacman -Qs python2-jinja && pacman -Rsc --noconfirm python2-jinja && exit $? || exit 0', - 'pacman -Qs python2-m2crypto && pacman -Rsc --noconfirm python2-m2crypto && exit $? || exit 0', - 'pacman -Qs python2-markupsafe && pacman -Rsc --noconfirm python2-markupsafe && exit $? || exit 0', - 'pacman -Qs python2-msgpack && pacman -Rsc --noconfirm python2-msgpack && exit $? || exit 0', - 'pacman -Qs python2-psutil && pacman -Rsc --noconfirm python2-psutil && exit $? || exit 0', - 'pacman -Qs python2-pyzmq && pacman -Rsc --noconfirm python2-pyzmq && exit $? || exit 0', - 'pacman -Qs zeromq && pacman -Rsc --noconfirm zeromq && exit $? || exit 0', - 'pacman -Qs apache-libcloud && pacman -Rsc --noconfirm apache-libcloud && exit $? || exit 0', - 'pacman -Qs python2-requests && pacman -Rsc --noconfirm python2-requests && exit $? || exit 0', - ], - 'Debian': [ - 'apt-get remove -y -o DPkg::Options::=--force-confold ' - '--purge salt-master salt-minion salt-syndic python-crypto ' - 'python-jinja2 python-m2crypto python-yaml msgpack-python python-zmq', - 'apt-get autoremove -y -o DPkg::Options::=--force-confold --purge', - 'rm -rf /etc/apt/sources.list.d/saltstack-salt-*' - ], - 'RedHat': [ - 'yum -y remove salt-minion salt-master', - 'yum -y remove python{0}-m2crypto python{0}-crypto ' - 'python{0}-msgpack python{0}-zmq python{0}-jinja2'.format( - GRAINS['osrelease'].split('.')[0] == '5' and '26' or '' - ), - ], - 'FreeBSD': [ - 'pkg delete -y swig sysutils/py-salt', - 'pkg autoremove -y' - ], - 'Solaris': [ - 'pkgin -y rm libtool-base autoconf automake libuuid gcc-compiler ' - 'gmake py27-setuptools py27-crypto swig', - 'svcs network/salt-minion >/dev/null 2>&1 && svcadm disable network/salt-minion >/dev/null 2>&1 || exit 0', - 'svcs network/salt-minion >/dev/null 2>&1 && svccfg delete network/salt-minion >/dev/null 2>&1 || exit 0', - 'svcs network/salt-master >/dev/null 2>&1 && svcadm disable network/salt-master >/dev/null 2>&1 || exit 0', - 'svcs network/salt-master >/dev/null 2>&1 && svccfg delete network/salt-master >/dev/null 2>&1 || exit 0', - 'svcs network/salt-syndic >/dev/null 2>&1 && svcadm disable network/salt-syndic >/dev/null 2>&1 || exit 0', - 'svcs network/salt-syndic >/dev/null 2>&1 && svccfg delete network/salt-syndic >/dev/null 2>&1 || exit 0', - 'pip-2.7 uninstall -y PyYaml Jinja2 M2Crypto msgpack-python pyzmq' - ], - 'Suse': [ - '(zypper --non-interactive se -i salt-master || exit 0 && zypper --non-interactive remove salt-master && exit 0) || ' - '(rpm -q salt-master && rpm -e --noscripts salt-master || exit 0)', - '(zypper --non-interactive se -i salt-minion || exit 0 && zypper --non-interactive remove salt-minion && exit 0) || ' - '(rpm -q salt-minion && rpm -e --noscripts salt-minion || exit 0)', - '(zypper --non-interactive se -i salt-syndic || exit 0 && zypper --non-interactive remove salt-syndic && exit 0) || ' - '(rpm -q salt-syndic && rpm -e --noscripts salt-syndic || exit 0)', - '(zypper --non-interactive se -i salt || exit 0 && zypper --non-interactive remove salt && exit 0) || ' - '(rpm -q salt && rpm -e --noscripts salt || exit 0)', - 'pip uninstall -y salt >/dev/null 2>&1 || exit 0', - 'pip uninstall -y PyYaml >/dev/null 2>&1 || exit 0', - 'zypper --non-interactive remove libzmq3 python-Jinja2 ' - 'python-M2Crypto python-PyYAML python-msgpack-python ' - 'python-pycrypto python-pyzmq', - ], - 'void': [ - "rm -rf /var/services/salt-*", - "xbps-remove -Ry salt; rc=$?; [ $rc -eq 6 ] || return $rc" - ] -} - -OS_REQUIRES_PIP_ALLOWED = ( - # Some distributions can only install salt or some of its dependencies - # passing -P to the bootstrap script. - # The GRAINS['os'] which are in this list, requires that extra argument. - 'SmartOS', - 'Suse', # Need to revisit openSUSE and SLES for the proper OS grain. - #'SUSE Enterprise Server', # Only SuSE SLES SP1 requires -P (commented out) -) - -# SLES grains differ from openSUSE, let do a 1:1 direct mapping -CLEANUP_COMMANDS_BY_OS_FAMILY['SUSE Enterprise Server'] = \ - CLEANUP_COMMANDS_BY_OS_FAMILY['Suse'] - - -IS_SUSE_SP1 = False -if os.path.isfile('/etc/SuSE-release'): - match = re.search( - r'PATCHLEVEL(?:[\s]+)=(?:[\s]+)1', - open('/etc/SuSE-release').read() - ) - IS_SUSE_SP1 = match is not None - - -def requires_pip_based_installations(): - if GRAINS['os'] == 'SUSE Enterprise Server' and IS_SUSE_SP1: - # Only SuSE SLES SP1 requires -P - return True - if GRAINS['os'] not in OS_REQUIRES_PIP_ALLOWED: - return False - return True - - -class InstallationTestCase(BootstrapTestCase): - - def setUp(self): - if os.geteuid() is not 0: - self.skipTest('you must be root to run this test') - - if GRAINS['os_family'] not in CLEANUP_COMMANDS_BY_OS_FAMILY: - self.skipTest( - 'There is not `tearDown()` clean up support for {0!r} OS ' - 'family.'.format( - GRAINS['os_family'] - ) - ) - - def tearDown(self): - for cleanup in CLEANUP_COMMANDS_BY_OS_FAMILY[GRAINS['os_family']]: - print 'Running cleanup command {0!r}'.format(cleanup) - self.assert_script_result( - 'Failed to execute cleanup command {0!r}'.format(cleanup), - ( - 0, # Proper exit code without errors. - - 4, # ZYPPER_EXIT_ERR_ZYPP: A problem reported by ZYPP - # library. - - 65, # FreeBSD throws this error code when the packages - # being un-installed were not installed in the first - # place. - - 100, # Same as above but on Ubuntu with a another errno - - 104, # ZYPPER_EXIT_INF_CAP_NOT_FOUND: Returned by the - # install and the remove command in case any of - # the arguments does not match any of the available - # (or installed) package names or other capabilities. - ), - self.run_script( - script=None, - args=cleanup.split(), - timeout=15 * 60, - stream_stds=True - ) - ) - - # As a last resort, by hand house cleaning... - for glob_rule in ('/etc/salt', - '/etc/init*/salt*', - '/etc/rc.d/init.d/salt*', - '/lib/systemd/system/salt*', - '/tmp/git', - '/usr/bin/salt*', - '/usr/lib*/python*/*-packages/salt*', - '/usr/lib/systemd/system/salt*', - '/usr/local/etc/salt*', - '/usr/share/doc/salt*', - '/usr/share/man/man*/salt*', - '/var/*/salt*', - ): - for entry in glob.glob(glob_rule): - if 'salttesting' in glob_rule: - continue - if os.path.isfile(entry): - print 'Removing file {0!r}'.format(entry) - os.remove(entry) - elif os.path.isdir(entry): - print 'Removing directory {0!r}'.format(entry) - shutil.rmtree(entry) - - def test_install_using_bash(self): - if not os.path.exists('/bin/bash'): - self.skipTest('\'/bin/bash\' was not found on this system') - - args = [] - if requires_pip_based_installations(): - args.append('-P') - - self.assert_script_result( - 'Failed to install using bash', - 0, - self.run_script( - args=args, - executable='/bin/bash', - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt-minion', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - - def test_install_using_sh(self): - args = [] - if requires_pip_based_installations(): - args.append('-P') - - self.assert_script_result( - 'Failed to install using sh', - 0, - self.run_script( - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt-minion', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - - def test_install_explicit_stable(self): - args = [] - if requires_pip_based_installations(): - args.append('-P') - - args.append('stable') - - self.assert_script_result( - 'Failed to install explicit stable using sh', - 0, - self.run_script( - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt-minion', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - - def test_install_testing(self): - args = [] - if requires_pip_based_installations(): - args.append('-P') - - args.append('testing') - - rc, out, err = self.run_script( - args=args, timeout=15 * 60, stream_stds=True - ) - if GRAINS['os_family'] == 'RedHat': - self.assert_script_result( - 'Failed to install testing', - 0, (rc, out, err) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt-minion', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - else: - self.assert_script_result( - 'Although system is not RedHat based, we managed to install ' - 'testing', - 1, (rc, out, err) - ) - - def test_install_stable_piped_through_sh(self): - args = 'cat {0} | sh '.format(BOOTSTRAP_SCRIPT_PATH).split() - if requires_pip_based_installations(): - args.extend('-s -- -P'.split()) - - self.assert_script_result( - 'Failed to install stable piped through sh', - 0, - self.run_script( - script=None, - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt-minion', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - - #def test_install_latest_from_git_develop(self): - # args = [] - # if GRAINS['os'] in OS_REQUIRES_PIP_ALLOWED: - # args.append('-P') - # - # args.extend(['git', 'develop']) - # - # self.assert_script_result( - # 'Failed to install using latest git develop', - # 0, - # self.run_script( - # args=args, - # timeout=15 * 60, - # stream_stds=True - # ) - # ) - # - # # Try to get the versions report - # self.assert_script_result( - # 'Failed to get the versions report (\'--versions-report\')', - # 0, - # self.run_script( - # script=None, - # args=('salt', '--versions-report'), - # timeout=15 * 60, - # stream_stds=True - # ) - # ) - - def test_install_specific_git_tag(self): - args = [] - if requires_pip_based_installations(): - args.append('-P') - - args.extend(['git', CURRENT_SALT_STABLE_VERSION]) - - self.assert_script_result( - 'Failed to install using specific git tag', - 0, - self.run_script( - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - - def test_install_specific_git_sha(self): - args = [] - if requires_pip_based_installations(): - args.append('-P') - - args.extend(['git', '2b6264de62bf2ea221bb2c0b8af36dfcfaafe7cf']) - - self.assert_script_result( - 'Failed to install using specific git sha', - 0, - self.run_script( - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report (\'--versions-report\')', - 0, - self.run_script( - script=None, - args=('salt', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - - def test_config_only_without_config_dir_fails(self): - ''' - Test running in configuration mode only without providing the necessary - configuration directory fails. - ''' - self.assert_script_result( - 'The script successfully executed even though no configuration ' - 'directory was provided.', - 1, - self.run_script(args=('-C',)) - ) - - def test_config_with_a_non_existing_configuration_dir_fails(self): - ''' - Do we fail if the passed configuration directory passed does not exits? - ''' - self.assert_script_result( - 'The script successfully executed even though the configuration ' - 'directory provided does not exist.', - 1, - self.run_script( - args=('-C', '-c', '/tmp/this-directory-must-not-exist') - ) - ) - - def test_config_only_without_actually_configuring_anything_fails(self): - ''' - Test running in configuration mode only without actually configuring - anything fails. - ''' - rc, out, err = self.run_script( - args=('-C', '-n', '-c', '/tmp'), - ) - - self.assert_script_result( - 'The script did not show a warning even though no configuration ' - 'was done.', - 0, (rc, out, err) - ) - self.assertIn( - 'WARN: No configuration or keys were copied over. No ' - 'configuration was done!', - '\n'.join(out) - ) - - def test_install_salt_master(self): - ''' - Test if installing a salt-master works - ''' - args = [] - if requires_pip_based_installations(): - args.append('-P') - - args.extend(['-N', '-M']) - - self.assert_script_result( - 'Failed to install salt-master', - 0, - self.run_script( - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Try to get the versions report - self.assert_script_result( - 'Failed to get the versions report from salt-master', - 0, - self.run_script( - script=None, - args=('salt-master', '--versions-report'), - timeout=15 * 60, - stream_stds=True - ) - ) - -# def test_install_salt_syndic(self): -# ''' -# Test if installing a salt-syndic works -# ''' -# if GRAINS['os'] == 'Debian': -# self.skipTest( -# 'Currently the debian stable package will have the syndic ' -# 'waiting for a connection to a master.' -# ) -# elif GRAINS['os'] == 'Ubuntu': -# self.skipTest( -# 'We\'re currently having issues having a syndic running ' -# 'right after installation, without any specific ' -# 'configuration, under Ubuntu' -# ) -# -# args = [] -# if requires_pip_based_installations(): -# args.append('-P') -# -# args.extend(['-N', '-S']) -# -# self.assert_script_result( -# 'Failed to install salt-syndic', -# 0, -# self.run_script( -# args=args, -# timeout=15 * 60, -# stream_stds=True -# ) -# ) -# -# # Try to get the versions report -# self.assert_script_result( -# 'Failed to get the versions report from salt-syndic', -# 0, -# self.run_script( -# script=None, -# args=('salt-syndic', '--versions-report'), -# timeout=15 * 60, -# stream_stds=True -# ) -# ) - - def test_install_pip_not_allowed(self): - ''' - Check if distributions which require `-P` to allow pip to install - packages, fail if that flag is not passed. - ''' - if not requires_pip_based_installations(): - self.skipTest( - 'Distribution {0} does not require the extra `-P` flag'.format( - GRAINS['os'] - ) - ) - - self.assert_script_result( - 'Even though {0} is flagged as requiring the extra `-P` flag to ' - 'allow packages to be installed by pip, it did not fail when we ' - 'did not pass it'.format(GRAINS['os']), - 1, - self.run_script( - timeout=15 * 60, - stream_stds=True - ) - ) - - def test_install_from_git_on_checked_out_repository(self): - ''' - Check if the script properly updates an already checked out repository. - ''' - if not os.path.isdir('/tmp/git'): - os.makedirs('/tmp/git') - - # Clone salt from git - self.assert_script_result( - 'Failed to clone salt\'s git repository', - 0, - self.run_script( - script=None, - args=('git', 'clone', 'https://github.com/saltstack/salt.git'), - cwd='/tmp/git', - timeout=15 * 60, - stream_stds=True - ) - ) - - # Check-out a specific revision - self.assert_script_result( - 'Failed to checkout v0.12.1 from salt\'s cloned git repository', - 0, - self.run_script( - script=None, - args=('git', 'checkout', 'v0.12.1'), - cwd='/tmp/git/salt', - timeout=15 * 60, - stream_stds=True - ) - ) - - # Now run the bootstrap script over an existing git checkout and see - # if it properly updates. - args = [] - if requires_pip_based_installations(): - args.append('-P') - - args.extend(['git', CURRENT_SALT_STABLE_VERSION]) - - self.assert_script_result( - 'Failed to install using specific git tag', - 0, - self.run_script( - args=args, - timeout=15 * 60, - stream_stds=True - ) - ) - - # Get the version from the salt binary just installed - # Do it as a two step so we can check the returning output. - rc, out, err = self.run_script( - script=None, - args=('salt', '--version'), - timeout=15 * 60, - stream_stds=True - ) - - self.assert_script_result( - 'Failed to get the salt version', - 0, (rc, out, err) - ) - - # Make sure the installation updated the git repository to the proper - # git tag before installing. - self.assertIn(CURRENT_SALT_STABLE_VERSION.lstrip('v'), '\n'.join(out)) diff --git a/tests/bootstrap/test_lint.py b/tests/bootstrap/test_lint.py deleted file mode 100644 index a05d0ca..0000000 --- a/tests/bootstrap/test_lint.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -''' - bootstrap.test_lint - ~~~~~~~~~~~~~~~~~~~ - - :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)` - :copyright: © 2013 by the UfSoft.org Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -''' -from bootstrap.unittesting import * - - -class LintTestCase(BootstrapTestCase): - def test_bashisms(self): - ''' - Lint check the bootstrap script for any possible bash'isms. - ''' - if not os.path.exists('/usr/bin/perl'): - self.skipTest('\'/usr/bin/perl\' was not found on this system') - self.assert_script_result( - 'Some bashisms were found', - 0, - self.run_script( - script=os.path.join(EXT_DIR, 'checkbashisms'), - args=('-pxfn', BOOTSTRAP_SCRIPT_PATH), - timeout=120, - stream_stds=True - ) - ) diff --git a/tests/bootstrap/test_usage.py b/tests/bootstrap/test_usage.py deleted file mode 100644 index adf87b3..0000000 --- a/tests/bootstrap/test_usage.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -''' - bootstrap.test_usage - ~~~~~~~~~~~~~~~~~~~~ - - :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)` - :copyright: © 2013 by the UfSoft.org Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -''' -from bootstrap.unittesting import * - - -class UsageTestCase(BootstrapTestCase): - def test_no_daemon_install_shows_warning(self): - ''' - Passing '-N'(no minion) without passing '-M'(install master) or - '-S'(install syndic) shows a warning. - ''' - rc, out, err = self.run_script( - args=('-N', '-n'), - ) - - self.assert_script_result( - 'Not installing any daemons nor configuring did not throw any ' - 'warning', - 0, (rc, out, err) - ) - self.assertIn(' * WARN: Nothing to install or configure', out) diff --git a/tests/bootstrap/unittesting.py b/tests/bootstrap/unittesting.py deleted file mode 100644 index 7027bc9..0000000 --- a/tests/bootstrap/unittesting.py +++ /dev/null @@ -1,206 +0,0 @@ -# -*- coding: utf-8 -*- -''' - bootstrap.unittesting - ~~~~~~~~~~~~~~~~~~~~~ - - Unit testing related classes, helpers. - - :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)` - :copyright: © 2013 by the SaltStack Team, see AUTHORS for more details. - :license: Apache 2.0, see LICENSE for more details. -''' - -# Import python libs -import os -import sys -import fcntl -import signal -import logging -import subprocess -from datetime import datetime, timedelta - -# Import salt testing libs -from salttesting import * -from salttesting.ext.os_data import GRAINS - -TEST_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -EXT_DIR = os.path.join(TEST_DIR, 'ext') -PARENT_DIR = os.path.dirname(TEST_DIR) -BOOTSTRAP_SCRIPT_PATH = os.path.join(PARENT_DIR, 'bootstrap-salt.sh') - - -class NonBlockingPopen(subprocess.Popen): - - _stdout_logger_name_ = 'salt-bootstrap.NonBlockingPopen.STDOUT.PID-{pid}' - _stderr_logger_name_ = 'salt-bootstrap.NonBlockingPopen.STDERR.PID-{pid}' - - def __init__(self, *args, **kwargs): - self.stream_stds = kwargs.pop('stream_stds', False) - self._stdout_logger = self._stderr_logger = None - super(NonBlockingPopen, self).__init__(*args, **kwargs) - if self.stdout is not None and self.stream_stds: - fod = self.stdout.fileno() - fol = fcntl.fcntl(fod, fcntl.F_GETFL) - fcntl.fcntl(fod, fcntl.F_SETFL, fol | os.O_NONBLOCK) - self.obuff = '' - - if self.stderr is not None and self.stream_stds: - fed = self.stderr.fileno() - fel = fcntl.fcntl(fed, fcntl.F_GETFL) - fcntl.fcntl(fed, fcntl.F_SETFL, fel | os.O_NONBLOCK) - self.ebuff = '' - - def poll(self): - if self._stdout_logger is None: - self._stdout_logger = logging.getLogger( - self._stdout_logger_name_.format(pid=self.pid) - ) - if self._stderr_logger is None: - self._stderr_logger = logging.getLogger( - self._stderr_logger_name_.format(pid=self.pid) - ) - poll = super(NonBlockingPopen, self).poll() - - if self.stdout is not None and self.stream_stds: - try: - obuff = self.stdout.read() - self.obuff += obuff - if obuff.strip(): - self._stdout_logger.info(obuff.strip()) - sys.stdout.write(obuff) - except IOError, err: - if err.errno not in (11, 35): - # We only handle Resource not ready properly, any other - # raise the exception - raise - if self.stderr is not None and self.stream_stds: - try: - ebuff = self.stderr.read() - self.ebuff += ebuff - if ebuff.strip(): - self._stderr_logger.info(ebuff.strip()) - sys.stderr.write(ebuff) - except IOError, err: - if err.errno not in (11, 35): - # We only handle Resource not ready properly, any other - # raise the exception - raise - - if poll is None: - # Not done yet - return poll - - if not self.stream_stds: - # Allow the same attribute access even though not streaming to stds - try: - self.obuff = self.stdout.read() - except IOError, err: - if err.errno not in (11, 35): - # We only handle Resource not ready properly, any other - # raise the exception - raise - try: - self.ebuff = self.stderr.read() - except IOError, err: - if err.errno not in (11, 35): - # We only handle Resource not ready properly, any other - # raise the exception - raise - return poll - - -class BootstrapTestCase(TestCase): - def run_script(self, - script=BOOTSTRAP_SCRIPT_PATH, - args=(), - cwd=PARENT_DIR, - timeout=None, - executable='/bin/sh', - stream_stds=False): - - cmd = [script] + list(args) - - popen_kwargs = { - 'cwd': cwd, - 'shell': True, - 'stderr': subprocess.PIPE, - 'stdout': subprocess.PIPE, - 'close_fds': True, - 'executable': executable, - - 'stream_stds': stream_stds, - - # detach from parent group (no more inherited signals!) - #'preexec_fn': os.setpgrp - } - - cmd = ' '.join(filter(None, [script] + list(args))) - - process = NonBlockingPopen(cmd, **popen_kwargs) - - if timeout is not None: - stop_at = datetime.now() + timedelta(seconds=timeout) - term_sent = False - - while process.poll() is None: - - if timeout is not None: - now = datetime.now() - - if now > stop_at: - if term_sent is False: - # Kill the process group since sending the term signal - # would only terminate the shell, not the command - # executed in the shell - os.killpg(os.getpgid(process.pid), signal.SIGINT) - term_sent = True - continue - - # As a last resort, kill the process group - os.killpg(os.getpgid(process.pid), signal.SIGKILL) - - return 1, [ - 'Process took more than {0} seconds to complete. ' - 'Process Killed! Current STDOUT: \n{1}'.format( - timeout, process.obuff - ) - ], [ - 'Process took more than {0} seconds to complete. ' - 'Process Killed! Current STDERR: \n{1}'.format( - timeout, process.ebuff - ) - ] - - process.communicate() - - try: - return ( - process.returncode, - process.obuff.splitlines(), - process.ebuff.splitlines() - ) - finally: - try: - process.terminate() - except OSError: - # process already terminated - pass - - def assert_script_result(self, fail_msg, expected_rcs, process_details): - if not isinstance(expected_rcs, (tuple, list)): - expected_rcs = (expected_rcs,) - - rc, out, err = process_details - if rc not in expected_rcs: - err_msg = '{0}:\n'.format(fail_msg) - if out: - err_msg = '{0}STDOUT:\n{1}\n'.format(err_msg, '\n'.join(out)) - if err: - err_msg = '{0}STDERR:\n{1}\n'.format(err_msg, '\n'.join(err)) - if not err and not out: - err_msg = ( - '{0} No stdout nor stderr captured. Exit code: {1}'.format( - err_msg, rc - ) - ) - raise AssertionError(err_msg.rstrip()) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..08d2c88 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,29 @@ +import functools +import os +import pytest +import subprocess +import testinfra + +if os.environ.get('KITCHEN_USERNAME') == 'vagrant': + if 'windows' in os.environ.get('KITCHEN_INSTANCE'): + test_host = testinfra.get_host('winrm://{KITCHEN_USERNAME}:{KITCHEN_PASSWORD}@{KITCHEN_HOSTNAME}:{KITCHEN_PORT}'.format(**os.environ), no_ssl=True) + else: + test_host = testinfra.get_host('paramiko://{KITCHEN_USERNAME}@{KITCHEN_HOSTNAME}:{KITCHEN_PORT}'.format(**os.environ), + ssh_identity_file=os.environ.get('KITCHEN_SSH_KEY')) +else: + test_host = testinfra.get_host('docker://{KITCHEN_USERNAME}@{KITCHEN_CONTAINER_ID}'.format(**os.environ)) + + +@pytest.fixture +def host(): + return test_host + + +@pytest.fixture +def salt(): + if 'windows' in os.environ.get('KITCHEN_INSTANCE'): + tmpconf = r'c:\Users\vagrant\AppData\Local\Temp\kitchen\etc\salt' + else: + test_host.run('sudo chown -R {0} /tmp/kitchen'.format(os.environ.get('KITCHEN_USERNAME'))) + tmpconf = '/tmp/kitchen/etc/salt' + return functools.partial(test_host.salt, config=tmpconf) diff --git a/tests/ext/checkbashisms b/tests/ext/checkbashisms deleted file mode 100755 index 5704819..0000000 --- a/tests/ext/checkbashisms +++ /dev/null @@ -1,640 +0,0 @@ -#! /usr/bin/perl -w - -# This script is essentially copied from /usr/share/lintian/checks/scripts, -# which is: -# Copyright (C) 1998 Richard Braakman -# Copyright (C) 2002 Josip Rodin -# This version is -# Copyright (C) 2003 Julian Gilbey -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -use strict; -use Getopt::Long qw(:config gnu_getopt); -use File::Temp qw/tempfile/; - -sub init_hashes; - -(my $progname = $0) =~ s|.*/||; - -my $usage = <<"EOF"; -Usage: $progname [-n] [-f] [-x] script ... - or: $progname --help - or: $progname --version -This script performs basic checks for the presence of bashisms -in /bin/sh scripts. -EOF - -my $version = <<"EOF"; -This is $progname, from the Debian devscripts package, version 2.11.6ubuntu1.4 -This code is copyright 2003 by Julian Gilbey , -based on original code which is copyright 1998 by Richard Braakman -and copyright 2002 by Josip Rodin. -This program comes with ABSOLUTELY NO WARRANTY. -You are free to redistribute this code under the terms of the -GNU General Public License, version 2, or (at your option) any later version. -EOF - -my ($opt_echo, $opt_force, $opt_extra, $opt_posix); -my ($opt_help, $opt_version); -my @filenames; - -# Detect if STDIN is a pipe -if (-p STDIN or -f STDIN) { - my ($tmp_fh, $tmp_filename) = tempfile("chkbashisms_tmp.XXXX", TMPDIR => 1, UNLINK => 1); - while (my $line = ) { - print $tmp_fh $line; - } - close($tmp_fh); - push(@ARGV, $tmp_filename); -} - -## -## handle command-line options -## -$opt_help = 1 if int(@ARGV) == 0; - -GetOptions("help|h" => \$opt_help, - "version|v" => \$opt_version, - "newline|n" => \$opt_echo, - "force|f" => \$opt_force, - "extra|x" => \$opt_extra, - "posix|p" => \$opt_posix, - ) - or die "Usage: $progname [options] filelist\nRun $progname --help for more details\n"; - -if ($opt_help) { print $usage; exit 0; } -if ($opt_version) { print $version; exit 0; } - -$opt_echo = 1 if $opt_posix; - -my $status = 0; -my $makefile = 0; -my (%bashisms, %string_bashisms, %singlequote_bashisms); - -my $LEADIN = qr'(?:(?:^|[`&;(|{])\s*|(?:if|then|do|while|shell)\s+)'; -init_hashes; - -foreach my $filename (@ARGV) { - my $check_lines_count = -1; - - my $display_filename = $filename; - if ($filename =~ /chkbashisms_tmp\.....$/) { - $display_filename = "(stdin)"; - } - - if (!$opt_force) { - $check_lines_count = script_is_evil_and_wrong($filename); - } - - if ($check_lines_count == 0 or $check_lines_count == 1) { - warn "script $display_filename does not appear to be a /bin/sh script; skipping\n"; - next; - } - - if ($check_lines_count != -1) { - warn "script $display_filename appears to be a shell wrapper; only checking the first " - . "$check_lines_count lines\n"; - } - - unless (open C, '<', $filename) { - warn "cannot open script $display_filename for reading: $!\n"; - $status |= 2; - next; - } - - my $cat_string = ""; - my $cat_indented = 0; - my $quote_string = ""; - my $last_continued = 0; - my $continued = 0; - my $found_rules = 0; - my $buffered_orig_line = ""; - my $buffered_line = ""; - - while () { - next unless ($check_lines_count == -1 or $. <= $check_lines_count); - - if ($. == 1) { # This should be an interpreter line - if (m,^\#!\s*(\S+),) { - my $interpreter = $1; - - if ($interpreter =~ m,/make$,) { - init_hashes if !$makefile++; - $makefile = 1; - } else { - init_hashes if $makefile--; - $makefile = 0; - } - next if $opt_force; - - if ($interpreter =~ m,/bash$,) { - warn "script $display_filename is already a bash script; skipping\n"; - $status |= 2; - last; # end this file - } - elsif ($interpreter !~ m,/(sh|posh)$,) { -### ksh/zsh? - warn "script $display_filename does not appear to be a /bin/sh script; skipping\n"; - $status |= 2; - last; - } - } else { - warn "script $display_filename does not appear to have a \#! interpreter line;\nyou may get strange results\n"; - } - } - - chomp; - my $orig_line = $_; - - # We want to remove end-of-line comments, so need to skip - # comments that appear inside balanced pairs - # of single or double quotes - - # Remove comments in the "quoted" part of a line that starts - # in a quoted block? The problem is that we have no idea - # whether the program interpreting the block treats the - # quote character as part of the comment or as a quote - # terminator. We err on the side of caution and assume it - # will be treated as part of the comment. - # s/^(?:.*?[^\\])?$quote_string(.*)$/$1/ if $quote_string ne ""; - - # skip comment lines - if (m,^\s*\#, && $quote_string eq '' && $buffered_line eq '' && $cat_string eq '') { - next; - } - - # Remove quoted strings so we can more easily ignore comments - # inside them - s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g; - s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g; - - # If the remaining string contains what looks like a comment, - # eat it. In either case, swap the unmodified script line - # back in for processing. - if (m/(?:^|[^[\\])[\s\&;\(\)](\#.*$)/) { - $_ = $orig_line; - s/\Q$1\E//; # eat comments - } else { - $_ = $orig_line; - } - - # Handle line continuation - if (!$makefile && $cat_string eq '' && m/\\$/) { - chop; - $buffered_line .= $_; - $buffered_orig_line .= $orig_line . "\n"; - next; - } - - if ($buffered_line ne '') { - $_ = $buffered_line . $_; - $orig_line = $buffered_orig_line . $orig_line; - $buffered_line =''; - $buffered_orig_line =''; - } - - if ($makefile) { - $last_continued = $continued; - if (/[^\\]\\$/) { - $continued = 1; - } else { - $continued = 0; - } - - # Don't match lines that look like a rule if we're in a - # continuation line before the start of the rules - if (/^[\w%-]+:+\s.*?;?(.*)$/ and !($last_continued and !$found_rules)) { - $found_rules = 1; - $_ = $1 if $1; - } - - last if m%^\s*(override\s|export\s)?\s*SHELL\s*:?=\s*(/bin/)?bash\s*%; - - # Remove "simple" target names - s/^[\w%.-]+(?:\s+[\w%.-]+)*::?//; - s/^\t//; - s/(?|<|;|\Z)/ - and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/) { - if ($2 =~ /^(\&|\||\d?>|<)/) { - # everything is ok - ; - } else { - $found = 1; - $match = $1; - $explanation = "sourced script with arguments"; - output_explanation($display_filename, $orig_line, $explanation); - } - } - - # Remove "quoted quotes". They're likely to be inside - # another pair of quotes; we're not interested in - # them for their own sake and removing them makes finding - # the limits of the outer pair far easier. - $line =~ s/(^|[^\\\'\"])\"\'\"/$1/g; - $line =~ s/(^|[^\\\'\"])\'\"\'/$1/g; - - while (my ($re,$expl) = each %singlequote_bashisms) { - if ($line =~ m/($re)/) { - $found = 1; - $match = $1; - $explanation = $expl; - output_explanation($display_filename, $orig_line, $explanation); - } - } - - my $re='(?); - } - } - - # $cat_line contains the version of the line we'll check - # for heredoc delimiters later. Initially, remove any - # spaces between << and the delimiter to make the following - # updates to $cat_line easier. - my $cat_line = $line; - $cat_line =~ s/(<\<-?)\s+/$1/g; - - # Ignore anything inside single quotes; it could be an - # argument to grep or the like. - $line =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g; - - # As above, with the exception that we don't remove the string - # if the quote is immediately preceeded by a < or a -, so we - # can match "foo <<-?'xyz'" as a heredoc later - # The check is a little more greedy than we'd like, but the - # heredoc test itself will weed out any false positives - $cat_line =~ s/(^|[^<\\\"-](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g; - - $re='(?); - } - } - - while (my ($re,$expl) = each %string_bashisms) { - if ($line =~ m/($re)/) { - $found = 1; - $match = $1; - $explanation = $expl; - output_explanation($display_filename, $orig_line, $explanation); - } - } - - # We've checked for all the things we still want to notice in - # double-quoted strings, so now remove those strings as well. - $line =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g; - $cat_line =~ s/(^|[^<\\\'-](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g; - while (my ($re,$expl) = each %bashisms) { - if ($line =~ m/($re)/) { - $found = 1; - $match = $1; - $explanation = $expl; - output_explanation($display_filename, $orig_line, $explanation); - } - } - - # Only look for the beginning of a heredoc here, after we've - # stripped out quoted material, to avoid false positives. - if ($cat_line =~ m/(?:^|[^<])\<\<(\-?)\s*(?:[\\]?(\w+)|[\'\"](.*?)[\'\"])/) { - $cat_indented = ($1 && $1 eq '-')? 1 : 0; - $cat_string = $2; - $cat_string = $3 if not defined $cat_string; - } - } - } - - warn "error: $filename: Unterminated heredoc found, EOF reached. Wanted: <$cat_string>\n" - if ($cat_string ne ''); - warn "error: $filename: Unterminated quoted string found, EOF reached. Wanted: <$quote_string>\n" - if ($quote_string ne ''); - warn "error: $filename: EOF reached while on line continuation.\n" - if ($buffered_line ne ''); - - close C; -} - -exit $status; - -sub output_explanation { - my ($filename, $line, $explanation) = @_; - - warn "possible bashism in $filename line $. ($explanation):\n$line\n"; - $status |= 1; -} - -# Returns non-zero if the given file is not actually a shell script, -# just looks like one. -sub script_is_evil_and_wrong { - my ($filename) = @_; - my $ret = -1; - # lintian's version of this function aborts if the file - # can't be opened, but we simply return as the next - # test in the calling code handles reporting the error - # itself - open (IN, '<', $filename) or return $ret; - my $i = 0; - my $var = "0"; - my $backgrounded = 0; - local $_; - while () { - chomp; - next if /^#/o; - next if /^$/o; - last if (++$i > 55); - if (m~ - # the exec should either be "eval"ed or a new statement - (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*) - - # eat anything between the exec and $0 - exec\s*.+\s* - - # optionally quoted executable name (via $0) - .?\$$var.?\s* - - # optional "end of options" indicator - (--\s*)? - - # Match expressions of the form '${1+$@}', '${1:+"$@"', - # '"${1+$@', "$@", etc where the quotes (before the dollar - # sign(s)) are optional and the second (or only if the $1 - # clause is omitted) parameter may be $@ or $*. - # - # Finally the whole subexpression may be omitted for scripts - # which do not pass on their parameters (i.e. after re-execing - # they take their parameters (and potentially data) from stdin - .?(\${1:?\+.?)?(\$(\@|\*))?~x) { - $ret = $. - 1; - last; - } elsif (/^\s*(\w+)=\$0;/) { - $var = $1; - } elsif (m~ - # Match scripts which use "foo $0 $@ &\nexec true\n" - # Program name - \S+\s+ - - # As above - .?\$$var.?\s* - (--\s*)? - .?(\${1:?\+.?)?(\$(\@|\*))?.?\s*\&~x) { - - $backgrounded = 1; - } elsif ($backgrounded and m~ - # the exec should either be "eval"ed or a new statement - (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*) - exec\s+true(\s|\Z)~x) { - - $ret = $. - 1; - last; - } elsif (m~\@DPATCH\@~) { - $ret = $. - 1; - last; - } - - } - close IN; - return $ret; -} - -sub init_hashes { - - %bashisms = ( - qr'(?:^|\s+)function \w+(\s|\(|\Z)' => q<'function' is useless>, - $LEADIN . qr'select\s+\w+' => q<'select' is not POSIX>, - qr'(test|-o|-a)\s*[^\s]+\s+==\s' => q, - qr'\[\s+[^\]]+\s+==\s' => q, - qr'\s\|\&' => q, - qr'[^\\\$]\{([^\s\\\}]*?,)+[^\\\}\s]*\}' => q, - qr'\{\d+\.\.\d+\}' => q, - qr'(?:^|\s+)\w+\[\d+\]=' => q, - $LEADIN . qr'read\s+(?:-[a-qs-zA-Z\d-]+)' => q, - $LEADIN . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)' - => q, - $LEADIN . qr'echo\s+(-n\s+)?-n?en?\s' => q, - $LEADIN . qr'exec\s+-[acl]' => q, - $LEADIN . qr'let\s' => q, - qr'(? q<'((' should be '$(('>, - qr'(?:^|\s+)(\[|test)\s+-a' => q, - qr'\&>' => qword 2\>&1>, - qr'(<\&|>\&)\s*((-|\d+)[^\s;|)}`&\\\\]|[^-\d\s]+(? - qword 2\>&1>, - qr'\[\[(?!:)' => q, - qr'/dev/(tcp|udp)' => q, - $LEADIN . qr'builtin\s' => q, - $LEADIN . qr'caller\s' => q, - $LEADIN . qr'compgen\s' => q, - $LEADIN . qr'complete\s' => q, - $LEADIN . qr'declare\s' => q, - $LEADIN . qr'dirs(\s|\Z)' => q, - $LEADIN . qr'disown\s' => q, - $LEADIN . qr'enable\s' => q, - $LEADIN . qr'mapfile\s' => q, - $LEADIN . qr'readarray\s' => q, - $LEADIN . qr'shopt(\s|\Z)' => q, - $LEADIN . qr'suspend\s' => q, - $LEADIN . qr'time\s' => q