diff --git a/panther/config/experiment_config.yaml b/panther/config/experiment_config.yaml index 0ea6cd0ab4..9aebf12c6c 100644 --- a/panther/config/experiment_config.yaml +++ b/panther/config/experiment_config.yaml @@ -34,8 +34,10 @@ tests: - "5000:5000" # Example port if needed - "8081:8081" # Changed to avoid port conflict steps: + # record_pcap: True wait: - duration: 10 # seconds to wait during the test + duration: 20 # seconds to wait during the test + # record_pcap: False assertions: - type: "service_responsive" service: "picoquic_server" diff --git a/panther/core/experiment_manager.py b/panther/core/experiment_manager.py index a78e3be8c7..edbe0a7d27 100644 --- a/panther/core/experiment_manager.py +++ b/panther/core/experiment_manager.py @@ -315,6 +315,8 @@ def execute_steps(self, steps: Dict[str, Any]): :param steps: Dictionary of steps to execute. """ for step_name, step_details in steps.items(): + if step_name == "record_pcap": + pass if step_name == "wait": duration = step_details.get("duration", 0) self.logger.info(f"Executing step 'wait' for {duration} seconds.") diff --git a/panther/plugins/environments/network_environment/shadow/shadow.generated.yml b/panther/outputs/.gitkeep similarity index 100% rename from panther/plugins/environments/network_environment/shadow/shadow.generated.yml rename to panther/outputs/.gitkeep diff --git a/panther/plugins/environments/network_environment/docker_compose/docker-compose-template.j2 b/panther/plugins/environments/network_environment/docker_compose/docker-compose-template.j2 index d506c5b221..34b607e746 100644 --- a/panther/plugins/environments/network_environment/docker_compose/docker-compose-template.j2 +++ b/panther/plugins/environments/network_environment/docker_compose/docker-compose-template.j2 @@ -25,19 +25,24 @@ services: ROLE: {{ service.role }} # TODO SSLKEYLOGFILE: "/app/logs/sslkeylogfile.txt" + UID: "${UID}" + GID: "${GID}" {% if service.role == 'client' %} TARGET: {{ service.target }} MESSAGE: "{{ service.message }}" {% endif %} tty: true stdin_open: true - privileged: true + # user: "${UID}:${GID}" + # Not needed with user + # privileged: true cap_add: - - NET_ADMIN - - NET_RAW + - CAP_NET_ADMIN + - CAP_NET_RAW working_dir: "{{ deployment_info[service_name]['working_dir'] }}" command: - - "touch /app/logs/{{ service_name }}.pcap & tshark -i any -w /app/logs/{{ service_name }}.pcap & {{ deployment_info[service_name]['command'] }}" + # TODO protocol + duration + - "(tshark -a duration:10 -i any -w /app/logs/{{ service_name }}.pcap;) & (sleep 2; {{ deployment_info[service_name]['command'] }})" volumes: - "{{ log_dir | realpath }}/{{ service_name }}/:/app/logs/" {% for volume in deployment_info[service_name]['volumes'] %} diff --git a/panther/plugins/environments/network_environment/docker_compose/docker-compose.generated.yml b/panther/plugins/environments/network_environment/docker_compose/docker-compose.generated.yml index 9cefbc686e..67bd1a1c66 100644 --- a/panther/plugins/environments/network_environment/docker_compose/docker-compose.generated.yml +++ b/panther/plugins/environments/network_environment/docker_compose/docker-compose.generated.yml @@ -21,19 +21,25 @@ services: # environment: ROLE: server - SSLKEYLOGFILE: "/opt/ticket/ticket.key" + # TODO + SSLKEYLOGFILE: "/app/logs/sslkeylogfile.txt" + UID: "${UID}" + GID: "${GID}" tty: true stdin_open: true - privileged: true + # user: "${UID}:${GID}" + # Not needed with user + # privileged: true cap_add: - - NET_ADMIN - - NET_RAW + - CAP_NET_ADMIN + - CAP_NET_RAW working_dir: "/opt/picoquic" command: - - "touch /app/logs/picoquic_server.pcap & tshark -i any -w /app/logs/picoquic_server.pcap & ./picoquicdemo -c /opt/certs/cert.pem -k /opt/certs/key.pem -a hq-interop -n servername -D -L -p 4443 > /app/logs/server.log 2>&1" + # TODO protocol + duration + - "(tshark -a duration:10 -i any -w /app/logs/picoquic_server.pcap;) & (sleep 2; ./picoquicdemo -c /opt/certs/cert.pem -k /opt/certs/key.pem -a hq-interop -l - -n servername -D -L -e eth0 -p 4443 > /app/logs/server.log 2>&1)" volumes: - - "/home/crochetch/Documents/Projects/VerificationQUIC/PANTHER-SCP/panther/outputs/experiment_2024-10-15_19-44-27/logs/picoquic_server/:/app/logs/" + - "/home/crochetch/Documents/Projects/VerificationQUIC/PANTHER-SCP/panther/outputs/experiment_2024-10-17_09-32-01/logs/picoquic_server/:/app/logs/" - "/home/crochetch/Documents/Projects/VerificationQUIC/PANTHER-SCP/panther/config/certs/cert.pem:/opt/certs/cert.pem" @@ -63,22 +69,28 @@ services: # environment: ROLE: client - SSLKEYLOGFILE: "/opt/ticket/ticket.key" + # TODO + SSLKEYLOGFILE: "/app/logs/sslkeylogfile.txt" + UID: "${UID}" + GID: "${GID}" TARGET: picoquic_server MESSAGE: "" tty: true stdin_open: true - privileged: true + # user: "${UID}:${GID}" + # Not needed with user + # privileged: true cap_add: - - NET_ADMIN - - NET_RAW + - CAP_NET_ADMIN + - CAP_NET_RAW working_dir: "/opt/picoquic" command: - - "touch /app/logs/picoquic_client.pcap & tshark -i any -w /app/logs/picoquic_client.pcap & ./picoquicdemo -c /opt/certs/cert.pem -k /opt/certs/key.pem -T /opt/ticket/ticket.key -a hq-interop -D -L -v 00000001 picoquic_server 4443 > /app/logs/client.log 2>&1" + # TODO protocol + duration + - "(tshark -a duration:10 -i any -w /app/logs/picoquic_client.pcap;) & (sleep 2; sleep 5;./picoquicdemo -c /opt/certs/cert.pem -k /opt/certs/key.pem -T /opt/ticket/ticket.key -a hq-interop -l - -D -L -e eth0 -v 00000001 picoquic_server 4443 > /app/logs/client.log 2>&1)" volumes: - - "/home/crochetch/Documents/Projects/VerificationQUIC/PANTHER-SCP/panther/outputs/experiment_2024-10-15_19-44-27/logs/picoquic_client/:/app/logs/" + - "/home/crochetch/Documents/Projects/VerificationQUIC/PANTHER-SCP/panther/outputs/experiment_2024-10-17_09-32-01/logs/picoquic_client/:/app/logs/" - "/home/crochetch/Documents/Projects/VerificationQUIC/PANTHER-SCP/panther/config/certs/cert.pem:/opt/certs/cert.pem" diff --git a/panther/plugins/environments/network_environment/docker_compose/docker_compose_plugin.py b/panther/plugins/environments/network_environment/docker_compose/docker_compose_plugin.py index dfc7b7c4f2..927d7a0c19 100644 --- a/panther/plugins/environments/network_environment/docker_compose/docker_compose_plugin.py +++ b/panther/plugins/environments/network_environment/docker_compose/docker_compose_plugin.py @@ -110,6 +110,7 @@ def generate_docker_compose(self, paths: Dict[str, str], timestamp: str): if not os.path.exists(log_dir): os.makedirs(log_dir) self.logger.info(f"Created log directory: {log_dir}") + template = self.jinja_env.get_template("docker-compose-template.j2") rendered = template.render( services=self.services, @@ -118,6 +119,7 @@ def generate_docker_compose(self, paths: Dict[str, str], timestamp: str): timestamp=timestamp, log_dir=self.log_dirs, ) + # Write the rendered content to docker-compose.generated.yml with open(self.compose_file_path, "w") as f: f.write(rendered) @@ -132,7 +134,7 @@ def generate_docker_compose(self, paths: Dict[str, str], timestamp: str): self.logger.error( f"Failed to generate Docker Compose file: {e}\n{traceback.format_exc()}" ) - raise e + exit(1) def launch_docker_compose(self): """ @@ -155,6 +157,11 @@ def launch_docker_compose(self): "-d" ], check=True, + # Now in docker build + env={ # TODO is it dangerous ? + "UID": str(os.getuid()), + "GID": str(os.getgid()), + }, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, # Ensures that output is in string format diff --git a/panther/plugins/environments/network_environment/shadow/__init__.py b/panther/plugins/environments/network_environment/localhost/__init__.py similarity index 100% rename from panther/plugins/environments/network_environment/shadow/__init__.py rename to panther/plugins/environments/network_environment/localhost/__init__.py diff --git a/panther/plugins/environments/network_environment/localhost/config.yaml b/panther/plugins/environments/network_environment/localhost/config.yaml new file mode 100644 index 0000000000..671f7659b4 --- /dev/null +++ b/panther/plugins/environments/network_environment/localhost/config.yaml @@ -0,0 +1 @@ +bash_compose_file: "run.sh" \ No newline at end of file diff --git a/panther/plugins/environments/network_environment/localhost/localhost_plugin.py b/panther/plugins/environments/network_environment/localhost/localhost_plugin.py new file mode 100644 index 0000000000..c05534038e --- /dev/null +++ b/panther/plugins/environments/network_environment/localhost/localhost_plugin.py @@ -0,0 +1,192 @@ +import os +from pathlib import Path +import socket +import subprocess +import logging +from typing import Dict, Any +import yaml +from jinja2 import Environment, FileSystemLoader +from core.interfaces.environments.network_environment_interface import ( + INetworkEnvironment, +) +import traceback + + +class LocalHostEnvironment(INetworkEnvironment): + def __init__( + self, + config_path: str, + output_dir: str, + network_driver: str = "bridge", + templates_dir: str = "plugins/environments/network_environment/docker_compose", + ): + self.logger = logging.getLogger("LocalHostEnvironment") + self.services_network_config_file_path = os.path.join( + os.getcwd(), + "plugins", + "environments", + "network_environment", + "docker_compose", + "docker-compose.generated.yml", + ) + self.network_name = "quic_network_dynamic" + self.network_driver = network_driver + self.templates_dir = templates_dir + self.output_dir = output_dir + self.log_dirs = os.path.join(self.output_dir, "logs") + self.rendered_docker_compose_path = os.path.join( + self.output_dir, "docker-compose.yml" + ) + self.compose_file_path = Path(self.services_network_config_file_path) + self.services = {} + self.deployment_commands = {} + self.timeout = 60 + self.jinja_env = Environment(loader=FileSystemLoader(self.templates_dir)) + self.jinja_env.filters['realpath'] = lambda x: os.path.abspath(x) + + def build_images(self): + """ + Builds Docker images for all implementations. + """ + self.logger.info("Building Docker images for all implementations") + self.logger.info("Docker images built successfully") + raise NotImplementedError("Method not implemented - In another module FOR NOW") + + def is_port_free(self, port: int) -> bool: + """ + Checks if a given port is free on the host. + """ + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(("localhost", port)) != 0 + + def find_free_port( + self, start_port: int = 5000, end_port: int = 6000, assigned_ports: set = None + ) -> int: + """ + Finds a free port within the specified range. + """ + for port in range(start_port, end_port): + if self.is_port_free(port) and ( + assigned_ports is None or port not in assigned_ports + ): + return port + raise RuntimeError(f"No free ports available in range {start_port}-{end_port}") + + def setup_environment( + self, services: Dict[str, Dict[str, Any]], deployment_info: Dict[str, Dict[str, Any]], paths: Dict[str, str], timestamp: str + ): + """ + Sets up the localhost environment by generating the docker-compose.yml file with deployment commands. + + :param services: Dictionary of services with their configurations. + :param deployment_info: Dictionary containing commands and volumes for each service. + :param paths: Dictionary containing various path configurations. + :param timestamp: The timestamp string to include in log paths. + """ + self.services = services + self.deployment_info = deployment_info + self.logger.debug( + f"Setting up localhost environment with services: {services} and deployment info: {deployment_info}" + ) + self.generate_docker_compose(paths=paths, timestamp=timestamp) + self.logger.info("localhost environment setup complete") + + + def deploy_services(self): + self.logger.info("Deploying services") + self.launch_docker_compose() + + def generate_docker_compose(self, paths: Dict[str, str], timestamp: str): + """ + Generates the docker-compose.yml file using the provided services and deployment commands. + + :param paths: Dictionary containing various path configurations. + :param timestamp: The timestamp string to include in log paths. + """ + try: + # Ensure the log directory for each service exists + for service_name in self.services.keys(): + log_dir = os.path.join(self.log_dirs, service_name) + if not os.path.exists(log_dir): + os.makedirs(log_dir) + self.logger.info(f"Created log directory: {log_dir}") + + template = self.jinja_env.get_template("docker-compose-template.j2") + rendered = template.render( + services=self.services, + deployment_info=self.deployment_info, + paths=paths, + timestamp=timestamp, + log_dir=self.log_dirs, + ) + + # Write the rendered content to docker-compose.generated.yml + with open(self.compose_file_path, "w") as f: + f.write(rendered) + + with open(self.rendered_docker_compose_path, "w") as f: + f.write(rendered) + + self.logger.info( + f"localhost file generated at '{self.compose_file_path}'" + ) + except Exception as e: + self.logger.error( + f"Failed to generate localhost file: {e}\n{traceback.format_exc()}" + ) + raise e + + def launch_docker_compose(self): + """ + Launches the localhost environment using the generated docker-compose.yml file. + """ + try: + with open( + os.path.join(self.output_dir, "logs", "docker-compose.log"), "w" + ) as log_file: + with open( + os.path.join(self.output_dir, "logs", "docker-compose.err.log"), "w" + ) as log_file_err: + result = subprocess.run( + [ + "docker", + "compose", + "-f", + str(self.compose_file_path), + "up", + "-d" + ], + check=True, + # Now in docker build + env={ # TODO is it dangerous ? + "UID": str(os.getuid()), + "GID": str(os.getgid()), + }, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, # Ensures that output is in string format + ) + # Write both stdout and stderr to the log file + log_file.write(result.stdout) + log_file_err.write(result.stderr) + self.logger.info("localhost environment launched successfully.") + except subprocess.CalledProcessError as e: + self.logger.error( + f"Failed to launch localhost environment: {e.stderr}" + ) + raise e + + def teardown_environment(self): + """ + Tears down the localhost environment by bringing down services. + """ + self.logger.info("Tearing down localhost environment") + raise NotImplementedError("Method not implemented - In another module FOR NOW") + os.path.join(self.output_dir, "logs", "docker-compose-teardown.log"), "w" + ) as log_file: + + def __str__(self) -> str: + return ( + super().__str__() + + f" (network_driver={self.network_driver}, network_name={self.network_name})" + ) diff --git a/panther/plugins/environments/network_environment/localhost/run.sh b/panther/plugins/environments/network_environment/localhost/run.sh new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/panther/plugins/environments/network_environment/localhost/run.sh @@ -0,0 +1 @@ + diff --git a/panther/plugins/environments/network_environment/localhost/run.sh.j2 b/panther/plugins/environments/network_environment/localhost/run.sh.j2 new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/panther/plugins/environments/network_environment/localhost/run.sh.j2 @@ -0,0 +1 @@ + diff --git a/panther/plugins/environments/network_environment/shadow_ns/__init__.py b/panther/plugins/environments/network_environment/shadow_ns/__init__.py new file mode 100644 index 0000000000..581bbc3f77 --- /dev/null +++ b/panther/plugins/environments/network_environment/shadow_ns/__init__.py @@ -0,0 +1,4 @@ +"""panther package. + +This package contains the panther application and its modules. +""" \ No newline at end of file diff --git a/panther/plugins/environments/network_environment/shadow_ns/shadow-template.j2 b/panther/plugins/environments/network_environment/shadow_ns/shadow-template.j2 new file mode 100644 index 0000000000..6189b4c8a8 --- /dev/null +++ b/panther/plugins/environments/network_environment/shadow_ns/shadow-template.j2 @@ -0,0 +1,58 @@ + +services: + {% for service_name, service in services.items() %} + {{ service_name }}: + image: {{ service.implementation }}_{{ service.version }}_panther:latest + container_name: {{ service_name }} + ports: + {% for port in service.ports %} + - "{{ port }}" + {% endfor %} + {% if service.role == 'client' %} + depends_on: + - {{ service.target }} + # condition: service_healthy + {% endif %} + # {% if service.role == 'server' %} + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:{{ service.healthcheck_port }}/health"] + # interval: 5s + # timeout: 2s + # retries: 5 + # start_period: 5s + # {% endif %} + environment: + ROLE: {{ service.role }} + # TODO + SSLKEYLOGFILE: "/app/logs/sslkeylogfile.txt" + UID: "${UID}" + GID: "${GID}" + {% if service.role == 'client' %} + TARGET: {{ service.target }} + MESSAGE: "{{ service.message }}" + {% endif %} + tty: true + stdin_open: true + # user: "${UID}:${GID}" + # Not needed with user + # privileged: true + # cap_add: + # - NET_ADMIN + # - NET_RAW + working_dir: "{{ deployment_info[service_name]['working_dir'] }}" + command: + - "tshark -i lo -w /app/logs/{{ service_name }}.pcap; sleep 2; {{ deployment_info[service_name]['command'] }}" + volumes: + - "{{ log_dir | realpath }}/{{ service_name }}/:/app/logs/" + {% for volume in deployment_info[service_name]['volumes'] %} + - "{{ volume.local | realpath }}:{{ volume.container }}" + {% endfor %} + networks: + panther_network: + aliases: + - {{ service_name }} + {% endfor %} + +networks: + panther_network: + driver: bridge diff --git a/panther/plugins/environments/network_environment/shadow_ns/shadow.generated.yml b/panther/plugins/environments/network_environment/shadow_ns/shadow.generated.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/panther/plugins/environments/network_environment/shadow/shadow_plugin.py b/panther/plugins/environments/network_environment/shadow_ns/shadow_ns_plugin.py similarity index 100% rename from panther/plugins/environments/network_environment/shadow/shadow_plugin.py rename to panther/plugins/environments/network_environment/shadow_ns/shadow_ns_plugin.py diff --git a/panther/plugins/implementations/quic/picoquic/Dockerfile b/panther/plugins/implementations/quic/picoquic/Dockerfile index 62ef724890..6e5d15330a 100644 --- a/panther/plugins/implementations/quic/picoquic/Dockerfile +++ b/panther/plugins/implementations/quic/picoquic/Dockerfile @@ -1,11 +1,16 @@ FROM ubuntu:20.04 ENV DEBIAN_FRONTEND=noninteractive + RUN ln -fs /usr/share/zoneinfo/UTC /etc/localtime && \ apt-get update && \ apt-get install -y build-essential git cmake software-properties-common \ - openssl libssl-dev pkg-config clang python3 net-tools tcpdump wireshark \ - tshark apt-utils + openssl libssl-dev pkg-config clang python3 net-tools tcpdump \ + apt-utils wireshark tshark libcap2-bin traceroute \ + iputils-ping iproute2 iperf3 netcat-openbsd curl dnsutils iperf + +# "yes" answer by 'dpkg-reconfigure wireshark-common' so you can run tshark as normal use +RUN dpkg-reconfigure -f noninteractive wireshark-common # Define build arguments for version-specific configurations ARG VERSION=master @@ -13,12 +18,40 @@ ARG DEPENDENCIES="[]" # JSON-formatted list of dependencies ENV VERSION=${VERSION} ENV DEPENDENCIES=${DEPENDENCIES} +ARG USER_UID=1000 +ARG USER_GID=1000 +ARG USER_N=elniak + +RUN addgroup --gid ${USER_GID} ${USER_N} && \ + adduser --disabled-password --gecos '' --uid ${USER_UID} --gid ${USER_GID} ${USER_N} && \ + usermod -aG wireshark ${USER_N} + +# RUN chown ${USER_N}:wireshark /usr/bin/dumpcap + +# RUN setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' /usr/bin/dumpcap && \ +# chgrp wireshark /usr/bin/dumpcap + # chmod 750 /usr/bin/dumpcap + +# RUN setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' /usr/bin/dumpcap +# RUN chmod +x /usr/bin/dumpcap + + +# Give the user ownership of the /app directory (or any directories you need) +RUN mkdir -p /app +RUN chown -R ${USER_N}:${USER_N} /app +RUN chown -R ${USER_N}:${USER_N} /opt + +# Switch to the new user + # Perl stuff is for the picotls test code RUN echo install Test::TCP | perl -MCPAN - RUN echo install Scope::Guard | perl -MCPAN - # Install jq for JSON parsing RUN apt-get install -y jq + +USER ${USER_N} + # Function to parse and build dependencies # TODO make more modular RUN cd /opt && \ diff --git a/panther/plugins/implementations/quic/picoquic/config.yaml b/panther/plugins/implementations/quic/picoquic/config.yaml index cff0e33a95..3840fec9db 100644 --- a/panther/plugins/implementations/quic/picoquic/config.yaml +++ b/panther/plugins/implementations/quic/picoquic/config.yaml @@ -13,14 +13,14 @@ picoquic: alpn: param: "-a" value: "hq-interop" - additional_parameters: "-D -L" + additional_parameters: "-l - -D -L" binary: dir: "/opt/picoquic" name: "./picoquicdemo" network: interface: param: "-e" - value: "implem" + value: "eth0" port: 4443 destination: "11.0.0.1" certificates: @@ -48,14 +48,14 @@ picoquic: alpn: param: "-a" value: "hq-interop" - additional_parameters: " -n servername -D -L" + additional_parameters: "-l - -n servername -D -L" binary: dir: "/opt/picoquic" name: "./picoquicdemo" network: interface: param: "-e" - value: "implem" + value: "eth0" port: 4443 destination: "11.0.0.1" certificates: diff --git a/panther/plugins/implementations/quic/picoquic/service_manager.py b/panther/plugins/implementations/quic/picoquic/service_manager.py index 5447228688..497fc61077 100644 --- a/panther/plugins/implementations/quic/picoquic/service_manager.py +++ b/panther/plugins/implementations/quic/picoquic/service_manager.py @@ -132,7 +132,9 @@ def generate_deployment_commands(self, service_params: Dict[str, Any], environme version_config = self.config.get("picoquic", {}).get("versions", {}).get(version, {}) # Determine if network interface parameters should be included based on environment - include_interface = environment not in ["docker_compose"] + # TODO + # include_interface = environment not in ["docker_compose"] + include_interface = True # Build parameters for the command template params = { @@ -206,6 +208,8 @@ def generate_deployment_commands(self, service_params: Dict[str, Any], environme # Clean up the command string command_str = command.replace('\t', ' ').replace('\n', ' ').strip() + # TODO make more clean with event + command_str = "sleep 5;" + command_str if role == "client" else command_str # Create the command list working_dir = version_config.get(role, {}).get("binary", {}).get("dir", "/opt/picoquic") @@ -259,13 +263,14 @@ def start_service(self, parameters: dict): """ Starts the Picoquic server or client based on the role. Parameters should include 'role'. + # TODO should be in envirnment """ role = parameters.get("role") if role not in ['server', 'client']: self.logger.error(f"Unknown role '{role}'. Cannot start service.") return - cmd = self.generate_command(role) + cmd = self.generate_deployment_commands(role) if not cmd: self.logger.error(f"Failed to generate command for role '{role}'.") return diff --git a/panther/utils/docker_builder.py b/panther/utils/docker_builder.py index b3f7deb6e5..cf9736a76f 100644 --- a/panther/utils/docker_builder.py +++ b/panther/utils/docker_builder.py @@ -7,7 +7,7 @@ from docker.errors import DockerException, NotFound, BuildError, APIError from pathlib import Path from typing import Any, Dict, Optional, List - +import os import yaml @@ -40,11 +40,12 @@ def log_docker_output(self, generator, task_name: str = "docker command executio if "stream" in output: output_str = output["stream"].strip("\r\n").strip("\n") if log_f: - log_f.write(f"{output_str}\n") + log_f.write(f"{task_name}:{output_str}\n") self.logger.info(f"{task_name}: {output_str}") elif "error" in output: if log_f: - log_f.write(f"{output['error']}\n") + log_f.write(f"{task_name}:{output['error']}\n") + self.logger.error(f"Error from {task_name}: {output['error']}") raise ValueError(f'Error from {task_name}: {output["error"]}') except StopIteration: @@ -83,7 +84,10 @@ def build_image(self, impl_name: str, version: str, dockerfile_path: Path, try: build_args = { 'VERSION': config.get('commit', 'master'), - 'DEPENDENCIES': dependencies_json + 'DEPENDENCIES': dependencies_json, + 'USER_UID': str(os.getuid()), + 'USER_GID': str(os.getgid()), + 'USER_N': os.getlogin() } # Open the build log file if specified if self.build_log_file: @@ -115,13 +119,13 @@ def build_image(self, impl_name: str, version: str, dockerfile_path: Path, if self.build_log_file: with open(self.build_log_file, 'a') as log_f: log_f.write(f"ERROR: {e}\n") - return None + exit(1) except Exception as e: self.logger.error(f"Unexpected error during build of '{image_tag}': {e}") if self.build_log_file: with open(self.build_log_file, 'a') as log_f: log_f.write(f"ERROR: {e}\n") - return None + exit(1) def image_exists(self, image_tag: str) -> bool: """