mirror of
https://github.com/saltstack/salt-bootstrap.git
synced 2025-04-16 09:40:21 +00:00
[stable] Merge develop into stable for new release 2018.08.15
This commit is contained in:
commit
de93938dfd
23 changed files with 225 additions and 2164 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -4,3 +4,7 @@
|
|||
|
||||
# Pycharm
|
||||
.idea
|
||||
|
||||
# test-kitchen
|
||||
.bundle
|
||||
Gemfile.lock
|
||||
|
|
114
.kitchen.yml
Normal file
114
.kitchen.yml
Normal file
|
@ -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/
|
63
.travis.yml
63
.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"
|
||||
|
|
|
@ -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
|
||||
|
|
4
Gemfile
Normal file
4
Gemfile
Normal file
|
@ -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'
|
|
@ -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``
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
4
tests/accept_key.sls
Normal file
4
tests/accept_key.sls
Normal file
|
@ -0,0 +1,4 @@
|
|||
accept_minion_key:
|
||||
salt.wheel:
|
||||
- name: key.accept
|
||||
- match: salt
|
|
@ -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.
|
||||
'''
|
|
@ -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()
|
|
@ -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))
|
|
@ -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
|
||||
)
|
||||
)
|
|
@ -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)
|
|
@ -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())
|
19
tests/conftest.py
Normal file
19
tests/conftest.py
Normal file
|
@ -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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <jdg\@debian.org>,
|
||||
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 = <STDIN>) {
|
||||
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 (<C>) {
|
||||
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/(?<!\$)\$\((\w+)\)/\${$1}/g;
|
||||
s/(\$){2}/$1/g;
|
||||
s/^[\s\t]*[@-]{1,2}//;
|
||||
}
|
||||
|
||||
if ($cat_string ne "" && (m/^\Q$cat_string\E$/ || ($cat_indented && m/^\t*\Q$cat_string\E$/))) {
|
||||
$cat_string = "";
|
||||
next;
|
||||
}
|
||||
my $within_another_shell = 0;
|
||||
if (m,(^|\s+)((/usr)?/bin/)?((b|d)?a|k|z|t?c)sh\s+-c\s*.+,) {
|
||||
$within_another_shell = 1;
|
||||
}
|
||||
# if cat_string is set, we are in a HERE document and need not
|
||||
# check for things
|
||||
if ($cat_string eq "" and !$within_another_shell) {
|
||||
my $found = 0;
|
||||
my $match = '';
|
||||
my $explanation = '';
|
||||
my $line = $_;
|
||||
|
||||
# Remove "" / '' as they clearly aren't quoted strings
|
||||
# and not considering them makes the matching easier
|
||||
$line =~ s/(^|[^\\])(\'\')+/$1/g;
|
||||
$line =~ s/(^|[^\\])(\"\")+/$1/g;
|
||||
|
||||
if ($quote_string ne "") {
|
||||
my $otherquote = ($quote_string eq "\"" ? "\'" : "\"");
|
||||
# Inside a quoted block
|
||||
if ($line =~ /(?:^|^.*?[^\\])$quote_string(.*)$/) {
|
||||
my $rest = $1;
|
||||
my $templine = $line;
|
||||
|
||||
# Remove quoted strings delimited with $otherquote
|
||||
$templine =~ s/(^|[^\\])$otherquote[^$quote_string]*?[^\\]$otherquote/$1/g;
|
||||
# Remove quotes that are themselves quoted
|
||||
# "a'b"
|
||||
$templine =~ s/(^|[^\\])$otherquote.*?$quote_string.*?[^\\]$otherquote/$1/g;
|
||||
# "\""
|
||||
$templine =~ s/(^|[^\\])$quote_string\\$quote_string$quote_string/$1/g;
|
||||
|
||||
# After all that, were there still any quotes left?
|
||||
my $count = () = $templine =~ /(^|[^\\])$quote_string/g;
|
||||
next if $count == 0;
|
||||
|
||||
$count = () = $rest =~ /(^|[^\\])$quote_string/g;
|
||||
if ($count % 2 == 0) {
|
||||
# Quoted block ends on this line
|
||||
# Ignore everything before the closing quote
|
||||
$line = $rest || '';
|
||||
$quote_string = "";
|
||||
} else {
|
||||
next;
|
||||
}
|
||||
} else {
|
||||
# Still inside the quoted block, skip this line
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# Check even if we removed the end of a quoted block
|
||||
# in the previous check, as a single line can end one
|
||||
# block and begin another
|
||||
if ($quote_string eq "") {
|
||||
# Possible start of a quoted block
|
||||
for my $quote ("\"", "\'") {
|
||||
my $templine = $line;
|
||||
my $otherquote = ($quote eq "\"" ? "\'" : "\"");
|
||||
|
||||
# Remove balanced quotes and their content
|
||||
$templine =~ s/(^|[^\\\"](?:\\\\)*)\'[^\']*\'/$1/g;
|
||||
$templine =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1/g;
|
||||
|
||||
# Don't flag quotes that are themselves quoted
|
||||
# "a'b"
|
||||
$templine =~ s/$otherquote.*?$quote.*?$otherquote//g;
|
||||
# "\""
|
||||
$templine =~ s/(^|[^\\])$quote\\$quote$quote/$1/g;
|
||||
# \' or \"
|
||||
$templine =~ s/\\[\'\"]//g;
|
||||
my $count = () = $templine =~ /(^|(?!\\))$quote/g;
|
||||
|
||||
# If there's an odd number of non-escaped
|
||||
# quotes in the line it's almost certainly the
|
||||
# start of a quoted block.
|
||||
if ($count % 2 == 1) {
|
||||
$quote_string = $quote;
|
||||
$line =~ s/^(.*)$quote.*$/$1/;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# since this test is ugly, I have to do it by itself
|
||||
# detect source (.) trying to pass args to the command it runs
|
||||
# The first expression weeds out '. "foo bar"'
|
||||
if (not $found and
|
||||
not m/$LEADIN\.\s+(\"[^\"]+\"|\'[^\']+\'|\$\([^)]+\)+(?:\/[^\s;]+)?)\s*(\&|\||\d?>|<|;|\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='(?<![\$\\\])\$\'[^\']+\'';
|
||||
if ($line =~ m/(.*)($re)/){
|
||||
my $count = () = $1 =~ /(^|[^\\])\'/g;
|
||||
if( $count % 2 == 0 ) {
|
||||
output_explanation($display_filename, $orig_line, q<$'...' should be "$(printf '...')">);
|
||||
}
|
||||
}
|
||||
|
||||
# $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='(?<![\$\\\])\$\"[^\"]+\"';
|
||||
if ($line =~ m/(.*)($re)/){
|
||||
my $count = () = $1 =~ /(^|[^\\])\"/g;
|
||||
if( $count % 2 == 0 ) {
|
||||
output_explanation($display_filename, $orig_line, q<$"foo" should be eval_gettext "foo">);
|
||||
}
|
||||
}
|
||||
|
||||
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 (<IN>) {
|
||||
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<should be 'b = a'>,
|
||||
qr'\[\s+[^\]]+\s+==\s' => q<should be 'b = a'>,
|
||||
qr'\s\|\&' => q<pipelining is not POSIX>,
|
||||
qr'[^\\\$]\{([^\s\\\}]*?,)+[^\\\}\s]*\}' => q<brace expansion>,
|
||||
qr'\{\d+\.\.\d+\}' => q<brace expansion, should be $(seq a b)>,
|
||||
qr'(?:^|\s+)\w+\[\d+\]=' => q<bash arrays, H[0]>,
|
||||
$LEADIN . qr'read\s+(?:-[a-qs-zA-Z\d-]+)' => q<read with option other than -r>,
|
||||
$LEADIN . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)'
|
||||
=> q<read without variable>,
|
||||
$LEADIN . qr'echo\s+(-n\s+)?-n?en?\s' => q<echo -e>,
|
||||
$LEADIN . qr'exec\s+-[acl]' => q<exec -c/-l/-a name>,
|
||||
$LEADIN . qr'let\s' => q<let ...>,
|
||||
qr'(?<![\$\(])\(\(.*\)\)' => q<'((' should be '$(('>,
|
||||
qr'(?:^|\s+)(\[|test)\s+-a' => q<test with unary -a (should be -e)>,
|
||||
qr'\&>' => q<should be \>word 2\>&1>,
|
||||
qr'(<\&|>\&)\s*((-|\d+)[^\s;|)}`&\\\\]|[^-\d\s]+(?<!\$)(?!\d))' =>
|
||||
q<should be \>word 2\>&1>,
|
||||
qr'\[\[(?!:)' => q<alternative test command ([[ foo ]] should be [ foo ])>,
|
||||
qr'/dev/(tcp|udp)' => q</dev/(tcp|udp)>,
|
||||
$LEADIN . qr'builtin\s' => q<builtin>,
|
||||
$LEADIN . qr'caller\s' => q<caller>,
|
||||
$LEADIN . qr'compgen\s' => q<compgen>,
|
||||
$LEADIN . qr'complete\s' => q<complete>,
|
||||
$LEADIN . qr'declare\s' => q<declare>,
|
||||
$LEADIN . qr'dirs(\s|\Z)' => q<dirs>,
|
||||
$LEADIN . qr'disown\s' => q<disown>,
|
||||
$LEADIN . qr'enable\s' => q<enable>,
|
||||
$LEADIN . qr'mapfile\s' => q<mapfile>,
|
||||
$LEADIN . qr'readarray\s' => q<readarray>,
|
||||
$LEADIN . qr'shopt(\s|\Z)' => q<shopt>,
|
||||
$LEADIN . qr'suspend\s' => q<suspend>,
|
||||
$LEADIN . qr'time\s' => q<time>,
|
||||
$LEADIN . qr'type\s' => q<type>,
|
||||
$LEADIN . qr'typeset\s' => q<typeset>,
|
||||
$LEADIN . qr'ulimit(\s|\Z)' => q<ulimit>,
|
||||
$LEADIN . qr'set\s+-[BHT]+' => q<set -[BHT]>,
|
||||
$LEADIN . qr'alias\s+-p' => q<alias -p>,
|
||||
$LEADIN . qr'unalias\s+-a' => q<unalias -a>,
|
||||
$LEADIN . qr'local\s+-[a-zA-Z]+' => q<local -opt>,
|
||||
qr'(?:^|\s+)\s*\(?\w*[^\(\w\s]+\S*?\s*\(\)\s*([\{|\(]|\Z)'
|
||||
=> q<function names should only contain [a-z0-9_]>,
|
||||
$LEADIN . qr'(push|pop)d(\s|\Z)' => q<(push|pop)d>,
|
||||
$LEADIN . qr'export\s+-[^p]' => q<export only takes -p as an option>,
|
||||
qr'(?:^|\s+)[<>]\(.*?\)' => q<\<() process substituion>,
|
||||
$LEADIN . qr'readonly\s+-[af]' => q<readonly -[af]>,
|
||||
$LEADIN . qr'(sh|\$\{?SHELL\}?) -[rD]' => q<sh -[rD]>,
|
||||
$LEADIN . qr'(sh|\$\{?SHELL\}?) --\w+' => q<sh --long-option>,
|
||||
$LEADIN . qr'(sh|\$\{?SHELL\}?) [-+]O' => q<sh [-+]O>,
|
||||
qr'\[\^[^]]+\]' => q<[^] should be [!]>,
|
||||
$LEADIN . qr'printf\s+-v' => q<'printf -v var ...' should be var='$(printf ...)'>,
|
||||
$LEADIN . qr'coproc\s' => q<coproc>,
|
||||
qr';;?&' => q<;;& and ;& special case operators>,
|
||||
$LEADIN . qr'jobs\s' => q<jobs>,
|
||||
# $LEADIN . qr'jobs\s+-[^lp]\s' => q<'jobs' with option other than -l or -p>,
|
||||
$LEADIN . qr'command\s+-[^p]\s' => q<'command' with option other than -p>,
|
||||
);
|
||||
|
||||
%string_bashisms = (
|
||||
qr'\$\[[^][]+\]' => q<'$[' should be '$(('>,
|
||||
qr'\$\{\w+\:\d+(?::\d+)?\}' => q<${foo:3[:1]}>,
|
||||
qr'\$\{!\w+[\@*]\}' => q<${!prefix[*|@]>,
|
||||
qr'\$\{!\w+\}' => q<${!name}>,
|
||||
qr'\$\{\w+(/.+?){1,2}\}' => q<${parm/?/pat[/str]}>,
|
||||
qr'\$\{\#?\w+\[[0-9\*\@]+\]\}' => q<bash arrays, ${name[0|*|@]}>,
|
||||
qr'\$\{?RANDOM\}?\b' => q<$RANDOM>,
|
||||
qr'\$\{?(OS|MACH)TYPE\}?\b' => q<$(OS|MACH)TYPE>,
|
||||
qr'\$\{?HOST(TYPE|NAME)\}?\b' => q<$HOST(TYPE|NAME)>,
|
||||
qr'\$\{?DIRSTACK\}?\b' => q<$DIRSTACK>,
|
||||
qr'\$\{?EUID\}?\b' => q<$EUID should be "$(id -u)">,
|
||||
qr'\$\{?UID\}?\b' => q<$UID should be "$(id -ru)">,
|
||||
qr'\$\{?SECONDS\}?\b' => q<$SECONDS>,
|
||||
qr'\$\{?BASH_[A-Z]+\}?\b' => q<$BASH_SOMETHING>,
|
||||
qr'\$\{?SHELLOPTS\}?\b' => q<$SHELLOPTS>,
|
||||
qr'\$\{?PIPESTATUS\}?\b' => q<$PIPESTATUS>,
|
||||
qr'\$\{?SHLVL\}?\b' => q<$SHLVL>,
|
||||
qr'<<<' => q<\<\<\< here string>,
|
||||
$LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\"[^\"]*(\\[abcEfnrtv0])+.*?[\"]' => q<unsafe echo with backslash>,
|
||||
qr'\$\(\([\s\w$*/+-]*\w\+\+.*?\)\)' => q<'$((n++))' should be '$n; $((n=n+1))'>,
|
||||
qr'\$\(\([\s\w$*/+-]*\+\+\w.*?\)\)' => q<'$((++n))' should be '$((n=n+1))'>,
|
||||
qr'\$\(\([\s\w$*/+-]*\w\-\-.*?\)\)' => q<'$((n--))' should be '$n; $((n=n-1))'>,
|
||||
qr'\$\(\([\s\w$*/+-]*\-\-\w.*?\)\)' => q<'$((--n))' should be '$((n=n-1))'>,
|
||||
qr'\$\(\([\s\w$*/+-]*\*\*.*?\)\)' => q<exponentiation is not POSIX>,
|
||||
$LEADIN . qr'printf\s["\'][^"\']+?%[qb].+?["\']' => q<printf %q|%b>,
|
||||
);
|
||||
|
||||
%singlequote_bashisms = (
|
||||
$LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\'[^\']*(\\[abcEfnrtv0])+.*?[\']' => q<unsafe echo with backslash>,
|
||||
$LEADIN . qr'source\s+[\"\']?(?:\.\/|\/|\$|[\w~.-])\S*' =>
|
||||
q<should be '.', not 'source'>,
|
||||
);
|
||||
|
||||
if ($opt_echo) {
|
||||
$bashisms{$LEADIN . qr'echo\s+-[A-Za-z]*n'} = q<echo -n>;
|
||||
}
|
||||
if ($opt_posix) {
|
||||
$bashisms{$LEADIN . qr'local\s+\w+(\s+\W|\s*[;&|)]|$)'} = q<local foo>;
|
||||
$bashisms{$LEADIN . qr'local\s+\w+='} = q<local foo=bar>;
|
||||
$bashisms{$LEADIN . qr'local\s+\w+\s+\w+'} = q<local x y>;
|
||||
$bashisms{$LEADIN . qr'((?:test|\[)\s+.+\s-[ao])\s'} = q<test -a/-o>;
|
||||
$bashisms{$LEADIN . qr'kill\s+-[^sl]\w*'} = q<kill -[0-9] or -[A-Z]>;
|
||||
$bashisms{$LEADIN . qr'trap\s+["\']?.*["\']?\s+.*[1-9]'} = q<trap with signal numbers>;
|
||||
}
|
||||
|
||||
if ($makefile) {
|
||||
$string_bashisms{qr'(\$\(|\`)\s*\<\s*([^\s\)]{2,}|[^DF])\s*(\)|\`)'} =
|
||||
q<'$(\< foo)' should be '$(cat foo)'>;
|
||||
} else {
|
||||
$bashisms{$LEADIN . qr'\w+\+='} = q<should be VAR="${VAR}foo">;
|
||||
$string_bashisms{qr'(\$\(|\`)\s*\<\s*\S+\s*(\)|\`)'} = q<'$(\< foo)' should be '$(cat foo)'>;
|
||||
}
|
||||
|
||||
if ($opt_extra) {
|
||||
$string_bashisms{qr'\$\{?BASH\}?\b'} = q<$BASH>;
|
||||
$string_bashisms{qr'(?:^|\s+)RANDOM='} = q<RANDOM=>;
|
||||
$string_bashisms{qr'(?:^|\s+)(OS|MACH)TYPE='} = q<(OS|MACH)TYPE=>;
|
||||
$string_bashisms{qr'(?:^|\s+)HOST(TYPE|NAME)='} = q<HOST(TYPE|NAME)=>;
|
||||
$string_bashisms{qr'(?:^|\s+)DIRSTACK='} = q<DIRSTACK=>;
|
||||
$string_bashisms{qr'(?:^|\s+)EUID='} = q<EUID=>;
|
||||
$string_bashisms{qr'(?:^|\s+)UID='} = q<UID=>;
|
||||
$string_bashisms{qr'(?:^|\s+)BASH(_[A-Z]+)?='} = q<BASH(_SOMETHING)=>;
|
||||
$string_bashisms{qr'(?:^|\s+)SHELLOPTS='} = q<SHELLOPTS=>;
|
||||
$string_bashisms{qr'\$\{?POSIXLY_CORRECT\}?\b'} = q<$POSIXLY_CORRECT>;
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
install-testsuite-deps.py
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Install the required dependencies to properly run the test-suite.
|
||||
|
||||
: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 re
|
||||
import sys
|
||||
import pprint
|
||||
import subprocess
|
||||
|
||||
# Import bootstrap libs
|
||||
from bootstrap.ext.os_data import GRAINS
|
||||
|
||||
|
||||
COMMANDS = []
|
||||
if GRAINS['os'] == 'SmartOS':
|
||||
COMMANDS.extend([
|
||||
'pkgin up',
|
||||
'pkgin -y in scmgit-base py27-pip',
|
||||
'pip install unittest2'
|
||||
])
|
||||
elif GRAINS['os'] == 'openSUSE':
|
||||
COMMANDS.extend([
|
||||
'zypper --non-interactive addrepo --refresh http://download.opensuse.org/repositories'
|
||||
'/devel:/languages:/python/{0}/devel:languages:python.repo'.format(
|
||||
GRAINS['osrelease']
|
||||
),
|
||||
'zypper --gpg-auto-import-keys --non-interactive refresh',
|
||||
'zypper --non-interactive install --auto-agree-with-licenses git python-pip',
|
||||
'pip install unittest2'
|
||||
])
|
||||
elif GRAINS['osfullname'].startswith('SUSE Linux Enterprise Server'):
|
||||
match = re.search(
|
||||
r'PATCHLEVEL(?:[\s]+)=(?:[\s]+)([0-9]+)',
|
||||
open('/etc/SuSE-release').read()
|
||||
)
|
||||
#if not match:
|
||||
# print 'Failed to get the current patch level for:\n{0}'.format(
|
||||
# pprint.pformat(GRAINS)
|
||||
# )
|
||||
COMMANDS.extend([
|
||||
'zypper --non-interactive addrepo --refresh http://download.opensuse.org/repositories'
|
||||
'/devel:/languages:/python/SLE_{0}{1}/devel:languages:python.repo'.format(
|
||||
GRAINS['osrelease'],
|
||||
match and '_SP{0}'.format(match.group(1)) or ''
|
||||
),
|
||||
'zypper --gpg-auto-import-keys --non-interactive refresh',
|
||||
'zypper --non-interactive install --auto-agree-with-licenses git python-pip',
|
||||
'pip install unittest2'
|
||||
])
|
||||
elif GRAINS['os'] == 'Amazon':
|
||||
COMMANDS.extend([
|
||||
'rpm -Uvh --force http://mirrors.kernel.org/fedora-epel/6/'
|
||||
'{0}/epel-release-6-8.noarch.rpm'.format(
|
||||
GRAINS['cpuarch'] == 'i686' and 'i386' or GRAINS['cpuarch']
|
||||
),
|
||||
'yum -y update',
|
||||
'yum -y install python-pip --enablerepo=epel-testing',
|
||||
'pip-python install unittest2'
|
||||
])
|
||||
elif GRAINS['os'] == 'Fedora':
|
||||
COMMANDS.extend([
|
||||
'yum -y update',
|
||||
'yum -y install python-pip',
|
||||
'pip-python install unittest2'
|
||||
])
|
||||
elif GRAINS['os_family'] == 'Debian':
|
||||
COMMANDS.extend([
|
||||
'apt-get update',
|
||||
'apt-get install -y -o DPkg::Options::=--force-confold '
|
||||
'-o Dpkg::Options::="--force-confdef" python-pip',
|
||||
'pip install unittest2'
|
||||
])
|
||||
else:
|
||||
print(
|
||||
'Failed gather the proper commands to allow the tests suite to be '
|
||||
'executed in this system.\nSystem Grains:\n{0}'.format(
|
||||
pprint.pformat(GRAINS)
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
for command in COMMANDS:
|
||||
print 'Executing {0!r}'.format(command)
|
||||
process = subprocess.Popen(command, shell=True)
|
||||
process.communicate()
|
||||
|
||||
print('\nDONE\n')
|
||||
exit(0)
|
1
tests/integration/__init__.py
Normal file
1
tests/integration/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
7
tests/integration/test_connection.py
Normal file
7
tests/integration/test_connection.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
def test_ping(host):
|
||||
with host.sudo():
|
||||
assert host.salt('test.ping')
|
4
tests/requirements.txt
Normal file
4
tests/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
testinfra
|
||||
paramiko
|
||||
pywinrm
|
||||
six>=1.10.0
|
|
@ -1,105 +0,0 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
A simple Powershell script to test installed salt minion on windows.
|
||||
|
||||
.PARAMETER version
|
||||
Salt version installed.
|
||||
|
||||
.PARAMETER runservice
|
||||
Boolean flag whenever to test if service is running.
|
||||
|
||||
.PARAMETER noservice
|
||||
Boolean flag whenever to test if service is not running.
|
||||
|
||||
.PARAMETER minion
|
||||
Name of the minion installed on this host.
|
||||
|
||||
.PARAMETER master
|
||||
Name of the master configured on this host.
|
||||
|
||||
.EXAMPLE
|
||||
./runtests.ps1
|
||||
Runs without any parameters. Uses all the default values/settings.
|
||||
#>
|
||||
|
||||
#===============================================================================
|
||||
# Commandlet Binding
|
||||
#===============================================================================
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
|
||||
[string]$version = $null,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
|
||||
[switch]$runservice,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
|
||||
[switch]$noservice,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
|
||||
[string]$minion = $null,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
|
||||
[string]$master = $null
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
# Script Functions
|
||||
#===============================================================================
|
||||
function Get-Grains ([string]$Name) {
|
||||
$Command = "salt-call --local --out json --out-indent -1 grains.get $Name"
|
||||
$Result = iex $Command | Out-String | ConvertFrom-Json
|
||||
|
||||
Write-Verbose "salt-call grains.get ${Name}:`n${Result}"
|
||||
return $Result."local"
|
||||
}
|
||||
|
||||
function Get-Service-Status([string]$Name) {
|
||||
$Service = Get-Service $Name -ErrorAction Stop
|
||||
$Status = $Service.Status
|
||||
|
||||
Write-Verbose "${Name}: ${Status}"
|
||||
return $Status
|
||||
}
|
||||
|
||||
function Assert-Equal {
|
||||
[CmdletBinding()]
|
||||
Param (
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
|
||||
[string]$Actual,
|
||||
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
|
||||
[string]$Expected
|
||||
)
|
||||
|
||||
If ($Actual -ne $Expected) {
|
||||
throw "Assert: $Actual != $Expected"
|
||||
}
|
||||
}
|
||||
|
||||
#===============================================================================
|
||||
# Do enabled checks
|
||||
#===============================================================================
|
||||
if ($True) {
|
||||
Get-Grains -Name os_family | Assert-Equal -Expected "Windows"
|
||||
}
|
||||
|
||||
if ($version) {
|
||||
Get-Grains -Name saltversion | Assert-Equal -Expected $version
|
||||
}
|
||||
|
||||
if ($master) {
|
||||
Get-Grains -Name master | Assert-Equal -Expected $master
|
||||
}
|
||||
|
||||
if ($minion) {
|
||||
Get-Grains -Name id | Assert-Equal -Expected $minion
|
||||
}
|
||||
|
||||
if ($runservice) {
|
||||
Get-Service-Status salt-minion | Assert-Equal -Expected "Running"
|
||||
}
|
||||
|
||||
if ($noservice) {
|
||||
Get-Service-Status salt-minion | Assert-Equal -Expected "Stopped"
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
test-bootstrap.py
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
salt-bootstrap script unit-testing
|
||||
|
||||
: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 pprint
|
||||
import tempfile
|
||||
|
||||
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
XML_OUTPUT_DIR = os.environ.get(
|
||||
'XML_TEST_REPORTS', os.path.join(
|
||||
tempfile.gettempdir(), 'xml-test-reports'
|
||||
)
|
||||
)
|
||||
HTML_OUTPUT_DIR = os.environ.get(
|
||||
'HTML_OUTPUT_DIR', os.path.join(
|
||||
tempfile.gettempdir(), 'html-test-results'
|
||||
)
|
||||
)
|
||||
|
||||
from salttesting.parser import SaltTestingParser
|
||||
from salttesting.ext.os_data import GRAINS
|
||||
|
||||
|
||||
class BootstrapSuiteParser(SaltTestingParser):
|
||||
|
||||
def setup_additional_options(self):
|
||||
self.test_selection_group.add_option(
|
||||
'-L', '--lint',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Run Lint tests'
|
||||
)
|
||||
self.test_selection_group.add_option(
|
||||
'-U', '--usage',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Run Usage tests'
|
||||
)
|
||||
self.test_selection_group.add_option(
|
||||
'-I', '--install',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Run Installation tests'
|
||||
)
|
||||
self.test_selection_group.add_option(
|
||||
'--stable-salt-version',
|
||||
default='v2014.1.10',
|
||||
help='Specify the current stable release of salt'
|
||||
)
|
||||
|
||||
def run_integration_suite(self, display_name, suffix='[!_]*.py'):
|
||||
'''
|
||||
Run an integration test suite
|
||||
'''
|
||||
return self.run_suite(
|
||||
os.path.join(TEST_DIR, 'bootstrap'),
|
||||
display_name,
|
||||
suffix
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = BootstrapSuiteParser(
|
||||
TEST_DIR,
|
||||
xml_output_dir=XML_OUTPUT_DIR,
|
||||
html_output_dir=HTML_OUTPUT_DIR,
|
||||
tests_logfile=os.path.join(tempfile.gettempdir(), 'bootstrap-runtests.log')
|
||||
)
|
||||
|
||||
options, _ = parser.parse_args()
|
||||
|
||||
if not any((options.lint, options.usage, options.install, options.name)):
|
||||
options.lint = True
|
||||
options.usage = True
|
||||
options.install = True
|
||||
|
||||
print 'Detected system grains:'
|
||||
pprint.pprint(GRAINS)
|
||||
|
||||
# Set the current stable version of salt
|
||||
os.environ['CURRENT_SALT_STABLE_VERSION'] = options.stable_salt_version
|
||||
|
||||
overall_status = []
|
||||
|
||||
if options.name:
|
||||
for name in options.name:
|
||||
results = parser.run_suite('', name)
|
||||
overall_status.append(results)
|
||||
if options.lint:
|
||||
status = parser.run_integration_suite('Lint', '*lint.py')
|
||||
overall_status.append(status)
|
||||
if options.usage:
|
||||
status = parser.run_integration_suite('Usage', '*usage.py')
|
||||
overall_status.append(status)
|
||||
if options.install:
|
||||
status = parser.run_integration_suite('Installation', '*install.py')
|
||||
overall_status.append(status)
|
||||
|
||||
if overall_status.count(False) > 0:
|
||||
parser.finalize(1)
|
||||
parser.finalize(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Reference in a new issue