mirror of
https://github.com/containers/podman-compose.git
synced 2025-07-01 21:20:18 +02:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
ed1e8650db | |||
8e2cd2bab8 | |||
b37076bc5e | |||
3db72df49d | |||
189c086d5b | |||
32b3d26ab1 | |||
35a66f5a8b |
7
docs/Changelog-1.4.1.md
Normal file
7
docs/Changelog-1.4.1.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Version 1.4.1 (2025-06-05)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- Fixed relative host path resolution for volume bind mount source
|
@ -0,0 +1 @@
|
|||||||
|
Fixed regression of dockerfile definition if working directory name ends with ".git".
|
@ -395,6 +395,7 @@ async def assert_volume(compose, mount_dict):
|
|||||||
os.makedirs(mount_src, exist_ok=True)
|
os.makedirs(mount_src, exist_ok=True)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
mount_dict["source"] = mount_src
|
||||||
return
|
return
|
||||||
if mount_dict["type"] != "volume" or not vol or not vol.get("name"):
|
if mount_dict["type"] != "volume" or not vol or not vol.get("name"):
|
||||||
return
|
return
|
||||||
@ -1741,7 +1742,7 @@ def normalize_service_final(service: dict, project_dir: str) -> dict:
|
|||||||
build = service["build"]
|
build = service["build"]
|
||||||
context = build if isinstance(build, str) else build.get("context", ".")
|
context = build if isinstance(build, str) else build.get("context", ".")
|
||||||
|
|
||||||
if not is_path_git_url(context):
|
if not is_context_git_url(context):
|
||||||
context = os.path.normpath(os.path.join(project_dir, context))
|
context = os.path.normpath(os.path.join(project_dir, context))
|
||||||
if not isinstance(service["build"], dict):
|
if not isinstance(service["build"], dict):
|
||||||
service["build"] = {}
|
service["build"] = {}
|
||||||
@ -2627,9 +2628,18 @@ async def compose_push(compose, args):
|
|||||||
await compose.podman.run([], "push", [cnt["image"]])
|
await compose.podman.run([], "push", [cnt["image"]])
|
||||||
|
|
||||||
|
|
||||||
def is_path_git_url(path):
|
def is_context_git_url(path: str) -> bool:
|
||||||
r = urllib.parse.urlparse(path)
|
r = urllib.parse.urlparse(path)
|
||||||
return r.scheme == 'git' or r.path.endswith('.git')
|
if r.scheme in ('git', 'http', 'https', 'ssh', 'file', 'rsync'):
|
||||||
|
return True
|
||||||
|
# URL contains a ":" character, a hint of a valid URL
|
||||||
|
if r.scheme != "" and r.netloc == "" and r.path != "":
|
||||||
|
return True
|
||||||
|
if r.scheme == "": # tweak path URL to get username from url parser
|
||||||
|
r = urllib.parse.urlparse("ssh://" + path)
|
||||||
|
if r.username is not None and r.username != "":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=None):
|
def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=None):
|
||||||
@ -2657,8 +2667,8 @@ def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=N
|
|||||||
list.append(cleanup_callbacks, cleanup_temp_dockfile)
|
list.append(cleanup_callbacks, cleanup_temp_dockfile)
|
||||||
|
|
||||||
build_args = []
|
build_args = []
|
||||||
|
# if givent context was not recognized as git url, try joining paths to get a file locally
|
||||||
if not is_path_git_url(ctx):
|
if not is_context_git_url(ctx):
|
||||||
if dockerfile:
|
if dockerfile:
|
||||||
dockerfile = os.path.join(ctx, dockerfile)
|
dockerfile = os.path.join(ctx, dockerfile)
|
||||||
else:
|
else:
|
||||||
|
@ -36,8 +36,9 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
|
|||||||
"selinux_container1_1",
|
"selinux_container1_1",
|
||||||
])
|
])
|
||||||
inspect_out = json.loads(out)
|
inspect_out = json.loads(out)
|
||||||
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", {})
|
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", [])
|
||||||
self.assertIn('./host_test_text.txt:/test_text.txt:z', create_command_list)
|
host_path = os.path.join(test_path(), "selinux", "host_test_text.txt")
|
||||||
|
self.assertIn(f'{host_path}:/test_text.txt:z', create_command_list)
|
||||||
|
|
||||||
out, _ = self.run_subprocess_assert_returncode([
|
out, _ = self.run_subprocess_assert_returncode([
|
||||||
"podman",
|
"podman",
|
||||||
@ -45,8 +46,9 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
|
|||||||
"selinux_container2_1",
|
"selinux_container2_1",
|
||||||
])
|
])
|
||||||
inspect_out = json.loads(out)
|
inspect_out = json.loads(out)
|
||||||
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", {})
|
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", [])
|
||||||
self.assertIn('./host_test_text.txt:/test_text.txt', create_command_list)
|
host_path = os.path.join(test_path(), "selinux", "host_test_text.txt")
|
||||||
|
self.assertIn(f'{host_path}:/test_text.txt', create_command_list)
|
||||||
finally:
|
finally:
|
||||||
out, _ = self.run_subprocess_assert_returncode([
|
out, _ = self.run_subprocess_assert_returncode([
|
||||||
podman_compose_path(),
|
podman_compose_path(),
|
||||||
|
@ -529,10 +529,24 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
(False, "z", ["--mount", "type=bind,source=./foo,destination=/mnt,z"]),
|
(
|
||||||
(False, "Z", ["--mount", "type=bind,source=./foo,destination=/mnt,Z"]),
|
False,
|
||||||
(True, "z", ["-v", "./foo:/mnt:z"]),
|
"z",
|
||||||
(True, "Z", ["-v", "./foo:/mnt:Z"]),
|
[
|
||||||
|
"--mount",
|
||||||
|
f"type=bind,source={get_test_file_path('test_dirname/foo')},destination=/mnt,z",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
False,
|
||||||
|
"Z",
|
||||||
|
[
|
||||||
|
"--mount",
|
||||||
|
f"type=bind,source={get_test_file_path('test_dirname/foo')},destination=/mnt,Z",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(True, "z", ["-v", f"{get_test_file_path('test_dirname/foo')}:/mnt:z"]),
|
||||||
|
(True, "Z", ["-v", f"{get_test_file_path('test_dirname/foo')}:/mnt:Z"]),
|
||||||
])
|
])
|
||||||
async def test_selinux_volume(self, prefer_volume, selinux_type, expected_additional_args):
|
async def test_selinux_volume(self, prefer_volume, selinux_type, expected_additional_args):
|
||||||
c = create_compose_mock()
|
c = create_compose_mock()
|
||||||
@ -567,6 +581,62 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
(
|
||||||
|
"absolute_path",
|
||||||
|
get_test_file_path('test_dirname/foo'),
|
||||||
|
[
|
||||||
|
"--mount",
|
||||||
|
f"type=bind,source={get_test_file_path('test_dirname/foo')},destination=/mnt",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"relative_path",
|
||||||
|
'./foo',
|
||||||
|
[
|
||||||
|
"--mount",
|
||||||
|
f"type=bind,source={get_test_file_path('test_dirname/foo')},destination=/mnt",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"home_dir",
|
||||||
|
'~/test_dirname/foo',
|
||||||
|
[
|
||||||
|
"--mount",
|
||||||
|
f"type=bind,source={os.path.expanduser('~/test_dirname/foo')},destination=/mnt",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
async def test_volumes_bind_mount_source(
|
||||||
|
self, test_name: str, mount_source: str, expected_additional_args: list
|
||||||
|
) -> None:
|
||||||
|
c = create_compose_mock()
|
||||||
|
cnt = get_minimal_container()
|
||||||
|
|
||||||
|
# This is supposed to happen during `_parse_compose_file`
|
||||||
|
# but that is probably getting skipped during testing
|
||||||
|
cnt["_service"] = cnt["service_name"]
|
||||||
|
|
||||||
|
cnt["volumes"] = [
|
||||||
|
{
|
||||||
|
"type": "bind",
|
||||||
|
"source": f"{mount_source}",
|
||||||
|
"target": "/mnt",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
args = await container_to_args(c, cnt)
|
||||||
|
self.assertEqual(
|
||||||
|
args,
|
||||||
|
[
|
||||||
|
"--name=project_name_service_name1",
|
||||||
|
"-d",
|
||||||
|
*expected_additional_args,
|
||||||
|
"--network=bridge:alias=service_name",
|
||||||
|
"busybox",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
("not_compat", False, "test_project_name", "test_project_name_network1"),
|
("not_compat", False, "test_project_name", "test_project_name_network1"),
|
||||||
("compat_no_dash", True, "test_project_name", "test_project_name_network1"),
|
("compat_no_dash", True, "test_project_name", "test_project_name_network1"),
|
||||||
|
@ -209,13 +209,3 @@ class TestContainerToBuildArgs(unittest.TestCase):
|
|||||||
|
|
||||||
with self.assertRaises(OSError):
|
with self.assertRaises(OSError):
|
||||||
container_to_build_args(c, cnt, args, lambda path: False)
|
container_to_build_args(c, cnt, args, lambda path: False)
|
||||||
|
|
||||||
def test_context_invalid_git_url_git_is_not_suffix(self):
|
|
||||||
c = create_compose_mock()
|
|
||||||
|
|
||||||
cnt = get_minimal_container()
|
|
||||||
cnt['build']['context'] = "https://github.com/test_repo.git/not_suffix"
|
|
||||||
args = get_minimal_args()
|
|
||||||
|
|
||||||
with self.assertRaises(OSError):
|
|
||||||
container_to_build_args(c, cnt, args, lambda path: False)
|
|
||||||
|
80
tests/unit/test_is_context_git_url.py
Normal file
80
tests/unit/test_is_context_git_url.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from parameterized import parameterized
|
||||||
|
|
||||||
|
from podman_compose import is_context_git_url
|
||||||
|
|
||||||
|
|
||||||
|
class TestIsContextGitUrl(unittest.TestCase):
|
||||||
|
@parameterized.expand([
|
||||||
|
("with_url_fragment", "http://host.xz/path/to/repo.git#fragment", True),
|
||||||
|
("suffix_and_prefix", "git://host.xz/path/to/repo.git", True),
|
||||||
|
("empty_url_path", "http://#fragment", True),
|
||||||
|
("no_prefix", "http://host.xz/path/to/repo", True),
|
||||||
|
("wrong_prefix_git", "gitt://host.xz/path/to/repo", False),
|
||||||
|
("wrong_prefix_http", "htt://host.xz/path/to/repo.git", False),
|
||||||
|
("user_path_ending_with_git", "path/to/workdir.git", False),
|
||||||
|
("", "/path/to/workdir.git", False),
|
||||||
|
("", "/path/to:workdir.git", False),
|
||||||
|
("", "/path/to@workdir.git", False),
|
||||||
|
("", "~/path/to/workdir.git", False),
|
||||||
|
# many of possible ways git url can look like
|
||||||
|
("", "http://example.com/my-project.git", True),
|
||||||
|
("", "file:///absolute/path/to/my-project.git", True),
|
||||||
|
("", "ssh:user@example.com:my-project", True),
|
||||||
|
("", "git@github.com:user/project.git", True),
|
||||||
|
("", "https://github.com/user/project.git", True),
|
||||||
|
("", "http://github.com/user/project.git", True),
|
||||||
|
("", "git@192.168.101.127:user/project.git", True),
|
||||||
|
("", "https://192.168.101.127/user/project.git", True),
|
||||||
|
("", "http://192.168.101.127/user/project.git", True),
|
||||||
|
("", "ssh://user@host.xz:port/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz:port/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/~user/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz/~user/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/~/path/to/repo.git", True),
|
||||||
|
("", "ssh://host.xz/~/path/to/repo.git", True),
|
||||||
|
("", "git://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "git://host.xz/~user/path/to/repo.git/", True),
|
||||||
|
("", "http://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "https://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "git@custom-gitlab:my-group/myrepo.git", True),
|
||||||
|
("", "ssh://user@host.xz:port/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz:port/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/~user/path/to/repo.git/", True),
|
||||||
|
("", "ssh://host.xz/~user/path/to/repo.git/", True),
|
||||||
|
("", "ssh://user@host.xz/~/path/to/repo.git", True),
|
||||||
|
("", "ssh://host.xz/~/path/to/repo.git", True),
|
||||||
|
("", "git://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "git://host.xz/~user/path/to/repo.git/", True),
|
||||||
|
("", "http://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "https://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "ssh:user@example.com:my-project", True),
|
||||||
|
("", "user@host.xz:/path/to/repo.git/", True),
|
||||||
|
("", "host.xz:/path/to/repo.git/", True),
|
||||||
|
("", "host:path/to/repo.git", True),
|
||||||
|
("", "host:path/to/repo", True),
|
||||||
|
("", "user@host.xz:~user/path/to/repo.git/", True),
|
||||||
|
("", "host.xz:~user/path/to/repo.git/", True),
|
||||||
|
("", "user@host.xz:path/to/repo.git", True),
|
||||||
|
("", "user@path/to/repo", True),
|
||||||
|
("", "host.xz:path/to/repo.git", True),
|
||||||
|
("", "rsync://host.xz/path/to/repo.git/", True),
|
||||||
|
("", "file:///path/to/repo.git/", True),
|
||||||
|
("", "file://~/path/to/repo.git/", True),
|
||||||
|
("", "github.com:containers/podman-compose.git", True),
|
||||||
|
("", "github:containers/podman-compose.git", True),
|
||||||
|
("", "https://github.com/test_repo.git/git_not_suffix", True),
|
||||||
|
])
|
||||||
|
def test_is_path_git_url(self, test_name: str, path: str, result: bool) -> None:
|
||||||
|
self.assertEqual(is_context_git_url(path), result)
|
@ -1,22 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from parameterized import parameterized
|
|
||||||
|
|
||||||
from podman_compose import is_path_git_url
|
|
||||||
|
|
||||||
|
|
||||||
class TestIsPathGitUrl(unittest.TestCase):
|
|
||||||
@parameterized.expand([
|
|
||||||
("prefix_git", "git://host.xz/path/to/repo", True),
|
|
||||||
("prefix_almost_git", "gitt://host.xz/path/to/repo", False),
|
|
||||||
("prefix_wrong", "http://host.xz/path/to/repo", False),
|
|
||||||
("suffix_git", "http://host.xz/path/to/repo.git", True),
|
|
||||||
("suffix_wrong", "http://host.xz/path/to/repo", False),
|
|
||||||
("suffix_with_url_fragment", "http://host.xz/path/to/repo.git#fragment", True),
|
|
||||||
("suffix_and_prefix", "git://host.xz/path/to/repo.git", True),
|
|
||||||
("empty_url_path", "http://#fragment", False),
|
|
||||||
])
|
|
||||||
def test_is_path_git_url(self, test_name, path, result):
|
|
||||||
self.assertEqual(is_path_git_url(path), result)
|
|
Reference in New Issue
Block a user