dockerfile_parse.py (4427B)
1 #!/usr/bin/env python3 2 # Copyright (c) 2018-2021 Ivan J. <parazyd@dyne.org> 3 # This file is part of libdevuansdk 4 # 5 # This source code is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This software is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this source code. If not, see <http://www.gnu.org/licenses/>. 17 """ 18 Dockerfile parser module 19 """ 20 from argparse import ArgumentParser 21 from sys import stdin 22 import json 23 import re 24 25 26 def rstrip_backslash(line): 27 """ 28 Strip backslashes from end of line 29 """ 30 line = line.rstrip() 31 if line.endswith('\\'): 32 return line[:-1] 33 return line 34 35 36 def parse_instruction(inst): 37 """ 38 Method for translating Dockerfile instructions to shell script 39 """ 40 ins = inst['instruction'].upper() 41 val = inst['value'] 42 43 # Valid Dockerfile instructions 44 cmds = ['ADD', 'ARG', 'CMD', 'COPY', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'FROM', 45 'HEALTHCHECK', 'LABEL', 'MAINTAINER', 'ONBUILD', 'RUN', 'SHELL', 46 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'] 47 48 if ins == 'ADD': 49 cmds.remove(ins) 50 val = val.replace('$', '\\$') 51 args = val.split(' ') 52 return 'wget -O %s %s\n' % (args[1], args[0]) 53 54 elif ins == 'ARG': 55 cmds.remove(ins) 56 return '%s\n' % val 57 58 elif ins == 'ENV': 59 cmds.remove(ins) 60 if '=' not in val: 61 val = val.replace(' ', '=', 1) 62 return 'export %s\n' % val 63 64 elif ins == 'RUN': 65 cmds.remove(ins) 66 # Replace `` with $() 67 while '`' in val: 68 val = val.replace('`', '"$(', 1) 69 val = val.replace('`', ')"', 1) 70 return '%s\n' % val.replace('$', '\\$') 71 72 elif ins == 'WORKDIR': 73 cmds.remove(ins) 74 return 'mkdir -p %s && cd %s\n' % (val, val) 75 76 elif ins in cmds: 77 # TODO: Look at CMD being added to /etc/rc.local 78 return '#\n# %s not implemented\n# Instruction: %s %s\n#\n' % \ 79 (ins, ins, val) 80 81 82 def main(): 83 """ 84 Main parsing routine 85 """ 86 parser = ArgumentParser() 87 parser.add_argument('-j', '--json', action='store_true', 88 help='output the data as a JSON structure') 89 parser.add_argument('-s', '--shell', action='store_true', 90 help='output the data as a shell script (default)') 91 parser.add_argument('--keeptabs', action='store_true', 92 help='do not replace \\t (tabs) in the strings') 93 parser.add_argument('Dockerfile') 94 args = parser.parse_args() 95 96 if args.Dockerfile != '-': 97 with open(args.Dockerfile) as file: 98 data = file.read().splitlines() 99 else: 100 data = stdin.read().splitlines() 101 102 instre = re.compile(r'^\s*(\w+)\s+(.*)$') 103 contre = re.compile(r'^.*\\\s*$') 104 commentre = re.compile(r'^\s*#') 105 106 instructions = [] 107 lineno = -1 108 in_continuation = False 109 cur_inst = {} 110 111 for line in data: 112 lineno += 1 113 if commentre.match(line): 114 continue 115 if not in_continuation: 116 rematch = instre.match(line) 117 if not rematch: 118 continue 119 cur_inst = { 120 'instruction': rematch.groups()[0].upper(), 121 'value': rstrip_backslash(rematch.groups()[1]), 122 } 123 else: 124 if cur_inst['value']: 125 cur_inst['value'] += rstrip_backslash(line) 126 else: 127 cur_inst['value'] = rstrip_backslash(line.lstrip()) 128 129 in_continuation = contre.match(line) 130 if not in_continuation and cur_inst is not None: 131 if not args.keeptabs: 132 cur_inst['value'] = cur_inst['value'].replace('\t', '') 133 instructions.append(cur_inst) 134 135 if args.json: 136 print(json.dumps(instructions)) 137 return 138 139 # Default to shell script output 140 script = '#!/bin/sh\n' 141 for i in instructions: 142 script += parse_instruction(i) 143 print(script) 144 145 146 if __name__ == '__main__': 147 main()