Last active
May 7, 2020 19:09
-
-
Save cloudnull/599b59d68f580ad9a887a77750be0120 to your computer and use it in GitHub Desktop.
ooo dockerfile converter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Licensed under the Apache License, Version 2.0 (the "License"); you may | |
# not use this file except in compliance with the License. You may obtain | |
# a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
# License for the specific language governing permissions and limitations | |
# under the License. | |
import os | |
import re | |
import sys | |
import yaml | |
TCIB_MAP = { | |
"tcib_path": None, | |
"tcib_args": {}, | |
"tcib_from": None, | |
"tcib_labels": {}, | |
"tcib_envs": {}, | |
"tcib_onbuilds": [], | |
"tcib_volumes": [], | |
"tcib_workdir": None, | |
"tcib_adds": [], | |
"tcib_copies": [], | |
"tcib_exposes": [], | |
"tcib_user": None, | |
"tcib_shell": None, | |
"tcib_runs": [], | |
"tcib_healthcheck": None, | |
"tcib_stopsignal": None, | |
"tcib_entrypoint": None, | |
"tcib_cmd": None, | |
"tcib_actions": [], | |
"tcib_gather_files": [], | |
} | |
DOCKER_VERB_MAP = { | |
"FROM": "tcib_from", | |
"RUN": "tcib_runs", | |
"CMD": "tcib_cmd", | |
"LABEL": "tcib_labels", | |
"EXPOSE": "tcib_exposes", | |
"ENV": "tcib_envs", | |
"ADD": "tcib_adds", | |
"COPY": "tcib_copies", | |
"ENTRYPOINT": "tcib_entrypoint", | |
"VOLUME": "tcib_volumes", | |
"USER": "tcib_user", | |
"WORKDIR": "tcib_workdir", | |
"ARG": "tcib_args", | |
"ONBUILD": "tcib_onbuilds", | |
"STOPSIGNAL": "tcib_stopsignal", | |
"HEALTHCHECK": "tcib_healthcheck", | |
"SHELL": "tcib_shell", | |
} | |
def line_reader(lines, return_lines=None): | |
"""Read all lines of a container file. | |
This will concatinate all them into a machine readable array. | |
:param Lines: list of lines to read. | |
:type Lines: List | |
:param return_lines: List of lines that will be returned. | |
:type return_lines: List | |
:returns: List | |
""" | |
if not return_lines: | |
return_lines = list() | |
try: | |
line = next(lines) | |
line = line.strip() | |
if line: | |
if line.endswith("\\"): | |
while True: | |
new_line = next(lines) | |
if not new_line.startswith("#"): | |
new_line = new_line.strip() | |
line = line.rstrip("\\") | |
line += " {line}".format(line=new_line.rstrip("\\")) | |
if not new_line.endswith("\\"): | |
break | |
return_lines.append(line) | |
else: | |
if not line.startswith("#"): | |
return_lines.append(line) | |
except StopIteration: | |
return return_lines | |
else: | |
return line_reader(lines, return_lines=return_lines) | |
def package_parse(packages_line, lines): | |
"""Parse a command line which runs a dnf install. | |
:param package_line: Line to parse | |
:type package_line: String | |
:param lines: List of lines | |
:type lines: List | |
:returns: List | |
""" | |
a = re.search(r".*dnf -y install (.*?) (&&|' ')", packages_line) | |
TCIB_MAP["tcib_packages"] = {"common": sorted(a.group(1).split())} | |
index = lines.index(packages_line) | |
lines.pop(index) | |
lines.insert( | |
0, | |
packages_line.replace( | |
a.group(1), r"{{ tcib_packages.common | join(' ') }}" | |
), | |
) | |
return lines | |
def module_parse(module_line, lines): | |
"""Parse a command line which runs a dnf module. | |
:param module_line: Line to parse | |
:type module_line: String | |
:param lines: List of lines | |
:type lines: List | |
:returns: List | |
""" | |
modules_list = TCIB_MAP["tcib_packages"]["modules"] = list() | |
pattern = re.compile( | |
r"dnf -y module (disable|enable|info|install|list|provides|" | |
r"remove|repoquery|reset|update)(.*?)(&&|' ')" | |
) | |
for match in re.findall(pattern, module_line): | |
key, value, _ = match | |
modules = [i for i in value.split() if i] | |
for module in modules: | |
modules_list.append({key: module}) | |
module_jinja = ( | |
r"RUN if [ '{{ tcib_distro }}' == 'rhel' ]; then " | |
r"{% for item in tcib_packages.modules %}" | |
r"{% set key, value = (item.items() | list).0 %}" | |
r"dnf module -y {{ key }} {{ value }}; " | |
r"{% endfor %}fi" | |
) | |
index = lines.index(module_line) | |
lines.pop(index) | |
lines.insert( | |
index, | |
module_line.replace( | |
" ".join( | |
[ | |
i[0] | |
for i in re.findall( | |
r"(dnf -y module.*?(&&|' '))", module_line | |
) | |
] | |
), | |
"", | |
), | |
) | |
lines.insert(index, module_jinja) | |
return lines | |
def line_parser(lines): | |
"""Line parser which will translate strings into machine data. | |
:param lines: List of lines | |
:type lines: List | |
""" | |
for line in lines: | |
verb, content = line.split(" ", 1) | |
if verb in ["ADD", "COPY", "RUN"]: | |
TCIB_MAP["tcib_actions"].append({verb.lower(): content.strip()}) | |
elif verb in ["FROM", "LABEL"]: | |
continue | |
else: | |
map_item = TCIB_MAP[DOCKER_VERB_MAP[verb]] | |
if isinstance(map_item, list): | |
map_item.append(content) | |
elif isinstance(map_item, dict): | |
try: | |
key, value = content.split("=", 1) | |
except ValueError: | |
key, value = content.split(" ", 1) | |
map_item[key] = value.strip('"') | |
else: | |
TCIB_MAP[DOCKER_VERB_MAP[verb]] = content | |
def main(containerfile): | |
"""Run the main application. | |
:param containerfile: File to parse, this requires the full path to the file. | |
:type containerfile: String | |
""" | |
with open(containerfile) as f: | |
lines = [ | |
" ".join(i.split()) for i in line_reader(lines=iter(f.readlines())) | |
] | |
r = re.compile(".*dnf.*install(.*)($| )") | |
packages_lines = list(filter(r.match, lines)) | |
if len(packages_lines) == 1: | |
lines = package_parse(packages_line=packages_lines[0], lines=lines) | |
elif len(packages_lines) > 1: | |
print( | |
"Warning: packages not parsed because there is more than one " | |
"install command, file '{}' will need to be manually converted to " | |
"using the packages structure.".format(containerfile) | |
) | |
r = re.compile(".*dnf.*module(.*)($| )") | |
module_lines = list(filter(r.match, lines)) | |
if len(module_lines) == 1: | |
lines = module_parse(module_line=module_lines[0], lines=lines) | |
elif len(module_lines) > 1: | |
print( | |
"Warning: modules not parsed because there is more than one " | |
"module command, file '{}' will need to be manually converted to " | |
"using the module structure.".format(containerfile) | |
) | |
line_parser(lines=lines) | |
render_vars = dict() | |
for key, value in TCIB_MAP.items(): | |
if value: | |
render_vars[key] = value | |
dir_path = os.path.dirname(containerfile) | |
var_file = "{var}.yaml".format( | |
var=os.path.basename(dir_path).replace("-container", "") | |
) | |
with open(os.path.join(dir_path, var_file), "w") as f: | |
f.write(yaml.dump(render_vars, default_flow_style=False, width=4096)) | |
if __name__ == "__main__": | |
main(containerfile=sys.argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment