Skip to content

Commit f2dba8b

Browse files
committed
fix(env): Remove Windows-incompatible processing logic for -v
We just pass the arguments straight to `docker run`, with no extra validation, instead. This means we also have to duplicate the test to test separately for `-v` and `-m` options.
1 parent 45bbd94 commit f2dba8b

File tree

2 files changed

+100
-110
lines changed

2 files changed

+100
-110
lines changed

src/dda/env/dev/types/linux_container.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ class LinuxContainerConfig(DeveloperEnvironmentConfig):
8989
> To mount a volume or use other options, use the more explicit `-m/--mount` option instead.
9090
"""
9191
),
92-
"callback": __validate_extra_volume_specs,
9392
}
9493
),
9594
] = msgspec.field(default_factory=list)
@@ -209,6 +208,9 @@ def start(self) -> None:
209208
for volume in self.extra_mounts:
210209
command.extend(("--mount", volume.as_csv()))
211210

211+
for volume_spec in self.config.extra_volume_specs:
212+
command.extend(("--volume", volume_spec))
213+
212214
command.append(self.config.image)
213215

214216
env = EnvVars()
@@ -429,13 +431,6 @@ def extra_mounts(self) -> list[Mount]:
429431
from dda.utils.container.model import Mount
430432

431433
mounts = []
432-
for spec in self.config.extra_volume_specs:
433-
src, dst, *extra = spec.split(":", 2)
434-
read_only = "ro" in extra
435-
436-
# Volume specs are always bind mounts.
437-
mounts.append(Mount(type="bind", path=dst, source=src, read_only=read_only))
438-
439434
for spec in self.config.extra_mount_specs:
440435
type_spec, src_spec, dst_spec, *flag_specs = spec.split(",")
441436
mount_type: Literal["bind", "volume"] = "bind" if type_spec == "type=bind" else "volume"
@@ -497,28 +492,6 @@ def __validate_mount_src_dst(mount_type: Literal["bind", "volume"], src: str, ds
497492
raise BadParameter(msg)
498493

499494

500-
def __validate_extra_volume_specs(_ctx: Context, _param: Option, value: list[str]) -> list[str]:
501-
from click import BadParameter
502-
503-
if not value:
504-
return value
505-
506-
for spec in value:
507-
try:
508-
src, dst, *extra = spec.split(":", 2)
509-
except ValueError as e:
510-
msg = f"Invalid volume spec: {spec}. Expected format: <source>:<destination>[:<flag>]"
511-
raise BadParameter(msg) from e
512-
flag = extra[0] if extra else None
513-
if flag not in {None, "readonly", "ro"}:
514-
msg = f"Invalid volume flag: {flag}. Only the `ro` flag is supported."
515-
raise BadParameter(msg)
516-
# Volume specs are always bind mounts.
517-
__validate_mount_src_dst("bind", src, dst)
518-
519-
return value
520-
521-
522495
def __validate_extra_mount_specs(_ctx: Context, _param: Option, value: list[str]) -> list[str]:
523496
from click import BadParameter
524497

tests/env/dev/types/test_linux_container.py

Lines changed: 97 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -619,54 +619,117 @@ def test_multiple_clones(self, dda, helpers, mocker, temp_dir, host_user_args):
619619
]
620620

621621
@pytest.mark.parametrize(
622-
("volume_mount_specs", "result_mounts"),
622+
("volume_specs"),
623623
[
624-
# Case 1: -v, Single absolute path
625-
pytest.param(
626-
["-v", "/tmp/mounted:/tmp/mounted_abs"],
627-
[Mount(type="bind", path="/tmp/mounted_abs", source="/tmp/mounted", read_only=False)],
628-
id="-v, single_absolute_path",
629-
),
630-
# Case 2: -v, Single relative path
631-
pytest.param(
632-
["-v", "./mounted:/tmp/mounted_rel"],
633-
[Mount(type="bind", path="/tmp/mounted_rel", source="./mounted", read_only=False)],
634-
id="-v, single_relative_path",
635-
),
636-
# Case 3: -v, --volume, Multiple mounts
637-
pytest.param(
638-
["-v", "/tmp/mounted:/tmp/mounted_abs", "--volume", "./mounted:/tmp/mounted_rel"],
639-
[
640-
Mount(type="bind", path="/tmp/mounted_abs", source="/tmp/mounted", read_only=False),
641-
Mount(type="bind", path="/tmp/mounted_rel", source="./mounted", read_only=False),
642-
],
643-
id="-v, --volume, multiple_mounts",
624+
# Case 1: Single -v
625+
["-v", "/tmp/mounted:/tmp/mounted_abs"],
626+
# Case 2: Single --volume
627+
["--volume", "/tmp/mounted:/tmp/mounted_abs"],
628+
# Case 3: -v and --volume
629+
["-v", "/tmp/mounted:/tmp/mounted_abs", "--volume", "./mounted:/tmp/mounted_rel"],
630+
],
631+
)
632+
def test_extra_volume_specs(self, dda, helpers, mocker, temp_dir, host_user_args, volume_specs):
633+
mocker.patch("dda.utils.ssh.write_server_config")
634+
635+
# Disable source and destination validation for the extra mount specs
636+
mocker.patch(
637+
"dda.env.dev.types.linux_container.__validate_mount_src_dst",
638+
return_value=None,
639+
)
640+
shared_dir = temp_dir / "data" / "env" / "dev" / "linux-container" / ".shared"
641+
starship_mount = get_starship_mount(shared_dir)
642+
cache_volumes = get_cache_volumes()
643+
644+
with (
645+
temp_dir.as_cwd(),
646+
helpers.hybrid_patch(
647+
"subprocess.run",
648+
return_values={
649+
# Start command checks the status
650+
1: CompletedProcess([], returncode=0, stdout="{}"),
651+
# Start method checks the status
652+
2: CompletedProcess([], returncode=0, stdout="{}"),
653+
# Capture container run
654+
# Readiness check
655+
4: CompletedProcess([], returncode=0, stdout="Server listening on :: port 22"),
656+
# Repo cloning
657+
5: CompletedProcess([], returncode=0, stdout="{}"),
658+
},
659+
) as calls,
660+
):
661+
result = dda("env", "dev", "start", "--no-pull", "--clone", *volume_specs)
662+
663+
result.check(
664+
exit_code=0,
665+
output=helpers.dedent(
666+
"""
667+
Creating and starting container: dda-linux-container-default
668+
Waiting for container: dda-linux-container-default
669+
Cloning repository: datadog-agent
670+
"""
644671
),
645-
# Case 4: -v, mount with ro flag
646-
pytest.param(
647-
["-v", "/tmp/mounted:/tmp/mounted_abs:ro"],
648-
[
649-
Mount(type="bind", path="/tmp/mounted_abs", source="/tmp/mounted", read_only=True),
650-
],
651-
id="-v, mount_with_ro",
672+
)
673+
assert calls == [
674+
(
675+
(
676+
[
677+
helpers.locate("docker"),
678+
"run",
679+
"--pull",
680+
"never",
681+
"-d",
682+
"--name",
683+
"dda-linux-container-default",
684+
"-p",
685+
"61938:22",
686+
"-p",
687+
"50069:9000",
688+
"-v",
689+
"/var/run/docker.sock:/var/run/docker.sock",
690+
*host_user_args,
691+
"-e",
692+
"DD_SHELL",
693+
"-e",
694+
AppEnvVars.TELEMETRY_API_KEY,
695+
"-e",
696+
AppEnvVars.TELEMETRY_USER_MACHINE_ID,
697+
"-e",
698+
GitEnvVars.AUTHOR_NAME,
699+
"-e",
700+
GitEnvVars.AUTHOR_EMAIL,
701+
*starship_mount,
702+
"-v",
703+
f"{shared_dir / 'shell' / 'zsh' / '.zsh_history'}:/root/.shared/shell/zsh/.zsh_history",
704+
*cache_volumes,
705+
*[(x if x != "-v" else "--volume") for x in volume_specs],
706+
"datadog/agent-dev-env-linux",
707+
],
708+
),
709+
{"encoding": "utf-8", "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "env": mocker.ANY},
652710
),
653-
# Case 5: -m, single bind mount
711+
]
712+
713+
@pytest.mark.parametrize(
714+
("mount_specs", "result_mounts"),
715+
[
716+
# Case 1: -m, single bind mount
654717
pytest.param(
655718
["-m", "type=bind,src=/tmp/mounted,dst=/tmp/mounted_abs"],
656719
[
657720
Mount(type="bind", path="/tmp/mounted_abs", source="/tmp/mounted", read_only=False),
658721
],
659722
id="-m, single_bind_mount",
660723
),
661-
# Case 6: -m, single volume mount
724+
# Case 2: -m, single volume mount
662725
pytest.param(
663726
["-m", "type=volume,src=some-volume,dst=/tmp/mounted_abs"],
664727
[
665728
Mount(type="volume", path="/tmp/mounted_abs", source="some-volume", read_only=False),
666729
],
667730
id="-m, single_volume_mount",
668731
),
669-
# Case 7: -m, mounts with flags
732+
# Case 3: -m, mounts with flags
670733
pytest.param(
671734
[
672735
"-m",
@@ -692,7 +755,7 @@ def test_multiple_clones(self, dda, helpers, mocker, temp_dir, host_user_args):
692755
],
693756
id="-m, mounts_with_flags",
694757
),
695-
# Case 8: -m, --mount, multiple mounts with different syntax
758+
# Case 4: -m, --mount, multiple mounts with different syntax
696759
pytest.param(
697760
[
698761
"-m",
@@ -711,7 +774,7 @@ def test_multiple_clones(self, dda, helpers, mocker, temp_dir, host_user_args):
711774
),
712775
],
713776
)
714-
def test_extra_mounts(self, dda, helpers, mocker, temp_dir, host_user_args, volume_mount_specs, result_mounts):
777+
def test_extra_mounts(self, dda, helpers, mocker, temp_dir, host_user_args, mount_specs, result_mounts):
715778
mocker.patch("dda.utils.ssh.write_server_config")
716779

717780
# Disable source and destination validation for the extra mount specs
@@ -740,14 +803,7 @@ def test_extra_mounts(self, dda, helpers, mocker, temp_dir, host_user_args, volu
740803
},
741804
) as calls,
742805
):
743-
result = dda(
744-
"env",
745-
"dev",
746-
"start",
747-
"--no-pull",
748-
"--clone",
749-
*volume_mount_specs,
750-
)
806+
result = dda("env", "dev", "start", "--no-pull", "--clone", *mount_specs)
751807

752808
result.check(
753809
exit_code=0,
@@ -803,45 +859,6 @@ def test_extra_mounts(self, dda, helpers, mocker, temp_dir, host_user_args, volu
803859
),
804860
]
805861

806-
@pytest.mark.parametrize(
807-
("volume_spec", "error_message"),
808-
[
809-
pytest.param(
810-
"/i/dont/exist:/valid/path",
811-
"Source must be an existing path on the host.",
812-
id="absolute_src_does_not_exist",
813-
),
814-
pytest.param(
815-
"./i/dont/exist:/valid/path",
816-
"Source must be an existing path on the host.",
817-
id="relative_src_does_not_exist",
818-
),
819-
pytest.param(
820-
"./:dir",
821-
"Destination must be an absolute path.",
822-
id="dst_is_not_absolute",
823-
),
824-
pytest.param(
825-
"/tmp:/container:foobar",
826-
"Invalid volume flag: foobar",
827-
id="invalid_arbitrary_flag",
828-
),
829-
pytest.param(
830-
"/tmpcontainer",
831-
"Expected format:",
832-
id="no_colon",
833-
),
834-
],
835-
)
836-
def test_invalid_volume_specs(self, dda, temp_dir, mocker, volume_spec, error_message):
837-
mocker.patch("subprocess.run", return_value=CompletedProcess([], returncode=0, stdout="{}"))
838-
839-
with temp_dir.as_cwd():
840-
result = dda("env", "dev", "start", "-v", volume_spec)
841-
842-
result.check_exit_code(2)
843-
assert error_message in result.output
844-
845862
@pytest.mark.requires_unix # This fails with Windows-style paths
846863
@pytest.mark.parametrize(
847864
("mount_spec", "error_message"),

0 commit comments

Comments
 (0)