Merge pull request #1999 from rhtenhove/container-automation

Container automation
This commit is contained in:
Steven Black
2022-07-13 11:07:38 -04:00
committed by GitHub
9 changed files with 151 additions and 60 deletions
+7
View File
@@ -0,0 +1,7 @@
*
!updateHostsFile.py
!data
!alternates
!extensions
!readmeData.json
!requirements.txt
-1
View File
@@ -17,7 +17,6 @@ jobs:
fail-fast: false
matrix:
python:
- '3.6'
- '3.7'
- '3.8'
- '3.9'
+43
View File
@@ -0,0 +1,43 @@
name: Create and publish a container image
on:
push:
branches:
- master
tags:
- "*"
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
name: Build and push container image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push container image
uses: docker/build-push-action@v3
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+10 -11
View File
@@ -1,14 +1,13 @@
# using container digest is recommended https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests
# https://cloud.google.com/architecture/using-container-images
FROM docker.io/python:3-alpine
FROM python:3@sha256:b7bfea0126f539ba570a01fb595ee84cc4e7dcac971ad83d12c848942fa52cb6
ENV IN_CONTAINER 1
WORKDIR /usr/src
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN git clone --depth 1 https://github.com/StevenBlack/hosts.git
WORKDIR /usr/src/hosts
RUN apk add --no-cache git sudo
# Now you launch this with
# $ docker build ./
# $ docker run -it (containerid) bash
COPY . /hosts
RUN pip install --no-cache-dir --upgrade -r /hosts/requirements.txt
ENV PATH $PATH:/hosts
WORKDIR /hosts
+28 -4
View File
@@ -72,19 +72,43 @@ You have two options to generate your own hosts file. You can do it in your own
### Option 1: Generate in a Docker container
We provide a [Dockerfile](https://github.com/StevenBlack/hosts/blob/master/Dockerfile) that you can use to create a Docker container with everything you need.
We provide a [Dockerfile](https://github.com/StevenBlack/hosts/blob/master/Dockerfile) that you can use to create a container image with everything you need.
The container will contain Python 3 and all its dependency requirements, and a copy of the latest version of this repository.
Build the Docker container like this:
```sh
docker build ./
docker build --no-cache . -t stevenblack-hosts
```
Access the terminal like this:
Then run your command as such:
```sh
docker run -it (containerid) bash
docker run --rm -it stevenblack-hosts updateHostsFile.py
```
> This will create the hosts file, and remove it with the container when done, so not very
> useful. Use the following example to automatically update your hosts file in place.
#### Linux example
This will replace your `/etc/hosts`.
Just run the following command. Set extensions to your preference.
```sh
docker run --pull always --rm -it -v /etc/hosts:/etc/hosts \
ghcr.io/StevenBlack/hosts updateHostsFile.py --auto \
--replace --extensions gambling porn
```
If you want to add custom hosts or a whitelist, create either ot both files as per
[the instructions](#how-do-i-control-which-sources-are-unified) and add the following
arguments _before_ `ghcr.io/StevenBlack/hosts` depending on which you wish to use.
```sh
-v "path/to/myhosts:/hosts/myhosts" \
-v "path/to/whitelist:/hosts/whitelist"
```
### Option 2: Generate it in your own environment
+25 -33
View File
@@ -444,7 +444,7 @@ class TestPromptForMove(Base):
move_file = self.prompt_for_move(
replace=True, auto=auto, skipstatichosts=False
)
self.assertTrue(move_file)
self.assertFalse(move_file)
mock_query.assert_not_called()
self.assert_called_once(mock_move)
@@ -484,7 +484,7 @@ class TestPromptForMove(Base):
move_file = self.prompt_for_move(
replace=False, auto=False, skipstatichosts=False
)
self.assertTrue(move_file)
self.assertFalse(move_file)
self.assert_called_once(mock_query)
self.assert_called_once(mock_move)
@@ -1264,61 +1264,53 @@ class TestUpdateReadmeData(BaseMockDir):
class TestMoveHostsFile(BaseStdout):
@mock.patch("os.path.abspath", side_effect=lambda f: f)
def test_move_hosts_no_name(self, _):
with self.mock_property("os.name"):
os.name = "foo"
def test_move_hosts_no_name(self, _): # TODO: Create test which tries to move actual file
with self.mock_property("platform.system") as obj:
obj.return_value = "foo"
mock_file = mock.Mock(name="foo")
move_hosts_file_into_place(mock_file)
expected = "does not exist"
output = sys.stdout.getvalue()
self.assertIn(expected, output)
@mock.patch("os.path.abspath", side_effect=lambda f: f)
def test_move_hosts_windows(self, _):
with self.mock_property("platform.system") as obj:
obj.return_value = "Windows"
mock_file = mock.Mock(name="foo")
move_hosts_file_into_place(mock_file)
expected = ""
output = sys.stdout.getvalue()
self.assertEqual(output, expected)
@mock.patch("os.path.abspath", side_effect=lambda f: f)
def test_move_hosts_windows(self, _):
with self.mock_property("os.name"):
os.name = "nt"
mock_file = mock.Mock(name="foo")
move_hosts_file_into_place(mock_file)
expected = (
"Automatically moving the hosts "
"file in place is not yet supported.\n"
"Please move the generated file to "
r"%SystemRoot%\system32\drivers\etc\hosts"
)
output = sys.stdout.getvalue()
self.assertIn(expected, output)
@mock.patch("os.path.abspath", side_effect=lambda f: f)
@mock.patch("subprocess.call", return_value=0)
def test_move_hosts_posix(self, *_):
with self.mock_property("os.name"):
os.name = "posix"
def test_move_hosts_posix(self, *_): # TODO: create test which tries to move an actual file
with self.mock_property("platform.system") as obj:
obj.return_value = "Linux"
mock_file = mock.Mock(name="foo")
move_hosts_file_into_place(mock_file)
expected = (
"Moving the file requires administrative "
"privileges. You might need to enter your password."
)
expected = "does not exist."
output = sys.stdout.getvalue()
self.assertIn(expected, output)
@mock.patch("os.path.abspath", side_effect=lambda f: f)
@mock.patch("subprocess.call", return_value=1)
def test_move_hosts_posix_fail(self, *_):
with self.mock_property("os.name"):
os.name = "posix"
with self.mock_property("platform.system") as obj:
obj.return_value = "Linux"
mock_file = mock.Mock(name="foo")
move_hosts_file_into_place(mock_file)
expected = "Moving the file failed."
expected = "does not exist."
output = sys.stdout.getvalue()
self.assertIn(expected, output)
Regular → Executable
+37 -10
View File
@@ -12,6 +12,7 @@ import json
import locale
import os
import platform
from pathlib import Path
import re
import shutil
import socket
@@ -41,6 +42,8 @@ except ImportError:
# Syntactic sugar for "sudo" command in UNIX / Linux
if platform.system() == "OpenBSD":
SUDO = ["/usr/bin/doas"]
elif platform.system() == "Windows":
SUDO = ["powershell", "Start-Process", "powershell", "-Verb", "runAs"]
else:
SUDO = ["/usr/bin/env", "sudo"]
@@ -467,7 +470,7 @@ def prompt_for_move(final_file, **move_params):
move_file = query_yes_no(prompt)
if move_file:
move_hosts_file_into_place(final_file)
move_file = move_hosts_file_into_place(final_file)
return move_file
@@ -1279,17 +1282,41 @@ def move_hosts_file_into_place(final_file):
filename = os.path.abspath(final_file.name)
if os.name == "posix":
try:
if not Path(filename).exists():
raise FileNotFoundError
except Exception:
print_failure(f"{filename} does not exist.")
return False
if platform.system() == "Windows":
target_file = str(Path(os.getenv("SystemRoot")) / "system32" / "drivers" / "etc" / "hosts")
else:
target_file = "/etc/hosts"
if os.getenv("IN_CONTAINER"):
# It's not allowed to remove/replace a mounted /etc/hosts, so we replace the content.
# This requires running the container user as root, as is the default.
print(f"Running in container, so we will replace the content of {target_file}.")
try:
with open(target_file, "w") as target_stream:
with open(filename, "r") as source_stream:
source = source_stream.read()
target_stream.write(source)
return True
except Exception:
print_failure(f"Replacing content of {target_file} failed.")
return False
elif platform.system() == "Linux" or platform.system() == "Windows":
print(
"Moving the file requires administrative privileges. You might need to enter your password."
)
if subprocess.call(SUDO + ["cp", filename, "/etc/hosts"]):
print_failure("Moving the file failed.")
elif os.name == "nt":
print("Automatically moving the hosts file in place is not yet supported.")
print(
"Please move the generated file to %SystemRoot%\\system32\\drivers\\etc\\hosts"
f"Replacing {target_file} requires root privileges. You might need to enter your password."
)
try:
subprocess.run(SUDO + [f"'cp {filename} {target_file}'"], check=True, shell=True)
return True
except subprocess.CalledProcessError:
print_failure(f"Replacing {target_file} failed.")
return False
def flush_dns_cache():
Regular → Executable
View File
Regular → Executable
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# Script by Steven Black
# https://github.com/StevenBlack