tdocker2sh - docker2sh - Convert Dockerfiles into shell scripts
HTML git clone https://git.parazyd.org/docker2sh
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tdocker2sh (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()