#!/usr/bin/env python3 # ------------------------------------------------------------- # Resolve docker-compose containers names # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Project: Nasqueron # Description: Guess by naive heuristics the containers # run from a docker-compose YAML configuration. # # As Docker doesn't record any link between # a docker compose or stack information and the # running containers, the approach consist to # compare the containers names in a config file, # with the running containers on the machine. # License: BSD-2-Clause # ------------------------------------------------------------- import subprocess import sys import yaml # ------------------------------------------------------------- # Parse docker-compose.yml # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def parse_compose_config(config_path): with open(config_path) as fd: return yaml.safe_load(fd) def resolve_container_prefix(service, image): image_short = image.split("/")[1] return image_short + "_" + service + "_" def resolve_containers_prefix(compose_config): return [ resolve_container_prefix(name, args["image"]) for name, args in compose_config["services"].items() ] # ------------------------------------------------------------- # Interact with Docker CLI # # This avoid to have to deal with version conflicts with docker # Python libraries. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def get_running_container_names(): process = subprocess.run(["docker", "ps"], capture_output=True) if process.returncode != 0: raise RuntimeError("Can't get containers: " + process.stderr.decode().strip()) # The output of docker ps starts by an header line to skip. # Then the last column contains the container name. docker_ps_lines = process.stdout.decode().strip().split("\n")[1:] return [line.split()[-1] for line in docker_ps_lines] # ------------------------------------------------------------- # Cross config file and running Docker information # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def is_container_name_match(config_container, running_container): # The service web for the image acme/foo is foo_web_1: # __ n = len(config_container) return ( running_container.startswith(config_container) and running_container[n:].isnumeric() ) def resolve_docker_compose_containers_name(config_path): config = parse_compose_config(config_path) config_containers = resolve_containers_prefix(config) running_containers = get_running_container_names() return [ running_container for config_container in config_containers for running_container in running_containers if is_container_name_match(config_container, running_container) ] # ------------------------------------------------------------- # Application entry point # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def run(config_path): containers = resolve_docker_compose_containers_name(config_path) print(" ".join(containers)) if __name__ == "__main__": argc = len(sys.argv) if argc < 2: print(f"Usage: {sys.argv[0]} ", file=sys.stderr) sys.exit(1) run(sys.argv[1])