libdevuansdk

common library for devuan's simple distro kits
git clone https://git.parazyd.org/libdevuansdk
Log | Files | Refs | Submodules | README | LICENSE

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()