mirror of
https://github.com/StevenBlack/hosts.git
synced 2026-10-04 14:21:04 +01:00
Merge pull request #1999 from rhtenhove/container-automation
Container automation
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
*
|
||||
!updateHostsFile.py
|
||||
!data
|
||||
!alternates
|
||||
!extensions
|
||||
!readmeData.json
|
||||
!requirements.txt
|
||||
@@ -17,7 +17,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python:
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
- '3.9'
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Regular → Executable
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Script by Steven Black
|
||||
# https://github.com/StevenBlack
|
||||
|
||||
Reference in New Issue
Block a user