Merge pull request #1171 from mokibit/fix-git-build-url-context

Fix using git URL as build context
This commit is contained in:
Povilas Kanapickas 2025-03-31 00:26:56 +03:00 committed by GitHub
commit c46ecb226b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 156 additions and 11 deletions

View File

@ -0,0 +1 @@
- Fix using git URL as build context

View File

@ -26,6 +26,7 @@ import signal
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import urllib.parse
from asyncio import Task from asyncio import Task
from enum import Enum from enum import Enum
@ -1663,7 +1664,9 @@ def normalize_service_final(service: dict, project_dir: str) -> dict:
if "build" in service: if "build" in service:
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", ".")
context = os.path.normpath(os.path.join(project_dir, context))
if not is_path_git_url(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"] = {}
service["build"]["context"] = context service["build"]["context"] = context
@ -2501,12 +2504,17 @@ 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):
r = urllib.parse.urlparse(path)
return r.scheme == 'git' or r.path.endswith('.git')
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):
build_desc = cnt["build"] build_desc = cnt["build"]
if not hasattr(build_desc, "items"): if not hasattr(build_desc, "items"):
build_desc = {"context": build_desc} build_desc = {"context": build_desc}
ctx = build_desc.get("context", ".") ctx = build_desc.get("context", ".")
dockerfile = build_desc.get("dockerfile") dockerfile = build_desc.get("dockerfile", "")
dockerfile_inline = build_desc.get("dockerfile_inline") dockerfile_inline = build_desc.get("dockerfile_inline")
if dockerfile_inline is not None: if dockerfile_inline is not None:
dockerfile_inline = str(dockerfile_inline) dockerfile_inline = str(dockerfile_inline)
@ -2524,7 +2532,10 @@ def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=N
if cleanup_callbacks is not None: if cleanup_callbacks is not None:
list.append(cleanup_callbacks, cleanup_temp_dockfile) list.append(cleanup_callbacks, cleanup_temp_dockfile)
else:
build_args = []
if not is_path_git_url(ctx):
if dockerfile: if dockerfile:
dockerfile = os.path.join(ctx, dockerfile) dockerfile = os.path.join(ctx, dockerfile)
else: else:
@ -2540,9 +2551,16 @@ def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=N
dockerfile = os.path.join(ctx, dockerfile) dockerfile = os.path.join(ctx, dockerfile)
if path_exists(dockerfile): if path_exists(dockerfile):
break break
if not path_exists(dockerfile):
raise OSError("Dockerfile not found in " + ctx) if path_exists(dockerfile):
build_args = ["-f", dockerfile, "-t", cnt["image"]] # normalize dockerfile path, as the user could have provided unpredictable file formats
dockerfile = os.path.normpath(os.path.join(ctx, dockerfile))
build_args.extend(["-f", dockerfile])
else:
raise OSError(f"Dockerfile not found in {ctx}")
build_args.extend(["-t", cnt["image"]])
if "platform" in cnt: if "platform" in cnt:
build_args.extend(["--platform", cnt["platform"]]) build_args.extend(["--platform", cnt["platform"]])
for secret in build_desc.get("secrets", []): for secret in build_desc.get("secrets", []):

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,9 @@
version: "3"
services:
test_context:
build:
context: https://github.com/mokibit/test-git-url-as-context.git
image: test-git-url-as-context
test_context_inline:
build: https://github.com/mokibit/test-git-url-as-context.git
image: test-git-url-as-context-inline

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0
import os
from parameterized import parameterized
import unittest
from tests.integration.test_utils import podman_compose_path
from tests.integration.test_utils import test_path
from tests.integration.test_utils import RunSubprocessMixin
def compose_yaml_path():
""" "Returns the path to the compose file used for this test module"""
base_path = os.path.join(test_path(), "build/git_url_context")
return os.path.join(base_path, "docker-compose.yml")
class TestComposeBuildGitUrlAsContext(unittest.TestCase, RunSubprocessMixin):
@parameterized.expand([
("git_url_context_test_context_1", "data_1.txt", b'test1\r\n'),
("git_url_context_test_context_1", "data_2.txt", b'test2\r\n'),
("git_url_context_test_context_inline_1", "data_1.txt", b'test1\r\n'),
("git_url_context_test_context_inline_1", "data_2.txt", b'test2\r\n'),
])
def test_build_git_url_as_context(self, container_name, file_name, output):
# test if container can access specific files from git repository when git url is used as
# a build context
try:
out, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(),
"up",
"-d",
])
out, _ = self.run_subprocess_assert_returncode([
"podman",
"exec",
"-ti",
f"{container_name}",
"sh",
"-c",
f"cat {file_name}",
])
self.assertEqual(out, output)
finally:
out, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(),
"down",
"-t",
"0",
])

View File

@ -46,7 +46,7 @@ class TestContainerToBuildArgs(unittest.TestCase):
args, args,
[ [
'-f', '-f',
'./Containerfile', 'Containerfile',
'-t', '-t',
'new-image', 'new-image',
'--no-cache', '--no-cache',
@ -67,7 +67,7 @@ class TestContainerToBuildArgs(unittest.TestCase):
args, args,
[ [
'-f', '-f',
'./Containerfile', 'Containerfile',
'-t', '-t',
'new-image', 'new-image',
'--platform', '--platform',
@ -90,7 +90,7 @@ class TestContainerToBuildArgs(unittest.TestCase):
args, args,
[ [
'-f', '-f',
'./Containerfile', 'Containerfile',
'-t', '-t',
'new-image', 'new-image',
'-t', '-t',
@ -115,7 +115,7 @@ class TestContainerToBuildArgs(unittest.TestCase):
args, args,
[ [
'-f', '-f',
'./Containerfile', 'Containerfile',
'-t', '-t',
'new-image', 'new-image',
'--label', '--label',
@ -141,7 +141,7 @@ class TestContainerToBuildArgs(unittest.TestCase):
args, args,
[ [
'-f', '-f',
'./Containerfile', 'Containerfile',
'-t', '-t',
'new-image', 'new-image',
'--no-cache', '--no-cache',
@ -180,3 +180,42 @@ class TestContainerToBuildArgs(unittest.TestCase):
for c in cleanup_callbacks: for c in cleanup_callbacks:
c() c()
self.assertFalse(os.path.exists(temp_dockerfile)) self.assertFalse(os.path.exists(temp_dockerfile))
def test_context_git_url(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['build']['context'] = "https://github.com/test_repo.git"
args = get_minimal_args()
args = container_to_build_args(c, cnt, args, lambda path: False)
self.assertEqual(
args,
[
'-t',
'new-image',
'--no-cache',
'--pull-always',
'https://github.com/test_repo.git',
],
)
def test_context_invalid_git_url_git_is_not_prefix(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['build']['context'] = "not_prefix://github.com/test_repo"
args = get_minimal_args()
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,22 @@
# 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)