188 lines
5.9 KiB
Python
188 lines
5.9 KiB
Python
#!/usr/bin/python3
|
|
import docker
|
|
import argparse
|
|
import shutil
|
|
import signal
|
|
import time
|
|
import sys
|
|
import os
|
|
|
|
label_name = "hoster.domains"
|
|
enclosing_pattern = "#-----------Docker-Hoster-Domains----------\n"
|
|
hosts_path = "/tmp/hosts"
|
|
hosts = {}
|
|
|
|
def signal_handler(signal, frame):
|
|
global hosts
|
|
hosts = {}
|
|
update_hosts_file()
|
|
sys.exit(0)
|
|
|
|
def main():
|
|
# register the exit signals
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
args = parse_args()
|
|
global hosts_path
|
|
hosts_path = args.file
|
|
|
|
dockerClient = docker.APIClient(base_url='unix://%s' % args.socket)
|
|
events = dockerClient.events(decode=True)
|
|
#get running containers
|
|
for c in dockerClient.containers(quiet=True, all=False):
|
|
container_id = c["Id"]
|
|
container = get_container_data(dockerClient, container_id)
|
|
hosts[container_id] = container
|
|
|
|
update_hosts_file()
|
|
|
|
#listen for events to keep the hosts file updated
|
|
for e in events:
|
|
if e.get("Type") != "container":
|
|
continue
|
|
|
|
status = e.get("status")
|
|
if not status:
|
|
continue # пропускаем события без статуса
|
|
|
|
container_id = e.get("id")
|
|
if not container_id:
|
|
continue
|
|
|
|
status = e["status"]
|
|
if status =="start":
|
|
container_id = e["id"]
|
|
container = get_container_data(dockerClient, container_id)
|
|
hosts[container_id] = container
|
|
update_hosts_file()
|
|
|
|
if status=="stop" or status=="die" or status=="destroy":
|
|
container_id = e["id"]
|
|
if container_id in hosts:
|
|
hosts.pop(container_id)
|
|
update_hosts_file()
|
|
|
|
if status=="rename":
|
|
container_id = e["id"]
|
|
if container_id in hosts:
|
|
container = get_container_data(dockerClient, container_id)
|
|
hosts[container_id] = container
|
|
update_hosts_file()
|
|
|
|
|
|
def get_container_data(dockerClient, container_id):
|
|
# extract all the info with the docker api
|
|
info = dockerClient.inspect_container(container_id)
|
|
|
|
# normalise basic names
|
|
container_hostname = info.get("Config", {}).get("Hostname", "") or ""
|
|
container_name = (info.get("Name", "") or "").lstrip("/")
|
|
|
|
# prefer old-style IP if present (compatibility), otherwise try networks
|
|
network_settings = info.get("NetworkSettings", {}) or {}
|
|
container_ip_fallback = network_settings.get("IPAddress") # may be '' or None
|
|
|
|
# full hostname with domainname if present
|
|
domainname = info.get("Config", {}).get("Domainname") or ""
|
|
if domainname:
|
|
container_hostname = container_hostname + "." + domainname
|
|
|
|
result = []
|
|
|
|
# iterate all networks (new API puts IPs here)
|
|
networks = network_settings.get("Networks") or {}
|
|
for net_name, net_vals in networks.items():
|
|
if not net_vals:
|
|
continue
|
|
ip = net_vals.get("IPAddress") or None
|
|
aliases = net_vals.get("Aliases") or [] # could be None
|
|
# if there is no ip, skip this network
|
|
if not ip:
|
|
continue
|
|
|
|
domains = set()
|
|
# include aliases if present
|
|
if aliases:
|
|
# aliases may include None or empty strings — filter them
|
|
domains.update([a for a in aliases if a])
|
|
# include container name and hostname
|
|
if container_name:
|
|
domains.add(container_name)
|
|
if container_hostname:
|
|
domains.add(container_hostname)
|
|
|
|
result.append({
|
|
"ip": ip,
|
|
"name": container_name,
|
|
"domains": domains
|
|
})
|
|
|
|
# if old-style IP exists and not already present in result, add it
|
|
if container_ip_fallback:
|
|
# check we don't duplicate the same IP already collected from networks
|
|
ip_already = any(r.get("ip") == container_ip_fallback for r in result)
|
|
if not ip_already:
|
|
result.append({
|
|
"ip": container_ip_fallback,
|
|
"name": container_name,
|
|
"domains": {container_name, container_hostname} if container_hostname else {container_name}
|
|
})
|
|
|
|
return result
|
|
|
|
|
|
def update_hosts_file():
|
|
if len(hosts)==0:
|
|
print("Removing all hosts before exit...")
|
|
else:
|
|
print("Updating hosts file with:")
|
|
|
|
for id,addresses in hosts.items():
|
|
for addr in addresses:
|
|
print("ip: %s domains: %s" % (addr["ip"], addr["domains"]))
|
|
|
|
#read all the lines of thge original file
|
|
lines = []
|
|
with open(hosts_path,"r+") as hosts_file:
|
|
lines = hosts_file.readlines()
|
|
|
|
#remove all the lines after the known pattern
|
|
for i,line in enumerate(lines):
|
|
if line==enclosing_pattern:
|
|
lines = lines[:i]
|
|
break;
|
|
|
|
#remove all the trailing newlines on the line list
|
|
if lines:
|
|
while lines[-1].strip()=="": lines.pop()
|
|
|
|
#append all the domain lines
|
|
if len(hosts)>0:
|
|
lines.append("\n\n"+enclosing_pattern)
|
|
|
|
for id, addresses in hosts.items():
|
|
for addr in addresses:
|
|
lines.append("%s %s\n"%(addr["ip"]," ".join(addr["domains"])))
|
|
|
|
lines.append("#-----Do-not-add-hosts-after-this-line-----\n\n")
|
|
|
|
#write it on the auxiliar file
|
|
aux_file_path = hosts_path+".aux"
|
|
with open(aux_file_path,"w") as aux_hosts:
|
|
aux_hosts.writelines(lines)
|
|
|
|
#replace etc/hosts with aux file, making it atomic
|
|
shutil.move(aux_file_path, hosts_path)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description='Synchronize running docker container IPs with host /etc/hosts file.')
|
|
parser.add_argument('socket', type=str, nargs="?", default="/tmp/docker.sock", help='The docker socket to listen for docker events.')
|
|
parser.add_argument('file', type=str, nargs="?", default="/tmp/hosts", help='The /etc/hosts file to sync the containers with.')
|
|
return parser.parse_args()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|