Skip to content

Commit a850269

Browse files
authored
Merge pull request #545 from GREENRAT-K405/fix/network-docker-compose
stabilize docker compose orchestration and networking logic
2 parents 530324d + ff3c574 commit a850269

2 files changed

Lines changed: 86 additions & 3 deletions

File tree

concore_cli/commands/build.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import shlex
33
import subprocess
44
import sys
5+
import shutil
56
from pathlib import Path
67
from rich.panel import Panel
78
from rich.progress import Progress, SpinnerColumn, TextColumn
@@ -88,26 +89,46 @@ def _write_docker_compose(output_path):
8889
if not services:
8990
return None
9091

91-
compose_lines = ["services:"]
92+
compose_lines = [
93+
"networks:",
94+
" concore-net:",
95+
" driver: bridge",
96+
"",
97+
"services:",
98+
]
9299

100+
named_volumes = set()
93101
for index, service in enumerate(services, start=1):
94102
service_name = re.sub(r"[^A-Za-z0-9_.-]", "-", service["container_name"]).strip(
95103
"-."
96104
)
97105
if not service_name:
98106
service_name = f"service-{index}"
99-
elif not service_name[0].isalnum():
107+
elif not service_name[0].isalpha():
100108
service_name = f"service-{service_name}"
101109

102-
compose_lines.append(f" {service_name}:")
110+
compose_lines.append(f" {_yaml_quote(service_name)}:")
103111
compose_lines.append(f" image: {_yaml_quote(service['image'])}")
104112
compose_lines.append(
105113
f" container_name: {_yaml_quote(service['container_name'])}"
106114
)
115+
compose_lines.append(" restart: on-failure")
116+
compose_lines.append(" networks:")
117+
compose_lines.append(" - concore-net")
118+
107119
if service["volumes"]:
108120
compose_lines.append(" volumes:")
109121
for volume_spec in service["volumes"]:
110122
compose_lines.append(f" - {_yaml_quote(volume_spec)}")
123+
part1 = volume_spec.split(":")[0]
124+
if re.match(r"^[a-zA-Z0-9_-]+$", part1):
125+
named_volumes.add(part1)
126+
127+
if named_volumes:
128+
compose_lines.append("")
129+
compose_lines.append("volumes:")
130+
for v in sorted(named_volumes):
131+
compose_lines.append(f" {v}:")
111132

112133
compose_lines.append("")
113134
compose_path = output_path / "docker-compose.yml"
@@ -180,6 +201,35 @@ def build_workflow(
180201

181202
progress.update(task, completed=True)
182203

204+
if exec_type == "docker":
205+
req_src = Path.cwd() / "requirements.txt"
206+
if not req_src.exists():
207+
req_src = source_path / "requirements.txt"
208+
req_dest = output_path / "src" / "requirements.txt"
209+
if req_src.exists() and (output_path / "src").exists():
210+
shutil.copy2(req_src, req_dest)
211+
elif (output_path / "src").exists():
212+
req_dest.touch()
213+
214+
# Append requirement copying to generated scripts robustly
215+
for s_name in ["build", "build.bat"]:
216+
s_path = output_path / s_name
217+
if s_path.exists():
218+
content = s_path.read_text(encoding="utf-8")
219+
lines = content.splitlines()
220+
if s_name == "build":
221+
insert_line = "cp ../src/requirements.txt ."
222+
else:
223+
insert_line = "copy ..\\src\\requirements.txt ."
224+
225+
new_lines = []
226+
for line in lines:
227+
if " build" in line and "-t " in line:
228+
new_lines.append(insert_line)
229+
new_lines.append(line)
230+
231+
s_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8")
232+
183233
if result.stdout:
184234
console.print(result.stdout)
185235

tests/test_cli.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ def test_build_command_docker_compose_single_node(self):
383383
self.assertIn("services:", compose_content)
384384
self.assertIn("container_name: 'N1'", compose_content)
385385
self.assertIn("image: 'docker-script'", compose_content)
386+
self.assertIn("networks:", compose_content)
387+
self.assertIn("concore-net:", compose_content)
388+
self.assertIn("- concore-net", compose_content)
389+
self.assertIn("restart: on-failure", compose_content)
386390

387391
metadata = json.loads(Path("out/STUDY.json").read_text())
388392
self.assertIn("docker-compose.yml", metadata["checksums"])
@@ -431,6 +435,35 @@ def test_build_command_docker_compose_multi_node(self):
431435
self.assertIn("container_name: 'C'", compose_content)
432436
self.assertIn("image: 'docker-common'", compose_content)
433437

438+
def test_build_command_docker_requirements_injection(self):
439+
with self.runner.isolated_filesystem(temp_dir=self.temp_dir):
440+
result = self.runner.invoke(cli, ["init", "test-project"])
441+
self.assertEqual(result.exit_code, 0)
442+
443+
Path("requirements.txt").write_text("pandas==1.0.0")
444+
445+
result = self.runner.invoke(
446+
cli,
447+
[
448+
"build",
449+
"test-project/workflow.graphml",
450+
"--source",
451+
"test-project/src",
452+
"--output",
453+
"out",
454+
"--type",
455+
"docker",
456+
],
457+
)
458+
self.assertEqual(result.exit_code, 0)
459+
460+
req_path = Path("out/src/requirements.txt")
461+
self.assertTrue(req_path.exists())
462+
self.assertEqual(req_path.read_text(), "pandas==1.0.0")
463+
464+
build_script = Path("out/build").read_text()
465+
self.assertIn("cp ../src/requirements.txt .", build_script)
466+
434467
def test_build_command_shared_source_specialization_merges_edge_params(self):
435468
with self.runner.isolated_filesystem(temp_dir=self.temp_dir):
436469
Path("src").mkdir()

0 commit comments

Comments
 (0)