README update for 4.1.1 and test improvement

* split out arg fixture to improve grainularity of test overriding
* README points out new arguments that improve likelihood of success

Signed-off-by: Adam Hill <adam@diginc.us>
This commit is contained in:
Adam Hill 2019-01-08 21:01:19 -06:00
parent aac258ad1c
commit 19fee6fa5e
No known key found for this signature in database
GPG Key ID: 2193804FCA429855
5 changed files with 86 additions and 40 deletions

View File

@ -5,6 +5,17 @@
</p>
<!-- Delete above HTML and insert markdown for dockerhub : ![Pi-hole](https://pi-hole.github.io/graphics/Vortex/Vortex_with_text.png) -->
## Docker Pi-Hole v4.1.1+ IMPORTANT upgrade notes
Starting with the v4.1.1 release your Pi-hole container may encounter issues starting the DNS service unless ran with the following settings:
- `--cap-add=NET_ADMIN` This previously optional argument is now required or strongly encouraged
- Starting in version 4.1.2 FTL, the DNS Service, is going to check this setting automatically
- `--dns=127.0.0.1 --dns=1.1.1.1` The second server can be any DNS IP of your choosing, but the **first dns must be 127.0.0.1**
- A WARNING stating "resolv.conf misconfiguration, see v4.1.1 release notes" may show in docker logs without this.
These are the raw [docker run cli](https://docs.docker.com/engine/reference/commandline/cli/) versions of the commands. We provide no official support for docker GUIs but the community forums may be able to help if you do not see a place for these settings. Remember, always consult your manual too!
## Overview
#### Renamed from `diginc/pi-hole` to `pihole/pihole`
@ -81,7 +92,7 @@ Here is a rundown of the other arguments passed into the example `docker run`:
| `-v /dir/for/pihole:/etc/pihole`<br/> **Recommended** | Volumes for your Pi-hole configs help persist changes across docker image updates
| `-v /dir/for/dnsmasq.d:/etc/dnsmasq.d`<br/> **Recommended** | Volumes for your dnsmasq configs help persist changes across docker image updates
| `--net=host`<br/> *Optional* | Alternative to `-p <port>:<port>` arguments (Cannot be used at same time as -p) if you don't run any other web application
| `--cap-add=NET_ADMIN`<br/> *Optional* | If you're forwarding port 67 you will also needs this for DHCP to work. (DHCP Reportedly works, I have not used however)
| `--cap-add=NET_ADMIN`<br/> *Required* | You will need this for FTL to work. (DHCP)
| `--dns=127.0.0.1`<br/> *Recommended* | Sets your container's resolve settings to localhost so it can resolve DHCP hostnames from Pi-hole's DNSMasq <!-- also fixes common resolution errors on container restart -->
| `--dns=1.1.1.1`<br/> *Optional* | Sets a backup server of your choosing in case DNSMasq has problems starting
@ -126,6 +137,9 @@ The standard Pi-hole customization abilities apply to this docker, but with dock
Do not attempt to upgrade (`pihole -up`) or reconfigure (`pihole -r`). New images will be released for upgrades, upgrading by replacing your old container with a fresh upgraded image is the 'docker way'. Long-living docker containers are not the docker way since they aim to be portable and reproducible, why not re-create them often! Just to prove you can.
0. Read the release notes for both this Docker release and the Pi-hole release
* This will help you avoid common problems due to any known issues with upgrading or newly required arguments or variables
* We will try to put common break/fixes at the top of this readme too
1. Download the latest version of the image: `docker pull pihole/pihole`
2. Throw away your container: `docker rm -f pihole`
* **Warning** When removing your pihole container you may be stuck without DNS until step 3; **docker pull** before **docker rm -f** to avoid DNS inturruption **OR** always have a fallback DNS server configured in DHCP to avoid this problem altogether.

View File

@ -1,5 +1,9 @@
#!/bin/bash
docker_checks() {
echo hi
}
prepare_configs() {
# Done in /start.sh, don't do twice
PH_TEST=true . $PIHOLE_INSTALL
@ -9,6 +13,7 @@ prepare_configs() {
set +e
mkdir -p /var/run/pihole /var/log/pihole
# Re-apply perms from basic-install over any volume mounts that may be present (or not)
# Also similar to preflights for FTL https://github.com/pi-hole/pi-hole/blob/master/advanced/Templates/pihole-FTL.service
chown pihole:root /etc/lighttpd
chown pihole:pihole "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" "/var/log/pihole" "${regexFile}"
chmod 644 "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf"

View File

@ -26,6 +26,7 @@ export adlistFile='/etc/pihole/adlists.list'
PH_TEST=true . $PIHOLE_INSTALL
echo " ::: Starting docker specific setup for docker pihole/pihole"
docker_checks
generate_password
validate_env || exit 1
prepare_configs

View File

@ -5,11 +5,38 @@ check_output = testinfra.get_backend(
"local://"
).get_module("Command").check_output
def DockerGeneric(request, args, image, cmd, entrypoint=''):
@pytest.fixture()
def args_dns():
return '--dns 127.0.0.1 --dns 1.1.1.1'
@pytest.fixture()
def args_caps():
return '--cap-add=NET_ADMIN'
@pytest.fixture()
def args_volumes():
return '-v /dev/null:/etc/pihole/adlists.default'
@pytest.fixture()
def args_env():
return '-e ServerIP="127.0.0.1" -e ServerIPv6="::1"'
@pytest.fixture()
def args(args_dns, args_caps, args_volumes, args_env):
return "{} {} {} {}".format(args_dns, args_caps, args_volumes, args_env)
@pytest.fixture()
def test_args(request):
''' arguments provided by tests '''
return ''
def DockerGeneric(request, args, test_args, image, cmd, entrypoint):
assert 'docker' in check_output('id'), "Are you in the docker group?"
# Always appended PYTEST arg to tell pihole we're testing
if 'pihole' in image:
args += " --dns 127.0.0.1 --dns 1.1.1.1 -v /dev/null:/etc/pihole/adlists.default -e PYTEST=1 --cap-add=NET_ADMIN"
docker_run = "docker run -d -t {args} {entry} {image} {cmd}".format(args=args, entry=entrypoint, image=image, cmd=cmd)
args = '{} -e PYTEST=1'.format(args)
docker_run = 'docker run -d -t {args} {test_args} {entry} {image} {cmd}'\
.format(args=args, test_args=test_args, entry=entrypoint, image=image, cmd=cmd)
print docker_run
docker_id = check_output(docker_run)
@ -39,15 +66,16 @@ def DockerGeneric(request, args, image, cmd, entrypoint=''):
@pytest.fixture
def Docker(request, args, image, cmd, entrypoint):
def Docker(request, test_args, args, image, cmd, entrypoint):
''' One-off Docker container run '''
return DockerGeneric(request, args, image, cmd, entrypoint)
return DockerGeneric(request, test_args, args, image, cmd, entrypoint)
@pytest.fixture(scope='module')
def DockerPersist(request, persist_args, persist_image, persist_cmd, Dig):
''' Persistent Docker container for multiple tests, instead of stopping container after one test '''
''' Uses DUP'd module scoped fixtures because smaller scoped fixtures won't mix with module scope '''
persistent_container = DockerGeneric(request, persist_args, persist_image, persist_cmd)
default_args = '--dns 127.0.0.1 --dns 1.1.1.1 -v /dev/null:/etc/pihole/adlists.default -e PYTEST=1 --cap-add=NET_ADMIN'
persistent_container = DockerGeneric(request, default_args, persist_args, persist_image, persist_cmd, '')
''' attach a dig conatiner for lookups '''
persistent_container.dig = Dig(persistent_container.id)
return persistent_container
@ -56,10 +84,6 @@ def DockerPersist(request, persist_args, persist_image, persist_cmd, Dig):
def entrypoint():
return ''
@pytest.fixture()
def args(request):
return '-e ServerIP="127.0.0.1" -e ServerIPv6="::1"'
@pytest.fixture(params=['amd64', 'armhf', 'aarch64'])
def arch(request):
return request.param
@ -147,7 +171,7 @@ def Dig(request):
args = '--link {}:test_pihole'.format(docker_id)
image = 'azukiapp/dig'
cmd = 'tail -f /dev/null'
dig_container = DockerGeneric(request, args, image, cmd)
dig_container = DockerGeneric(request, '', args, image, cmd, '')
return dig_container
return dig

View File

@ -3,16 +3,13 @@ import time
import re
DEFAULTARGS = '-e ServerIP="127.0.0.1" '
@pytest.mark.parametrize('args,expected_ipv6,expected_stdout', [
(DEFAULTARGS, True, 'IPv4 and IPv6'),
(DEFAULTARGS + '-e "IPv6=True"', True, 'IPv4 and IPv6'),
(DEFAULTARGS + '-e "IPv6=False"', False, 'IPv4'),
(DEFAULTARGS + '-e "IPv6=foobar"', False, 'IPv4'),
@pytest.mark.parametrize('test_args,expected_ipv6,expected_stdout', [
('', True, 'IPv4 and IPv6'),
('-e "IPv6=True"', True, 'IPv4 and IPv6'),
('-e "IPv6=False"', False, 'IPv4'),
('-e "IPv6=foobar"', False, 'IPv4'),
])
def test_IPv6_not_True_removes_ipv6(Docker, args, expected_ipv6, expected_stdout):
def test_IPv6_not_True_removes_ipv6(Docker, test_args, expected_ipv6, expected_stdout):
''' When a user overrides IPv6=True they only get IPv4 listening webservers '''
IPV6_LINE = 'use-ipv6.pl'
WEB_CONFIG = '/etc/lighttpd/lighttpd.conf'
@ -27,8 +24,8 @@ def test_IPv6_not_True_removes_ipv6(Docker, args, expected_ipv6, expected_stdout
assert (IPV6_LINE in config) == expected_ipv6
@pytest.mark.parametrize('args', [DEFAULTARGS + '-e "WEB_PORT=999"'])
def test_overrides_default_WEB_PORT(Docker, args):
@pytest.mark.parametrize('test_args', ['-e "WEB_PORT=999"'])
def test_overrides_default_WEB_PORT(Docker, test_args):
''' When a --net=host user sets WEB_PORT to avoid synology's 80 default IPv4 and or IPv6 ports are updated'''
CONFIG_LINE = 'server.port\s*=\s*999'
WEB_CONFIG = '/etc/lighttpd/lighttpd.conf'
@ -47,26 +44,26 @@ def test_overrides_default_WEB_PORT(Docker, args):
assert int(Docker.run('grep -rl "://pi.hole:999/" /var/www/html/ | wc -l').stdout) >= 1
@pytest.mark.parametrize('args,expected_error', [
(DEFAULTARGS + '-e WEB_PORT="LXXX"', 'WARNING: Custom WEB_PORT not used - LXXX is not an integer'),
(DEFAULTARGS + '-e WEB_PORT="1,000"', 'WARNING: Custom WEB_PORT not used - 1,000 is not an integer'),
(DEFAULTARGS + '-e WEB_PORT="99999"', 'WARNING: Custom WEB_PORT not used - 99999 is not within valid port range of 1-65535'),
@pytest.mark.parametrize('test_args,expected_error', [
('-e WEB_PORT="LXXX"', 'WARNING: Custom WEB_PORT not used - LXXX is not an integer'),
('-e WEB_PORT="1,000"', 'WARNING: Custom WEB_PORT not used - 1,000 is not an integer'),
('-e WEB_PORT="99999"', 'WARNING: Custom WEB_PORT not used - 99999 is not within valid port range of 1-65535'),
])
def test_bad_input_to_WEB_PORT(Docker, args, expected_error):
def test_bad_input_to_WEB_PORT(Docker, test_args, expected_error):
function = Docker.run('. /bash_functions.sh ; eval `grep setup_web_port /start.sh`')
assert expected_error in function.stdout
# DNS Environment Variable behavior in combinations of modified pihole LTE settings
@pytest.mark.parametrize('args, expected_stdout, dns1, dns2', [
@pytest.mark.parametrize('args_env, expected_stdout, dns1, dns2', [
('-e ServerIP="1.2.3.4"', 'default DNS', '8.8.8.8', '8.8.4.4' ),
('-e ServerIP="1.2.3.4" -e DNS1="1.2.3.4"', 'custom DNS', '1.2.3.4', '8.8.4.4' ),
('-e ServerIP="1.2.3.4" -e DNS2="1.2.3.4"', 'custom DNS', '8.8.8.8', '1.2.3.4' ),
('-e ServerIP="1.2.3.4" -e DNS1="1.2.3.4" -e DNS2="2.2.3.4"', 'custom DNS', '1.2.3.4', '2.2.3.4' ),
('-e ServerIP="1.2.3.4" -e DNS1="1.2.3.4" -e DNS2="no"', 'custom DNS', '1.2.3.4', None ),
('-e ServerIP="1.2.3.4" -e DNS2="no"', 'custom DNS', '8.8.8.8', None ),
('-e ServerIP="1.2.3.4" -e DNS1="1.2.3.4" -e DNS2="no"', 'custom DNS', '1.2.3.4', None ),
('-e ServerIP="1.2.3.4" -e DNS2="no"', 'custom DNS', '8.8.8.8', None ),
])
def test_override_default_servers_with_DNS_EnvVars(Docker, args, expected_stdout, dns1, dns2):
def test_override_default_servers_with_DNS_EnvVars(Docker, args_env, expected_stdout, dns1, dns2):
''' on first boot when DNS vars are NOT set explain default google DNS settings are used
or when DNS vars are set override the pihole DNS settings '''
assert Docker.run('test -f /.piholeFirstBoot').rc == 0
@ -78,7 +75,7 @@ def test_override_default_servers_with_DNS_EnvVars(Docker, args, expected_stdout
assert expected_servers == docker_dns_servers
@pytest.mark.parametrize('args, dns1, dns2, expected_stdout', [
@pytest.mark.parametrize('args_env, dns1, dns2, expected_stdout', [
('-e ServerIP="1.2.3.4"', '9.9.9.1', '9.9.9.2',
'Existing DNS servers used'),
('-e ServerIP="1.2.3.4" -e DNS1="1.2.3.4"', '9.9.9.1', '9.9.9.2',
@ -88,7 +85,7 @@ def test_override_default_servers_with_DNS_EnvVars(Docker, args, expected_stdout
('-e ServerIP="1.2.3.4" -e DNS1="1.2.3.4" -e DNS2="2.2.3.4"', '1.2.3.4', '2.2.3.4',
'Docker DNS variables not used\nExisting DNS servers used'),
])
def test_DNS_Envs_are_secondary_to_setupvars(Docker, args, expected_stdout, dns1, dns2):
def test_DNS_Envs_are_secondary_to_setupvars(Docker, args_env, expected_stdout, dns1, dns2):
''' on second boot when DNS vars are set just use pihole DNS settings
or when DNS vars and FORCE_DNS var are set override the pihole DNS settings '''
# Given we are not booting for the first time
@ -117,12 +114,12 @@ def test_DNS_Envs_are_secondary_to_setupvars(Docker, args, expected_stdout, dns1
assert 'server={}'.format(dns2) == searchDns2
@pytest.mark.parametrize('args, expected_stdout, expected_config_line', [
@pytest.mark.parametrize('args_env, expected_stdout, expected_config_line', [
('-e ServerIP="1.2.3.4"', 'binding to default interface: eth0', 'interface=eth0' ),
('-e ServerIP="1.2.3.4" -e INTERFACE="eth0"', 'binding to default interface: eth0', 'interface=eth0' ),
('-e ServerIP="1.2.3.4" -e INTERFACE="br0"', 'binding to custom interface: br0', 'interface=br0'),
])
def test_DNS_interface_override_defaults(Docker, args, expected_stdout, expected_config_line):
def test_DNS_interface_override_defaults(Docker, args_env, expected_stdout, expected_config_line):
''' When INTERFACE environment var is passed in, overwrite dnsmasq interface '''
function = Docker.run('. /bash_functions.sh ; eval `grep setup_dnsmasq /start.sh`')
assert expected_stdout in function.stdout
@ -157,19 +154,19 @@ def test_debian_setup_php_env(Docker, expected_lines, repeat_function):
# Overwrite entrypoint / cmd with noop, just run our method for this unit
@pytest.mark.parametrize('entrypoint,cmd', [('--entrypoint=tail','-f /dev/null')])
@pytest.mark.parametrize('args', [('-e ServerIP=1.2.3.4')])
def test_webPassword_random_generation(Docker, args):
@pytest.mark.parametrize('args_env', [('-e ServerIP=1.2.3.4')])
def test_webPassword_random_generation(Docker, args_env):
''' When a user sets webPassword env the admin password gets set to that '''
function = Docker.run('. /bash_functions.sh ; eval `grep generate_password /start.sh`')
assert 'assigning random password' in function.stdout.lower()
@pytest.mark.parametrize('entrypoint,cmd', [('--entrypoint=tail','-f /dev/null')])
@pytest.mark.parametrize('args,secure,setupVarsHash', [
@pytest.mark.parametrize('args_env,secure,setupVarsHash', [
('-e ServerIP=1.2.3.4 -e WEBPASSWORD=login', True, 'WEBPASSWORD=6060d59351e8c2f48140f01b2c3f3b61652f396c53a5300ae239ebfbe7d5ff08'),
('-e ServerIP=1.2.3.4 -e WEBPASSWORD=""', False, ''),
])
def test_webPassword_env_assigns_password_to_file_or_removes_if_empty(Docker, args, secure, setupVarsHash):
def test_webPassword_env_assigns_password_to_file_or_removes_if_empty(Docker, args_env, secure, setupVarsHash):
''' When a user sets webPassword env the admin password gets set or removed if empty '''
function = Docker.run('. /bash_functions.sh ; eval `grep setup_web_password /start.sh`')
@ -179,3 +176,8 @@ def test_webPassword_env_assigns_password_to_file_or_removes_if_empty(Docker, ar
else:
assert 'password removed' in function.stdout.lower()
assert Docker.run('grep -q \'^WEBPASSWORD=$\' /etc/pihole/setupVars.conf').rc == 0
def test_docker_checks_for_resolvconf_misconfiguration(Docker):
pass