Skip to content

Commit 2c9ede3

Browse files
committed
docker: use python for docker-update-hosts
1 parent 36eea20 commit 2c9ede3

File tree

4 files changed

+108
-72
lines changed

4 files changed

+108
-72
lines changed

roles/docker/files/docker-update-hosts

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import asyncio
2+
import subprocess
3+
import sys
4+
import tempfile
5+
6+
hosts_file = '/etc/hosts'
7+
begin_block = "# BEGIN DOCKER CONTAINERS"
8+
end_block = "# END DOCKER CONTAINERS"
9+
10+
def parse_time_interval(interval_str):
11+
"""Parse the time interval string into seconds."""
12+
units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
13+
unit = interval_str[-1]
14+
if unit in units:
15+
try:
16+
value = int(interval_str[:-1])
17+
return value * units[unit]
18+
except ValueError:
19+
raise ValueError("Invalid time interval format.")
20+
else:
21+
raise ValueError("Unsupported time unit.")
22+
23+
def ensure_managed_section_exists():
24+
"""Ensures the managed section exists in the hosts file."""
25+
with open(hosts_file, 'r+') as file:
26+
contents = file.read()
27+
# Check if both markers exist
28+
if begin_block not in contents or end_block not in contents:
29+
print(f"Appending managed section markers to {hosts_file}.", flush = True)
30+
# Move to the end of the file before appending
31+
file.write(f"\n{begin_block}\n{end_block}\n")
32+
33+
async def update_hosts_file_async():
34+
"""
35+
Asynchronously run the synchronous update_hosts_file function
36+
to avoid blocking the asyncio event loop.
37+
"""
38+
loop = asyncio.get_running_loop()
39+
await loop.run_in_executor(None, update_hosts_file)
40+
41+
def update_hosts_file():
42+
print(f"Updating hosts file", flush = True)
43+
44+
# Create a temporary file
45+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
46+
hosts_file_tmp = temp_file.name
47+
48+
# Build and execute the shell command
49+
shell_command = (
50+
f"docker container ls -q | xargs -r docker container inspect | "
51+
f"jq -r '.[] | if (.NetworkSettings.Networks[].IPAddress | length > 0) then "
52+
f"\"\\(.NetworkSettings.Networks[].IPAddress) \\(.NetworkSettings.Networks[].Aliases | select(length > 0) | join(\" \")) "
53+
f"\\(.Name | sub(\"^/\"; \"\") | sub(\"_1$\"; \"\") | sub(\"-1$\"; \"\")).saltbox\" else "
54+
f"\"# no ip address: \\(.Name | sub(\"^/\"; \"\"))\" end' | "
55+
f"sed -ne \"/^{begin_block}$/ {{p; r /dev/stdin\" -e \":a; n; /^{end_block}$/ {{p; b}}; ba}}; p\" {hosts_file} "
56+
f"> {hosts_file_tmp}"
57+
)
58+
59+
subprocess.run(shell_command, shell=True, check=True)
60+
61+
# Change file permission
62+
subprocess.run(['chmod', '644', hosts_file_tmp], check=True)
63+
64+
# Move the temporary file to replace the original hosts file
65+
subprocess.run(['mv', hosts_file_tmp, hosts_file], check=True)
66+
67+
async def periodic_update(interval_str):
68+
interval_seconds = parse_time_interval(interval_str)
69+
while True:
70+
# Schedule the synchronous function to run without blocking the asyncio event loop
71+
await update_hosts_file_async()
72+
await asyncio.sleep(interval_seconds)
73+
74+
async def monitor_docker_events():
75+
# Command to filter Docker events for container start and network disconnect
76+
cmd = "docker events --filter 'event=start' --filter 'event=disconnect'"
77+
# Start the subprocess
78+
process = await asyncio.create_subprocess_shell(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
79+
80+
print("Monitoring for Docker container start and network disconnect events", flush = True)
81+
async for line in process.stdout:
82+
print(f"Event received: {line.decode('utf-8').strip()}", flush = True)
83+
await update_hosts_file_async()
84+
85+
# Wait for the process to exit (though in practice, this might run indefinitely)
86+
await process.wait()
87+
88+
async def main():
89+
# Ensure the managed section exists
90+
ensure_managed_section_exists()
91+
92+
if len(sys.argv) < 2:
93+
print("Usage: python script.py <interval>", flush = True)
94+
return
95+
96+
interval_str = sys.argv[1]
97+
# Start both the Docker event monitoring and the periodic update tasks
98+
await asyncio.gather(
99+
monitor_docker_events(),
100+
periodic_update(interval_str)
101+
)
102+
103+
if __name__ == '__main__':
104+
asyncio.run(main())

roles/docker/tasks/subtasks/dns.yml

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,10 @@
1919
path: /etc/systemd/system/docker-update-hosts.service
2020
state: absent
2121

22-
- name: DNS | Import 'docker-update-hosts'
23-
ansible.builtin.copy:
24-
src: docker-update-hosts
25-
force: true
26-
dest: "/usr/local/bin/docker-update-hosts"
27-
owner: "root"
28-
group: "root"
29-
mode: "0755"
30-
31-
- name: DNS | Set 'docker-update-hosts' as executable
22+
- name: DNS | Delete 'docker-update-hosts'
3223
ansible.builtin.file:
33-
path: /usr/local/bin/docker-update-hosts
34-
owner: "root"
35-
group: "root"
36-
mode: a+x
24+
path: "/usr/local/bin/docker-update-hosts"
25+
state: absent
3726

3827
- name: DNS | Import 'saltbox_managed_docker_update_hosts.service'
3928
ansible.builtin.template:

roles/docker/templates/docker-update-hosts.service.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ After=docker.service
1414
PartOf=docker.service
1515

1616
[Service]
17-
ExecStart=/usr/local/bin/docker-update-hosts {{ docker_update_hosts_service_runtime_max }}
17+
ExecStart=/usr/bin/python3.10 /srv/git/saltbox/roles/docker/files/docker-update-hosts.py {{ docker_update_hosts_service_runtime_max }}
1818
Restart=always
1919
RestartSec=20s
2020
StartLimitInterval=0

0 commit comments

Comments
 (0)