""" Docker layer size report python docker_layer_sizes.py <image> Returns the size of each layer of the image and the command that created it. This is a decorative layer on top of the docker history command. """ import subprocess import sys import re from pygments import highlight from pygments.styles import get_style_by_name from pygments.lexers import DockerLexer from pygments.formatters import Terminal256Formatter INDENT = 8 LEXER = DockerLexer() FORMATTER = Terminal256Formatter(style=get_style_by_name("rrt")) MULTISPACE = re.compile(r" {2,}") NOP_PREFIX = re.compile(r".*#\(nop\) +", re.DOTALL) class Command: shell = "" def __init__(self, string): self.size, self.instr = string.split("\t", 1) if self.size == "0B": self.size = "" if self.cmd == "SHELL": self.__class__.shell = self.text[1:-1] @property def cmd(self): return self.instr.split(" ", 1)[0] @property def text(self): return self.instr.split(" ", 1)[1] def __repr__(self): instr = re.sub(NOP_PREFIX, "", self.instr) instr = re.sub(MULTISPACE, " ", instr) if self.cmd != "SHELL": instr = instr.replace(f"RUN {self.__class__.shell}", "RUN") instr = instr.replace(self.__class__.shell, "RUN") instr = instr.replace(" RUN", "\n" + " " * (INDENT + 3)) instr = highlight(instr, LEXER, FORMATTER).strip() instr = [x.strip() for x in instr.split("&&")] instr = f"\n{'':>12}&&".join(instr) return f"{self.size:>6} {instr}" def data(image) -> str: """Ask docker for our data.""" proc = subprocess.run( [ "docker", "history", "--no-trunc", "--format", "{{.Size}}\t{{.CreatedBy}}", image, ], check=True, capture_output=True, ) return [Command(line) for line in proc.stdout.decode("utf-8").strip().split("\n")][ ::-1 ] def main(image): """Run the program.""" output = data(image) for line in output: print(line) if __name__ == "__main__": main(sys.argv[1])