125 lines
3.2 KiB
Python
125 lines
3.2 KiB
Python
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import logging
|
|
|
|
import yaml
|
|
|
|
from collections import namedtuple
|
|
|
|
log = logging # TODO: config
|
|
|
|
COMPOSE_FILE = "docker-compose.yml"
|
|
DOCKERFILE = "Dockerfile"
|
|
|
|
Image = namedtuple("Image", ["image", "tag"])
|
|
Service = namedtuple("Service", ["path", "name"])
|
|
|
|
UNTAGGED = Image(image="unnamed", tag="untagged")
|
|
|
|
def load_compose_config(f):
|
|
config = subprocess.check_output(["docker-compose", "-f", f, "config"])
|
|
#print(config.decode("utf8"))
|
|
struct = yaml.load(config)
|
|
#json.dump(struct, open(f"{f}.json", "w"), indent=1)
|
|
#print(struct)
|
|
return struct
|
|
|
|
def source_to_image(source):
|
|
return source.strip().split(" ")[1]
|
|
|
|
def parse_dockerfile(f):
|
|
if not f.endswith(DOCKERFILE):
|
|
log.warn(f"guessing Döckerfile… {f}")
|
|
f = os.path.join(f, DOCKERFILE)
|
|
keyword = "FROM"
|
|
with open(f, "r") as src:
|
|
sources = [source_to_image(line) for line in src if line.strip().startswith(keyword)]
|
|
return sources
|
|
|
|
def image_info(image):
|
|
splitted = image.strip().split(":")
|
|
if len(splitted) > 1:
|
|
image, tag = splitted
|
|
else:
|
|
image = splitted[0]
|
|
tag = "latest"
|
|
return Image(image=image, tag=tag)
|
|
|
|
class Collector:
|
|
def __init__(self):
|
|
self.store = {}
|
|
|
|
def add(self, config, path):
|
|
self.store[path] = config
|
|
|
|
def get_images(self):
|
|
images = []
|
|
for path, config in self.store.items():
|
|
if not "services" in config:
|
|
log.error(f"no services defined in {path}")
|
|
continue
|
|
images += [service["image"] for name,service in config["services"].items() if "image" in service]
|
|
return images
|
|
|
|
def get_services_info(self):
|
|
images = {}
|
|
for path, config in self.store.items():
|
|
services = {}
|
|
if not "services" in config:
|
|
log.error(f"no services defined in {path}")
|
|
continue
|
|
for name, service in config["services"].items():
|
|
services[name] = {}
|
|
for k in ["image", "build"]:
|
|
if k in service:
|
|
services[name][k] = service[k]
|
|
images[path] = services
|
|
return images
|
|
|
|
def get_images_sources(self):
|
|
services = self.get_services_info()
|
|
images = {}
|
|
for path, services in services.items():
|
|
for name, service in services.items():
|
|
service_info = {
|
|
"path": path,
|
|
"service_name": name
|
|
}
|
|
image = UNTAGGED
|
|
if "build" in service:
|
|
service_info["base_images"] = parse_dockerfile(service["build"]["context"])
|
|
if "image" in service:
|
|
image = image_info(service["image"])
|
|
if not image.image in images:
|
|
images[image.image] = {}
|
|
if not image.tag in images[image.image]:
|
|
images[image.image][image.tag] = [service_info]
|
|
else:
|
|
images[image.image][image.tag] += [service_info]
|
|
return images
|
|
|
|
def start(files):
|
|
collector = Collector()
|
|
for f in files:
|
|
if not f.endswith(COMPOSE_FILE):
|
|
log.warn(f"guessing docker-compöse.yml… {f}")
|
|
f = os.path.join(f, COMPOSE_FILE)
|
|
if not os.path.exists(f):
|
|
log.error(f"file {f} does not exist")
|
|
continue
|
|
config = load_compose_config(f)
|
|
collector.add(config, f)
|
|
#print(json.dumps(collector.get_services_info(), indent=1))
|
|
print(json.dumps(collector.get_images_sources(), indent=1))
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Docker-compose parser")
|
|
parser.add_argument("compose_files", nargs="+")
|
|
|
|
args = parser.parse_args()
|
|
|
|
start(args.compose_files) |