docker2sh

Convert Dockerfiles into shell scripts
git clone https://git.parazyd.org/docker2sh
Log | Files | Refs | README | LICENSE

commit 0c6fe99b9d8031c830abbab1a05d18757fd37d32
parent 8a0b794dd328b411a64ba250be772ad675ed8f12
Author: parazyd <parazyd@dyne.org>
Date:   Wed, 20 Nov 2019 14:11:08 +0100

Add docker2sh.py.

Diffstat:
Adocker2sh.py | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 176 insertions(+), 0 deletions(-)

diff --git a/docker2sh.py b/docker2sh.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018-2019 Dyne.org Foundation +# docker2sh is maintained by Ivan J. <parazyd@dyne.org> +# +# 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 base64 import b64encode +from bz2 import compress +from os.path import dirname, join +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 compress_and_b64(file, basepath=None): + """ + Compress a file and turn it to base64 for output + """ + spl = file.split() + if basepath: + file = open(join(basepath, spl[0])).read() + else: + file = open(spl[0]).read() + + comp = compress(file.encode()) + b64 = b64encode(comp) + + cat = 'cat << __EOFF__ | base64 -d | bunzip2 | sudo tee %s >/dev/null' % (spl[1]) + return '\n'.join([cat, b64.decode(), '__EOFF__']) + '\n' + + +def parse_instruction(inst, dfile=None): + """ + 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]) + + if ins == 'ARG': + cmds.remove(ins) + return '%s\n' % val + + if ins == 'ENV': + cmds.remove(ins) + if '=' not in val: + val = val.replace(' ', '=', 1) + return 'export %s\n' % val + + if ins == 'RUN': + cmds.remove(ins) + # Replace `` with $() + while '`' in val: + val = val.replace('`', '"$(', 1) + val = val.replace('`', ')"', 1) + return '%s\n' % val.replace('$', '\\$') + + if ins == 'WORKDIR': + cmds.remove(ins) + return 'mkdir -p %s && cd %s\n' % (val, val) + + if ins == 'COPY': + cmds.remove(ins) + if '/' in dfile: + return compress_and_b64(val, basepath=dirname(dfile)) + return compress_and_b64(val) + + if 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) + + return '' + + +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, dfile=args.Dockerfile) + print(script) + + +if __name__ == '__main__': + main()