From 2e44156fd4220c66341d393f594bbb4fc36ba3f7 Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Tue, 14 Apr 2026 08:57:51 +0530 Subject: [PATCH 1/4] stabilize docker compose orchestration and networking logic --- concore_cli/commands/build.py | 46 ++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/concore_cli/commands/build.py b/concore_cli/commands/build.py index 5865500..70fac80 100644 --- a/concore_cli/commands/build.py +++ b/concore_cli/commands/build.py @@ -2,6 +2,7 @@ import shlex import subprocess import sys +import shutil from pathlib import Path from rich.panel import Panel from rich.progress import Progress, SpinnerColumn, TextColumn @@ -88,8 +89,10 @@ def _write_docker_compose(output_path): if not services: return None - compose_lines = ["services:"] + compose_lines = ["networks:", " concore-net:", " driver: bridge", "", "services:"] + prev_service_name = None + named_volumes = set() for index, service in enumerate(services, start=1): service_name = re.sub(r"[^A-Za-z0-9_.-]", "-", service["container_name"]).strip( "-." @@ -104,10 +107,30 @@ def _write_docker_compose(output_path): compose_lines.append( f" container_name: {_yaml_quote(service['container_name'])}" ) + compose_lines.append(" restart: on-failure") + compose_lines.append(" networks:") + compose_lines.append(" - concore-net") + + # Chain services sequentially to prevent ZMQ race conditions + if prev_service_name: + compose_lines.append(" depends_on:") + compose_lines.append(f" - {prev_service_name}") + if service["volumes"]: compose_lines.append(" volumes:") for volume_spec in service["volumes"]: compose_lines.append(f" - {_yaml_quote(volume_spec)}") + part1 = volume_spec.split(':')[0] + if re.match(r"^[a-zA-Z0-9_-]+$", part1): + named_volumes.add(part1) + + prev_service_name = service_name + + if named_volumes: + compose_lines.append("") + compose_lines.append("volumes:") + for v in sorted(named_volumes): + compose_lines.append(f" {v}:") compose_lines.append("") compose_path = output_path / "docker-compose.yml" @@ -180,6 +203,27 @@ def build_workflow( progress.update(task, completed=True) + if exec_type == "docker": + req_src = Path.cwd() / "requirements.txt" + if not req_src.exists(): + req_src = source_path / "requirements.txt" + req_dest = output_path / "src" / "requirements.txt" + if req_src.exists() and (output_path / "src").exists(): + shutil.copy2(req_src, req_dest) + elif (output_path / "src").exists(): + req_dest.touch() + + # Append requirement copying to generated scripts + for s_name in ["build", "build.bat"]: + s_path = output_path / s_name + if s_path.exists(): + content = s_path.read_text(encoding="utf-8") + if s_name == "build": + content = content.replace("docker build", "cp ../src/requirements.txt .\ndocker build") + else: + content = content.replace("docker build", "copy ..\\src\\requirements.txt .\n docker build") + s_path.write_text(content, encoding="utf-8") + if result.stdout: console.print(result.stdout) From f96a1b76178a3574a86f2853af5b600427cc0a5c Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Tue, 14 Apr 2026 10:33:06 +0530 Subject: [PATCH 2/4] ruff formating --- concore_cli/commands/build.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/concore_cli/commands/build.py b/concore_cli/commands/build.py index 70fac80..d94f4c5 100644 --- a/concore_cli/commands/build.py +++ b/concore_cli/commands/build.py @@ -89,7 +89,13 @@ def _write_docker_compose(output_path): if not services: return None - compose_lines = ["networks:", " concore-net:", " driver: bridge", "", "services:"] + compose_lines = [ + "networks:", + " concore-net:", + " driver: bridge", + "", + "services:", + ] prev_service_name = None named_volumes = set() @@ -110,20 +116,20 @@ def _write_docker_compose(output_path): compose_lines.append(" restart: on-failure") compose_lines.append(" networks:") compose_lines.append(" - concore-net") - + # Chain services sequentially to prevent ZMQ race conditions if prev_service_name: compose_lines.append(" depends_on:") compose_lines.append(f" - {prev_service_name}") - + if service["volumes"]: compose_lines.append(" volumes:") for volume_spec in service["volumes"]: compose_lines.append(f" - {_yaml_quote(volume_spec)}") - part1 = volume_spec.split(':')[0] + part1 = volume_spec.split(":")[0] if re.match(r"^[a-zA-Z0-9_-]+$", part1): named_volumes.add(part1) - + prev_service_name = service_name if named_volumes: @@ -219,9 +225,15 @@ def build_workflow( if s_path.exists(): content = s_path.read_text(encoding="utf-8") if s_name == "build": - content = content.replace("docker build", "cp ../src/requirements.txt .\ndocker build") + content = content.replace( + "docker build", + "cp ../src/requirements.txt .\ndocker build", + ) else: - content = content.replace("docker build", "copy ..\\src\\requirements.txt .\n docker build") + content = content.replace( + "docker build", + "copy ..\\src\\requirements.txt .\n docker build", + ) s_path.write_text(content, encoding="utf-8") if result.stdout: From 0aa594b942e86d34936581ebb24b3826ac752f79 Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Tue, 14 Apr 2026 11:00:29 +0530 Subject: [PATCH 3/4] apply review suggestions for compose stability and test coverage --- concore_cli/commands/build.py | 33 ++++++++++++++------------------- tests/test_cli.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/concore_cli/commands/build.py b/concore_cli/commands/build.py index d94f4c5..a33c092 100644 --- a/concore_cli/commands/build.py +++ b/concore_cli/commands/build.py @@ -97,7 +97,6 @@ def _write_docker_compose(output_path): "services:", ] - prev_service_name = None named_volumes = set() for index, service in enumerate(services, start=1): service_name = re.sub(r"[^A-Za-z0-9_.-]", "-", service["container_name"]).strip( @@ -105,10 +104,10 @@ def _write_docker_compose(output_path): ) if not service_name: service_name = f"service-{index}" - elif not service_name[0].isalnum(): + elif not service_name[0].isalpha(): service_name = f"service-{service_name}" - compose_lines.append(f" {service_name}:") + compose_lines.append(f" {_yaml_quote(service_name)}:") compose_lines.append(f" image: {_yaml_quote(service['image'])}") compose_lines.append( f" container_name: {_yaml_quote(service['container_name'])}" @@ -117,11 +116,6 @@ def _write_docker_compose(output_path): compose_lines.append(" networks:") compose_lines.append(" - concore-net") - # Chain services sequentially to prevent ZMQ race conditions - if prev_service_name: - compose_lines.append(" depends_on:") - compose_lines.append(f" - {prev_service_name}") - if service["volumes"]: compose_lines.append(" volumes:") for volume_spec in service["volumes"]: @@ -130,7 +124,6 @@ def _write_docker_compose(output_path): if re.match(r"^[a-zA-Z0-9_-]+$", part1): named_volumes.add(part1) - prev_service_name = service_name if named_volumes: compose_lines.append("") @@ -219,22 +212,24 @@ def build_workflow( elif (output_path / "src").exists(): req_dest.touch() - # Append requirement copying to generated scripts + # Append requirement copying to generated scripts robustly for s_name in ["build", "build.bat"]: s_path = output_path / s_name if s_path.exists(): content = s_path.read_text(encoding="utf-8") + lines = content.splitlines() if s_name == "build": - content = content.replace( - "docker build", - "cp ../src/requirements.txt .\ndocker build", - ) + insert_line = "cp ../src/requirements.txt ." else: - content = content.replace( - "docker build", - "copy ..\\src\\requirements.txt .\n docker build", - ) - s_path.write_text(content, encoding="utf-8") + insert_line = "copy ..\\src\\requirements.txt ." + + new_lines = [] + for line in lines: + if " build" in line and "-t " in line: + new_lines.append(insert_line) + new_lines.append(line) + + s_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8") if result.stdout: console.print(result.stdout) diff --git a/tests/test_cli.py b/tests/test_cli.py index a36a66a..d47f9d0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -383,6 +383,10 @@ def test_build_command_docker_compose_single_node(self): self.assertIn("services:", compose_content) self.assertIn("container_name: 'N1'", compose_content) self.assertIn("image: 'docker-script'", compose_content) + self.assertIn("networks:", compose_content) + self.assertIn("concore-net:", compose_content) + self.assertIn("- concore-net", compose_content) + self.assertIn("restart: on-failure", compose_content) metadata = json.loads(Path("out/STUDY.json").read_text()) self.assertIn("docker-compose.yml", metadata["checksums"]) @@ -431,6 +435,35 @@ def test_build_command_docker_compose_multi_node(self): self.assertIn("container_name: 'C'", compose_content) self.assertIn("image: 'docker-common'", compose_content) + def test_build_command_docker_requirements_injection(self): + with self.runner.isolated_filesystem(temp_dir=self.temp_dir): + result = self.runner.invoke(cli, ["init", "test-project"]) + self.assertEqual(result.exit_code, 0) + + Path("requirements.txt").write_text("pandas==1.0.0") + + result = self.runner.invoke( + cli, + [ + "build", + "test-project/workflow.graphml", + "--source", + "test-project/src", + "--output", + "out", + "--type", + "docker", + ], + ) + self.assertEqual(result.exit_code, 0) + + req_path = Path("out/src/requirements.txt") + self.assertTrue(req_path.exists()) + self.assertEqual(req_path.read_text(), "pandas==1.0.0") + + build_script = Path("out/build").read_text() + self.assertIn("cp ../src/requirements.txt .", build_script) + def test_build_command_shared_source_specialization_merges_edge_params(self): with self.runner.isolated_filesystem(temp_dir=self.temp_dir): Path("src").mkdir() From ff3c57453bc280aa8a430dc3a45e68647ba778eb Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Tue, 14 Apr 2026 11:13:04 +0530 Subject: [PATCH 4/4] style: final ruff format normalization --- concore_cli/commands/build.py | 1 - 1 file changed, 1 deletion(-) diff --git a/concore_cli/commands/build.py b/concore_cli/commands/build.py index a33c092..ee21470 100644 --- a/concore_cli/commands/build.py +++ b/concore_cli/commands/build.py @@ -124,7 +124,6 @@ def _write_docker_compose(output_path): if re.match(r"^[a-zA-Z0-9_-]+$", part1): named_volumes.add(part1) - if named_volumes: compose_lines.append("") compose_lines.append("volumes:")