docker2sh

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

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