docker2sh (4369B)
1 #!/usr/bin/env python3 2 """ 3 Dockerfile parser module 4 """ 5 from argparse import ArgumentParser 6 from base64 import b64encode 7 from bz2 import compress 8 from os.path import dirname, join 9 from sys import stdin 10 import json 11 import re 12 13 14 def rstrip_backslash(line): 15 """ 16 Strip backslashes from end of line 17 """ 18 line = line.rstrip() 19 if line.endswith('\\'): 20 return line[:-1] 21 return line 22 23 24 def compress_and_b64(file, basepath=None): 25 """ 26 Compress a file and turn it to base64 for output 27 """ 28 spl = file.split() 29 if basepath: 30 file = open(join(basepath, spl[0])).read() 31 else: 32 file = open(spl[0]).read() 33 34 comp = compress(file.encode()) 35 b64 = b64encode(comp) 36 37 cat = 'cat << __EOFF__ | base64 -d | bunzip2 > %s' % (spl[1]) 38 return '\n'.join([cat, b64.decode(), '__EOFF__']) + '\n' 39 40 41 def parse_instruction(inst, dfile=None): 42 """ 43 Method for translating Dockerfile instructions to shell script 44 """ 45 ins = inst['instruction'].upper() 46 val = inst['value'] 47 48 # Valid Dockerfile instructions 49 cmds = ['ADD', 'ARG', 'CMD', 'COPY', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'FROM', 50 'HEALTHCHECK', 'LABEL', 'MAINTAINER', 'ONBUILD', 'RUN', 'SHELL', 51 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'] 52 53 if ins == 'ADD': 54 val = val.replace('$', '\\$') 55 args = val.split(' ') 56 return 'wget -O %s %s\n' % (args[1], args[0]) 57 58 if ins == 'ARG': 59 return '%s\n' % val 60 61 if ins == 'ENV': 62 if '=' not in val: 63 val = val.replace(' ', '=', 1) 64 val = val.replace('$', '\\$') 65 return 'export %s\n' % val 66 67 if ins == 'RUN': 68 # Replace `` with $() 69 while '`' in val: 70 val = val.replace('`', '"$(', 1) 71 val = val.replace('`', ')"', 1) 72 return '%s\n' % val.replace('$', '\\$') 73 74 if ins == 'WORKDIR': 75 return 'mkdir -p %s && cd %s\n' % (val, val) 76 77 if ins == 'COPY': 78 if '/' in dfile: 79 return compress_and_b64(val, basepath=dirname(dfile)) 80 return compress_and_b64(val) 81 82 if ins in cmds: 83 # TODO: Look at CMD being added to /etc/rc.local 84 return '#\n# %s not implemented\n# Instruction: %s %s\n#\n' % \ 85 (ins, ins, val) 86 87 # Silently ignore unknown instructions 88 return '' 89 90 91 def main(): 92 """ 93 Main parsing routine 94 """ 95 parser = ArgumentParser() 96 parser.add_argument('-j', '--json', action='store_true', 97 help='output the data as a JSON structure') 98 parser.add_argument('-s', '--shell', action='store_true', 99 help='output the data as a shell script (default)') 100 parser.add_argument('--keeptabs', action='store_true', 101 help='do not replace \\t (tabs) in the strings') 102 parser.add_argument('Dockerfile') 103 args = parser.parse_args() 104 105 if args.Dockerfile != '-': 106 with open(args.Dockerfile) as file: 107 data = file.read().splitlines() 108 else: 109 data = stdin.read().splitlines() 110 111 instre = re.compile(r'^\s*(\w+)\s+(.*)$') 112 contre = re.compile(r'^.*\\\s*$') 113 commentre = re.compile(r'^\s*#') 114 115 instructions = [] 116 lineno = -1 117 in_continuation = False 118 cur_inst = {} 119 120 for line in data: 121 lineno += 1 122 if commentre.match(line): 123 continue 124 if not in_continuation: 125 rematch = instre.match(line) 126 if not rematch: 127 continue 128 cur_inst = { 129 'instruction': rematch.groups()[0].upper(), 130 'value': rstrip_backslash(rematch.groups()[1]), 131 } 132 else: 133 if cur_inst['value']: 134 cur_inst['value'] += rstrip_backslash(line) 135 else: 136 cur_inst['value'] = rstrip_backslash(line.lstrip()) 137 138 in_continuation = contre.match(line) 139 if not in_continuation and cur_inst is not None: 140 if not args.keeptabs: 141 cur_inst['value'] = cur_inst['value'].replace('\t', '') 142 instructions.append(cur_inst) 143 144 if args.json: 145 print(json.dumps(instructions)) 146 return 147 148 # Default to shell script output 149 script = '#!/bin/sh\n' 150 for i in instructions: 151 script += parse_instruction(i, dfile=args.Dockerfile) 152 print(script) 153 154 155 if __name__ == '__main__': 156 main()