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

View File

@ -4,7 +4,7 @@ from __future__ import unicode_literals
VERSION_EXPLANATION = (
'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 '
'and place your service definitions at the root of the file to use '
'version 1.\nFor more on the Compose file format versions, see '

View File

@ -7,7 +7,6 @@ from string import Template
import six
from .errors import ConfigurationError
from compose.const import COMPOSEFILE_V1 as V1
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):
if version in (V2_0, V1):
if version <= V2_0:
interpolator = Interpolator(Template, environment)
else:
interpolator = Interpolator(TemplateWithDefaults, environment)

View File

@ -7,9 +7,8 @@ import yaml
from compose.config import types
from compose.const import COMPOSEFILE_V1 as V1
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_3 as V3_3
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):
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 = [
denormalize_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:
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([
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']
)
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'] = [
p.legacy_repr() if isinstance(p, types.ServicePort) else p
for p in service_dict['ports']

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
import sys
from .version import ComposeVersion
DEFAULT_TIMEOUT = 10
HTTP_TIMEOUT = 60
IMAGE_EVENTS = ['delete', 'import', 'load', 'pull', 'push', 'save', 'tag', 'untag']
@ -19,15 +21,15 @@ NANOCPUS_SCALE = 1000000000
SECRETS_PATH = '/run/secrets'
COMPOSEFILE_V1 = '1'
COMPOSEFILE_V2_0 = '2.0'
COMPOSEFILE_V2_1 = '2.1'
COMPOSEFILE_V2_2 = '2.2'
COMPOSEFILE_V1 = ComposeVersion('1')
COMPOSEFILE_V2_0 = ComposeVersion('2.0')
COMPOSEFILE_V2_1 = ComposeVersion('2.1')
COMPOSEFILE_V2_2 = ComposeVersion('2.2')
COMPOSEFILE_V3_0 = '3.0'
COMPOSEFILE_V3_1 = '3.1'
COMPOSEFILE_V3_2 = '3.2'
COMPOSEFILE_V3_3 = '3.3'
COMPOSEFILE_V3_0 = ComposeVersion('3.0')
COMPOSEFILE_V3_1 = ComposeVersion('3.1')
COMPOSEFILE_V3_2 = ComposeVersion('3.2')
COMPOSEFILE_V3_3 = ComposeVersion('3.3')
API_VERSIONS = {
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
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]
front_network = [n for n in networks if n['Name'] == front_name][0]
# lookup by ID instead of name in case of duplicates
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]
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.service import ConvergenceStrategy
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
@ -150,7 +151,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
client=self.client,
config_data=load_config({
'version': V2_0,
'version': str(V2_0),
'services': {
'net': {
'image': 'busybox:latest',
@ -178,7 +179,7 @@ class ProjectTest(DockerClientTestCase):
return Project.from_config(
name='composetest',
config_data=load_config({
'version': V2_0,
'version': str(V2_0),
'services': {
'web': {
'image': 'busybox:latest',
@ -820,7 +821,7 @@ class ProjectTest(DockerClientTestCase):
def test_up_with_enable_ipv6(self):
self.require_api_version('1.23')
config_data = build_config(
version=V2_0,
version=V2_1,
services=[{
'name': 'web',
'image': 'busybox:latest',
@ -1003,7 +1004,7 @@ class ProjectTest(DockerClientTestCase):
network_name = 'network_with_label'
config_data = build_config(
version=V2_0,
version=V2_1,
services=[{
'name': 'web',
'image': 'busybox:latest',
@ -1063,7 +1064,7 @@ class ProjectTest(DockerClientTestCase):
volume_name = 'volume_with_label'
config_data = build_config(
version=V2_0,
version=V2_1,
services=[{
'name': 'web',
'image': 'busybox:latest',
@ -1103,7 +1104,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile(
'base.yml',
{
'version': V2_0,
'version': str(V2_0),
'services': {
'simple': {'image': 'busybox:latest', 'command': 'top'},
'another': {
@ -1122,7 +1123,7 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile(
'override.yml',
{
'version': V2_0,
'version': str(V2_0),
'services': {
'another': {
'logging': {
@ -1155,7 +1156,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile(
'base.yml',
{
'version': V2_0,
'version': str(V2_0),
'services': {
'simple': {
'image': 'busybox:latest',
@ -1168,7 +1169,7 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile(
'override.yml',
{
'version': V2_0,
'version': str(V2_0),
'services': {
'simple': {
'ports': ['1234:1234']
@ -1186,6 +1187,7 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers()
self.assertEqual(len(containers), 1)
@v2_2_only()
def test_project_up_config_scale(self):
config_data = build_config(
version=V2_2,
@ -1454,7 +1456,7 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile(
'base.yml',
{
'version': V2_0,
'version': str(V2_0),
'services': {
'simple': {
'image': 'busybox:latest',

View File

@ -1,11 +1,10 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import functools
import os
import pytest
from docker.utils import version_lt
from pytest import skip
from .. import unittest
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_1
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.progress_stream import stream_output
from compose.service import Service
@ -43,7 +43,7 @@ def get_links(container):
def engine_max_version():
if 'DOCKER_VERSION' not in os.environ:
return V3_2
return V3_3
version = os.environ['DOCKER_VERSION'].partition('-')[0]
if version_lt(version, '1.10'):
return V1
@ -51,37 +51,32 @@ def engine_max_version():
return V2_0
if version_lt(version, '1.13'):
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 decorator(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
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 min_version_skip(version):
return pytest.mark.skipif(
engine_max_version() < version,
reason="Engine version %s is too low" % version
)
def v2_only():
return build_version_required_decorator((V1,))
return min_version_skip(V2_0)
def v2_1_only():
return build_version_required_decorator((V1, V2_0))
return min_version_skip(V2_1)
def v2_2_only():
return build_version_required_decorator((V1, V2_0, V2_1))
return min_version_skip(V2_0)
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):
@ -137,7 +132,7 @@ class DockerClientTestCase(unittest.TestCase):
def require_api_version(self, minimum):
api_version = self.client.version()['ApiVersion']
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):
if not is_cluster(self.client):

View File

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

View File

@ -378,7 +378,7 @@ class ConfigTest(unittest.TestCase):
base_file = config.ConfigFile(
'base.yaml',
{
'version': V2_1,
'version': str(V2_1),
'services': {
'web': {
'image': 'example/web',
@ -830,7 +830,7 @@ class ConfigTest(unittest.TestCase):
service = config.load(
build_config_details(
{
'version': V3_3,
'version': str(V3_3),
'services': {
'web': {
'build': {
@ -1523,7 +1523,7 @@ class ConfigTest(unittest.TestCase):
def test_isolation_option(self):
actual = config.load(build_config_details({
'version': V2_1,
'version': str(V2_1),
'services': {
'web': {
'image': 'win10',
@ -4122,7 +4122,7 @@ class SerializeTest(unittest.TestCase):
assert serialized_config['secrets']['two'] == secrets_dict['two']
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)],
'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 InvalidInterpolation
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
@ -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
@ -75,7 +77,7 @@ def test_interpolate_environment_variables_in_volumes(mock_env):
},
'other': {},
}
value = interpolate_environment_variables("2.0", volumes, 'volume', mock_env)
value = interpolate_environment_variables(V2_0, volumes, 'volume', mock_env)
assert value == expected
@ -100,7 +102,7 @@ def test_interpolate_environment_variables_in_secrets(mock_env):
},
'other': {},
}
value = interpolate_environment_variables("3.1", secrets, 'volume', mock_env)
value = interpolate_environment_variables(V3_1, secrets, 'volume', mock_env)
assert value == expected

View File

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