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..102988a
--- /dev/null
+++ b/.kitchen.yml
@@ -0,0 +1,114 @@
+---
+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
+ salt_bootstrap_options: -MPfq git %s
+ 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: -MPfq -y -x python2.7 git %s
+ - name: amazon-1
+ driver_config:
+ image: amazonlinux:1
+ platform: rhel
+ run_command: /sbin/init
+ provision_command:
+ - yum install -y upstart
+ - 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
+ - name: py2-git-2018.3
+ provisioner:
+ salt_version: 2018.3
+ - name: py2-git-fluorine
+ provisioner:
+ salt_version: fluorine
+ - name: py2-git-develop
+ provisioner:
+ salt_version: develop
+ - name: py2-stable-2017.7
+ provisioner:
+ salt_version: 2017.7
+ salt_bootstrap_options: -MP stable %s
+ excludes:
+ - arch
+ - centos-6
+ - fedora
+ - opensuse
+ - name: py2-stable-2018.3
+ provisioner:
+ salt_version: 2018.3
+ salt_bootstrap_options: -MP stable
+ excludes:
+ - centos-6
+
+verifier:
+ name: shell
+ remote_exec: false
+ command: pytest -v tests/integration/
diff --git a/.travis.yml b/.travis.yml
index d6247c1..331b9a1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,17 +1,54 @@
+language: ruby
+
+services:
+ - docker
+
+addons:
+ apt:
+ packages:
+ - curl
+ - git
+ - shellcheck
+ - python-pip
+
+rvm:
+ - ruby
+
+env:
+ matrix:
+ - PLATFORM=fedora
+ - PLATFORM=centos-7
+ - PLATFORM=centos-6
+ - PLATFORM=amazon-1
+ - PLATFORM=ubuntu-1804
+ - PLATFORM=ubuntu-1604
+ - PLATFORM=ubuntu-1404
+ - PLATFORM=debian-8
+ - PLATFORM=debian-9
+ - PLATFORM=arch
+ - PLATFORM=opensuse
+
+matrix:
+ allow_failures:
+ - env: PLATFORM=ubuntu-1804
+ - env: PLATFORM=ubuntu-1604
+ - env: PLATFORM=debian-8
+ - env: PLATFORM=debian-9
+ - env: PLATFORM=centos-6
+
+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 -l warn -c 6 "$PLATFORM"
+ - bundle exec kitchen verify "$PLATFORM"
-notifications:
- irc:
- channels: "irc.freenode.org#salt-devel"
- on_success: change
- on_failure: change
+after_script:
+ - bundle exec kitchen list $PLATFORM
+ - bundle exec kitchen destroy "$PLATFORM"
diff --git a/ChangeLog b/ChangeLog
index 76195fa..551c497 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
Version TBD (In Progress on the Develop Branch):
+Version 2018.08.15:
+ * Add tests using kitchen-salt #1279
+ * Add python-futures to Py2 installs #1279
+
Version 2018.08.13:
* Fedora Py3 fixes (The-Loeki) #1273
* Handle commented lines in the requirements files for pip pkgs (rallytime) #1271
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/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``
diff --git a/bootstrap-salt.sh b/bootstrap-salt.sh
index 9f7cf21..c3af792 100755
--- a/bootstrap-salt.sh
+++ b/bootstrap-salt.sh
@@ -18,7 +18,7 @@
#======================================================================================================================
set -o nounset # Treat unset variables as an error
-__ScriptVersion="2018.08.13"
+__ScriptVersion="2018.08.15"
__ScriptName="bootstrap-salt.sh"
__ScriptFullName="$0"
@@ -3140,7 +3140,7 @@ install_debian_8_git_deps() {
__PACKAGES="libzmq3 libzmq3-dev lsb-release python-apt python-crypto python-jinja2"
__PACKAGES="${__PACKAGES} python-m2crypto python-msgpack python-requests python-systemd"
- __PACKAGES="${__PACKAGES} python-yaml python-zmq"
+ __PACKAGES="${__PACKAGES} python-yaml python-zmq python-concurrent.futures"
if [ "$_INSTALL_CLOUD" -eq $BS_TRUE ]; then
# Install python-libcloud if asked to
@@ -3211,7 +3211,7 @@ install_debian_9_git_deps() {
PY_PKG_VER=""
# These packages are PY2-ONLY
- __PACKAGES="${__PACKAGES} python-backports-abc python-m2crypto"
+ __PACKAGES="${__PACKAGES} python-backports-abc python-m2crypto python-concurrent.futures"
fi
__PACKAGES="${__PACKAGES} python${PY_PKG_VER}-apt python${PY_PKG_VER}-crypto python${PY_PKG_VER}-jinja2"
@@ -3429,7 +3429,7 @@ install_fedora_deps() {
fi
fi
- __PACKAGES="${__PACKAGES} dnf-utils libyaml python${PY_PKG_VER}-crypto python${PY_PKG_VER}-jinja2"
+ __PACKAGES="${__PACKAGES} procps-ng dnf-utils libyaml python${PY_PKG_VER}-crypto python${PY_PKG_VER}-jinja2"
__PACKAGES="${__PACKAGES} python${PY_PKG_VER}-msgpack python${PY_PKG_VER}-requests python${PY_PKG_VER}-zmq"
# shellcheck disable=SC2086
@@ -3800,7 +3800,7 @@ install_centos_git_deps() {
fi
fi
- __PACKAGES="python${PY_PKG_VER}-crypto python${PY_PKG_VER}-jinja2"
+ __PACKAGES="${__PACKAGES} python${PY_PKG_VER}-crypto python${PY_PKG_VER}-jinja2"
__PACKAGES="${__PACKAGES} python${PY_PKG_VER}-msgpack python${PY_PKG_VER}-requests"
__PACKAGES="${__PACKAGES} python${PY_PKG_VER}-tornado python${PY_PKG_VER}-zmq"
@@ -3815,7 +3815,7 @@ install_centos_git_deps() {
if [ "${_PY_EXE}" != "" ] && [ "$_PIP_ALLOWED" -eq "$BS_TRUE" ]; then
# If "-x" is defined, install dependencies with pip based on the Python version given.
- _PIP_PACKAGES="m2crypto jinja2 msgpack-python pycrypto PyYAML tornado<5.0 zmq"
+ _PIP_PACKAGES="m2crypto jinja2 msgpack-python pycrypto PyYAML tornado<5.0 zmq futures>=2.0"
# install swig and openssl on cent6
if [ "$DISTRO_MAJOR_VERSION" -eq 6 ]; then
@@ -4608,6 +4608,7 @@ _eof
# which is already installed
__PACKAGES="m2crypto ${pkg_append}-crypto ${pkg_append}-jinja2 ${pkg_append}-PyYAML"
__PACKAGES="${__PACKAGES} ${pkg_append}-msgpack ${pkg_append}-requests ${pkg_append}-zmq"
+ __PACKAGES="${__PACKAGES} ${pkg_append}-futures"
# shellcheck disable=SC2086
__yum_install_noinput ${__PACKAGES} || return 1
@@ -4627,6 +4628,9 @@ install_amazon_linux_ami_git_deps() {
PIP_EXE='pip'
if __check_command_exists python2.7; then
if ! __check_command_exists pip2.7; then
+ if ! __check_command_exists easy_install-2.7; then
+ __yum_install_noinput python27-setuptools
+ fi
/usr/bin/easy_install-2.7 pip || return 1
fi
PIP_EXE='/usr/local/bin/pip2.7'
@@ -4646,7 +4650,7 @@ install_amazon_linux_ami_git_deps() {
if [ "$_INSTALL_CLOUD" -eq $BS_TRUE ]; then
__check_pip_allowed "You need to allow pip based installations (-P) in order to install apache-libcloud"
- __PACKAGES="${__PACKAGES} python-pip"
+ __PACKAGES="${__PACKAGES} python27-pip"
__PIP_PACKAGES="${__PIP_PACKAGES} apache-libcloud>=$_LIBCLOUD_MIN_VERSION"
fi
@@ -4795,7 +4799,7 @@ install_arch_linux_stable() {
pacman -S --noconfirm --needed bash || return 1
pacman -Su --noconfirm || return 1
# We can now resume regular salt update
- pacman -Syu --noconfirm salt || return 1
+ pacman -Syu --noconfirm salt python2-futures || return 1
return 0
}
@@ -5649,7 +5653,7 @@ install_opensuse_git_deps() {
__git_clone_and_checkout || return 1
- __PACKAGES="libzmq5 python-Jinja2 python-m2crypto python-msgpack-python python-pycrypto python-pyzmq python-xml"
+ __PACKAGES="libzmq5 python-Jinja2 python-m2crypto python-msgpack-python python-pycrypto python-pyzmq python-xml python-futures"
if [ -f "${_SALT_GIT_CHECKOUT_DIR}/requirements/base.txt" ]; then
# We're on the develop branch, install whichever tornado is on the requirements file
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/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
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..3fc60e5
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,19 @@
+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
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