Merge 2.x and 3.x schemas

Signed-off-by: aiordache <anca.iordache@docker.com>
This commit is contained in:
aiordache 2020-06-26 10:21:48 +02:00
parent 789bfb0e8b
commit b78c1ec193
30 changed files with 522 additions and 7748 deletions

View file

@ -20,6 +20,8 @@ from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from ..helpers import create_host_file
from compose.cli.command import get_project
from compose.config.errors import DuplicateOverrideFileFound
from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V4 as VERSION
from compose.container import Container
from compose.project import OneOffFilter
from compose.utils import nanoseconds_from_time_seconds
@ -29,10 +31,6 @@ from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster
from tests.integration.testcases import pull_busybox
from tests.integration.testcases import SWARM_SKIP_RM_VOLUMES
from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_only
from tests.integration.testcases import v3_only
DOCKER_COMPOSE_EXECUTABLE = 'docker-compose'
@ -42,7 +40,7 @@ ProcessResult = namedtuple('ProcessResult', 'stdout stderr')
BUILD_CACHE_TEXT = 'Using cache'
BUILD_PULL_TEXT = 'Status: Image is up to date for busybox:1.27.2'
COMPOSE_COMPATIBILITY_DICT = {
'version': '2.3',
'version': str(VERSION),
'volumes': {'foo': {'driver': 'default'}},
'networks': {'bar': {}},
'services': {
@ -287,7 +285,7 @@ services:
output = yaml.safe_load(result.stdout)
expected = {
'version': '2.0',
'version': str(VERSION),
'volumes': {'data': {'driver': 'local'}},
'networks': {'front': {}},
'services': {
@ -311,7 +309,7 @@ services:
self.base_dir = 'tests/fixtures/restart'
result = self.dispatch(['config'])
assert yaml.safe_load(result.stdout) == {
'version': '2.0',
'version': str(VERSION),
'services': {
'never': {
'image': 'busybox',
@ -343,10 +341,12 @@ services:
assert 'networks' in json_result
assert json_result['networks'] == {
'networks_foo': {
'external': True # {'name': 'networks_foo'}
'external': True,
'name': 'networks_foo'
},
'bar': {
'external': {'name': 'networks_bar'}
'external': True,
'name': 'networks_bar'
}
}
@ -355,14 +355,14 @@ services:
result = self.dispatch(['config'])
json_result = yaml.safe_load(result.stdout)
assert json_result == {
'version': str(VERSION),
'services': {
'web': {
'command': 'true',
'image': 'alpine:latest',
'ports': ['5643/tcp', '9999/tcp']
'ports': [{'target': 5643}, {'target': 9999}]
}
},
'version': '2.4'
}
}
def test_config_with_env_file(self):
@ -370,14 +370,14 @@ services:
result = self.dispatch(['--env-file', '.env2', 'config'])
json_result = yaml.safe_load(result.stdout)
assert json_result == {
'version': str(VERSION),
'services': {
'web': {
'command': 'false',
'image': 'alpine:latest',
'ports': ['5644/tcp', '9998/tcp']
'ports': [{'target': 5644}, {'target': 9998}]
}
},
'version': '2.4'
}
}
def test_config_with_dot_env_and_override_dir(self):
@ -385,14 +385,14 @@ services:
result = self.dispatch(['--project-directory', 'alt/', 'config'])
json_result = yaml.safe_load(result.stdout)
assert json_result == {
'version': str(VERSION),
'services': {
'web': {
'command': 'echo uwu',
'image': 'alpine:3.10.1',
'ports': ['3341/tcp', '4449/tcp']
'ports': [{'target': 3341}, {'target': 4449}]
}
},
'version': '2.4'
}
}
def test_config_external_volume_v2(self):
@ -403,11 +403,11 @@ services:
assert json_result['volumes'] == {
'foo': {
'external': True,
'name': 'foo',
},
'bar': {
'external': {
'name': 'some_bar',
},
'external': True,
'name': 'some_bar',
}
}
@ -435,11 +435,11 @@ services:
assert json_result['volumes'] == {
'foo': {
'external': True,
'name': 'foo',
},
'bar': {
'external': {
'name': 'some_bar',
},
'external': True,
'name': 'some_bar',
}
}
@ -479,7 +479,7 @@ services:
self.base_dir = 'tests/fixtures/v1-config'
result = self.dispatch(['config'])
assert yaml.safe_load(result.stdout) == {
'version': '2.1',
'version': str(V1),
'services': {
'net': {
'image': 'busybox',
@ -498,13 +498,11 @@ services:
},
}
@v3_only()
def test_config_v3(self):
self.base_dir = 'tests/fixtures/v3-full'
result = self.dispatch(['config'])
assert yaml.safe_load(result.stdout) == {
'version': '3.5',
'version': str(VERSION),
'volumes': {
'foobar': {
'labels': {
@ -576,12 +574,14 @@ services:
},
}
@pytest.mark.skip(reason='deprecated option')
def test_config_compatibility_mode(self):
self.base_dir = 'tests/fixtures/compatibility-mode'
result = self.dispatch(['--compatibility', 'config'])
assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT
@pytest.mark.skip(reason='deprecated option')
@mock.patch.dict(os.environ)
def test_config_compatibility_mode_from_env(self):
self.base_dir = 'tests/fixtures/compatibility-mode'
@ -590,6 +590,7 @@ services:
assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT
@pytest.mark.skip(reason='deprecated option')
@mock.patch.dict(os.environ)
def test_config_compatibility_mode_from_env_and_option_precedence(self):
self.base_dir = 'tests/fixtures/compatibility-mode'
@ -1018,7 +1019,6 @@ services:
result = self.dispatch(['down', '--rmi', 'bogus'], returncode=1)
assert '--rmi flag must be' in result.stderr
@v2_only()
def test_down(self):
self.base_dir = 'tests/fixtures/v2-full'
@ -1103,7 +1103,6 @@ services:
assert '{} exited with code 0'.format(simple_name) in result.stdout
assert '{} exited with code 0'.format(another_name) in result.stdout
@v2_only()
def test_up(self):
self.base_dir = 'tests/fixtures/v2-simple'
self.dispatch(['up', '-d'], None)
@ -1135,7 +1134,6 @@ services:
for service in services:
assert self.lookup(container, service.name)
@v2_only()
def test_up_no_start(self):
self.base_dir = 'tests/fixtures/v2-full'
self.dispatch(['up', '--no-start'], None)
@ -1166,7 +1164,6 @@ services:
]
assert len(remote_volumes) > 0
@v2_only()
def test_up_no_start_remove_orphans(self):
self.base_dir = 'tests/fixtures/v2-simple'
self.dispatch(['up', '--no-start'], None)
@ -1182,7 +1179,6 @@ services:
stopped=True) + next.containers(stopped=True)), services)
assert len(stopped2) == 1
@v2_only()
def test_up_no_ansi(self):
self.base_dir = 'tests/fixtures/v2-simple'
result = self.dispatch(['--no-ansi', 'up', '-d'], None)
@ -1190,7 +1186,6 @@ services:
assert "%c[1A" % 27 not in result.stderr
assert "%c[1B" % 27 not in result.stderr
@v2_only()
def test_up_with_default_network_config(self):
filename = 'default-network-config.yml'
@ -1204,7 +1199,6 @@ services:
assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false'
@v2_only()
def test_up_with_network_aliases(self):
filename = 'network-aliases.yml'
self.base_dir = 'tests/fixtures/networks'
@ -1232,7 +1226,6 @@ services:
assert 'forward_facing' in front_aliases
assert 'ahead' in front_aliases
@v2_only()
def test_up_with_network_internal(self):
self.require_api_version('1.23')
filename = 'network-internal.yml'
@ -1250,7 +1243,6 @@ services:
assert networks[0]['Internal'] is True
@v2_only()
def test_up_with_network_static_addresses(self):
filename = 'network-static-addresses.yml'
ipv4_address = '172.16.100.100'
@ -1274,7 +1266,6 @@ services:
assert ipv4_address in ipam_config.values()
assert ipv6_address in ipam_config.values()
@v2_only()
def test_up_with_networks(self):
self.base_dir = 'tests/fixtures/networks'
self.dispatch(['up', '-d'], None)
@ -1322,7 +1313,6 @@ services:
# app has aliased db to "database"
assert self.lookup(app_container, "database")
@v2_only()
def test_up_missing_network(self):
self.base_dir = 'tests/fixtures/networks'
@ -1332,7 +1322,6 @@ services:
assert 'Service "web" uses an undefined network "foo"' in result.stderr
@v2_only()
@no_cluster('container networks not supported in Swarm')
def test_up_with_network_mode(self):
c = self.client.create_container(
@ -1371,7 +1360,6 @@ services:
assert not container_mode_container.get('NetworkSettings.Networks')
assert container_mode_container.get('HostConfig.NetworkMode') == container_mode_source
@v2_only()
def test_up_external_networks(self):
filename = 'external-networks.yml'
@ -1395,7 +1383,6 @@ services:
container = self.project.containers()[0]
assert sorted(list(container.get('NetworkSettings.Networks'))) == sorted(network_names)
@v2_only()
def test_up_with_external_default_network(self):
filename = 'external-default.yml'
@ -1418,7 +1405,6 @@ services:
container = self.project.containers()[0]
assert list(container.get('NetworkSettings.Networks')) == [network_name]
@v2_1_only()
def test_up_with_network_labels(self):
filename = 'network-label.yml'
@ -1438,7 +1424,6 @@ services:
assert 'label_key' in networks[0]['Labels']
assert networks[0]['Labels']['label_key'] == 'label_val'
@v2_1_only()
def test_up_with_volume_labels(self):
filename = 'volume-label.yml'
@ -1458,7 +1443,6 @@ services:
assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val'
@v2_only()
def test_up_no_services(self):
self.base_dir = 'tests/fixtures/no-services'
self.dispatch(['up', '-d'], None)
@ -1515,7 +1499,6 @@ services:
bar_container.id
)
@v3_only()
def test_up_with_healthcheck(self):
def wait_on_health_status(container, status):
def condition():
@ -1649,7 +1632,6 @@ services:
os.kill(proc.pid, signal.SIGTERM)
wait_on_condition(ContainerCountCondition(self.project, 0))
@v2_only()
def test_up_handles_force_shutdown(self):
self.base_dir = 'tests/fixtures/sleeps-composefile'
proc = start_process(self.base_dir, ['up', '-t', '200'])
@ -1674,7 +1656,6 @@ services:
proc.wait()
assert proc.returncode == 1
@v2_only()
@no_cluster('Container PID mode does not work across clusters')
def test_up_with_pid_mode(self):
c = self.client.create_container(
@ -1738,7 +1719,6 @@ services:
assert stdout == "operator\n"
assert stderr == ""
@v3_only()
def test_exec_workdir(self):
self.base_dir = 'tests/fixtures/links-composefile'
os.environ['COMPOSE_API_VERSION'] = '1.35'
@ -1748,7 +1728,6 @@ services:
stdout, stderr = self.dispatch(['exec', '-T', '--workdir', '/etc', 'console', 'ls'])
assert 'passwd' in stdout
@v2_2_only()
def test_exec_service_with_environment_overridden(self):
name = 'service'
self.base_dir = 'tests/fixtures/environment-exec'
@ -1793,7 +1772,6 @@ services:
assert len(db.containers()) == 1
assert len(console.containers()) == 0
@v2_only()
def test_run_service_with_dependencies(self):
self.base_dir = 'tests/fixtures/v2-dependencies'
self.dispatch(['run', 'web', '/bin/true'], None)
@ -2105,7 +2083,6 @@ services:
container = service.containers(stopped=True, one_off=True)[0]
assert workdir == container.get('Config.WorkingDir')
@v2_only()
def test_run_service_with_use_aliases(self):
filename = 'network-aliases.yml'
self.base_dir = 'tests/fixtures/networks'
@ -2127,7 +2104,6 @@ services:
assert 'forward_facing' in front_aliases
assert 'ahead' in front_aliases
@v2_only()
def test_run_interactive_connects_to_network(self):
self.base_dir = 'tests/fixtures/networks'
@ -2153,7 +2129,6 @@ services:
aliases = set(config['Aliases'] or []) - {container.short_id}
assert not aliases
@v2_only()
def test_run_detached_connects_to_network(self):
self.base_dir = 'tests/fixtures/networks'
self.dispatch(['up', '-d'])
@ -2332,7 +2307,6 @@ services:
assert 'failed' in result.stderr
assert 'No containers to start' in result.stderr
@v2_only()
def test_up_logging(self):
self.base_dir = 'tests/fixtures/logging-composefile'
self.dispatch(['up', '-d'])
@ -2563,11 +2537,6 @@ services:
assert len(project.get_service('simple').containers()) == 0
assert len(project.get_service('another').containers()) == 0
def test_scale_v2_2(self):
self.base_dir = 'tests/fixtures/scale'
result = self.dispatch(['scale', 'web=1'], returncode=1)
assert 'incompatible with the v2.2 format' in result.stderr
def test_up_scale_scale_up(self):
self.base_dir = 'tests/fixtures/scale'
project = self.project

View file

@ -21,11 +21,7 @@ from compose.config import ConfigurationError
from compose.config import types
from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_1 as V2_1
from compose.const import COMPOSEFILE_V2_2 as V2_2
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_1 as V3_1
from compose.const import COMPOSEFILE_V4 as VERSION
from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE
from compose.container import Container
@ -37,16 +33,11 @@ from compose.service import ConvergenceStrategy
from tests.integration.testcases import if_runtime_available
from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster
from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_3_only
from tests.integration.testcases import v2_only
from tests.integration.testcases import v3_only
def build_config(**kwargs):
return config.Config(
version=kwargs.get('version'),
version=kwargs.get('version', VERSION),
services=kwargs.get('services'),
volumes=kwargs.get('volumes'),
networks=kwargs.get('networks'),
@ -106,7 +97,6 @@ class ProjectTest(DockerClientTestCase):
def test_parallel_pull_with_no_image(self):
config_data = build_config(
version=V2_3,
services=[{
'name': 'web',
'build': {'context': '.'},
@ -162,14 +152,12 @@ class ProjectTest(DockerClientTestCase):
db = project.get_service('db')
assert db._get_volumes_from() == [data_container.id + ':rw']
@v2_only()
@no_cluster('container networks not supported in Swarm')
def test_network_mode_from_service(self):
project = Project.from_config(
name='composetest',
client=self.client,
config_data=load_config({
'version': str(V2_0),
'services': {
'net': {
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -190,14 +178,12 @@ class ProjectTest(DockerClientTestCase):
net = project.get_service('net')
assert web.network_mode.mode == 'container:' + net.containers()[0].id
@v2_only()
@no_cluster('container networks not supported in Swarm')
def test_network_mode_from_container(self):
def get_project():
return Project.from_config(
name='composetest',
config_data=load_config({
'version': str(V2_0),
'services': {
'web': {
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -448,7 +434,6 @@ class ProjectTest(DockerClientTestCase):
assert db_container.id != old_db_id
assert db_container.get('Volumes./etc') == db_volume_path
@v2_3_only()
def test_recreate_preserves_mounts(self):
web = self.create_service('web')
db = self.create_service('db', volumes=[types.MountSpec(type='volume', target='/etc')])
@ -656,10 +641,8 @@ class ProjectTest(DockerClientTestCase):
service = project.get_service('web')
assert len(service.containers()) == 1
@v2_only()
def test_project_up_networks(self):
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -701,10 +684,8 @@ class ProjectTest(DockerClientTestCase):
foo_data = self.client.inspect_network('composetest_foo')
assert foo_data['Driver'] == 'bridge'
@v2_only()
def test_up_with_ipam_config(self):
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -761,10 +742,8 @@ class ProjectTest(DockerClientTestCase):
}],
}
@v2_only()
def test_up_with_ipam_options(self):
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -796,10 +775,8 @@ class ProjectTest(DockerClientTestCase):
"com.docker.compose.network.test": "9-29-045"
}
@v2_1_only()
def test_up_with_network_static_addresses(self):
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -845,13 +822,11 @@ class ProjectTest(DockerClientTestCase):
assert ipam_config.get('IPv4Address') == '172.16.100.100'
assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
@v2_3_only()
def test_up_with_network_priorities(self):
mac_address = '74:6f:75:68:6f:75'
def get_config_data(p1, p2, p3):
return build_config(
version=V2_3,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -910,11 +885,9 @@ class ProjectTest(DockerClientTestCase):
net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n3']
assert net_config['MacAddress'] == mac_address
@v2_1_only()
def test_up_with_enable_ipv6(self):
self.require_api_version('1.23')
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -954,10 +927,8 @@ class ProjectTest(DockerClientTestCase):
get('IPAMConfig', {}))
assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
@v2_only()
def test_up_with_network_static_addresses_missing_subnet(self):
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -990,10 +961,8 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(ProjectError):
project.up()
@v2_1_only()
def test_up_with_network_link_local_ips(self):
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1025,10 +994,8 @@ class ProjectTest(DockerClientTestCase):
assert 'LinkLocalIPs' in ipam_config
assert ipam_config['LinkLocalIPs'] == ['169.254.8.8']
@v2_1_only()
def test_up_with_custom_name_resources(self):
config_data = build_config(
version=V2_2,
services=[{
'name': 'web',
'volumes': [VolumeSpec.parse('foo:/container-path')],
@ -1062,11 +1029,9 @@ class ProjectTest(DockerClientTestCase):
assert network['Labels']['com.docker.compose.test_value'] == 'sharpdressedman'
assert volume['Labels']['com.docker.compose.test_value'] == 'thefuror'
@v2_1_only()
def test_up_with_isolation(self):
self.require_api_version('1.24')
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1082,11 +1047,9 @@ class ProjectTest(DockerClientTestCase):
service_container = project.get_service('web').containers(stopped=True)[0]
assert service_container.inspect()['HostConfig']['Isolation'] == 'default'
@v2_1_only()
def test_up_with_invalid_isolation(self):
self.require_api_version('1.24')
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1101,12 +1064,10 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(ProjectError):
project.up()
@v2_3_only()
@if_runtime_available('runc')
def test_up_with_runtime(self):
self.require_api_version('1.30')
config_data = build_config(
version=V2_3,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1122,11 +1083,9 @@ class ProjectTest(DockerClientTestCase):
service_container = project.get_service('web').containers(stopped=True)[0]
assert service_container.inspect()['HostConfig']['Runtime'] == 'runc'
@v2_3_only()
def test_up_with_invalid_runtime(self):
self.require_api_version('1.30')
config_data = build_config(
version=V2_3,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1141,12 +1100,10 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(ProjectError):
project.up()
@v2_3_only()
@if_runtime_available('nvidia')
def test_up_with_nvidia_runtime(self):
self.require_api_version('1.30')
config_data = build_config(
version=V2_3,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1162,11 +1119,9 @@ class ProjectTest(DockerClientTestCase):
service_container = project.get_service('web').containers(stopped=True)[0]
assert service_container.inspect()['HostConfig']['Runtime'] == 'nvidia'
@v2_only()
def test_project_up_with_network_internal(self):
self.require_api_version('1.23')
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1188,14 +1143,12 @@ class ProjectTest(DockerClientTestCase):
assert network['Internal'] is True
@v2_1_only()
def test_project_up_with_network_label(self):
self.require_api_version('1.23')
network_name = 'network_with_label'
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1223,12 +1176,10 @@ class ProjectTest(DockerClientTestCase):
assert 'label_key' in networks[0]['Labels']
assert networks[0]['Labels']['label_key'] == 'label_val'
@v2_only()
def test_project_up_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1248,14 +1199,12 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local'
@v2_1_only()
def test_project_up_with_volume_labels(self):
self.require_api_version('1.23')
volume_name = 'volume_with_label'
config_data = build_config(
version=V2_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1290,12 +1239,10 @@ class ProjectTest(DockerClientTestCase):
assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val'
@v2_only()
def test_project_up_logging_with_multiple_files(self):
base_file = config.ConfigFile(
'base.yml',
{
'version': str(V2_0),
'services': {
'simple': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'},
'another': {
@ -1314,7 +1261,6 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile(
'override.yml',
{
'version': str(V2_0),
'services': {
'another': {
'logging': {
@ -1342,12 +1288,10 @@ class ProjectTest(DockerClientTestCase):
assert log_config
assert log_config.get('Type') == 'none'
@v2_only()
def test_project_up_port_mappings_with_multiple_files(self):
base_file = config.ConfigFile(
'base.yml',
{
'version': str(V2_0),
'services': {
'simple': {
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1360,7 +1304,6 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile(
'override.yml',
{
'version': str(V2_0),
'services': {
'simple': {
'ports': ['1234:1234']
@ -1378,10 +1321,8 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers()
assert len(containers) == 1
@v2_2_only()
def test_project_up_config_scale(self):
config_data = build_config(
version=V2_2,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1406,12 +1347,10 @@ class ProjectTest(DockerClientTestCase):
project.up()
assert len(project.containers()) == 3
@v2_only()
def test_initialize_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1430,12 +1369,10 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local'
@v2_only()
def test_project_up_implicit_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1454,12 +1391,10 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local'
@v3_only()
def test_project_up_with_secrets(self):
node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
config_data = build_config(
version=V3_1,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1491,12 +1426,10 @@ class ProjectTest(DockerClientTestCase):
output = container.logs()
assert output == b"This is the secret\n"
@v3_only()
def test_project_up_with_added_secrets(self):
node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
config_input1 = {
'version': V3_1,
'services': [
{
'name': 'web',
@ -1545,12 +1478,11 @@ class ProjectTest(DockerClientTestCase):
output = container.logs()
assert output == b"This is the secret\n"
@v2_only()
def test_initialize_volumes_invalid_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
config_data = build_config(
version=V2_0,
version=VERSION,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1566,14 +1498,12 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(APIError if is_cluster(self.client) else config.ConfigurationError):
project.volumes.initialize()
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_updated_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1605,7 +1535,6 @@ class ProjectTest(DockerClientTestCase):
vol_name
) in str(e.value)
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_updated_driver_opts(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
@ -1615,7 +1544,6 @@ class ProjectTest(DockerClientTestCase):
driver_opts = {'o': 'bind', 'device': tmpdir, 'type': 'none'}
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1651,13 +1579,11 @@ class ProjectTest(DockerClientTestCase):
vol_name, driver_opts['device']
) in str(e.value)
@v2_only()
def test_initialize_volumes_updated_blank_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1688,7 +1614,6 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local'
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_external_volumes(self):
# Use composetest_ prefix so it gets garbage-collected in tearDown()
@ -1696,7 +1621,6 @@ class ProjectTest(DockerClientTestCase):
full_vol_name = 'composetest_{0}'.format(vol_name)
self.client.create_volume(vol_name)
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1715,12 +1639,10 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(NotFound):
self.client.inspect_volume(full_vol_name)
@v2_only()
def test_initialize_volumes_inexistent_external_volume(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
config_data = build_config(
version=V2_0,
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1740,7 +1662,6 @@ class ProjectTest(DockerClientTestCase):
vol_name
) in str(e.value)
@v2_only()
def test_project_up_named_volumes_in_binds(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name)
@ -1748,7 +1669,6 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile(
'base.yml',
{
'version': str(V2_0),
'services': {
'simple': {
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1839,7 +1759,6 @@ class ProjectTest(DockerClientTestCase):
mock_log.warning.assert_not_called()
@v2_1_only()
def test_project_up_healthy_dependency(self):
config_dict = {
'version': '2.1',
@ -1876,7 +1795,6 @@ class ProjectTest(DockerClientTestCase):
assert 'svc1' in svc2.get_dependency_names()
assert svc1.is_healthy()
@v2_1_only()
def test_project_up_unhealthy_dependency(self):
config_dict = {
'version': '2.1',
@ -1915,7 +1833,6 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(HealthCheckFailed):
svc1.is_healthy()
@v2_1_only()
def test_project_up_no_healthcheck_dependency(self):
config_dict = {
'version': '2.1',

View file

@ -12,13 +12,7 @@ from compose.config.config import resolve_environment
from compose.config.environment import Environment
from compose.const import API_VERSIONS
from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_0 as V2_1
from compose.const import COMPOSEFILE_V2_2 as V2_2
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_0 as V3_0
from compose.const import COMPOSEFILE_V3_2 as V3_2
from compose.const import COMPOSEFILE_V3_5 as V3_5
from compose.const import COMPOSEFILE_V4 as VERSION
from compose.const import LABEL_PROJECT
from compose.progress_stream import stream_output
from compose.service import Service
@ -45,17 +39,11 @@ def get_links(container):
def engine_max_version():
if 'DOCKER_VERSION' not in os.environ:
return V3_5
return VERSION
version = os.environ['DOCKER_VERSION'].partition('-')[0]
if version_lt(version, '1.10'):
return V1
if version_lt(version, '1.12'):
return V2_0
if version_lt(version, '1.13'):
return V2_1
if version_lt(version, '17.06'):
return V3_2
return V3_5
return VERSION
def min_version_skip(version):
@ -66,23 +54,23 @@ def min_version_skip(version):
def v2_only():
return min_version_skip(V2_0)
return min_version_skip(VERSION)
def v2_1_only():
return min_version_skip(V2_1)
return min_version_skip(VERSION)
def v2_2_only():
return min_version_skip(V2_2)
return min_version_skip(VERSION)
def v2_3_only():
return min_version_skip(V2_3)
return min_version_skip(VERSION)
def v3_only():
return min_version_skip(V3_0)
return min_version_skip(VERSION)
class DockerClientTestCase(unittest.TestCase):

View file

@ -34,19 +34,19 @@ class TestHandleConnectionErrors(object):
def test_api_error_version_mismatch(self, mock_logging):
with pytest.raises(errors.ConnectionError):
with handle_connection_errors(mock.Mock(api_version='1.22')):
with handle_connection_errors(mock.Mock(api_version='1.38')):
raise APIError(None, None, b"client is newer than server")
_, args, _ = mock_logging.error.mock_calls[0]
assert "Docker Engine of version 1.10.0 or greater" in args[0]
assert "Docker Engine of version 18.06.0 or greater" in args[0]
def test_api_error_version_mismatch_unicode_explanation(self, mock_logging):
with pytest.raises(errors.ConnectionError):
with handle_connection_errors(mock.Mock(api_version='1.22')):
with handle_connection_errors(mock.Mock(api_version='1.38')):
raise APIError(None, None, u"client is newer than server")
_, args, _ = mock_logging.error.mock_calls[0]
assert "Docker Engine of version 1.10.0 or greater" in args[0]
assert "Docker Engine of version 18.06.0 or greater" in args[0]
def test_api_error_version_other(self, mock_logging):
msg = b"Something broke!"

View file

@ -27,20 +27,12 @@ from compose.config.serialize import serialize_config
from compose.config.serialize import serialize_ns_time_value
from compose.config.types import VolumeSpec
from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_1 as V2_1
from compose.const import COMPOSEFILE_V2_2 as V2_2
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_0 as V3_0
from compose.const import COMPOSEFILE_V3_1 as V3_1
from compose.const import COMPOSEFILE_V3_2 as V3_2
from compose.const import COMPOSEFILE_V3_3 as V3_3
from compose.const import COMPOSEFILE_V3_5 as V3_5
from compose.const import COMPOSEFILE_V4 as VERSION
from compose.const import IS_WINDOWS_PLATFORM
from tests import mock
from tests import unittest
DEFAULT_VERSION = V2_0
DEFAULT_VERSION = VERSION
def make_service_dict(name, service_dict, working_dir='.', filename=None):
@ -73,8 +65,10 @@ class ConfigTest(unittest.TestCase):
service_dicts = config.load(
build_config_details(
{
'foo': {'image': 'busybox'},
'bar': {'image': 'busybox', 'environment': ['FOO=1']},
'services': {
'foo': {'image': 'busybox'},
'bar': {'image': 'busybox', 'environment': ['FOO=1']},
}
},
'tests/fixtures/extends',
'common.yml'
@ -169,23 +163,23 @@ class ConfigTest(unittest.TestCase):
def test_valid_versions(self):
for version in ['2', '2.0']:
cfg = config.load(build_config_details({'version': version}))
assert cfg.version == V2_0
assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '2.1'}))
assert cfg.version == V2_1
assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '2.2'}))
assert cfg.version == V2_2
assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '2.3'}))
assert cfg.version == V2_3
assert cfg.version == VERSION
for version in ['3', '3.0']:
cfg = config.load(build_config_details({'version': version}))
assert cfg.version == V3_0
assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '3.1'}))
assert cfg.version == V3_1
assert cfg.version == VERSION
def test_v1_file_version(self):
cfg = config.load(build_config_details({'web': {'image': 'busybox'}}))
@ -197,7 +191,7 @@ class ConfigTest(unittest.TestCase):
assert list(s['name'] for s in cfg.services) == ['version']
def test_wrong_version_type(self):
for version in [None, 1, 2, 2.0]:
for version in [1, 2, 2.0]:
with pytest.raises(ConfigurationError) as excinfo:
config.load(
build_config_details(
@ -213,12 +207,12 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo:
config.load(
build_config_details(
{'version': '2.18'},
{'version': '1'},
filename='filename.yml',
)
)
assert 'Version in "filename.yml" is unsupported' in excinfo.exconly()
assert 'Version in "filename.yml" is invalid' in excinfo.exconly()
assert VERSION_EXPLANATION in excinfo.exconly()
def test_version_1_is_invalid(self):
@ -328,7 +322,6 @@ class ConfigTest(unittest.TestCase):
}
}, 'working_dir', 'filename.yml')
)
assert 'Unexpected type for "version" key in "filename.yml"' \
in mock_logging.warning.call_args[0][0]
@ -376,7 +369,7 @@ class ConfigTest(unittest.TestCase):
base_file = config.ConfigFile(
'base.yaml',
{
'version': str(V2_1),
'version': '2',
'services': {
'web': {
'image': 'example/web',
@ -511,7 +504,15 @@ class ConfigTest(unittest.TestCase):
for invalid_name in ['?not?allowed', ' ', '', '!', '/', '\xe2']:
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details(
{invalid_name: {'image': 'busybox'}}))
{
'version': '2',
'services': {
invalid_name:
{
'image': 'busybox'
}
}
}))
assert 'Invalid service name \'%s\'' % invalid_name in exc.exconly()
def test_load_config_invalid_service_names_v2(self):
@ -543,17 +544,24 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details(
{
'web': {'image': 'busybox', 'name': 'bogus'},
'version': '2',
'services': {
'web': {'image': 'busybox', 'name': 'bogus'}
}
},
'working_dir',
'filename.yml',
))
assert "Unsupported config option for web: 'name'" in exc.exconly()
assert "Unsupported config option for services.web: 'name'" in exc.exconly()
def test_load_invalid_service_definition(self):
config_details = build_config_details(
{'web': 'wrong'},
{
'version': '2',
'services': {
'web': 'wrong'
}
},
'working_dir',
'filename.yml')
with pytest.raises(ConfigurationError) as exc:
@ -585,7 +593,10 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo:
config.load(
build_config_details(
{1: {'image': 'busybox'}},
{
'version': '2',
'services': {1: {'image': 'busybox'}}
},
'working_dir',
'filename.yml'
)
@ -836,10 +847,10 @@ class ConfigTest(unittest.TestCase):
def test_load_with_multiple_files_and_invalid_override(self):
base_file = config.ConfigFile(
'base.yaml',
{'web': {'image': 'example/web'}})
{'version': '2', 'services': {'web': {'image': 'example/web'}}})
override_file = config.ConfigFile(
'override.yaml',
{'bogus': 'thing'})
{'version': '2', 'services': {'bogus': 'thing'}})
details = config.ConfigDetails('.', [base_file, override_file])
with pytest.raises(ConfigurationError) as exc:
@ -977,7 +988,6 @@ class ConfigTest(unittest.TestCase):
service = config.load(
build_config_details(
{
'version': str(V3_3),
'services': {
'web': {
'build': {
@ -1424,7 +1434,7 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details(
{
'version': str(V2_1),
'version': str(VERSION),
'networks': {
'foo': {
'driver': 'default',
@ -1455,7 +1465,6 @@ class ConfigTest(unittest.TestCase):
networks = config.load(
build_config_details(
{
'version': str(V2_1),
'networks': {
'foo': {
'driver': 'default',
@ -1487,7 +1496,10 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details(
{
'foo': {'image': 'busybox', 'privilige': 'something'},
'version': str(VERSION),
'services': {
'foo': {'image': 'busybox', 'privilige': 'something'},
}
},
'tests/fixtures/extends',
'filename.yml'
@ -1508,7 +1520,10 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details(
{
'foo': {'image': 1},
'version': str(VERSION),
'services': {
'foo': {'image': 1},
}
},
'tests/fixtures/extends',
'filename.yml'
@ -1555,7 +1570,10 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details(
{
'foo': {'image': 'busybox', 'links': 'an_link'},
'version': str(VERSION),
'services': {
'foo': {'image': 'busybox', 'links': 'an_link'},
}
},
'tests/fixtures/extends',
'filename.yml'
@ -1583,7 +1601,10 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details(
{
'web': {'build': '.', 'devices': ['/dev/foo:/dev/foo', '/dev/foo:/dev/foo']}
'version': str(VERSION),
'services': {
'web': {'build': '.', 'devices': ['/dev/foo:/dev/foo', '/dev/foo:/dev/foo']}
}
},
'tests/fixtures/extends',
'filename.yml'
@ -1597,7 +1618,10 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details(
{
'web': {'build': '.', 'command': [1]}
'version': str(VERSION),
'services': {
'web': {'build': '.', 'command': [1]}
}
},
'tests/fixtures/extends',
'filename.yml'
@ -1622,10 +1646,13 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo:
config.load(
build_config_details(
{'web': {
'image': 'busybox',
'extra_hosts': 'somehost:162.242.195.82'
}},
{
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'extra_hosts': 'somehost:162.242.195.82'}}
},
'working_dir',
'filename.yml'
)
@ -1638,13 +1665,16 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo:
config.load(
build_config_details(
{'web': {
'image': 'busybox',
'extra_hosts': [
{'somehost': '162.242.195.82'},
{'otherhost': '50.31.209.229'}
]
}},
{
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'extra_hosts': [
{'somehost': '162.242.195.82'},
{'otherhost': '50.31.209.229'}
]}}
},
'working_dir',
'filename.yml'
)
@ -1658,13 +1688,16 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details(
{
'web': {
'image': 'busybox',
'ulimits': {
'nofile': {
"not_soft_or_hard": 100,
"soft": 10000,
"hard": 20000,
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'ulimits': {
'nofile': {
"not_soft_or_hard": 100,
"soft": 10000,
"hard": 20000,
}
}
}
}
@ -1679,9 +1712,12 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details(
{
'web': {
'image': 'busybox',
'ulimits': {'nofile': {"soft": 10000}}
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'ulimits': {'nofile': {"soft": 10000}}
}
}
},
'working_dir',
@ -1695,10 +1731,13 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details(
{
'web': {
'image': 'busybox',
'ulimits': {
'nofile': {"soft": 10000, "hard": 1000}
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'ulimits': {
'nofile': {"soft": 10000, "hard": 1000}
}
}
}
},
@ -1711,10 +1750,12 @@ class ConfigTest(unittest.TestCase):
for expose in expose_values:
service = config.load(
build_config_details(
{'web': {
'image': 'busybox',
'expose': expose
}},
{
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'expose': expose}}},
'working_dir',
'filename.yml'
)
@ -1726,10 +1767,12 @@ class ConfigTest(unittest.TestCase):
for entrypoint in entrypoint_values:
service = config.load(
build_config_details(
{'web': {
'image': 'busybox',
'entrypoint': entrypoint
}},
{
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'entrypoint': entrypoint}}},
'working_dir',
'filename.yml'
)
@ -1738,9 +1781,12 @@ class ConfigTest(unittest.TestCase):
def test_logs_warning_for_boolean_in_environment(self):
config_details = build_config_details({
'web': {
'image': 'busybox',
'environment': {'SHOW_STUFF': True}
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'environment': {'SHOW_STUFF': True}
}
}
})
@ -1752,10 +1798,12 @@ class ConfigTest(unittest.TestCase):
def test_config_valid_environment_dict_key_contains_dashes(self):
services = config.load(
build_config_details(
{'web': {
'image': 'busybox',
'environment': {'SPRING_JPA_HIBERNATE_DDL-AUTO': 'none'}
}},
{
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox',
'environment': {'SPRING_JPA_HIBERNATE_DDL-AUTO': 'none'}}}},
'working_dir',
'filename.yml'
)
@ -1794,9 +1842,12 @@ web:
def test_validate_extra_hosts_invalid(self):
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details({
'web': {
'image': 'alpine',
'extra_hosts': "www.example.com: 192.168.0.17",
'version': str(VERSION),
'services': {
'web': {
'image': 'alpine',
'extra_hosts': "www.example.com: 192.168.0.17",
}
}
}))
assert "web.extra_hosts contains an invalid type" in exc.exconly()
@ -1804,22 +1855,28 @@ web:
def test_validate_extra_hosts_invalid_list(self):
with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details({
'web': {
'image': 'alpine',
'extra_hosts': [
{'www.example.com': '192.168.0.17'},
{'api.example.com': '192.168.0.18'}
],
'version': str(VERSION),
'services': {
'web': {
'image': 'alpine',
'extra_hosts': [
{'www.example.com': '192.168.0.17'},
{'api.example.com': '192.168.0.18'}
],
}
}
}))
assert "which is an invalid type" in exc.exconly()
def test_normalize_dns_options(self):
actual = config.load(build_config_details({
'web': {
'image': 'alpine',
'dns': '8.8.8.8',
'dns_search': 'domain.local',
'version': str(VERSION),
'services': {
'web': {
'image': 'alpine',
'dns': '8.8.8.8',
'dns_search': 'domain.local',
}
}
}))
assert actual.services == [
@ -1947,7 +2004,6 @@ web:
def test_isolation_option(self):
actual = config.load(build_config_details({
'version': str(V2_1),
'services': {
'web': {
'image': 'win10',
@ -1966,7 +2022,6 @@ web:
def test_runtime_option(self):
actual = config.load(build_config_details({
'version': str(V2_3),
'services': {
'web': {
'image': 'nvidia/cuda',
@ -2088,7 +2143,7 @@ web:
}
actual = config.merge_service_dicts_from_files(
base, override, V3_2
base, override, VERSION
)
assert actual['volumes'] == [
@ -2135,7 +2190,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2169,7 +2224,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2201,7 +2256,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2233,7 +2288,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2262,7 +2317,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2282,7 +2337,7 @@ web:
}
}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2304,7 +2359,7 @@ web:
}
}
override = {}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'alpine:edge',
'logging': {
@ -2332,7 +2387,7 @@ web:
'ports': ['1245:1245/udp']
}
actual = config.merge_service_dicts(base, override, V3_1)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
@ -2348,7 +2403,7 @@ web:
}
}
override = {}
actual = config.merge_service_dicts(base, override, V2_1)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == base
def test_merge_depends_on_mixed_syntax(self):
@ -2363,7 +2418,7 @@ web:
'depends_on': ['app3']
}
actual = config.merge_service_dicts(base, override, V2_1)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'busybox',
'depends_on': {
@ -2401,7 +2456,7 @@ web:
'labels': {'com.docker.compose.test': 'yes'}
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'busybox',
'pid': 'host',
@ -2417,7 +2472,7 @@ web:
}
override = {'secrets': ['other-src.txt']}
actual = config.merge_service_dicts(base, override, V3_1)
actual = config.merge_service_dicts(base, override, VERSION)
assert secret_sort(actual['secrets']) == secret_sort([
{'source': 'src.txt'},
{'source': 'other-src.txt'}
@ -2437,7 +2492,7 @@ web:
}
]
}
actual = config.merge_service_dicts(base, override, V3_1)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['secrets'] == override['secrets']
def test_merge_different_configs(self):
@ -2449,7 +2504,7 @@ web:
}
override = {'configs': ['other-src.txt']}
actual = config.merge_service_dicts(base, override, V3_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert secret_sort(actual['configs']) == secret_sort([
{'source': 'src.txt'},
{'source': 'other-src.txt'}
@ -2469,7 +2524,7 @@ web:
}
]
}
actual = config.merge_service_dicts(base, override, V3_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['configs'] == override['configs']
def test_merge_deploy(self):
@ -2484,7 +2539,7 @@ web:
}
}
}
actual = config.merge_service_dicts(base, override, V3_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['deploy'] == override['deploy']
def test_merge_deploy_override(self):
@ -2540,7 +2595,7 @@ web:
'update_config': {'max_failure_ratio': 0.712, 'parallelism': 4}
}
}
actual = config.merge_service_dicts(base, override, V3_5)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['deploy'] == {
'mode': 'replicated',
'endpoint_mode': 'vip',
@ -2596,7 +2651,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V3_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['credential_spec'] == override['credential_spec']
def test_merge_scale(self):
@ -2609,7 +2664,7 @@ web:
'scale': 4,
}
actual = config.merge_service_dicts(base, override, V2_2)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {'image': 'bar', 'scale': 4}
def test_merge_blkio_config(self):
@ -2644,7 +2699,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_2)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'bar',
'blkio_config': {
@ -2671,7 +2726,7 @@ web:
'extra_hosts': ['bar:5.6.7.8', 'foo:127.0.0.1']
}
actual = config.merge_service_dicts(base, override, V2_0)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['extra_hosts'] == {
'foo': '127.0.0.1',
'bar': '5.6.7.8',
@ -2695,7 +2750,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['healthcheck'] == {
'start_period': base['healthcheck']['start_period'],
'test': override['healthcheck']['test'],
@ -2721,7 +2776,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['healthcheck'] == {'disabled': True}
def test_merge_healthcheck_override_enables(self):
@ -2743,7 +2798,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['healthcheck'] == override['healthcheck']
def test_merge_device_cgroup_rules(self):
@ -2756,7 +2811,7 @@ web:
'device_cgroup_rules': ['c 7:128 rwm', 'f 0:128 n']
}
actual = config.merge_service_dicts(base, override, V2_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert sorted(actual['device_cgroup_rules']) == sorted(
['c 7:128 rwm', 'x 3:244 rw', 'f 0:128 n']
)
@ -2771,7 +2826,7 @@ web:
'isolation': 'hyperv',
}
actual = config.merge_service_dicts(base, override, V2_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {
'image': 'bar',
'isolation': 'hyperv',
@ -2793,7 +2848,7 @@ web:
}
}
actual = config.merge_service_dicts(base, override, V2_3)
actual = config.merge_service_dicts(base, override, VERSION)
assert actual['storage_opt'] == {
'size': '2G',
'readonly': 'false',
@ -3350,6 +3405,7 @@ class PortsTest(unittest.TestCase):
assert "non-unique" in exc.value.msg
@pytest.mark.skip(reason="Validator is one_off (generic error)")
def test_config_invalid_ports_format_validation(self):
for invalid_ports in self.INVALID_PORT_MAPPINGS:
with pytest.raises(ConfigurationError) as exc:
@ -4369,7 +4425,8 @@ class EnvTest(unittest.TestCase):
service_dict = config.load(
build_config_details(
{'foo': {'build': '.', 'volumes': ['$HOSTENV:$CONTAINERENV']}},
{'services': {
'foo': {'build': '.', 'volumes': ['$HOSTENV:$CONTAINERENV']}}},
"tests/fixtures/env",
)
).services[0]
@ -4377,7 +4434,8 @@ class EnvTest(unittest.TestCase):
service_dict = config.load(
build_config_details(
{'foo': {'build': '.', 'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}},
{'services': {
'foo': {'build': '.', 'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}}},
"tests/fixtures/env",
)
).services[0]
@ -4498,7 +4556,11 @@ class ExtendsTest(unittest.TestCase):
config.load(
build_config_details(
{
'web': {'image': 'busybox', 'extends': {}},
'version': '3',
'services':
{
'web': {'image': 'busybox', 'extends': {}},
}
},
'tests/fixtures/extends',
'filename.yml'
@ -4512,7 +4574,14 @@ class ExtendsTest(unittest.TestCase):
config.load(
build_config_details(
{
'web': {'image': 'busybox', 'extends': {'file': 'common.yml'}},
'version': '3',
'services':
{
'web': {
'image': 'busybox',
'extends': {'file': 'common.yml'}
}
}
},
'tests/fixtures/extends',
'filename.yml'
@ -4526,14 +4595,18 @@ class ExtendsTest(unittest.TestCase):
config.load(
build_config_details(
{
'web': {
'image': 'busybox',
'extends': {
'file': 'common.yml',
'service': 'web',
'rogue_key': 'is not allowed'
}
},
'version': '3',
'services':
{
'web': {
'image': 'busybox',
'extends': {
'file': 'common.yml',
'service': 'web',
'rogue_key': 'is not allowed'
}
},
}
},
'tests/fixtures/extends',
'filename.yml'
@ -4548,11 +4621,14 @@ class ExtendsTest(unittest.TestCase):
config.load(
build_config_details(
{
'web': {
'image': 'busybox',
'extends': {
'file': 1,
'service': 'web',
'version': '3',
'services': {
'web': {
'image': 'busybox',
'extends': {
'file': 1,
'service': 'web',
}
}
},
},
@ -5205,7 +5281,7 @@ class SerializeTest(unittest.TestCase):
}
}
assert denormalize_service_dict(service_dict, V3_0) == {
assert denormalize_service_dict(service_dict, VERSION) == {
'image': 'busybox',
'command': 'true',
'depends_on': ['service2', 'service3']
@ -5221,7 +5297,11 @@ class SerializeTest(unittest.TestCase):
}
}
assert denormalize_service_dict(service_dict, V2_1) == service_dict
assert denormalize_service_dict(service_dict, VERSION) == {
'image': 'busybox',
'command': 'true',
'depends_on': ['service2', 'service3']
}
def test_serialize_time(self):
data = {
@ -5255,7 +5335,7 @@ class SerializeTest(unittest.TestCase):
processed_service = config.process_service(config.ServiceConfig(
'.', 'test', 'test', service_dict
))
denormalized_service = denormalize_service_dict(processed_service, V2_3)
denormalized_service = denormalize_service_dict(processed_service, VERSION)
assert denormalized_service['healthcheck']['interval'] == '100s'
assert denormalized_service['healthcheck']['timeout'] == '30s'
assert denormalized_service['healthcheck']['start_period'] == '2090ms'
@ -5266,7 +5346,7 @@ class SerializeTest(unittest.TestCase):
}
image_digest = 'busybox@sha256:abcde'
assert denormalize_service_dict(service_dict, V3_0, image_digest) == {
assert denormalize_service_dict(service_dict, VERSION, image_digest) == {
'image': 'busybox@sha256:abcde'
}
@ -5275,7 +5355,7 @@ class SerializeTest(unittest.TestCase):
'image': 'busybox'
}
assert denormalize_service_dict(service_dict, V3_0) == {
assert denormalize_service_dict(service_dict, VERSION) == {
'image': 'busybox'
}
@ -5308,10 +5388,10 @@ class SerializeTest(unittest.TestCase):
serialized_service = serialized_config['services']['web']
assert secret_sort(serialized_service['secrets']) == secret_sort(service_dict['secrets'])
assert 'secrets' in serialized_config
assert serialized_config['secrets']['two'] == secrets_dict['two']
assert serialized_config['secrets']['two'] == {'external': True, 'name': 'two'}
def test_serialize_ports(self):
config_dict = config.Config(version=V2_0, services=[
config_dict = config.Config(version=VERSION, services=[
{
'ports': [types.ServicePort('80', '8080', None, None, None)],
'image': 'alpine',
@ -5320,10 +5400,10 @@ class SerializeTest(unittest.TestCase):
], volumes={}, networks={}, secrets={}, configs={})
serialized_config = yaml.safe_load(serialize_config(config_dict))
assert '8080:80/tcp' in serialized_config['services']['web']['ports']
assert [{'published': 8080, 'target': 80}] == serialized_config['services']['web']['ports']
def test_serialize_ports_with_ext_ip(self):
config_dict = config.Config(version=V3_5, services=[
config_dict = config.Config(version=VERSION, services=[
{
'ports': [types.ServicePort('80', '8080', None, None, '127.0.0.1')],
'image': 'alpine',
@ -5363,7 +5443,7 @@ class SerializeTest(unittest.TestCase):
serialized_service = serialized_config['services']['web']
assert secret_sort(serialized_service['configs']) == secret_sort(service_dict['configs'])
assert 'configs' in serialized_config
assert serialized_config['configs']['two'] == configs_dict['two']
assert serialized_config['configs']['two'] == {'external': True, 'name': 'two'}
def test_serialize_bool_string(self):
cfg = {

View file

@ -8,9 +8,7 @@ from compose.config.interpolation import Interpolator
from compose.config.interpolation import InvalidInterpolation
from compose.config.interpolation import TemplateWithDefaults
from compose.config.interpolation import UnsetRequiredSubstitution
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_4 as V3_4
from compose.const import COMPOSEFILE_V4 as VERSION
@pytest.fixture
@ -63,7 +61,7 @@ def test_interpolate_environment_variables_in_services(mock_env):
}
}
}
value = interpolate_environment_variables(V2_0, services, 'service', mock_env)
value = interpolate_environment_variables(VERSION, services, 'service', mock_env)
assert value == expected
@ -88,7 +86,7 @@ def test_interpolate_environment_variables_in_volumes(mock_env):
},
'other': {},
}
value = interpolate_environment_variables(V2_0, volumes, 'volume', mock_env)
value = interpolate_environment_variables(VERSION, volumes, 'volume', mock_env)
assert value == expected
@ -113,7 +111,7 @@ def test_interpolate_environment_variables_in_secrets(mock_env):
},
'other': {},
}
value = interpolate_environment_variables(V3_4, secrets, 'secret', mock_env)
value = interpolate_environment_variables(VERSION, secrets, 'secret', mock_env)
assert value == expected
@ -184,7 +182,7 @@ def test_interpolate_environment_services_convert_types_v2(mock_env):
}
}
value = interpolate_environment_variables(V2_3, entry, 'service', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert value == expected
@ -257,7 +255,7 @@ def test_interpolate_environment_services_convert_types_v3(mock_env):
}
}
value = interpolate_environment_variables(V3_4, entry, 'service', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert value == expected
@ -265,21 +263,21 @@ def test_interpolate_environment_services_convert_types_invalid(mock_env):
entry = {'service1': {'privileged': '${POSINT}'}}
with pytest.raises(ConfigurationError) as exc:
interpolate_environment_variables(V2_3, entry, 'service', mock_env)
interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert 'Error while attempting to convert service.service1.privileged to '\
'appropriate type: "50" is not a valid boolean value' in exc.exconly()
entry = {'service1': {'cpus': '${TRUE}'}}
with pytest.raises(ConfigurationError) as exc:
interpolate_environment_variables(V2_3, entry, 'service', mock_env)
interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert 'Error while attempting to convert service.service1.cpus to '\
'appropriate type: "True" is not a valid float' in exc.exconly()
entry = {'service1': {'ulimits': {'nproc': '${FLOAT}'}}}
with pytest.raises(ConfigurationError) as exc:
interpolate_environment_variables(V2_3, entry, 'service', mock_env)
interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert 'Error while attempting to convert service.service1.ulimits.nproc to '\
'appropriate type: "0.145" is not a valid integer' in exc.exconly()
@ -302,7 +300,7 @@ def test_interpolate_environment_network_convert_types(mock_env):
}
}
value = interpolate_environment_variables(V3_4, entry, 'network', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'network', mock_env)
assert value == expected
@ -319,13 +317,13 @@ def test_interpolate_environment_external_resource_convert_types(mock_env):
}
}
value = interpolate_environment_variables(V3_4, entry, 'network', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'network', mock_env)
assert value == expected
value = interpolate_environment_variables(V3_4, entry, 'volume', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'volume', mock_env)
assert value == expected
value = interpolate_environment_variables(V3_4, entry, 'secret', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'secret', mock_env)
assert value == expected
value = interpolate_environment_variables(V3_4, entry, 'config', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'config', mock_env)
assert value == expected
@ -356,7 +354,7 @@ def test_interpolate_service_name_uses_dot(mock_env):
}
}
value = interpolate_environment_variables(V3_4, entry, 'service', mock_env)
value = interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert value == expected

View file

@ -6,7 +6,7 @@ from compose.config.types import ServicePort
from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec
from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V4 as VERSION
def test_parse_extra_hosts_list():
@ -233,26 +233,26 @@ class TestVolumesFromSpec(object):
VolumeFromSpec.parse('unknown:format:ro', self.services, V1)
def test_parse_v2_from_service(self):
volume_from = VolumeFromSpec.parse('servicea', self.services, V2_0)
volume_from = VolumeFromSpec.parse('servicea', self.services, VERSION)
assert volume_from == VolumeFromSpec('servicea', 'rw', 'service')
def test_parse_v2_from_service_with_mode(self):
volume_from = VolumeFromSpec.parse('servicea:ro', self.services, V2_0)
volume_from = VolumeFromSpec.parse('servicea:ro', self.services, VERSION)
assert volume_from == VolumeFromSpec('servicea', 'ro', 'service')
def test_parse_v2_from_container(self):
volume_from = VolumeFromSpec.parse('container:foo', self.services, V2_0)
volume_from = VolumeFromSpec.parse('container:foo', self.services, VERSION)
assert volume_from == VolumeFromSpec('foo', 'rw', 'container')
def test_parse_v2_from_container_with_mode(self):
volume_from = VolumeFromSpec.parse('container:foo:ro', self.services, V2_0)
volume_from = VolumeFromSpec.parse('container:foo:ro', self.services, VERSION)
assert volume_from == VolumeFromSpec('foo', 'ro', 'container')
def test_parse_v2_invalid_type(self):
with pytest.raises(ConfigurationError) as exc:
VolumeFromSpec.parse('bogus:foo:ro', self.services, V2_0)
VolumeFromSpec.parse('bogus:foo:ro', self.services, VERSION)
assert "Unknown volumes_from type 'bogus'" in exc.exconly()
def test_parse_v2_invalid(self):
with pytest.raises(ConfigurationError):
VolumeFromSpec.parse('unknown:format:ro', self.services, V2_0)
VolumeFromSpec.parse('unknown:format:ro', self.services, VERSION)

View file

@ -14,9 +14,7 @@ from compose.config import ConfigurationError
from compose.config.config import Config
from compose.config.types import VolumeFromSpec
from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_4 as V2_4
from compose.const import COMPOSEFILE_V3_7 as V3_7
from compose.const import COMPOSEFILE_V4 as VERSION
from compose.const import DEFAULT_TIMEOUT
from compose.const import LABEL_SERVICE
from compose.container import Container
@ -29,6 +27,17 @@ from compose.service import ImageType
from compose.service import Service
def build_config(**kwargs):
return Config(
version=kwargs.get('version', VERSION),
services=kwargs.get('services'),
volumes=kwargs.get('volumes'),
networks=kwargs.get('networks'),
secrets=kwargs.get('secrets'),
configs=kwargs.get('configs'),
)
class ProjectTest(unittest.TestCase):
def setUp(self):
self.mock_client = mock.create_autospec(docker.APIClient)
@ -36,7 +45,7 @@ class ProjectTest(unittest.TestCase):
self.mock_client.api_version = docker.constants.DEFAULT_DOCKER_API_VERSION
def test_from_config_v1(self):
config = Config(
config = build_config(
version=V1,
services=[
{
@ -67,8 +76,7 @@ class ProjectTest(unittest.TestCase):
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
def test_from_config_v2(self):
config = Config(
version=V2_0,
config = build_config(
services=[
{
'name': 'web',
@ -174,8 +182,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[{
'name': 'test',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -202,8 +209,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[
{
'name': 'vol',
@ -230,8 +236,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=None,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[
{
'name': 'vol',
@ -540,7 +545,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
config_data=build_config(
version=V1,
services=[
{
@ -565,8 +570,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[
{
'name': 'test',
@ -596,8 +600,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[
{
'name': 'aaa',
@ -623,8 +626,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[
{
'name': 'foo',
@ -644,8 +646,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[
{
'name': 'foo',
@ -679,8 +680,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -697,8 +697,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
@ -748,8 +747,8 @@ class ProjectTest(unittest.TestCase):
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,
}
config_data = Config(
version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None
config_data = build_config(
services=[service_config], networks={}, volumes={}, secrets=None, configs=None
)
project = Project.from_config(name='test', client=self.mock_client, config_data=config_data)
@ -770,8 +769,7 @@ class ProjectTest(unittest.TestCase):
assert project.get_service('web').platform == 'linux/s390x'
def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self):
config_data = Config(
version=V3_7,
config_data = build_config(
services=[
{'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG},
{'name': 'db', 'image': BUSYBOX_IMAGE_WITH_TAG, 'stop_grace_period': '1s'},
@ -802,8 +800,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config(
name='test',
client=self.mock_client,
config_data=Config(
version=V2_0,
config_data=build_config(
services=[{
'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG,