Source code for gyoza.worker.execution_pipeline.steps.prepare_inputs

import json
import os

from gyoza.worker import shared_runtime
from gyoza.worker.execution_pipeline.types import ExecutionContext

GYOZA_WORKING_DIRECTORY = os.getenv("GYOZA_WORKING_DIRECTORY", "/tmp")

_INPUT_FILENAME = "input.json"
_OUTPUT_FILENAME = "output.json"


[docs] class PrepareInputs: """Prepare the inputs for the container execution. Creates a unique gyoza directory for the run, saves inputs as a JSON file, and sets GYOZA_INPUT_PATH and GYOZA_OUTPUT_PATH environment variables. When the run carries an outputs template (fields pre-filled with upload URLs by the caller), it is pre-written to the output file so the op runtime inside the container can deliver those outputs. """ def __call__(self, ctx: ExecutionContext) -> ExecutionContext: """Execute the step. Parameters ---------- ctx : ExecutionContext Execution context with inputs to convert. Returns ------- ExecutionContext Context with gyoza_dir, volumes, and env_vars configured. """ gyoza_dir = self._generate_op_dir(ctx.oprun_id) self._save_inputs(ctx.inputs, gyoza_dir) self._save_output_template(ctx.outputs, gyoza_dir) self._setup_volumes(ctx, gyoza_dir) self._setup_env_vars(ctx, gyoza_dir) # Store gyoza_dir in extras for cleanup later ctx.extras["gyoza_dir"] = gyoza_dir return ctx def _generate_op_dir(self, oprun_id: str) -> str: """Generate the operation directory path using the oprun_id. Parameters ---------- oprun_id : str OpRun identifier used as directory name. Returns ------- str Path to the gyoza directory. """ return os.path.join(GYOZA_WORKING_DIRECTORY, oprun_id) def _save_inputs(self, inputs: dict, gyoza_dir: str) -> None: """Save inputs as a JSON file in the gyoza directory. Parameters ---------- inputs : dict Input parameters to save. gyoza_dir : str Directory where the JSON file will be saved. """ os.makedirs(gyoza_dir, exist_ok=True) os.chmod(gyoza_dir, 0o777) self._write_json(inputs, os.path.join(gyoza_dir, _INPUT_FILENAME)) def _save_output_template(self, outputs: dict, gyoza_dir: str) -> None: """Pre-write the outputs template as the output JSON file. Skipped when the template is empty, so runs without pre-filled outputs behave exactly as before (no output file until the container writes one). The file is made writable by anybody, for the same reason the directory is: the worker writes it and the *container* rewrites it at the end of the run, and the two are not the same user — op images run as an unprivileged user while the worker is usually root. Without this the container fails with ``Permission denied`` on the very last step, after the op has finished and its outputs are already uploaded, and the run is marked FAILED with everything about it successful. Parameters ---------- outputs : dict Partial outputs template from the run. gyoza_dir : str Directory where the JSON file will be saved. """ if not outputs: return output_path = os.path.join(gyoza_dir, _OUTPUT_FILENAME) self._write_json(outputs, output_path) # Same reason the directory is 0o777: the container runs under a # different uid. Without this the file keeps the worker's umask and # ownership, so the container can create files here but gets [Errno 13] # writing its results over this one. os.chmod(output_path, 0o666) @staticmethod def _write_json(data: dict, json_path: str) -> None: """Write *data* as JSON to *json_path*. Parameters ---------- data : dict Data to serialise. json_path : str Destination file path. """ with open(json_path, "w") as f: json.dump(data, f) def _setup_volumes(self, ctx: ExecutionContext, gyoza_dir: str) -> None: """Mount the worker's shared paths and the run's own directory. Parameters ---------- ctx : ExecutionContext Execution context to update. gyoza_dir : str Path to mount inside the container. """ ctx.volumes.update(shared_runtime.mounts()) cache_dir = shared_runtime.cache_dir() ctx.volumes[cache_dir] = {"bind": cache_dir, "mode": "rw"} ctx.volumes[gyoza_dir] = {"bind": gyoza_dir, "mode": "rw"} def _setup_env_vars(self, ctx: ExecutionContext, gyoza_dir: str) -> None: """Set the worker's shared variables and the run's own paths. Gyoza's own variables are set last, so a shared one cannot shadow them. Parameters ---------- ctx : ExecutionContext Execution context to update. gyoza_dir : str Base directory for input/output files. """ ctx.env_vars.update(shared_runtime.env()) ctx.env_vars[shared_runtime.CACHE_DIR_VAR] = shared_runtime.cache_dir() ctx.env_vars["GYOZA_INPUT_PATH"] = os.path.join(gyoza_dir, _INPUT_FILENAME) ctx.env_vars["GYOZA_OUTPUT_PATH"] = os.path.join(gyoza_dir, _OUTPUT_FILENAME)
prepare_inputs = PrepareInputs()