Merge branch 'containers:main' into container_scaling_update

This commit is contained in:
Yashodhan Pise 2025-03-22 18:06:39 +05:30 committed by GitHub
commit 04b9982bc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 212 additions and 23 deletions

View File

@ -1,7 +1,7 @@
[MESSAGES CONTROL]
# C0111 missing-docstring: missing-class-docstring, missing-function-docstring, missing-method-docstring, missing-module-docstrin
# consider-using-with: we need it for color formatter pipe
disable=too-many-lines,too-many-branches,too-many-locals,too-many-statements,too-many-arguments,too-many-instance-attributes,fixme,multiple-statements,missing-docstring,line-too-long,consider-using-f-string,consider-using-with,unnecessary-lambda-assignment
disable=too-many-lines,too-many-branches,too-many-locals,too-many-statements,too-many-arguments,too-many-instance-attributes,fixme,multiple-statements,missing-docstring,line-too-long,consider-using-f-string,consider-using-with,unnecessary-lambda-assignment,broad-exception-caught
# allow _ for ignored variables
# allow generic names like a,b,c and i,j,k,l,m,n and x,y,z
# allow k,v for key/value

View File

@ -0,0 +1 @@
- Fixed interpolation of the environment variable **COMPOSE_PROJECT_NAME** when it is set from the top-level **name** value within the Compose file.

View File

@ -0,0 +1 @@
- Fixed project name evaluation order to match the order defined in the [compose spec](https://docs.docker.com/compose/how-tos/project-name/#set-a-project-name).

View File

@ -0,0 +1 @@
- Fixed handling SIGINT when running "up" command to shutdown gracefully

View File

@ -1980,6 +1980,25 @@ class PodmanCompose:
sys.exit(1)
content = normalize(content)
# log(filename, json.dumps(content, indent = 2))
# See also https://docs.docker.com/compose/how-tos/project-name/#set-a-project-name
# **project_name** is initialized to the argument of the `-p` command line flag.
if not project_name:
project_name = self.environ.get("COMPOSE_PROJECT_NAME")
if not project_name:
project_name = content.get("name")
if not project_name:
project_name = dir_basename.lower()
# More strict then actually needed for simplicity:
# podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]*
project_name_normalized = norm_re.sub("", project_name)
if not project_name_normalized:
raise RuntimeError(f"Project name [{project_name}] normalized to empty")
project_name = project_name_normalized
self.project_name = project_name
self.environ.update({"COMPOSE_PROJECT_NAME": self.project_name})
content = rec_subs(content, self.environ)
if isinstance(services := content.get('services'), dict):
for service in services.values():
@ -2012,19 +2031,6 @@ class PodmanCompose:
log.debug(" ** merged:\n%s", json.dumps(compose, indent=2))
# ver = compose.get('version')
if not project_name:
project_name = compose.get("name")
if project_name is None:
# More strict then actually needed for simplicity:
# podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]*
project_name = self.environ.get("COMPOSE_PROJECT_NAME", dir_basename.lower())
project_name = norm_re.sub("", project_name)
if not project_name:
raise RuntimeError(f"Project name [{dir_basename}] normalized to empty")
self.project_name = project_name
self.environ.update({"COMPOSE_PROJECT_NAME": self.project_name})
services = compose.get("services")
if services is None:
services = {}
@ -2629,7 +2635,7 @@ async def compose_build(compose, args):
status = 0
for t in asyncio.as_completed(tasks):
s = await t
if s is not None:
if s is not None and s != 0:
status = s
return status
@ -2794,9 +2800,22 @@ async def compose_up(compose: PodmanCompose, args):
max_service_length = curr_length if curr_length > max_service_length else max_service_length
tasks = set()
async def handle_sigint():
log.info("Caught SIGINT or Ctrl+C, shutting down...")
try:
log.info("Shutting down gracefully, please wait...")
down_args = argparse.Namespace(**dict(args.__dict__, volumes=False))
await compose.commands["down"](compose, down_args)
except Exception as e:
log.error("Error during shutdown: %s", e)
finally:
for task in tasks:
task.cancel()
if sys.platform != 'win32':
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, lambda: [t.cancel("User exit") for t in tasks])
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(handle_sigint()))
for i, cnt in enumerate(compose.containers):
# Add colored service prefix to output by piping output through sed

View File

@ -0,0 +1,3 @@
FROM busybox
RUN false

View File

@ -0,0 +1,8 @@
version: "3"
services:
bad:
build:
context: bad
good:
build:
context: good

View File

@ -0,0 +1,3 @@
FROM busybox
#ensure that this build finishes second so that it has a chance to overwrite the return code
RUN sleep 0.5

View File

@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0
import os
import unittest
from tests.integration.test_utils import RunSubprocessMixin
from tests.integration.test_utils import podman_compose_path
from tests.integration.test_utils import test_path
def compose_yaml_path():
""" "Returns the path to the compose file used for this test module"""
base_path = os.path.join(test_path(), "build_fail_multi")
return os.path.join(base_path, "docker-compose.yml")
class TestComposeBuildFailMulti(unittest.TestCase, RunSubprocessMixin):
def test_build_fail_multi(self):
output, error = self.run_subprocess_assert_returncode(
[
podman_compose_path(),
"-f",
compose_yaml_path(),
"build",
# prevent the successful build from being cached to ensure it runs long enough
"--no-cache",
],
expected_returncode=1,
)
self.assertIn("RUN false", str(output))
self.assertIn("while running runtime: exit status 1", str(error))

View File

@ -1,5 +1,7 @@
version: "3"
name: my-project-name
services:
env-test:
image: busybox
@ -8,3 +10,9 @@ services:
ZZVAR1: myval1
ZZVAR2: 2-$ZZVAR1
ZZVAR3: 3-$ZZVAR2
project-name-test:
image: busybox
command: sh -c "echo $$PNAME"
environment:
PNAME: ${COMPOSE_PROJECT_NAME}

View File

@ -36,3 +36,54 @@ class TestComposeEnv(unittest.TestCase, RunSubprocessMixin):
compose_yaml_path(),
"down",
])
"""
Tests interpolation of COMPOSE_PROJECT_NAME in the podman-compose config,
which is different from external environment variables because COMPOSE_PROJECT_NAME
is a predefined environment variable generated from the `name` value in the top-level
of the compose.yaml.
See also
- https://docs.docker.com/reference/compose-file/interpolation/
- https://docs.docker.com/reference/compose-file/version-and-name/#name-top-level-element
- https://docs.docker.com/compose/how-tos/environment-variables/envvars/
- https://github.com/compose-spec/compose-spec/blob/main/04-version-and-name.md
"""
def test_project_name(self):
try:
output, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(),
"run",
"project-name-test",
])
self.assertIn("my-project-name", str(output))
finally:
self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(),
"down",
])
def test_project_name_override(self):
try:
output, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(),
"run",
"-e",
"COMPOSE_PROJECT_NAME=project-name-override",
"project-name-test",
])
self.assertIn("project-name-override", str(output))
finally:
self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_yaml_path(),
"down",
])

View File

@ -1,14 +1,18 @@
version: "3"
services:
web1:
container1:
image: busybox
command: httpd -f -p 80 -h /var/www/html
command: ["busybox", "sleep", "infinity"]
volumes:
- type: bind
source: ./docker-compose.yml
target: /var/www/html/index.html
source: ./host_test_text.txt
target: /test_text.txt
bind:
selinux: z
ports:
- "8080:80"
container2:
image: busybox
command: ["busybox", "sleep", "infinity"]
volumes:
- type: bind
source: ./host_test_text.txt
target: /test_text.txt

View File

@ -0,0 +1 @@
# There must be a source file in the host for volumes type: bind

View File

@ -0,0 +1,58 @@
# SPDX-License-Identifier: GPL-2.0
import json
import os
import subprocess
import unittest
from tests.integration.test_utils import RunSubprocessMixin
from tests.integration.test_utils import podman_compose_path
from tests.integration.test_utils import test_path
class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
def test_selinux(self):
# test if when using volumes type:bind with selinux:z option, container ackquires a
# respective host:source:z mapping in CreateCommand list
compose_path = os.path.join(test_path(), "selinux", "docker-compose.yml")
try:
# change working directory to where docker-compose.yml file is so that containers can
# directly access host source file for mounting from that working directory
subprocess.run(
[
podman_compose_path(),
"-f",
compose_path,
"up",
"-d",
"container1",
"container2",
],
cwd=os.path.join(test_path(), 'selinux'),
)
out, _ = self.run_subprocess_assert_returncode([
"podman",
"inspect",
"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)
out, _ = self.run_subprocess_assert_returncode([
"podman",
"inspect",
"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)
finally:
out, _ = self.run_subprocess_assert_returncode([
podman_compose_path(),
"-f",
compose_path,
"down",
"-t",
"0",
])