7 Commits
main ... 1.4.x

Author SHA1 Message Date
ed1e8650db Merge pull request #1244 from mokibit/1.4-backport-1243
1.4.x backport: Fix dockerfile definition if directory name ends with ".git"
2025-06-17 23:20:06 +03:00
8e2cd2bab8 Fix dockerfile definition if directory name ends with ".git"
After changes in 92f0a8583a, the
dockerfile parameter is igored if the (local) work directory's name ends
in `.git`.
This commit fixes the regression and adds more tests.

Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2025-06-17 23:10:17 +03:00
b37076bc5e Merge pull request #1233 from p12tic/release
Release notes for 1.4.1
2025-06-05 17:15:24 +03:00
3db72df49d Release notes for 1.4.1
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2025-06-05 17:09:50 +03:00
189c086d5b Merge pull request #1232 from p12tic/1.4-backport-1231
[1.4 backports] Fix relative host path resolution for volume bind mount source
2025-06-05 17:08:16 +03:00
32b3d26ab1 Add newsfragment
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2025-06-05 17:00:37 +03:00
35a66f5a8b Fix relative host path resolution for volume bind mount source
e03d675b9b broke relative host path
resolution by deleting os.chdir(). After this commit current working
directory is not relevant anymore.

Fixes e03d675b9b.

Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2025-06-05 16:58:42 +03:00
8 changed files with 183 additions and 45 deletions

7
docs/Changelog-1.4.1.md Normal file
View File

@ -0,0 +1,7 @@
Version 1.4.1 (2025-06-05)
==========================
Bug fixes
---------
- Fixed relative host path resolution for volume bind mount source

View File

@ -0,0 +1 @@
Fixed regression of dockerfile definition if working directory name ends with ".git".

View File

@ -395,6 +395,7 @@ async def assert_volume(compose, mount_dict):
os.makedirs(mount_src, exist_ok=True)
except OSError:
pass
mount_dict["source"] = mount_src
return
if mount_dict["type"] != "volume" or not vol or not vol.get("name"):
return
@ -1741,7 +1742,7 @@ def normalize_service_final(service: dict, project_dir: str) -> dict:
build = service["build"]
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))
if not isinstance(service["build"], dict):
service["build"] = {}
@ -2627,9 +2628,18 @@ async def compose_push(compose, args):
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)
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):
@ -2657,8 +2667,8 @@ def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=N
list.append(cleanup_callbacks, cleanup_temp_dockfile)
build_args = []
if not is_path_git_url(ctx):
# if givent context was not recognized as git url, try joining paths to get a file locally
if not is_context_git_url(ctx):
if dockerfile:
dockerfile = os.path.join(ctx, dockerfile)
else:

View File

@ -36,8 +36,9 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
"selinux_container1_1",
])
inspect_out = json.loads(out)
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", {})
self.assertIn('./host_test_text.txt:/test_text.txt:z', create_command_list)
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", [])
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([
"podman",
@ -45,8 +46,9 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
"selinux_container2_1",
])
inspect_out = json.loads(out)
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", {})
self.assertIn('./host_test_text.txt:/test_text.txt', create_command_list)
create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", [])
host_path = os.path.join(test_path(), "selinux", "host_test_text.txt")
self.assertIn(f'{host_path}:/test_text.txt', create_command_list)
finally:
out, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),

View File

@ -529,10 +529,24 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
)
@parameterized.expand([
(False, "z", ["--mount", "type=bind,source=./foo,destination=/mnt,z"]),
(False, "Z", ["--mount", "type=bind,source=./foo,destination=/mnt,Z"]),
(True, "z", ["-v", "./foo:/mnt:z"]),
(True, "Z", ["-v", "./foo:/mnt:Z"]),
(
False,
"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):
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([
("not_compat", False, "test_project_name", "test_project_name_network1"),
("compat_no_dash", True, "test_project_name", "test_project_name_network1"),

View File

@ -209,13 +209,3 @@ class TestContainerToBuildArgs(unittest.TestCase):
with self.assertRaises(OSError):
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)

View 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)

View File

@ -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)