Replace Docker tests with Molecule. Fix acceptance tests. (#57)

* [NEW]: added Molecule support
* [UPDATE]: fix linting errors
* [UPDATE]: Fixed missing smbclient dependency in travis config file
* Changed container name; added nmblookup version check
* [UPDATE]: Added samba-testsuite package in travis.yml
This commit is contained in:
Robin Ophalvens 2020-06-20 08:18:05 +00:00 committed by GitHub
parent 83b6add0f4
commit 812643507e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 578 additions and 28 deletions

View File

@ -1,38 +1,43 @@
# .travis.yml Execution script for role tests on Travis-CI
--- ---
language: python
# Use the new container infrastructure
sudo: required sudo: required
env: env:
global:
- ROLE_NAME: samba
matrix: matrix:
- DISTRIBUTION: centos - MOLECULE_DISTRO: centos7
VERSION: 7 - MOLECULE_DISTRO: debian9
- DISTRIBUTION: ubuntu - MOLECULE_DISTRO: fedora29
VERSION: 18.04 - MOLECULE_DISTRO: ubuntu1804
- DISTRIBUTION: debian
VERSION: 9
- DISTRIBUTION: fedora
VERSION: 29
#Enable docker support
services: services:
- docker - docker
before_install: install:
# Install latest Git
- sudo apt-get update - sudo apt-get update
- sudo apt-get install --only-upgrade git - sudo apt-get install bats curl smbclient samba-testsuite
- sudo apt-get install smbclient # Install dependencies for Molecule test
# Allow fetching other branches than master - python3 -m pip install molecule yamllint ansible-lint docker
- git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # Check ansible, molecule and nmblookup version
# Fetch the branch with test code - ansible --version
- git fetch origin docker-tests - molecule --version
- git worktree add docker-tests origin/docker-tests - nmblookup --version
# Create ansible.cfg with correct roles_path
- printf '[defaults]\nroles_path=../' >ansible.cfg
before_script:
#Renames ansible-role-bind to bertvv.bind to make it match with Ansible Galaxy
- cd ../
- mv ansible-role-$ROLE_NAME bertvv.$ROLE_NAME
- cd bertvv.$ROLE_NAME
script: script:
# Create container and apply test playbook #Run molecule test
- ./docker-tests/docker-tests.sh - molecule test
# Run functional tests on the container
- SUT_IP=172.17.0.2 ./docker-tests/functional-tests.sh
notifications: notifications:
webhooks: https://galaxy.ansible.com/api/v1/notifications/ webhooks: https://galaxy.ansible.com/api/v1/notifications/

33
.yamllint Normal file
View File

@ -0,0 +1,33 @@
---
# Based on ansible-lint config
extends: default
rules:
braces:
max-spaces-inside: 1
level: error
brackets:
max-spaces-inside: 1
level: error
colons:
max-spaces-after: -1
level: error
commas:
max-spaces-after: -1
level: error
comments: disable
comments-indentation: disable
document-start: disable
empty-lines:
max: 3
level: error
hyphens:
level: error
indentation: disable
key-duplicates: enable
line-length: disable
new-line-at-end-of-file: disable
new-lines:
type: unix
trailing-spaces: disable
truthy: disable

View File

@ -211,10 +211,50 @@ See the [test playbook](https://github.com/bertvv/ansible-role-samba/blob/docker
## Testing ## Testing
Two test environments are provided for this role: one set up with Vagrant, one with Docker. The Docker test environment is also used for the Travis-CI tests. Each test environment is stored in a separate orphan branch. See the README of each for details on how to set it up locally. This role is tested using [Ansible Molecule](https://molecule.readthedocs.io/). Tests are launched automatically on [Travis CI](https://travis-ci.org/bertvv/ansible-role-samba) after each commit and PR.
- [Docker tests](https://github.com/bertvv/ansible-role-samba/blob/docker-tests/README.md) This Molecule configuration will:
- [Vagrant tests](https://github.com/bertvv/ansible-role-samba/blob/vagrant-tests/README.md)
- Run Yamllint and Ansible Lint
- Create a Docker container
- Run a syntax check
- Apply the role with a [test playbook](molecule/default/converge.yml)
- Run acceptance tests with [BATS](https://github.com/bats-core/bats-core/)
This process is repeated for the supported Linux distributions.
### Local test environment
If you want to set up a local test environment, you can use this reproducible setup based on Vagrant+VirtualBox: <https://github.com/bertvv/ansible-testenv>. Steps to install the necessary tools manually:
1. Docker, BATS and smbclient should be installed on your machine (assumed to run Linux). No Docker containers should be running when you start the test.
2. As recommended by Molecule, create a python virtual environment
3. Install the software tools `python3 -m pip install molecule docker yamllint ansible-lint`
4. Navigate to the root of the role directory and run `molecule test`
Molecule automatically deletes the containers after a test. If you would like to check out the containers yourself, run `molecule converge` followed by `molecule login --host HOSTNAME`.
The Docker containers are based on images created by [Jeff Geerling](https://hub.docker.com/u/geerlingguy), specifically for Ansible testing (look for images named `geerlingguy/docker-DISTRO-ansible`). You can use any of his images, but only the distributions mentioned in [meta/main.yml](meta/main.yml) are supported.
The default config will start a Centos 7 container. Choose another distro by setting the `MOLECULE_DISTRO` variable with the command, e.g.:
``` bash
MOLECULE_DISTRO=debian9 molecule test
```
or
``` bash
MOLECULE_DISTRO=debian9 molecule converge
```
You can run the acceptance tests on both servers with `molecule verify` or manually with
```console
SUT_IP=172.17.0.2 bats molecule/default/files/samba.bats
```
You need to initialise the variable `SUT_IP`, the system under test's IP address. The server, `smb1`, should have IP address 172.17.0.2.
## Contributing ## Contributing
@ -250,4 +290,5 @@ Pull requests are also very welcome. Please create a topic branch for your propo
[Sven Eeckeman](https://github.com/SvenEeckeman), [Sven Eeckeman](https://github.com/SvenEeckeman),
[Tiemo Kieft](https://github.com/blubber), [Tiemo Kieft](https://github.com/blubber),
[Tobias Wolter](https://github.com/towo), [Tobias Wolter](https://github.com/towo),
[Tomohiko Ozawa](https://github.com/kota65535). [Tomohiko Ozawa](https://github.com/kota65535),
[Robin Ophalvens](https://github.com/RobinOphalvens).

View File

@ -0,0 +1,93 @@
---
- name: Converge
hosts: all
pre_tasks:
- name: Ensure package database is up-to-date
apt:
update-cache: true
failed_when: false
changed_when: false
when: ansible_os_family == 'Debian'
- name: Create users
user:
name: "{{ item }}"
groups: users
append: true
with_items:
- usr1
- usr2
- timemachine
vars:
samba_netbios_name: SAMBA_TEST
samba_server_string: 'Welcome to the test file server'
samba_workgroup: TESTGROUP
samba_global_include: global-include.conf
samba_load_homes: true
samba_load_printers: false
samba_create_varwww_symlinks: true
samba_log: /var/log/samba.log
samba_log_size: 60000
samba_log_level: '3 passdb:5 auth:10 winbind:2 '
# The smbclient version of the Travis CI environment crashes when `min
# protocol' is set:
# protocol negotiation failed: NT_STATUS_INVALID_NETWORK_RESPONSE
# Uncomment the following lines if you want to test this setting locally.
#
# samba_server_min_protocol: SMB2
# samba_server_max_protocol: SMB3
samba_map_to_guest: Never
samba_users:
- name: usr1
password: usr1
- name: usr2
password: usr2
- name: timemachine
password: timemachine
samba_username_map:
- from: 'User Two'
to: usr2
samba_shares_root: /srv/samba
samba_shares:
- name: restrictedshare
- name: privateshare
comment: 'Only readable/writeable by usr1'
valid_users: usr1
write_list: usr1
group: usr1
browseable: 'no'
- name: protectedshare
public: 'yes'
comment: 'Public, but only writeable by usr2'
write_list: usr2
group: users
browseable: 'yes'
include_file: protectedshare-include.conf
- name: publicshare
comment: 'Public share, writeable by all members of group users'
public: 'yes'
write_list: +users
group: users
setype: public_content_t
browseable: 'yes'
- name: guestshare
comment: 'Share accessible for guests'
guest_ok: 'yes'
writable: 'yes'
browseable: 'yes'
- name: TimeMachine
comment: 'Share useable as a TimeMachine backup target on MacOS'
vfs_objects:
- name: fruit
options:
- name: time machine
value: 'yes'
- name: streams_xattr
path: /srv/timemachine
write_list: timemachine
owner: timemachine
group: timemachine
public: 'no'
guest_ok: 'no'
browseable: 'no'
roles:
- role: bertvv.samba

View File

@ -0,0 +1,111 @@
#! /usr/bin/env bash
#
# Author: Bert Van Vreckem <bert.vanvreckem@gmail.com>
#
# Run BATS test files in the current directory, and the ones in the subdirectory
# matching the host name.
#
# The script installs BATS if needed. It's best to put ${bats_install_dir} in
# your .gitignore.
set -o errexit # abort on nonzero exitstatus
set -o nounset # abort on unbound variable
#{{{ Variables
test_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
bats_archive="v1.1.0.tar.gz"
bats_url="https://github.com/bats-core/bats-core/archive/${bats_archive}"
bats_install_dir="/opt"
bats_default_location="${bats_install_dir}/bats/libexec/bats"
test_file_pattern="*.bats"
# Color definitions
readonly reset='\e[0m'
readonly yellow='\e[0;33m'
readonly cyan='\e[0;36m'
#}}}
main() {
bats=$(find_bats_executable)
if [ -z "${bats}" ]; then
install_bats
bats="${bats_default_location}"
fi
debug "Using BATS executable at: ${bats}"
# List all test cases (i.e. files in the test dir matching the test file
# pattern)
# Tests to be run on all hosts
global_tests=$(find_tests "${test_dir}" 1)
# Tests for individual hosts
host_tests=$(find_tests "${test_dir}/${HOSTNAME}")
# Loop over test files
for test_case in ${global_tests} ${host_tests}; do
info "Running test ${test_case}"
${bats} "${test_case}"
done
}
#{{{ Functions
# Tries to find BATS executable in the PATH or the place where this script
# installs it.
find_bats_executable() {
if which bats > /dev/null; then
which bats
elif [ -x "${bats_default_location}" ]; then
echo "${bats_default_location}"
else
echo ""
fi
}
# Usage: install_bats
install_bats() {
pushd "${bats_install_dir}" > /dev/null 2>&1
curl --location --remote-name "${bats_url}"
tar xzf "${bats_archive}"
mv bats-* bats
rm "${bats_archive}"
popd > /dev/null 2>&1
}
# Usage: find_tests DIR [MAX_DEPTH]
#
# Finds BATS test suites in the specified directory
find_tests() {
local max_depth=""
if [ "$#" -eq "2" ]; then
max_depth="-maxdepth $2"
fi
local tests
tests=$(find "$1" ${max_depth} -type f -name "${test_file_pattern}" -printf '%p\n' 2> /dev/null)
echo "${tests}"
}
# Usage: info [ARG]...
#
# Prints all arguments on the standard output stream
info() {
printf "${yellow}### %s${reset}\n" "${*}"
}
# Usage: debug [ARG]...
#
# Prints all arguments on the standard output stream
debug() {
printf "${cyan}### %s${reset}\n" "${*}"
}
#}}}
main

View File

@ -0,0 +1,208 @@
#! /usr/bin/env bats
#
# Author: Bert Van Vreckem <bert.vanvreckem@gmail.com>
#
# Test a Samba server
sut_wins_name=SAMBA_TEST # NetBIOS name
workgroup=TESTGROUP # Workgroup
# The name of a directory and file that will be created to test for
# write access (= random string)
test_dir=peghawJaup
test_file=Nocideicye
# {{{Helper functions
# Checks if a user has shell access to the system
# Usage: assert_can_login USER PASSWD
assert_can_login() {
echo $2 | su -c 'ls ${HOME}' - $1
}
# Checks that a user has NO shell access to the system
# Usage: assert_cannot_login USER
assert_cannot_login() {
run sudo su -c 'ls' - $1
[ "0" -ne "${status}" ]
}
# Check that the guest account has read access
# Usage: assert_guest_read SHARE
assert_guest_read() {
local share="${1}"
run smbclient "//${SUT_IP}/${share}" \
--user=% \
--command='ls'
echo "${output}"
[ "${status}" -eq "0" ]
}
# Check that a user has read acces to a share
# Usage: read_access SHARE USER PASSWORD
assert_read_access() {
local share="${1}"
local user="${2}"
local password="${3}"
run smbclient "//${SUT_IP}/${share}" \
--user=${user}%${password} \
--command='ls'
echo "${output}"
[ "${status}" -eq "0" ]
}
# Check that a user has NO read access to a share
# Usage: no_read_access SHARE USER PASSWORD
assert_no_read_access() {
local share="${1}"
local user="${2}"
local password="${3}"
run smbclient "//${SUT_IP}/${share}" \
--user=${user}%${password} \
--command='ls'
echo "${output}"
[ "${status}" -eq "1" ]
}
# Check that a user has write access to a share
# Usage: write_access SHARE USER PASSWORD
assert_write_access() {
local share="${1}"
local user="${2}"
local password="${3}"
run smbclient "//${SUT_IP}/${share}" \
--user=${user}%${password} \
--command="mkdir ${test_dir};rmdir ${test_dir}"
echo "${output}"
# Output should NOT contain any error message. Checking on exit status is
# not reliable, it can be 0 when the command failed...
[ -z "$(echo ${output} | grep NT_STATUS_)" ]
}
# Check that a user has NO write access to a share
# Usage: no_write_access SHARE USER PASSWORD
assert_no_write_access() {
local share="${1}"
local user="${2}"
local password="${3}"
run smbclient "//${SUT_IP}/${share}" \
--user=${user}%${password} \
--command="mkdir ${test_dir};rmdir ${test_dir}"
echo "${output}"
# Output should contain an error message (beginning with NT_STATUS, usually
# NT_STATUS_MEDIA_WRITE_PROTECTED
[ -n "$(echo ${output} | grep NT_STATUS_)" ]
}
# Check that users from the same group can write to each others files
# Usage: assert_group_write_file SHARE USER1 PASSWD1 USER2 PASSWD2
assert_group_write_file() {
local share="${1}"
local user1="${2}"
local passwd1="${3}"
local user2="${4}"
local passwd2="${5}"
echo "Hello world!" > ${test_file}
smbclient "//${SUT_IP}/${share}" --user=${user1}%${passwd1} \
--command="put ${test_file}"
# In order to overwrite the file, write access is needed. This will fail
# if user2 doesnt have write access.
smbclient "//${SUT_IP}/${share}" --user=${user2}%${passwd2} \
--command="put ${test_file}"
}
# Check that users from the same group can write to each others directories
# Usage: assert_group_write_dir SHARE USER1 PASSWD1 USER2 PASSWD2
assert_group_write_dir() {
local share="${1}"
local user1="${2}"
local passwd1="${3}"
local user2="${4}"
local passwd2="${5}"
smbclient "//${SUT_IP}/${share}" --user=${user1}%${passwd1} \
--command="mkdir ${test_dir}; mkdir ${test_dir}/tst"
run smbclient "//${SUT_IP}/${share}" --user=${user2}%${passwd2} \
--command="rmdir ${test_dir}/tst"
[ -z $(echo "${output}" | grep NT_STATUS_ACCESS_DENIED) ]
}
#}}}
@test 'NetBIOS name resolution should work' {
#skip
# Look up the Samba servers NetBIOS name under the specified workgroup
# The result should contain the IP followed by NetBIOS name
nmblookup -U ${SUT_IP} --workgroup ${workgroup} ${sut_wins_name} | grep "^${SUT_IP} ${sut_wins_name}"
}
# Read / write access to shares
@test 'read access for share restrictedshare' {
# Share User Password
assert_read_access restrictedshare usr1 usr1
assert_read_access restrictedshare usr2 usr2
}
@test 'write access for share restrictedshare' {
# Share User Password
assert_no_write_access restrictedshare usr1 usr1
assert_no_write_access restrictedshare usr2 usr2
}
@test 'read access for share privateshare' {
# Share User Password
assert_read_access privateshare usr1 usr1
assert_no_read_access privateshare usr2 usr2
}
@test 'write access for share privateshare' {
# Share User Password
assert_write_access privateshare usr1 usr1
assert_no_write_access privateshare usr2 usr2
}
@test 'read access for share protectedshare' {
# Share User Password
assert_read_access protectedshare usr1 usr1
assert_read_access protectedshare usr2 usr2
}
@test 'write access for share protectedshare' {
# Share User Password
assert_no_write_access protectedshare usr1 usr1
assert_write_access protectedshare usr2 usr2
}
@test 'read access for share publicshare' {
# Share User Password
assert_read_access publicshare usr1 usr1
assert_read_access publicshare usr2 usr2
}
@test 'write access for share publicshare' {
# Share User Password
assert_write_access publicshare usr1 usr1
assert_write_access publicshare usr2 usr2
}
@test 'Guest access in share guestshare' {
assert_guest_read guestshare
}

View File

@ -0,0 +1,33 @@
---
dependency:
name: galaxy
driver:
#Specifies the driver that should be used. Podman should also work
name: docker
lint: |
yamllint .
ansible-lint --exclude=molecule/default/verify.yml
platforms:
#Set name and hostname
- name: smb1
hostname: smb1
#Specify which image should be used. Geerlingguys images are Ansible compatible and have Systemd installed
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"
#Command to execute when the container starts
command: ${MOLECULE_DOCKER_COMMAND:-""}
#Volumes to mount within the container. Important to enable systemd
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
#Give extended privileges to the container. Necessary for Systemd to operate within the container.
# DO NOT use extended privileges in a production environment!
privileged: true
#Allocate pseudo-TTY
tty: True
environment:
container: docker
provisioner:
name: ansible
#Runs the verify.yml playbook. Testinfra is also a supported method. Check the Molecule documention for more information.
verifier:
name: ansible

View File

@ -0,0 +1,11 @@
# {{ ansible_managed }}
# These come from Debians 4.8.4 config file
panic action = /usr/share/samba/panic-action %d
obey pam restrictions = yes
unix password sync = yes
passwd program = /usr/bin/passwd %u
passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
pam password change = yes
# vim: ft=samba

View File

@ -0,0 +1,6 @@
# Additional Samba options for protectedshare
# {{ ansible_managed }}
delete readonly = yes
# vim: ft=samba

View File

@ -0,0 +1,9 @@
---
- name: Verify
hosts: all
tasks:
# We run the BATS tests from the localhost, since they are black box tests
- name: "Run BATS tests for {{ ansible_hostname }}"
shell: SUT_IP={{ ansible_default_ipv4.address }} bats {{ playbook_dir }}/files/samba.bats
delegate_to: localhost
changed_when: false