Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28fa065ef9 | ||
|
|
b2cc65ef2c | ||
|
|
06b344dfac | ||
|
|
13d8399d4a | ||
|
|
adedc2277a |
5
.pypirc
Normal file
5
.pypirc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[distutils]
|
||||||
|
index_servers =
|
||||||
|
private-gitea
|
||||||
|
|
||||||
|
[private-gitea]
|
||||||
0
assets/.gitkeep
Normal file
0
assets/.gitkeep
Normal file
0
assets/compose-files/.gitkeep
Normal file
0
assets/compose-files/.gitkeep
Normal file
0
assets/dockerfiles/.gitkeep
Normal file
0
assets/dockerfiles/.gitkeep
Normal file
7
assets/dockerfiles/binary.dockerfile
Normal file
7
assets/dockerfiles/binary.dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM fedora:41
|
||||||
|
|
||||||
|
RUN dnf install -y socat
|
||||||
|
WORKDIR /service/
|
||||||
|
ADD ./okay /service/okay
|
||||||
|
RUN chmod +x ./okay
|
||||||
|
ENTRYPOINT ["socat", "-d", "TCP-LISTEN:3000,reuseaddr,fork", "EXEC:timeout -k 5 30 ./okay"]
|
||||||
9
assets/dockerfiles/crypto.dockerfile
Normal file
9
assets/dockerfiles/crypto.dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.6-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y socat gcc g++ make libffi-dev libssl-dev
|
||||||
|
WORKDIR /service/
|
||||||
|
COPY ./requirements.txt ./requirements.txt
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
ADD ./main.py ./main.py
|
||||||
|
RUN chmod +x main.py
|
||||||
|
ENTRYPOINT socat -d TCP-LISTEN:3000,reuseaddr,fork EXEC:'timeout -k 5 30 python3 -u main.py'
|
||||||
20
assets/dockerfiles/web.dockerfile
Normal file
20
assets/dockerfiles/web.dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM docker.io/oven/bun AS frontend
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN bun i
|
||||||
|
COPY frontend/ .
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV DOCKER=1
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./backend/requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY ./backend/ .
|
||||||
|
COPY --from=frontend /app/dist /app/frontend
|
||||||
|
|
||||||
|
CMD ["python3", "app.py"]
|
||||||
25
assets/dockerfiles/with-chunks.dockerfile
Normal file
25
assets/dockerfiles/with-chunks.dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FROM docker.io/oven/bun AS frontend
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN bun i
|
||||||
|
COPY frontend/ .
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV DOCKER=1
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./backend/requirements.txt \
|
||||||
|
.
|
||||||
|
RUN pip install --no-cache-dir -r \
|
||||||
|
requirements.txt
|
||||||
|
COPY ./backend/ \
|
||||||
|
\
|
||||||
|
\
|
||||||
|
.
|
||||||
|
COPY --from=frontend /app/dist /app/frontend
|
||||||
|
|
||||||
|
CMD ["python3", "app.py"]
|
||||||
0
src/docktoranalyzer/__init__.py
Normal file
0
src/docktoranalyzer/__init__.py
Normal file
6
src/docktoranalyzer/common/errors.py
Normal file
6
src/docktoranalyzer/common/errors.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class NotImplemented(Exception):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
# TODO: Implement a way to print the method not impelemented
|
||||||
29
src/docktoranalyzer/dockerfile/command_parser_utils.py
Normal file
29
src/docktoranalyzer/dockerfile/command_parser_utils.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
from docktoranalyzer.dockerfile.docker_constants import DockerConstants
|
||||||
|
|
||||||
|
|
||||||
|
def command_parser(command_line: str):
|
||||||
|
|
||||||
|
PARENTHESIS_OFFSET = 1
|
||||||
|
|
||||||
|
args: list[str] = []
|
||||||
|
array_insides: str = ""
|
||||||
|
|
||||||
|
command_line = command_line.strip()
|
||||||
|
|
||||||
|
arr_regex = re.compile(DockerConstants.EXEC_FORM_REGEX)
|
||||||
|
match = arr_regex.search(command_line)
|
||||||
|
|
||||||
|
if match is not None:
|
||||||
|
array_insides = command_line[match.start(): -1]
|
||||||
|
command_line = command_line[0:match.start() - PARENTHESIS_OFFSET]
|
||||||
|
|
||||||
|
args.extend(shlex.split(
|
||||||
|
command_line
|
||||||
|
))
|
||||||
|
args.extend(shlex.split(
|
||||||
|
array_insides
|
||||||
|
))
|
||||||
|
|
||||||
|
return args
|
||||||
8
src/docktoranalyzer/dockerfile/docker_constants.py
Normal file
8
src/docktoranalyzer/dockerfile/docker_constants.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from typing import Final
|
||||||
|
|
||||||
|
|
||||||
|
class DockerConstants:
|
||||||
|
|
||||||
|
LINE_CONTINUATION_REGEX: Final[str] = r"(^[\s]*\\[\s]*$|[\s]+\\[\s]*$)"
|
||||||
|
COMMENT_REGEX: Final[str] = r"[\s]*#"
|
||||||
|
EXEC_FORM_REGEX: Final[str] = r"(?<!\\\[)(?<=\[).*[^\\](?=\])"
|
||||||
38
src/docktoranalyzer/dockerfile/dockerfile_.py
Normal file
38
src/docktoranalyzer/dockerfile/dockerfile_.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from docktoranalyzer.dockerfile.dockerfile_instructions import (
|
||||||
|
DockerInstruction,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Make DockerStage have a DockerFS
|
||||||
|
class DockerStage:
|
||||||
|
"""_summary_
|
||||||
|
Class that holds a Build Stage
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, instructions: list[DockerInstruction]):
|
||||||
|
self.commands: list[DockerInstruction] = []
|
||||||
|
|
||||||
|
self.commands = instructions
|
||||||
|
|
||||||
|
|
||||||
|
class DockerFS:
|
||||||
|
"""_summary_
|
||||||
|
Class to take map file instructions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, last_build_stage: DockerStage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Dockerfile:
|
||||||
|
"""_summary_
|
||||||
|
Class holding a representation of Dockerfile
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, build_stages: list[DockerStage]):
|
||||||
|
self.stages: list[DockerStage] = []
|
||||||
|
self.fs: DockerFS
|
||||||
|
|
||||||
|
self.stages = build_stages
|
||||||
|
self.fs = DockerFS(self.stages[-1])
|
||||||
0
src/docktoranalyzer/dockerfile/dockerfile_errors.py
Normal file
0
src/docktoranalyzer/dockerfile/dockerfile_errors.py
Normal file
238
src/docktoranalyzer/dockerfile/dockerfile_instructions.py
Normal file
238
src/docktoranalyzer/dockerfile/dockerfile_instructions.py
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
from docktoranalyzer.dockerfile.command_parser_utils import command_parser
|
||||||
|
from docktoranalyzer.dockerfile.docker_constants import DockerConstants
|
||||||
|
from docktoranalyzer.dockerfile.instruction_enums import DockerInstructionType
|
||||||
|
|
||||||
|
|
||||||
|
# UGLY: refactor this
|
||||||
|
def split_options_value(option: str) -> list[str]:
|
||||||
|
return option.split("=")
|
||||||
|
|
||||||
|
|
||||||
|
def split_args(command):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# MARK: InstructionChunk
|
||||||
|
class InstructionChunk:
|
||||||
|
|
||||||
|
def __init__(self, chunk_lines: list[str]):
|
||||||
|
# UGLY: could preallocate space
|
||||||
|
self.lines: list[str] = []
|
||||||
|
|
||||||
|
tmp = chunk_lines.copy()
|
||||||
|
regex = re.compile(DockerConstants.LINE_CONTINUATION_REGEX)
|
||||||
|
|
||||||
|
for line in tmp:
|
||||||
|
line = regex.sub("", line)
|
||||||
|
self.lines.append(line)
|
||||||
|
|
||||||
|
def is_empty(self):
|
||||||
|
|
||||||
|
if len(self.lines[0]) == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.lines)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# MARK: DockerInstruction
|
||||||
|
class DockerInstruction:
|
||||||
|
"""_summary_
|
||||||
|
Base Structure for all docker instructions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
self.type: DockerInstructionType = DockerInstructionType.UNPARSED
|
||||||
|
self.chunk: InstructionChunk = chunk
|
||||||
|
self.command: str = ""
|
||||||
|
self.args: str = ""
|
||||||
|
|
||||||
|
if self.chunk.is_empty():
|
||||||
|
self.type = DockerInstructionType.EMPTY
|
||||||
|
return
|
||||||
|
|
||||||
|
total_command = " ".join(self.chunk.lines)
|
||||||
|
command_words = command_parser(total_command)
|
||||||
|
self.command = command_words[0]
|
||||||
|
self.args = command_words[1:]
|
||||||
|
|
||||||
|
|
||||||
|
# MARK: COMMENT
|
||||||
|
class DockerCOMMENT(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.COMMENT
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on ADD
|
||||||
|
# MARK: ADD
|
||||||
|
class DockerADD(DockerInstruction):
|
||||||
|
|
||||||
|
# --key=value
|
||||||
|
__OPTIONS: set[str] = {
|
||||||
|
"--keep-git-dir",
|
||||||
|
"--checksum",
|
||||||
|
"--chown",
|
||||||
|
"--chmod",
|
||||||
|
"--link",
|
||||||
|
"--exclude",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.options: list[str] = []
|
||||||
|
self.type = DockerInstructionType.ADD
|
||||||
|
self.remote: list[str] = []
|
||||||
|
self.local: list[str] = []
|
||||||
|
self.destination: str = ""
|
||||||
|
|
||||||
|
for arg in self.args:
|
||||||
|
|
||||||
|
if arg in DockerADD.__OPTIONS:
|
||||||
|
self.options.append(
|
||||||
|
arg
|
||||||
|
)
|
||||||
|
|
||||||
|
if "]" in arg or "[" in arg:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DockerARG(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on CMD
|
||||||
|
# MARK: CMD
|
||||||
|
class DockerCMD(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.CMD
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on Copy
|
||||||
|
# MARK: COPY
|
||||||
|
class DockerCOPY(DockerInstruction):
|
||||||
|
|
||||||
|
# --key=value
|
||||||
|
__OPTIONS: set[str] = {
|
||||||
|
"--from",
|
||||||
|
"--chown",
|
||||||
|
"--chmod",
|
||||||
|
"--link",
|
||||||
|
"--parents",
|
||||||
|
"--exclude",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.COPY
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on ENTRYPOINT
|
||||||
|
class DockerENTRYPOINT(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.ENTRYPOINT
|
||||||
|
|
||||||
|
|
||||||
|
class DockerENV(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerEXPOSE(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on FROM
|
||||||
|
# MARK: FROM
|
||||||
|
class DockerFROM(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.FROM
|
||||||
|
|
||||||
|
|
||||||
|
class DockerHEALTHCHECK(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerLABEL(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerMAINTAINER(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerONBUILD(DockerInstruction):
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on run
|
||||||
|
# MARK: RUN
|
||||||
|
class DockerRUN(DockerInstruction):
|
||||||
|
|
||||||
|
# This variable is only --key=value
|
||||||
|
__OPTIONS: set[str] = {"--mount", "--netowrk", "--security"}
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.RUN
|
||||||
|
|
||||||
|
|
||||||
|
class DockerSHELL(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerSTOPSIGNAL(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerUSER(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerVOLUME(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Work on workdir
|
||||||
|
# MARK: WORKDIR
|
||||||
|
class DockerWORKDIR(DockerInstruction):
|
||||||
|
|
||||||
|
def __init__(self, chunk: list[str]):
|
||||||
|
super().__init__(chunk)
|
||||||
|
|
||||||
|
self.type = DockerInstructionType.WORKDIR
|
||||||
|
|
||||||
|
# TODO: Add workdirectory property
|
||||||
194
src/docktoranalyzer/dockerfile/dockerfile_parser.py
Normal file
194
src/docktoranalyzer/dockerfile/dockerfile_parser.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
from docktoranalyzer.dockerfile.docker_constants import DockerConstants
|
||||||
|
from docktoranalyzer.dockerfile.instruction_enums import DockerInstructionType
|
||||||
|
from docktoranalyzer.dockerfile.dockerfile_ import DockerStage, Dockerfile
|
||||||
|
from docktoranalyzer.dockerfile.dockerfile_instructions import (
|
||||||
|
DockerADD,
|
||||||
|
DockerCMD,
|
||||||
|
DockerCOMMENT,
|
||||||
|
DockerCOPY,
|
||||||
|
DockerENTRYPOINT,
|
||||||
|
DockerFROM,
|
||||||
|
DockerInstruction,
|
||||||
|
DockerRUN,
|
||||||
|
DockerWORKDIR,
|
||||||
|
InstructionChunk,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerFileParser:
|
||||||
|
|
||||||
|
DEFAULT_ESCAPE_STRING = "\\"
|
||||||
|
DEFAULT_LINE_CONTINUATION = "\\"
|
||||||
|
DIRECTIVE_REGEX = re.compile(
|
||||||
|
"# +(?P<directive_name>[^\\s]*)=(?P<value>[^\\s]*)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
raise TypeError("Static classes cannot be instantiated")
|
||||||
|
|
||||||
|
# MARK: dockerfile_factory()
|
||||||
|
@staticmethod
|
||||||
|
def dockerfile_factory(dockerfile_path: Path):
|
||||||
|
|
||||||
|
if not dockerfile_path.is_file():
|
||||||
|
raise FileNotFoundError(f"{dockerfile_path} is not a valid path")
|
||||||
|
|
||||||
|
dockerfile = dockerfile_path.open()
|
||||||
|
docker_instructions = dockerfile.readlines()
|
||||||
|
dockerfile.close()
|
||||||
|
|
||||||
|
chunks = DockerFileParser.__parse_chunks(docker_instructions)
|
||||||
|
instructions = DockerFileParser.__parse_instructions(chunks)
|
||||||
|
stages = DockerFileParser.__parse_stages(instructions)
|
||||||
|
|
||||||
|
return Dockerfile(stages.copy())
|
||||||
|
|
||||||
|
# MARK: __parse_directives()
|
||||||
|
@staticmethod
|
||||||
|
def __parse_directives(docker_lines: list[str]):
|
||||||
|
|
||||||
|
found_directives: set[str] = set()
|
||||||
|
|
||||||
|
for line in docker_lines:
|
||||||
|
|
||||||
|
line_length = len(line)
|
||||||
|
|
||||||
|
# Line is too short to
|
||||||
|
# make a directive
|
||||||
|
if line_length < 4:
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = DockerFileParser.DIRECTIVE_REGEX.match(line)
|
||||||
|
|
||||||
|
# No match found
|
||||||
|
if match is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
directive_name = match.group("directive_name")
|
||||||
|
value = match.group("value")
|
||||||
|
|
||||||
|
# Duplicate directive, ignore
|
||||||
|
if directive_name in found_directives:
|
||||||
|
continue
|
||||||
|
|
||||||
|
found_directives.add(directive_name)
|
||||||
|
|
||||||
|
if directive_name == "escape":
|
||||||
|
DockerFileParser.ESCAPE_STRING = value
|
||||||
|
|
||||||
|
# MARK: __parse_chunks()
|
||||||
|
@staticmethod
|
||||||
|
def __parse_chunks(
|
||||||
|
instruction_lines: list[str],
|
||||||
|
line_continuation_regex: str = DockerConstants.LINE_CONTINUATION_REGEX,
|
||||||
|
) -> list[InstructionChunk]:
|
||||||
|
|
||||||
|
continuation_check = re.compile(line_continuation_regex)
|
||||||
|
comment_check = re.compile(DockerConstants.COMMENT_REGEX)
|
||||||
|
chunks: list[InstructionChunk] = []
|
||||||
|
accumulator: list[str] = []
|
||||||
|
|
||||||
|
for line in instruction_lines:
|
||||||
|
line = line.rstrip()
|
||||||
|
accumulator.append(line)
|
||||||
|
|
||||||
|
# If line is a comment, it can't continue
|
||||||
|
if comment_check.search(line) is not None:
|
||||||
|
|
||||||
|
if len(accumulator) > 1:
|
||||||
|
accumulator.pop()
|
||||||
|
chunks.append(InstructionChunk(accumulator))
|
||||||
|
|
||||||
|
chunks.append(InstructionChunk([line]))
|
||||||
|
accumulator = []
|
||||||
|
|
||||||
|
# If line doesn't continue, join everything found
|
||||||
|
if continuation_check.search(line) is None:
|
||||||
|
chunks.append(InstructionChunk(accumulator))
|
||||||
|
accumulator = []
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
# MARK: __parse_instruction()
|
||||||
|
@staticmethod
|
||||||
|
def __parse_instructions(
|
||||||
|
instruction_chunks: list[InstructionChunk],
|
||||||
|
) -> list[DockerInstruction]:
|
||||||
|
|
||||||
|
docker_instructions: list[DockerInstruction] = []
|
||||||
|
|
||||||
|
for chunk in instruction_chunks:
|
||||||
|
docker_instructions.append(
|
||||||
|
DockerFileParser.__instruction_mapper(chunk)
|
||||||
|
)
|
||||||
|
|
||||||
|
return docker_instructions
|
||||||
|
|
||||||
|
# MARK: __instruction_mapper()
|
||||||
|
@staticmethod
|
||||||
|
def __instruction_mapper(
|
||||||
|
chunk: InstructionChunk,
|
||||||
|
) -> DockerInstruction:
|
||||||
|
|
||||||
|
if chunk.is_empty():
|
||||||
|
return DockerInstruction(chunk)
|
||||||
|
|
||||||
|
command = shlex.split(chunk.lines[0])[0]
|
||||||
|
|
||||||
|
if command == "#":
|
||||||
|
return DockerCOMMENT(chunk)
|
||||||
|
|
||||||
|
instruction_type: DockerInstructionType = DockerInstructionType[
|
||||||
|
f"{command}"
|
||||||
|
]
|
||||||
|
|
||||||
|
match instruction_type:
|
||||||
|
|
||||||
|
case DockerInstructionType.CMD:
|
||||||
|
return DockerCMD(chunk)
|
||||||
|
|
||||||
|
case DockerInstructionType.COPY:
|
||||||
|
return DockerCOPY(chunk)
|
||||||
|
|
||||||
|
case DockerInstructionType.ENTRYPOINT:
|
||||||
|
return DockerENTRYPOINT(chunk)
|
||||||
|
|
||||||
|
case DockerInstructionType.FROM:
|
||||||
|
return DockerFROM(chunk)
|
||||||
|
|
||||||
|
case DockerInstructionType.RUN:
|
||||||
|
return DockerRUN(chunk)
|
||||||
|
|
||||||
|
case DockerInstructionType.ADD:
|
||||||
|
return DockerADD(chunk)
|
||||||
|
|
||||||
|
case DockerInstructionType.WORKDIR:
|
||||||
|
return DockerWORKDIR(chunk)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
return DockerInstruction(chunk)
|
||||||
|
|
||||||
|
# MARK: __parse_stages()
|
||||||
|
@staticmethod
|
||||||
|
def __parse_stages(
|
||||||
|
instructions: list[DockerInstruction],
|
||||||
|
) -> list[DockerStage]:
|
||||||
|
|
||||||
|
stages: list[DockerStage] = []
|
||||||
|
accumulator: list[DockerInstruction] = []
|
||||||
|
|
||||||
|
for instruction in instructions:
|
||||||
|
|
||||||
|
if instruction.type is DockerInstructionType.FROM:
|
||||||
|
|
||||||
|
stages.append(DockerStage(accumulator.copy()))
|
||||||
|
accumulator = []
|
||||||
|
|
||||||
|
accumulator.append(instruction)
|
||||||
|
|
||||||
|
stages.append(DockerStage(accumulator.copy()))
|
||||||
|
|
||||||
|
return stages
|
||||||
30
src/docktoranalyzer/dockerfile/instruction_enums.py
Normal file
30
src/docktoranalyzer/dockerfile/instruction_enums.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
|
||||||
|
class DockerInstructionType(Enum):
|
||||||
|
|
||||||
|
# Special values
|
||||||
|
UNPARSED = auto()
|
||||||
|
UNKOWN = auto()
|
||||||
|
EMPTY = auto()
|
||||||
|
COMMENT = auto()
|
||||||
|
|
||||||
|
# Docker Instructions
|
||||||
|
ADD = auto()
|
||||||
|
ARG = auto()
|
||||||
|
CMD = auto()
|
||||||
|
COPY = auto()
|
||||||
|
ENTRYPOINT = auto()
|
||||||
|
ENV = auto()
|
||||||
|
EXPOSE = auto()
|
||||||
|
FROM = auto()
|
||||||
|
HEALTHCHECK = auto()
|
||||||
|
LABEL = auto()
|
||||||
|
MAINTAINER = auto()
|
||||||
|
ONBUILD = auto()
|
||||||
|
RUN = auto()
|
||||||
|
SHELL = auto()
|
||||||
|
STOPSIGNAL = auto()
|
||||||
|
USER = auto()
|
||||||
|
VOLUME = auto()
|
||||||
|
WORKDIR = auto()
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/integration-tests/.gitkeep
Normal file
0
tests/integration-tests/.gitkeep
Normal file
36
tests/unit-tests/instruction_test.py
Normal file
36
tests/unit-tests/instruction_test.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# content of test_sample.py
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
import docktoranalyzer
|
||||||
|
import docktoranalyzer.dockerfile
|
||||||
|
from docktoranalyzer.dockerfile.docker_constants import DockerConstants
|
||||||
|
from docktoranalyzer.dockerfile.dockerfile_parser import DockerFileParser
|
||||||
|
|
||||||
|
# TODO: use a glob to take files and rege
|
||||||
|
@pytest.fixture
|
||||||
|
def docker_file_arrays():
|
||||||
|
|
||||||
|
# I need to count one stage over to account for instructions
|
||||||
|
# the first FROM
|
||||||
|
return [
|
||||||
|
{"path": "./assets/dockerfiles/binary.dockerfile", "stages": 2},
|
||||||
|
{"path": "./assets/dockerfiles/crypto.dockerfile", "stages": 2},
|
||||||
|
{"path": "./assets/dockerfiles/web.dockerfile", "stages": 3},
|
||||||
|
{"path": "./assets/dockerfiles/with-chunks.dockerfile", "stages": 3},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Make tests for regex
|
||||||
|
|
||||||
|
|
||||||
|
def test_dockerfile_parser(docker_file_arrays):
|
||||||
|
|
||||||
|
for docker_file_info in docker_file_arrays:
|
||||||
|
|
||||||
|
docker_path = docker_file_info["path"]
|
||||||
|
actual_stages = docker_file_info["stages"]
|
||||||
|
|
||||||
|
docker = DockerFileParser.dockerfile_factory(Path(docker_path))
|
||||||
|
|
||||||
|
assert len(docker.stages) == actual_stages
|
||||||
Loading…
x
Reference in New Issue
Block a user