def __init__(self, source=None): """Create a new Dockerfile. Optionally parse a source Dockerfile and populate the new Dockerfile instance with the source Dockerfile commands. Args: source: optional string naming a path to a source Dockerfile on disk used to initialize the `dockerphile.Dockerfile` commands. Returns: Nothing. Instantiates `self` attributes for class instance created. Raises: Nothing. """ self.sequence = [] if source is not None: escape_directive = parse_escape_directive(source) if escape_directive is not None: self.sequence.append(escape_directive) for command in parse_file(source): instruction = to_instruction(command) if instruction is None: continue elif isinstance(instruction, list): self.sequence.extend(instruction) else: self.sequence.append(instruction)
def test_parse_file_success(): ret = dockerfile.parse_file('testfiles/Dockerfile.ok') assert ret == ( dockerfile.Command( cmd='from', sub_cmd=None, json=False, value=('ubuntu:xenial',), start_line=1, original='FROM ubuntu:xenial', ), dockerfile.Command( cmd='cmd', sub_cmd=None, json=True, value=('echo', 'hi'), start_line=2, original='CMD ["echo", "hi"]', ), )
def convert(filepath): environment = [] working_directory = [] entry_cmd = {"entrypoint": "", "cmd": ""} hostname = "building-" + randomname(10) for command in dockerfile.parse_file(filepath): if command.cmd == "from": pass #from_command_lxd(hostname, command.value) elif command.cmd == "run": pass #run_command_lxd(hostname, command.value) elif command.cmd == "copy": pass #copy_command_lxd(hostname, command.value) elif command.cmd == "env": pass environment.append(env_command_lxd(hostname, command.value)) elif command.cmd == "volume": volume_command_lxd(hostname, command.value) elif command.cmd == "entrypoint": entry_cmd["entrypoint"] = command.value entry_cmd["entrypoint_json"] = command.json elif command.cmd == "cmd": entry_cmd["cmd"] = command.value entry_cmd["cmd_json"] = command.json else: print(command.cmd) lxdcontroller.stop_machine(hostname) lxdcontroller.delete_machine(hostname) print(make_startup_command(entry_cmd))
def from_file(cls, dockerfile: str) -> ParsedDockerfile: return cls(dockerfile, parse_file(dockerfile))
def test_parse_file_ioerror(): with pytest.raises(dockerfile.GoIOError) as excinfo: dockerfile.parse_file('Dockerfile.dne') assert 'Dockerfile.dne' in excinfo.value.args[0]
def GenerateMap(debug: bool = False): """ Parse all the dockerfiles in a collection and extract the stages and the dependencies (images) of each stage; cross-relate them with the declarations of default images; and build a map of all the images in the collection. """ cmap = CollectionMap() for dfilename in [Path(x.name).stem for x in CDIR.glob("*.dockerfile")]: if debug: print("·", dfilename) dfile = Dockerfile() stg = None for item in dockerfile.parse_file(str(CDIR / f"{dfilename}.dockerfile")): if item.cmd == "arg": _val = item.value[0] if not _val.startswith("REGISTRY="): if _val.startswith("IMAGE="): if dfile.argimg is not None: raise Exception( f"ARG IMAGE was already defined in <{dfilename}> [{_val}]!" ) # Extract image name from IMAGE="name" dfile.argimg = _val[7:-1] else: raise Exception(f"Unknown ARG <{_val}>!") continue if item.cmd == "from": if stg is not None: # This was not the first stage in this dockerfile, save the previous one dfile.addStage(stg) stg = Stage() _val = item.value[0] stg.value = dfile.markOrigin(_val) if len(item.value) != 1: # Second argument must be 'AS', between the image and the tag if item.value[1].upper() != "AS": raise Exception("Second item should be 'AS'!") stg.tag = item.value[2] continue if item.cmd == "copy": if "--from=" not in item.flags[0].lower(): raise Exception( f"Second item of <{item.flags}> should be '--from=*'!") stg.addDep(dfile.markOrigin(item.flags[0][7:])) continue if item.cmd == "run" and len(item.flags) > 0: _val = item.flags[0] if _val.startswith("--mount=type="): stg.addDep( dfile.markOrigin( _val.split(",from=")[1].split(",")[0])) else: raise Exception(f"Unknown RUN flag <{_val}>!") if stg is None: raise Exception(f"No stages found in dockerfile <{dfilename}>!") dfile.addStage(stg) cmap.addDockerfile(dfilename, dfile) for key, args in DefaultOpts.items(): if args[0] not in cmap.data: raise Exception(f"Dockerfile <{args[0]}> not found in data!") cmap.data[args[0]].addArtifact((f"!R|{key}", args[1], args[2])) return cmap
def reload(self): """ Re-parse Dockerfile. """ self._commands = dockerfile.parse_file(self.dockerfile_location)
def main(dockerfile_in_name: str, dockerfile_out_name: str) -> None: commands: List[DockerCommand] = dockerfile.parse_file(dockerfile_in_name) optimized_commands = optimize_docker_commands(commands) with open(dockerfile_out_name, 'w', encoding='utf-8') as f: write_docker_commands(f, optimized_commands)
def planner(filename): """generates a plan. which will be executed at later point. Parameters --------- filename: string Dockerfile name """ config = Config() commands = dockerfile.parse_file(filename) argdict = dict() from_image = "" from_tag = "latest" for command in commands: value = command.value original = command.original cmd = command.cmd if cmd == 'arg': # ARG command only accepts single argument value = command.value[0] name, value = value.split("=") # TODO arg replace arg with value its set argdict.update(name, value) elif cmd == 'from': rep = _substitute(original, argdict) command = dockefile.parse_string(rep) value = command.value if len(value) > 1: # multi build # TODO handle multi build # do we need to? # for example `from python:3 as base` pass else: sp = rep.split(":") if len(sp) != 2: raise SimplePushException("from should only have one tag") elif len(sp) > 2: from_tag = sp[1] from_image = sp[0] else: from_image = rep # TODO get config form registry elif cmd == "run": raise SimplePushException("will not handle run command") elif cmd == "workdir": # workdir expects single argument config.working_dir = value[0] elif cmd == "copy" or cmd == "add": # TODO generate layer and add layer to manifest pass elif cmd == 'env': # refer https://docs.docker.com/engine/reference/builder/#env # two forms # first form if len(value) > 1: env_key = value[0] env_value = " ".join(value[1:]) # TODO instead of appending, it should be maintained as dictionary # and update config.config.append(f"{env_key}={env_value}") else: if "=" not in original: raise SimplePushException( "syntax error at {cmd.start_line}. check https://docs.docker.com/engine/reference/builder/#env" ) # second form (already desired form) # just add to config config.config.append(value) elif cmd == "entrypoint": ## entrypoint will not be updated if config.config.entrypoint is None: config.config.entrypoint = value elif cmd == "label": # annotations # TODO fill it up later pass elif cmd == "cmd": config.config.cmd = value elif cmd == "expose": config.config.exposed_ports.update({port: {} for port in value}) elif cmd == "volume": config.config.volumes.update({volume: {} for volume in value}) elif cmd == "user": config.config.user = value[0] elif cmd == "onbuild": ## will not be used # TODO did not find any info on image spec # ignoring for now config.config.on_build = value[0] elif cmd == "stopsignal": config.config.stopsignal = value[0] elif cmd == "shell": # ignoring now # TODO pass
def ParseDockerfile(self, dfilepath: Path, debug: bool = False): dfilename = Path(dfilepath.name).stem dkey = dfilename if dfilename == 'Dockerfile': dkey = dfilepath.parent.name dfilename = f"{dkey}/{dfilename}" if debug: print(f" · {dfilename!s}") dfile = Dockerfile() stg = None for item in dockerfile.parse_file(str(dfilepath)): if item.cmd.upper() == "ARG": _val = item.value[0] if not _val.startswith("REGISTRY="): if _val.startswith("IMAGE="): if dfile.argimg is not None: raise Exception( f"ARG IMAGE was already defined in <{dfilename}> [{_val}]!" ) # Extract image name from IMAGE="name" dfile.argimg = _val[7:-1] elif _val.startswith("ARCHITECTURE="): print(" ! Field ARCHITECTURE not handled yet") else: raise Exception(f"Unknown ARG <{_val}>!") continue if item.cmd.upper() == "FROM": if stg is not None: # This was not the first stage in this dockerfile, save the previous one dfile.addStage(stg) stg = Stage() _val = item.value[0] stg.value = dfile.markOrigin(_val) if len(item.value) != 1: # Second argument must be 'AS', between the image and the tag if item.value[1].upper() != "AS": raise Exception("Second item should be 'AS'!") stg.tag = item.value[2] continue if item.cmd.upper() == "COPY" and len(item.flags) > 0: if "--from=" in item.flags[0].lower(): stg.addDep(dfile.markOrigin(item.flags[0][7:])) continue if item.cmd.upper() == "RUN" and len(item.flags) > 0: _val = item.flags[0] if _val.startswith("--mount=type=cache"): stg.addDep( dfile.markOrigin( _val.split(",from=")[1].split(",")[0])) continue if stg is None: raise Exception(f"No stages found in dockerfile <{dfilename}>!") dfile.addStage(stg) self.AddDockerfile(dkey, dfile)