tImplement a python Dockerfile parser. - libdevuansdk - common library for devuan's simple distro kits HTML git clone https://git.parazyd.org/libdevuansdk DIR Log DIR Files DIR Refs DIR Submodules DIR README DIR LICENSE --- DIR commit c922031a0cb2eade8922cf3c2d40dff2b13cd54c DIR parent 2cf0d648bad5a2bfe0026158228d6e355a9b7e61 HTML Author: parazyd <parazyd@dyne.org> Date: Tue, 18 Sep 2018 15:29:46 +0200 Implement a python Dockerfile parser. This tool can translate Dockerfiles into shell scripts and JSON structures. It is imagined to be used for the blend concept, where a Dockerfile could be a system and can be translated into a blend file, and used by an sdk. Diffstat: A extra/dockerfile_parse.py | 149 +++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+), 0 deletions(-) --- DIR diff --git a/extra/dockerfile_parse.py b/extra/dockerfile_parse.py t@@ -0,0 +1,149 @@ +#!/usr/bin/env python +# Copyright (c) 2018 Dyne.org Foundation +# libdevuansdk is maintained by Ivan J. <parazyd@dyne.org> +# +# This file is part of libdevuansdk +# +# This source code is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this source code. If not, see <http://www.gnu.org/licenses/>. +""" +Dockerfile parser module +""" +from argparse import ArgumentParser +from sys import stdin +import json +import re + + +def rstrip_backslash(line): + """ + Strip backslashes from end of line + """ + line = line.rstrip() + if line.endswith('\\'): + return line[:-1] + return line + + +def parse_instruction(inst): + """ + Method for translating Dockerfile instructions to shell script + """ + ins = inst['instruction'].upper() + val = inst['value'] + + # Valid Dockerfile instructions + cmds = ['ADD', 'ARG', 'CMD', 'COPY', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'FROM', + 'HEALTHCHECK', 'LABEL', 'MAINTAINER', 'ONBUILD', 'RUN', 'SHELL', + 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'] + + if ins == 'ADD': + cmds.remove(ins) + val = val.replace('$', '\\$') + args = val.split(' ') + return 'wget -O %s %s\n' % (args[1], args[0]) + + elif ins == 'ARG': + cmds.remove(ins) + return '%s\n' % val + + elif ins == 'ENV': + cmds.remove(ins) + if '=' not in val: + val = val.replace(' ', '=', 1) + return 'export %s\n' % val + + elif ins == 'RUN': + cmds.remove(ins) + # Replace `` with $() + while '`' in val: + val = val.replace('`', '"$(', 1) + val = val.replace('`', ')"', 1) + return '%s\n' % val.replace('$', '\\$') + + elif ins == 'WORKDIR': + cmds.remove(ins) + return 'mkdir -p %s && cd %s\n' % (val, val) + + elif ins in cmds: + # TODO: Look at CMD being added to /etc/rc.local + return '#\n# %s not implemented\n# Instruction: %s %s\n#\n' % \ + (ins, ins, val) + + +def main(): + """ + Main parsing routine + """ + parser = ArgumentParser() + parser.add_argument('-j', '--json', action='store_true', + help='output the data as a JSON structure') + parser.add_argument('-s', '--shell', action='store_true', + help='output the data as a shell script (default)') + parser.add_argument('--keeptabs', action='store_true', + help='do not replace \\t (tabs) in the strings') + parser.add_argument('Dockerfile') + args = parser.parse_args() + + if args.Dockerfile != '-': + with open(args.Dockerfile) as file: + data = file.read().splitlines() + else: + data = stdin.read().splitlines() + + instre = re.compile(r'^\s*(\w+)\s+(.*)$') + contre = re.compile(r'^.*\\\s*$') + commentre = re.compile(r'^\s*#') + + instructions = [] + lineno = -1 + in_continuation = False + cur_inst = {} + + for line in data: + lineno += 1 + if commentre.match(line): + continue + if not in_continuation: + rematch = instre.match(line) + if not rematch: + continue + cur_inst = { + 'instruction': rematch.groups()[0].upper(), + 'value': rstrip_backslash(rematch.groups()[1]), + } + else: + if cur_inst['value']: + cur_inst['value'] += rstrip_backslash(line) + else: + cur_inst['value'] = rstrip_backslash(line.lstrip()) + + in_continuation = contre.match(line) + if not in_continuation and cur_inst is not None: + if not args.keeptabs: + cur_inst['value'] = cur_inst['value'].replace('\t', '') + instructions.append(cur_inst) + + if args.json: + print(json.dumps(instructions)) + return + + # Default to shell script output + script = '#!/bin/sh\n' + for i in instructions: + script += parse_instruction(i) + print(script) + + +if __name__ == '__main__': + main()