Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compose/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ def load_mapping(config_files, get_func, entity_type):
config['driver_opts']
)

if 'labels' in config:
config['labels'] = parse_labels(config['labels'])

return mapping


Expand Down
4 changes: 3 additions & 1 deletion compose/config/config_schema_v2.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@
"name": {"type": "string"}
},
"additionalProperties": false
}
},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
},
Expand All @@ -268,6 +269,7 @@
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/list_or_dict"},
"additionalProperties": false
},
"additionalProperties": false
Expand Down
5 changes: 4 additions & 1 deletion compose/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

class Network(object):
def __init__(self, client, project, name, driver=None, driver_opts=None,
ipam=None, external_name=None, internal=False):
ipam=None, external_name=None, internal=False, labels=None):
self.client = client
self.project = project
self.name = name
Expand All @@ -24,6 +24,7 @@ def __init__(self, client, project, name, driver=None, driver_opts=None,
self.ipam = create_ipam_config_from_dict(ipam)
self.external_name = external_name
self.internal = internal
self.labels = labels

def ensure(self):
if self.external_name:
Expand Down Expand Up @@ -70,6 +71,7 @@ def ensure(self):
options=self.driver_opts,
ipam=self.ipam,
internal=self.internal,
labels=self.labels,
)

def remove(self):
Expand Down Expand Up @@ -118,6 +120,7 @@ def build_networks(name, config_data, client):
ipam=data.get('ipam'),
external_name=data.get('external_name'),
internal=data.get('internal'),
labels=data.get('labels'),
)
for network_name, data in network_config.items()
}
Expand Down
8 changes: 5 additions & 3 deletions compose/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@

class Volume(object):
def __init__(self, client, project, name, driver=None, driver_opts=None,
external_name=None):
external_name=None, labels=None):
self.client = client
self.project = project
self.name = name
self.driver = driver
self.driver_opts = driver_opts
self.external_name = external_name
self.labels = labels

def create(self):
return self.client.create_volume(
self.full_name, self.driver, self.driver_opts
self.full_name, self.driver, self.driver_opts, labels=self.labels
)

def remove(self):
Expand Down Expand Up @@ -68,7 +69,8 @@ def from_config(cls, name, config_data, client):
name=vol_name,
driver=data.get('driver'),
driver_opts=data.get('driver_opts'),
external_name=data.get('external_name')
external_name=data.get('external_name'),
labels=data.get('labels')
)
for vol_name, data in config_volumes.items()
}
Expand Down
41 changes: 41 additions & 0 deletions tests/acceptance/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from tests.integration.testcases import DockerClientTestCase
from tests.integration.testcases import get_links
from tests.integration.testcases import pull_busybox
from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_only


Expand Down Expand Up @@ -767,6 +768,46 @@ def test_up_with_external_default_network(self):
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'

self.base_dir = 'tests/fixtures/networks'
self._project = get_project(self.base_dir, [filename])

self.dispatch(['-f', filename, 'up', '-d'], returncode=0)

network_with_label = '{}_network_with_label'.format(self.project.name)

networks = [
n for n in self.client.networks()
if n['Name'].startswith('{}_'.format(self.project.name))
]

assert [n['Name'] for n in networks] == [network_with_label]

assert networks[0]['Labels'] == {'label_key': 'label_val'}

@v2_1_only()
def test_up_with_volume_labels(self):
filename = 'volume-label.yml'

self.base_dir = 'tests/fixtures/volumes'
self._project = get_project(self.base_dir, [filename])

self.dispatch(['-f', filename, 'up', '-d'], returncode=0)

volume_with_label = '{}_volume_with_label'.format(self.project.name)

volumes = [
v for v in self.client.volumes().get('Volumes', [])
if v['Name'].startswith('{}_'.format(self.project.name))
]

assert [v['Name'] for v in volumes] == [volume_with_label]

assert volumes[0]['Labels'] == {'label_key': 'label_val'}

@v2_only()
def test_up_no_services(self):
self.base_dir = 'tests/fixtures/no-services'
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/networks/network-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "2.1"

services:
web:
image: busybox
command: top
networks:
- network_with_label

networks:
network_with_label:
labels:
- "label_key=label_val"
13 changes: 13 additions & 0 deletions tests/fixtures/volumes/volume-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "2.1"

services:
web:
image: busybox
command: top
volumes:
- volume_with_label:/data

volumes:
volume_with_label:
labels:
- "label_key=label_val"
76 changes: 76 additions & 0 deletions tests/integration/project_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,42 @@ def test_project_up_with_network_internal(self):

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 = config.Config(
version=V2_0,
services=[{
'name': 'web',
'image': 'busybox:latest',
'networks': {network_name: None}
}],
volumes={},
networks={
network_name: {'labels': {'label_key': 'label_val'}}
}
)

project = Project.from_config(
client=self.client,
name='composetest',
config_data=config_data
)

project.up()

networks = [
n for n in self.client.networks()
if n['Name'].startswith('composetest_')
]

assert [n['Name'] for n in networks] == ['composetest_{}'.format(network_name)]

assert networks[0]['Labels'] == {'label_key': 'label_val'}

@v2_only()
def test_project_up_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
Expand All @@ -847,6 +883,46 @@ def test_project_up_volumes(self):
self.assertEqual(volume_data['Name'], full_vol_name)
self.assertEqual(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 = config.Config(
version=V2_0,
services=[{
'name': 'web',
'image': 'busybox:latest',
'volumes': [VolumeSpec.parse('{}:/data'.format(volume_name))]
}],
volumes={
volume_name: {
'labels': {
'label_key': 'label_val'
}
}
},
networks={},
)

project = Project.from_config(
client=self.client,
name='composetest',
config_data=config_data,
)

project.up()

volumes = [
v for v in self.client.volumes().get('Volumes', [])
if v['Name'].startswith('composetest_')
]

assert [v['Name'] for v in volumes] == ['composetest_{}'.format(volume_name)]

assert volumes[0]['Labels'] == {'label_key': 'label_val'}

@v2_only()
def test_project_up_logging_with_multiple_files(self):
base_file = config.ConfigFile(
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,59 @@ def test_load_config_link_local_ips_network(self):
}
}

def test_load_config_volume_and_network_labels(self):
base_file = config.ConfigFile(
'base.yaml',
{
'version': '2.1',
'services': {
'web': {
'image': 'example/web',
},
},
'networks': {
'with_label': {
'labels': {
'label_key': 'label_val'
}
}
},
'volumes': {
'with_label': {
'labels': {
'label_key': 'label_val'
}
}
}
}
)

details = config.ConfigDetails('.', [base_file])
network_dict = config.load(details).networks
volume_dict = config.load(details).volumes

self.assertEqual(
network_dict,
{
'with_label': {
'labels': {
'label_key': 'label_val'
}
}
}
)

self.assertEqual(
volume_dict,
{
'with_label': {
'labels': {
'label_key': 'label_val'
}
}
}
)

def test_load_config_invalid_service_names(self):
for invalid_name in ['?not?allowed', ' ', '', '!', '/', '\xe2']:
with pytest.raises(ConfigurationError) as exc:
Expand Down