diff --git a/docker-compose.yml b/docker-compose.yml index 7e143d5..b93feaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,4 +6,4 @@ services: volumes: - ./docker-compose.py:/docker-compose.py - ./sample:/services - command: bash -c 'python3 /docker-compose.py /services/*' \ No newline at end of file + command: bash -c 'python3 /docker_compose.py /services/*' \ No newline at end of file diff --git a/docker-compose.py b/docker_compose.py similarity index 92% rename from docker-compose.py rename to docker_compose.py index c98e2e0..64f8f8e 100644 --- a/docker-compose.py +++ b/docker_compose.py @@ -109,7 +109,7 @@ class Collector: def start(files, ignores): collector = Collector() for f in files: - if any([i in f for i in ignores]): + if ignores and any([i in f for i in ignores]): log.warn(f"skip {f} due to ignore rule") continue if not f.endswith(COMPOSE_FILE): @@ -124,14 +124,15 @@ def start(files, ignores): #print(json.dumps(collector.get_images_sources(), indent=1)) return collector.get_images_sources() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Docker-compose parser") +def args_setup(description): + parser = argparse.ArgumentParser(description=description) parser.add_argument("compose_files", nargs="+") parser.add_argument("--output", "-o") - parser.add_argument("--ignore", "-i", nargs="+") - + parser.add_argument("--ignore", "-i", nargs="+", default=False) + return parser + +if __name__ == "__main__": + parser = args_setup("Docker-compose parser") args = parser.parse_args() diff --git a/image_tags.py b/image_tags.py index ef3ea73..551b210 100644 --- a/image_tags.py +++ b/image_tags.py @@ -1,43 +1,80 @@ -import requests -from datetime import datetime import argparse import json +import logging +from datetime import datetime + +from packaging import version +import requests + + +log = logging TAGS = "https://registry.hub.docker.com/v2/repositories/{image}/tags/" LIB_PREFIX = "library/" TAG_STORE = {} -def get_tags(image): - if "/" not in image: - image = LIB_PREFIX + image - url = TAGS.format(image=image) +def api_call(url): result = requests.get(url) + if not result.ok: + log.error(result, result.url) + return {} data = result.json() tags = {} for entry in data["results"]: - tags[entry["name"]] = datetime.strptime(entry["last_updated"], "%Y-%m-%dT%H:%M:%S.%fZ") + tags[entry["name"]] = datetime.strptime(entry["last_updated"], "%Y-%m-%dT%H:%M:%S.%fZ") if entry["last_updated"] else "------" + if data['next']: + tags.update(api_call(data['next'])) return tags +def get_tags(image): + if "/" not in image: + image = LIB_PREFIX + image + if ":" in image: + image = image.split(":")[0] + url = TAGS.format(image=image) + tags = api_call(url) + if not len(tags): + raise ValueError(f"Unknown image '{image}'") + return tags + +def replace(string, replacements): + for k,v in replacements: + string = string.replace(k,v) + return string + + +def compare(base, other, replacements=[("-","+"),]): + base = replace(base, replacements) + other = replace(other, replacements) + v1 = version.parse(base) + v2 = version.parse(other) + result = v1 < v2 + log.debug(f"{v1} < {v2}: {result}") + return result + def get_new_tags(image): if not ":" in image: - print("using implicit latest, skip") + log.warn("using implicit latest, skip") return image_name, current_tag = image.split(":") if not image_name in TAG_STORE: TAG_STORE[image_name] = get_tags(image_name) - if current_tag in TAG_STORE[image_name]: - first_update = TAG_STORE[image_name][current_tag] - else: - print("!!! FALLBACK!") - first_update = TAG_STORE[image_name].entry_set()[0] - print(first_update) + #if current_tag in TAG_STORE[image_name]: + # first_update = TAG_STORE[image_name][current_tag] + #else: + # print("!!! FALLBACK!") + # first_update = list(TAG_STORE[image_name].values())[0] + #print(first_update) new_tags = {} for tag in TAG_STORE[image_name]: - print("("+str(tag)+")") - update = TAG_STORE[image_name][tag] - if update > first_update: + log.debug("check("+str(tag)+")") + if compare(current_tag, tag): + log.debug("NEWER!!!") + update = TAG_STORE[image_name][tag] new_tags[tag] = str(update) + log.debug(tag) + return new_tags if __name__=="__main__": @@ -47,7 +84,7 @@ if __name__=="__main__": args= parser.parse_args() for i in args.image: - print(i) + log.debug(i) if args.list: print(json.dumps({k:str(v) for k,v in get_tags(i).items()}, indent=1)) else: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0bfc676 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests +packaging \ No newline at end of file diff --git a/show_updateable.py b/show_updateable.py new file mode 100644 index 0000000..5c6becb --- /dev/null +++ b/show_updateable.py @@ -0,0 +1,35 @@ +import argparse +import json + +import docker_compose +import image_tags + + + + +def main(args): + updates = {} + images = docker_compose.start(args.compose_files, args.ignore) + for image in images: + for tag in images[image]: + image_ref = f"{image}:{tag}" + try: + newer_tags = image_tags.get_new_tags(image_ref) + except ValueError as e: + newer_tags = e.args + updates[image_ref] = { + "updates": newer_tags, + "usages": images[image][tag] + } + if args.output: + with open(args.output, "w") as out: + json.dump(updates, out, indent=1, sort_keys=True) + else: + print(json.dumps(updates, indent=1)) + + +if __name__=="__main__": + parser = docker_compose.args_setup("Show updates for docker-compose style services") + args = parser.parse_args() + main(args) + \ No newline at end of file