diff --git a/auto_dev/commands/run.py b/auto_dev/commands/run.py index b0d63a74..a2e4647f 100644 --- a/auto_dev/commands/run.py +++ b/auto_dev/commands/run.py @@ -117,6 +117,12 @@ def dev(ctx, agent_public_id: PublicId, verbose: bool, force: bool, fetch: bool) @click.option("--fetch/--no-fetch", help="Fetch from registry or use local service package", default=True) @click.option("--keysfile", help="Path to the private keys file.", type=click.File(), default="keys.json") @click.option("--number_of_agents", "-n", help="Number of agents to run.", type=int, default=1) +@click.option( + "--env_file", + help="Path to the environment file.", + type=click.File(), + default=".env" if Path(".env").exists() else None, +) @click.pass_context def prod( ctx, @@ -126,6 +132,7 @@ def prod( fetch: bool, keysfile: click.File, number_of_agents: int, + env_file: click.File, ) -> None: """Run an agent in production mode. @@ -154,6 +161,9 @@ def prod( if not Path(keysfile.name).exists(): logger.error(f"Keys file not found at {keysfile.name}") sys.exit(1) + if not Path(env_file.name).exists(): + logger.error(f"Environment file not found at {env_file.name}") + sys.exit(1) runner = ProdAgentRunner( service_public_id=service_public_id, @@ -163,6 +173,7 @@ def prod( fetch=fetch, keysfile=Path(keysfile.name).absolute(), number_of_agents=number_of_agents, + env_file=Path(env_file.name).absolute(), ) runner.run() logger.info("Agent run complete. 😎") diff --git a/auto_dev/enums.py b/auto_dev/enums.py index aecc2513..d966537a 100644 --- a/auto_dev/enums.py +++ b/auto_dev/enums.py @@ -10,6 +10,7 @@ class FileType(Enum): JSON = "json" TEXT = "txt" PYTHON = "py" + ENV = "env" class FileOperation(Enum): diff --git a/auto_dev/services/runner/prod_runner.py b/auto_dev/services/runner/prod_runner.py index 029cba68..f4e7f29c 100644 --- a/auto_dev/services/runner/prod_runner.py +++ b/auto_dev/services/runner/prod_runner.py @@ -10,6 +10,7 @@ from dataclasses import dataclass import rich +from dotenv import dotenv_values from aea.skills.base import PublicId from aea.cli.push_all import push_all_packages from aea.configurations.base import PackageType @@ -17,11 +18,11 @@ from aea.configurations.constants import PACKAGES, SERVICES, DEFAULT_SERVICE_CONFIG_FILE from autonomy.configurations.base import PACKAGE_TYPE_TO_CONFIG_CLASS -from auto_dev.utils import change_dir, load_autonolas_yaml +from auto_dev.utils import FileType, change_dir, write_to_file, load_autonolas_yaml from auto_dev.exceptions import UserInputError from auto_dev.cli_executor import CommandExecutor -from auto_dev.services.runner.base import AgentRunner from auto_dev.workflow_manager import Task +from auto_dev.services.runner.base import AgentRunner TENDERMINT_RESET_TIMEOUT = 10 @@ -48,6 +49,7 @@ class ProdAgentRunner(AgentRunner): fetch: bool = False keysfile: Path = "keys.json" number_of_agents: int = 1 + env_file: Path = ".env" def run(self) -> None: """Run the agent.""" @@ -145,6 +147,12 @@ def validate(self) -> None: if not self.keysfile.is_file(): self.logger.error(f"Keys file {self.keysfile} is not a file.") sys.exit(1) + if not self.env_file.exists(): + self.logger.error(f"Environment file {self.env_file} not found.") + sys.exit(1) + if not self.env_file.is_file(): + self.logger.error(f"Environment file {self.env_file} is not a file.") + sys.exit(1) available_keys = json.loads(self.keysfile.read_text()) if len(available_keys) < 1: @@ -191,10 +199,21 @@ def build_deployment(self) -> None: """Build the deployment.""" self.logger.info("Building the deployment...") env_vars = self.generate_env_vars() + self.execute_command( f"autonomy deploy build {self.keysfile} --o abci_build", env_vars=env_vars, ) + + # Note: autonomy deploy build doesn't write the env vars to the agent env files, even with --aev. + # So we do it manually here. + for agent_id in range(self.number_of_agents): + agent_env_path = Path(f"abci_build/agent_{agent_id}.env") + if agent_env_path.exists(): + existing_env = dotenv_values(agent_env_path) + agent_env_vars = {**env_vars, **existing_env} + write_to_file(agent_env_path, agent_env_vars, file_type=FileType.ENV) + self.logger.info("Deployment built successfully. 🎉") def manage_keys( @@ -206,8 +225,10 @@ def manage_keys( def generate_env_vars(self) -> dict: """Generate the environment variables for the deployment.""" + env_vars = dotenv_values(self.env_file) return { "ALL_PARTICIPANTS": json.dumps(self.all_participants), + **env_vars, } def execute_agent( @@ -220,7 +241,8 @@ def execute_agent( task = Task(command="docker compose up -d --remove-orphans", working_dir="abci_build").work() if task.is_failed: - raise RuntimeError(f"Agent execution failed. {task.client.output}") + msg = f"Agent execution failed. {task.client.output}" + raise RuntimeError(msg) self.logger.info("Agent execution complete. 😎") def execute_command(self, command: str, verbose=None, env_vars=None, spinner=False) -> None: diff --git a/auto_dev/utils.py b/auto_dev/utils.py index a4181989..ef525d25 100644 --- a/auto_dev/utils.py +++ b/auto_dev/utils.py @@ -357,6 +357,9 @@ def write_to_file(file_path: str, content: Any, file_type: FileType = FileType.T json.dump(content, f, **json_kwargs) elif file_type is FileType.PYTHON: f.write(content) + elif file_type is FileType.ENV: + for key, value in content.items(): + f.write(f"{key}={value}\n") else: msg = f"Invalid file_type {file_type}, must be one of {list(FileType)}." raise ValueError(msg)