Improved version comparisons throughout the codebase

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2017-07-06 17:25:41 -07:00 committed by Joffrey F
parent 6ff6528d45
commit 56a23bfcd2
13 changed files with 97 additions and 79 deletions

View File

@ -18,6 +18,7 @@ from ..const import COMPOSEFILE_V1 as V1
from ..utils import build_string_dict from ..utils import build_string_dict
from ..utils import parse_nanoseconds_int from ..utils import parse_nanoseconds_int
from ..utils import splitdrive from ..utils import splitdrive
from ..version import ComposeVersion
from .environment import env_vars_from_file from .environment import env_vars_from_file
from .environment import Environment from .environment import Environment
from .environment import split_env from .environment import split_env
@ -188,15 +189,16 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
if version == '1': if version == '1':
raise ConfigurationError( raise ConfigurationError(
'Version in "{}" is invalid. {}' 'Version in "{}" is invalid. {}'
.format(self.filename, VERSION_EXPLANATION)) .format(self.filename, VERSION_EXPLANATION)
)
if version == '2': if version == '2':
version = const.COMPOSEFILE_V2_0 return const.COMPOSEFILE_V2_0
if version == '3': if version == '3':
version = const.COMPOSEFILE_V3_0 return const.COMPOSEFILE_V3_0
return version return ComposeVersion(version)
def get_service(self, name): def get_service(self, name):
return self.get_service_dicts()[name] return self.get_service_dicts()[name]
@ -496,7 +498,7 @@ def process_config_file(config_file, environment, service_name=None):
'service', 'service',
environment) environment)
if config_file.version != V1: if config_file.version > V1:
processed_config = dict(config_file.config) processed_config = dict(config_file.config)
processed_config['services'] = services processed_config['services'] = services
processed_config['volumes'] = interpolate_config_section( processed_config['volumes'] = interpolate_config_section(
@ -509,14 +511,13 @@ def process_config_file(config_file, environment, service_name=None):
config_file.get_networks(), config_file.get_networks(),
'network', 'network',
environment) environment)
if config_file.version in (const.COMPOSEFILE_V3_1, const.COMPOSEFILE_V3_2, if config_file.version >= const.COMPOSEFILE_V3_1:
const.COMPOSEFILE_V3_3):
processed_config['secrets'] = interpolate_config_section( processed_config['secrets'] = interpolate_config_section(
config_file, config_file,
config_file.get_secrets(), config_file.get_secrets(),
'secrets', 'secrets',
environment) environment)
if config_file.version in (const.COMPOSEFILE_V3_3): if config_file.version >= const.COMPOSEFILE_V3_3:
processed_config['configs'] = interpolate_config_section( processed_config['configs'] = interpolate_config_section(
config_file, config_file,
config_file.get_configs(), config_file.get_configs(),

View File

@ -4,7 +4,7 @@ from __future__ import unicode_literals
VERSION_EXPLANATION = ( VERSION_EXPLANATION = (
'You might be seeing this error because you\'re using the wrong Compose file version. ' 'You might be seeing this error because you\'re using the wrong Compose file version. '
'Either specify a supported version ("2.0", "2.1", "3.0", "3.1", "3.2") and place ' 'Either specify a supported version (e.g "2.2" or "3.3") and place '
'your service definitions under the `services` key, or omit the `version` key ' 'your service definitions under the `services` key, or omit the `version` key '
'and place your service definitions at the root of the file to use ' 'and place your service definitions at the root of the file to use '
'version 1.\nFor more on the Compose file format versions, see ' 'version 1.\nFor more on the Compose file format versions, see '

View File

@ -7,7 +7,6 @@ from string import Template
import six import six
from .errors import ConfigurationError from .errors import ConfigurationError
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_0
@ -28,7 +27,7 @@ class Interpolator(object):
def interpolate_environment_variables(version, config, section, environment): def interpolate_environment_variables(version, config, section, environment):
if version in (V2_0, V1): if version <= V2_0:
interpolator = Interpolator(Template, environment) interpolator = Interpolator(Template, environment)
else: else:
interpolator = Interpolator(TemplateWithDefaults, environment) interpolator = Interpolator(TemplateWithDefaults, environment)

View File

@ -7,9 +7,8 @@ import yaml
from compose.config import types from compose.config import types
from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_1 as V2_1 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_V3_0 as V3_0
from compose.const import COMPOSEFILE_V3_2 as V3_2 from compose.const import COMPOSEFILE_V3_2 as V3_2
from compose.const import COMPOSEFILE_V3_3 as V3_3
def serialize_config_type(dumper, data): def serialize_config_type(dumper, data):
@ -41,7 +40,7 @@ yaml.SafeDumper.add_representer(six.text_type, serialize_string)
def denormalize_config(config, image_digests=None): def denormalize_config(config, image_digests=None):
result = {'version': V2_1 if config.version == V1 else config.version} result = {'version': str(V2_1) if config.version == V1 else str(config.version)}
denormalized_services = [ denormalized_services = [
denormalize_service_dict( denormalize_service_dict(
service_dict, service_dict,
@ -107,7 +106,7 @@ def denormalize_service_dict(service_dict, version, image_digest=None):
if version == V1 and 'network_mode' not in service_dict: if version == V1 and 'network_mode' not in service_dict:
service_dict['network_mode'] = 'bridge' service_dict['network_mode'] = 'bridge'
if 'depends_on' in service_dict and version not in (V2_1, V2_2): if 'depends_on' in service_dict and (version < V2_1 or version >= V3_0):
service_dict['depends_on'] = sorted([ service_dict['depends_on'] = sorted([
svc for svc in service_dict['depends_on'].keys() svc for svc in service_dict['depends_on'].keys()
]) ])
@ -122,7 +121,7 @@ def denormalize_service_dict(service_dict, version, image_digest=None):
service_dict['healthcheck']['timeout'] service_dict['healthcheck']['timeout']
) )
if 'ports' in service_dict and version not in (V3_2, V3_3): if 'ports' in service_dict and version < V3_2:
service_dict['ports'] = [ service_dict['ports'] = [
p.legacy_repr() if isinstance(p, types.ServicePort) else p p.legacy_repr() if isinstance(p, types.ServicePort) else p
for p in service_dict['ports'] for p in service_dict['ports']

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
import sys import sys
from .version import ComposeVersion
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
HTTP_TIMEOUT = 60 HTTP_TIMEOUT = 60
IMAGE_EVENTS = ['delete', 'import', 'load', 'pull', 'push', 'save', 'tag', 'untag'] IMAGE_EVENTS = ['delete', 'import', 'load', 'pull', 'push', 'save', 'tag', 'untag']
@ -19,15 +21,15 @@ NANOCPUS_SCALE = 1000000000
SECRETS_PATH = '/run/secrets' SECRETS_PATH = '/run/secrets'
COMPOSEFILE_V1 = '1' COMPOSEFILE_V1 = ComposeVersion('1')
COMPOSEFILE_V2_0 = '2.0' COMPOSEFILE_V2_0 = ComposeVersion('2.0')
COMPOSEFILE_V2_1 = '2.1' COMPOSEFILE_V2_1 = ComposeVersion('2.1')
COMPOSEFILE_V2_2 = '2.2' COMPOSEFILE_V2_2 = ComposeVersion('2.2')
COMPOSEFILE_V3_0 = '3.0' COMPOSEFILE_V3_0 = ComposeVersion('3.0')
COMPOSEFILE_V3_1 = '3.1' COMPOSEFILE_V3_1 = ComposeVersion('3.1')
COMPOSEFILE_V3_2 = '3.2' COMPOSEFILE_V3_2 = ComposeVersion('3.2')
COMPOSEFILE_V3_3 = '3.3' COMPOSEFILE_V3_3 = ComposeVersion('3.3')
API_VERSIONS = { API_VERSIONS = {
COMPOSEFILE_V1: '1.21', COMPOSEFILE_V1: '1.21',

10
compose/version.py Normal file
View File

@ -0,0 +1,10 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from distutils.version import LooseVersion
class ComposeVersion(LooseVersion):
""" A hashable version object """
def __hash__(self):
return hash(self.vstring)

View File

@ -850,8 +850,13 @@ class CLITestCase(DockerClientTestCase):
# Two networks were created: back and front # Two networks were created: back and front
assert sorted(n['Name'].split('/')[-1] for n in networks) == [back_name, front_name] assert sorted(n['Name'].split('/')[-1] for n in networks) == [back_name, front_name]
back_network = [n for n in networks if n['Name'] == back_name][0] # lookup by ID instead of name in case of duplicates
front_network = [n for n in networks if n['Name'] == front_name][0] back_network = self.client.inspect_network(
[n for n in networks if n['Name'] == back_name][0]['Id']
)
front_network = self.client.inspect_network(
[n for n in networks if n['Name'] == front_name][0]['Id']
)
web_container = self.project.get_service('web').containers()[0] web_container = self.project.get_service('web').containers()[0]
app_container = self.project.get_service('app').containers()[0] app_container = self.project.get_service('app').containers()[0]

View File

@ -34,6 +34,7 @@ from compose.project import Project
from compose.project import ProjectError from compose.project import ProjectError
from compose.service import ConvergenceStrategy from compose.service import ConvergenceStrategy
from tests.integration.testcases import v2_1_only 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 v2_only
from tests.integration.testcases import v3_only from tests.integration.testcases import v3_only
@ -150,7 +151,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest', name='composetest',
client=self.client, client=self.client,
config_data=load_config({ config_data=load_config({
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'net': { 'net': {
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -178,7 +179,7 @@ class ProjectTest(DockerClientTestCase):
return Project.from_config( return Project.from_config(
name='composetest', name='composetest',
config_data=load_config({ config_data=load_config({
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'web': { 'web': {
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -820,7 +821,7 @@ class ProjectTest(DockerClientTestCase):
def test_up_with_enable_ipv6(self): def test_up_with_enable_ipv6(self):
self.require_api_version('1.23') self.require_api_version('1.23')
config_data = build_config( config_data = build_config(
version=V2_0, version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -1003,7 +1004,7 @@ class ProjectTest(DockerClientTestCase):
network_name = 'network_with_label' network_name = 'network_with_label'
config_data = build_config( config_data = build_config(
version=V2_0, version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -1063,7 +1064,7 @@ class ProjectTest(DockerClientTestCase):
volume_name = 'volume_with_label' volume_name = 'volume_with_label'
config_data = build_config( config_data = build_config(
version=V2_0, version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -1103,7 +1104,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yml', 'base.yml',
{ {
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'simple': {'image': 'busybox:latest', 'command': 'top'}, 'simple': {'image': 'busybox:latest', 'command': 'top'},
'another': { 'another': {
@ -1122,7 +1123,7 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile( override_file = config.ConfigFile(
'override.yml', 'override.yml',
{ {
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'another': { 'another': {
'logging': { 'logging': {
@ -1155,7 +1156,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yml', 'base.yml',
{ {
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'simple': { 'simple': {
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -1168,7 +1169,7 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile( override_file = config.ConfigFile(
'override.yml', 'override.yml',
{ {
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'simple': { 'simple': {
'ports': ['1234:1234'] 'ports': ['1234:1234']
@ -1186,6 +1187,7 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers() containers = project.containers()
self.assertEqual(len(containers), 1) self.assertEqual(len(containers), 1)
@v2_2_only()
def test_project_up_config_scale(self): def test_project_up_config_scale(self):
config_data = build_config( config_data = build_config(
version=V2_2, version=V2_2,
@ -1454,7 +1456,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yml', 'base.yml',
{ {
'version': V2_0, 'version': str(V2_0),
'services': { 'services': {
'simple': { 'simple': {
'image': 'busybox:latest', 'image': 'busybox:latest',

View File

@ -1,11 +1,10 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import functools
import os import os
import pytest
from docker.utils import version_lt from docker.utils import version_lt
from pytest import skip
from .. import unittest from .. import unittest
from ..helpers import is_cluster from ..helpers import is_cluster
@ -17,7 +16,8 @@ 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_0
from compose.const import COMPOSEFILE_V2_0 as V2_1 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_2 as V2_2
from compose.const import COMPOSEFILE_V3_2 as V3_2 from compose.const import COMPOSEFILE_V3_0 as V3_0
from compose.const import COMPOSEFILE_V3_3 as V3_3
from compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
from compose.progress_stream import stream_output from compose.progress_stream import stream_output
from compose.service import Service from compose.service import Service
@ -43,7 +43,7 @@ def get_links(container):
def engine_max_version(): def engine_max_version():
if 'DOCKER_VERSION' not in os.environ: if 'DOCKER_VERSION' not in os.environ:
return V3_2 return V3_3
version = os.environ['DOCKER_VERSION'].partition('-')[0] version = os.environ['DOCKER_VERSION'].partition('-')[0]
if version_lt(version, '1.10'): if version_lt(version, '1.10'):
return V1 return V1
@ -51,37 +51,32 @@ def engine_max_version():
return V2_0 return V2_0
if version_lt(version, '1.13'): if version_lt(version, '1.13'):
return V2_1 return V2_1
return V3_2 if version_lt(version, '17.06'):
return V2_2
return V3_3
def build_version_required_decorator(ignored_versions): def min_version_skip(version):
def decorator(f): return pytest.mark.skipif(
@functools.wraps(f) engine_max_version() < version,
def wrapper(self, *args, **kwargs): reason="Engine version %s is too low" % version
max_version = engine_max_version() )
if max_version in ignored_versions:
skip("Engine version %s is too low" % max_version)
return
return f(self, *args, **kwargs)
return wrapper
return decorator
def v2_only(): def v2_only():
return build_version_required_decorator((V1,)) return min_version_skip(V2_0)
def v2_1_only(): def v2_1_only():
return build_version_required_decorator((V1, V2_0)) return min_version_skip(V2_1)
def v2_2_only(): def v2_2_only():
return build_version_required_decorator((V1, V2_0, V2_1)) return min_version_skip(V2_0)
def v3_only(): def v3_only():
return build_version_required_decorator((V1, V2_0, V2_1, V2_2)) return min_version_skip(V3_0)
class DockerClientTestCase(unittest.TestCase): class DockerClientTestCase(unittest.TestCase):
@ -137,7 +132,7 @@ class DockerClientTestCase(unittest.TestCase):
def require_api_version(self, minimum): def require_api_version(self, minimum):
api_version = self.client.version()['ApiVersion'] api_version = self.client.version()['ApiVersion']
if version_lt(api_version, minimum): if version_lt(api_version, minimum):
skip("API version is too low ({} < {})".format(api_version, minimum)) pytest.skip("API version is too low ({} < {})".format(api_version, minimum))
def get_volume_data(self, volume_name): def get_volume_data(self, volume_name):
if not is_cluster(self.client): if not is_cluster(self.client):

View File

@ -9,6 +9,7 @@ from compose import bundle
from compose import service from compose import service
from compose.cli.errors import UserError from compose.cli.errors import UserError
from compose.config.config import Config from compose.config.config import Config
from compose.const import COMPOSEFILE_V2_0 as V2_0
@pytest.fixture @pytest.fixture
@ -74,7 +75,7 @@ def test_to_bundle():
{'name': 'b', 'build': './b'}, {'name': 'b', 'build': './b'},
] ]
config = Config( config = Config(
version=2, version=V2_0,
services=services, services=services,
volumes={'special': {}}, volumes={'special': {}},
networks={'extra': {}}, networks={'extra': {}},

View File

@ -378,7 +378,7 @@ class ConfigTest(unittest.TestCase):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yaml', 'base.yaml',
{ {
'version': V2_1, 'version': str(V2_1),
'services': { 'services': {
'web': { 'web': {
'image': 'example/web', 'image': 'example/web',
@ -830,7 +830,7 @@ class ConfigTest(unittest.TestCase):
service = config.load( service = config.load(
build_config_details( build_config_details(
{ {
'version': V3_3, 'version': str(V3_3),
'services': { 'services': {
'web': { 'web': {
'build': { 'build': {
@ -1523,7 +1523,7 @@ class ConfigTest(unittest.TestCase):
def test_isolation_option(self): def test_isolation_option(self):
actual = config.load(build_config_details({ actual = config.load(build_config_details({
'version': V2_1, 'version': str(V2_1),
'services': { 'services': {
'web': { 'web': {
'image': 'win10', 'image': 'win10',
@ -4122,7 +4122,7 @@ class SerializeTest(unittest.TestCase):
assert serialized_config['secrets']['two'] == secrets_dict['two'] assert serialized_config['secrets']['two'] == secrets_dict['two']
def test_serialize_ports(self): def test_serialize_ports(self):
config_dict = config.Config(version='2.0', services=[ config_dict = config.Config(version=V2_0, services=[
{ {
'ports': [types.ServicePort('80', '8080', None, None, None)], 'ports': [types.ServicePort('80', '8080', None, None, None)],
'image': 'alpine', 'image': 'alpine',

View File

@ -8,6 +8,8 @@ from compose.config.interpolation import interpolate_environment_variables
from compose.config.interpolation import Interpolator from compose.config.interpolation import Interpolator
from compose.config.interpolation import InvalidInterpolation from compose.config.interpolation import InvalidInterpolation
from compose.config.interpolation import TemplateWithDefaults from compose.config.interpolation import TemplateWithDefaults
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V3_1 as V3_1
@pytest.fixture @pytest.fixture
@ -50,7 +52,7 @@ def test_interpolate_environment_variables_in_services(mock_env):
} }
} }
} }
value = interpolate_environment_variables("2.0", services, 'service', mock_env) value = interpolate_environment_variables(V2_0, services, 'service', mock_env)
assert value == expected assert value == expected
@ -75,7 +77,7 @@ def test_interpolate_environment_variables_in_volumes(mock_env):
}, },
'other': {}, 'other': {},
} }
value = interpolate_environment_variables("2.0", volumes, 'volume', mock_env) value = interpolate_environment_variables(V2_0, volumes, 'volume', mock_env)
assert value == expected assert value == expected
@ -100,7 +102,7 @@ def test_interpolate_environment_variables_in_secrets(mock_env):
}, },
'other': {}, 'other': {},
} }
value = interpolate_environment_variables("3.1", secrets, 'volume', mock_env) value = interpolate_environment_variables(V3_1, secrets, 'volume', mock_env)
assert value == expected assert value == expected

View File

@ -10,6 +10,8 @@ from .. import mock
from .. import unittest from .. import unittest
from compose.config.config import Config from compose.config.config import Config
from compose.config.types import VolumeFromSpec 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 LABEL_SERVICE from compose.const import LABEL_SERVICE
from compose.container import Container from compose.container import Container
from compose.project import Project from compose.project import Project
@ -21,9 +23,9 @@ class ProjectTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.mock_client = mock.create_autospec(docker.APIClient) self.mock_client = mock.create_autospec(docker.APIClient)
def test_from_config(self): def test_from_config_v1(self):
config = Config( config = Config(
version=None, version=V1,
services=[ services=[
{ {
'name': 'web', 'name': 'web',
@ -53,7 +55,7 @@ class ProjectTest(unittest.TestCase):
def test_from_config_v2(self): def test_from_config_v2(self):
config = Config( config = Config(
version=2, version=V2_0,
services=[ services=[
{ {
'name': 'web', 'name': 'web',
@ -166,7 +168,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=None, version=V2_0,
services=[{ services=[{
'name': 'test', 'name': 'test',
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -194,7 +196,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=None, version=V2_0,
services=[ services=[
{ {
'name': 'vol', 'name': 'vol',
@ -221,7 +223,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=None, client=None,
config_data=Config( config_data=Config(
version=None, version=V2_0,
services=[ services=[
{ {
'name': 'vol', 'name': 'vol',
@ -361,7 +363,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=None, version=V1,
services=[ services=[
{ {
'name': 'test', 'name': 'test',
@ -386,7 +388,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=None, version=V2_0,
services=[ services=[
{ {
'name': 'test', 'name': 'test',
@ -417,7 +419,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=None, version=V2_0,
services=[ services=[
{ {
'name': 'aaa', 'name': 'aaa',
@ -444,7 +446,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=2, version=V2_0,
services=[ services=[
{ {
'name': 'foo', 'name': 'foo',
@ -465,7 +467,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=2, version=V2_0,
services=[ services=[
{ {
'name': 'foo', 'name': 'foo',
@ -500,7 +502,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version=None, version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': 'busybox:latest', 'image': 'busybox:latest',
@ -518,7 +520,7 @@ class ProjectTest(unittest.TestCase):
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=Config(
version='2', version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': 'busybox:latest', 'image': 'busybox:latest',