mirror of
https://github.com/containers/podman-compose.git
synced 2025-04-25 01:49:06 +02:00
Merge branch 'containers:main' into container_scaling_update
This commit is contained in:
commit
04b9982bc5
@ -1,7 +1,7 @@
|
|||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
# C0111 missing-docstring: missing-class-docstring, missing-function-docstring, missing-method-docstring, missing-module-docstrin
|
# 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
|
# 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 _ for ignored variables
|
||||||
# allow generic names like a,b,c and i,j,k,l,m,n and x,y,z
|
# allow generic names like a,b,c and i,j,k,l,m,n and x,y,z
|
||||||
# allow k,v for key/value
|
# allow k,v for key/value
|
||||||
|
1
newsfragments/1165-PROJECT_NAME-interpolation.bugfix
Normal file
1
newsfragments/1165-PROJECT_NAME-interpolation.bugfix
Normal 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.
|
1
newsfragments/1165-project-name-evaluation-order.bugfix
Normal file
1
newsfragments/1165-project-name-evaluation-order.bugfix
Normal 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).
|
1
newsfragments/sigint-up.bugfix
Normal file
1
newsfragments/sigint-up.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
- Fixed handling SIGINT when running "up" command to shutdown gracefully
|
@ -1980,6 +1980,25 @@ class PodmanCompose:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
content = normalize(content)
|
content = normalize(content)
|
||||||
# log(filename, json.dumps(content, indent = 2))
|
# 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)
|
content = rec_subs(content, self.environ)
|
||||||
if isinstance(services := content.get('services'), dict):
|
if isinstance(services := content.get('services'), dict):
|
||||||
for service in services.values():
|
for service in services.values():
|
||||||
@ -2012,19 +2031,6 @@ class PodmanCompose:
|
|||||||
log.debug(" ** merged:\n%s", json.dumps(compose, indent=2))
|
log.debug(" ** merged:\n%s", json.dumps(compose, indent=2))
|
||||||
# ver = compose.get('version')
|
# 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")
|
services = compose.get("services")
|
||||||
if services is None:
|
if services is None:
|
||||||
services = {}
|
services = {}
|
||||||
@ -2629,7 +2635,7 @@ async def compose_build(compose, args):
|
|||||||
status = 0
|
status = 0
|
||||||
for t in asyncio.as_completed(tasks):
|
for t in asyncio.as_completed(tasks):
|
||||||
s = await t
|
s = await t
|
||||||
if s is not None:
|
if s is not None and s != 0:
|
||||||
status = s
|
status = s
|
||||||
|
|
||||||
return status
|
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
|
max_service_length = curr_length if curr_length > max_service_length else max_service_length
|
||||||
|
|
||||||
tasks = set()
|
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':
|
if sys.platform != 'win32':
|
||||||
loop = asyncio.get_event_loop()
|
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):
|
for i, cnt in enumerate(compose.containers):
|
||||||
# Add colored service prefix to output by piping output through sed
|
# Add colored service prefix to output by piping output through sed
|
||||||
|
0
tests/integration/build_fail_multi/__init__.py
Normal file
0
tests/integration/build_fail_multi/__init__.py
Normal file
3
tests/integration/build_fail_multi/bad/Dockerfile
Normal file
3
tests/integration/build_fail_multi/bad/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
FROM busybox
|
||||||
|
|
||||||
|
RUN false
|
8
tests/integration/build_fail_multi/docker-compose.yml
Normal file
8
tests/integration/build_fail_multi/docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
bad:
|
||||||
|
build:
|
||||||
|
context: bad
|
||||||
|
good:
|
||||||
|
build:
|
||||||
|
context: good
|
3
tests/integration/build_fail_multi/good/Dockerfile
Normal file
3
tests/integration/build_fail_multi/good/Dockerfile
Normal 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
|
@ -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))
|
@ -1,5 +1,7 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
|
name: my-project-name
|
||||||
|
|
||||||
services:
|
services:
|
||||||
env-test:
|
env-test:
|
||||||
image: busybox
|
image: busybox
|
||||||
@ -8,3 +10,9 @@ services:
|
|||||||
ZZVAR1: myval1
|
ZZVAR1: myval1
|
||||||
ZZVAR2: 2-$ZZVAR1
|
ZZVAR2: 2-$ZZVAR1
|
||||||
ZZVAR3: 3-$ZZVAR2
|
ZZVAR3: 3-$ZZVAR2
|
||||||
|
|
||||||
|
project-name-test:
|
||||||
|
image: busybox
|
||||||
|
command: sh -c "echo $$PNAME"
|
||||||
|
environment:
|
||||||
|
PNAME: ${COMPOSE_PROJECT_NAME}
|
||||||
|
@ -36,3 +36,54 @@ class TestComposeEnv(unittest.TestCase, RunSubprocessMixin):
|
|||||||
compose_yaml_path(),
|
compose_yaml_path(),
|
||||||
"down",
|
"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",
|
||||||
|
])
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
web1:
|
container1:
|
||||||
image: busybox
|
image: busybox
|
||||||
command: httpd -f -p 80 -h /var/www/html
|
command: ["busybox", "sleep", "infinity"]
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ./docker-compose.yml
|
source: ./host_test_text.txt
|
||||||
target: /var/www/html/index.html
|
target: /test_text.txt
|
||||||
bind:
|
bind:
|
||||||
selinux: z
|
selinux: z
|
||||||
ports:
|
container2:
|
||||||
- "8080:80"
|
image: busybox
|
||||||
|
command: ["busybox", "sleep", "infinity"]
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: ./host_test_text.txt
|
||||||
|
target: /test_text.txt
|
||||||
|
1
tests/integration/selinux/host_test_text.txt
Normal file
1
tests/integration/selinux/host_test_text.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
# There must be a source file in the host for volumes type: bind
|
58
tests/integration/selinux/test_podman_compose_selinux.py
Normal file
58
tests/integration/selinux/test_podman_compose_selinux.py
Normal 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",
|
||||||
|
])
|
Loading…
Reference in New Issue
Block a user