Skip to content

Commit

Permalink
Allow docker.port to accept a pattern for the container name
Browse files Browse the repository at this point in the history
This modifies this function to allow a user to get results for all
containers matching a given pattern.
  • Loading branch information
terminalmage committed May 16, 2018
1 parent f5c3e2c commit 0d02cee
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 7 deletions.
32 changes: 25 additions & 7 deletions salt/modules/dockermod.py
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,16 @@ def port(name, private_port=None):
name
Container name or ID
.. versionchanged:: Fluorine
This value can now be a pattern expression (using the
pattern-matching characters defined in fnmatch_). If a pattern
expression is used, this function will return a dictionary mapping
container names which match the pattern to the mappings for those
containers. When no pattern expression is used, a dictionary of the
mappings for the specified container name will be returned.
.. _fnmatch: https://docs.python.org/2/library/fnmatch.html
private_port : None
If specified, get information for that specific port. Can be specified
either as a port number (i.e. ``5000``), or as a port number plus the
Expand All @@ -2104,12 +2114,10 @@ def port(name, private_port=None):
salt myminion docker.port mycontainer 5000
salt myminion docker.port mycontainer 5000/udp
'''
# docker.client.Client.port() doesn't do what we need, so just inspect the
# container and get the information from there. It's what they're already
# doing (poorly) anyway.
mappings = inspect_container(name).get('NetworkSettings', {}).get('Ports', {})
if not mappings:
return {}
pattern_used = bool(re.search(r'[*?\[]', name))
names = fnmatch.filter(list_containers(all=True), name) \
if pattern_used \
else [name]

if private_port is None:
pattern = '*'
Expand All @@ -2132,7 +2140,17 @@ def port(name, private_port=None):
except AttributeError:
raise SaltInvocationError(err)

return dict((x, mappings[x]) for x in fnmatch.filter(mappings, pattern))
ret = {}
for c_name in names:
# docker.client.Client.port() doesn't do what we need, so just inspect
# the container and get the information from there. It's what they're
# already doing (poorly) anyway.
mappings = inspect_container(c_name).get(
'NetworkSettings', {}).get('Ports', {})
ret[c_name] = dict((x, mappings[x])
for x in fnmatch.filter(mappings, pattern))

return ret.get(name, {}) if not pattern_used else ret


def ps_(filters=None, **kwargs):
Expand Down
101 changes: 101 additions & 0 deletions tests/unit/modules/test_dockermod.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,3 +1053,104 @@ def _run(**kwargs):
call('prune_volumes', filters={'label': ['foo', 'bar=baz']}),
]
)

def test_port(self):
'''
Test docker.port function. Note that this test case does not test what
happens when a specific container name is passed and that container
does not exist. When that happens, the Docker API will just raise a 404
error. Since we're using as side_effect to mock
docker.inspect_container, it would be meaningless to code raising an
exception into it and then test that we raised that exception.
'''
ports = {
'foo': {
'5555/tcp': [
{'HostIp': '0.0.0.0', 'HostPort': '32768'}
],
'6666/tcp': [
{'HostIp': '0.0.0.0', 'HostPort': '32769'}
],
},
'bar': {
'4444/udp': [
{'HostIp': '0.0.0.0', 'HostPort': '32767'}
],
'5555/tcp': [
{'HostIp': '0.0.0.0', 'HostPort': '32768'}
],
'6666/tcp': [
{'HostIp': '0.0.0.0', 'HostPort': '32769'}
],
},
'baz': {
'5555/tcp': [
{'HostIp': '0.0.0.0', 'HostPort': '32768'}
],
'6666/udp': [
{'HostIp': '0.0.0.0', 'HostPort': '32769'}
],
},
}
list_mock = MagicMock(return_value=['bar', 'baz', 'foo'])
inspect_mock = MagicMock(
side_effect=lambda x: {'NetworkSettings': {'Ports': ports.get(x)}}
)
with patch.object(docker_mod, 'list_containers', list_mock), \
patch.object(docker_mod, 'inspect_container', inspect_mock):

# Test with specific container name
ret = docker_mod.port('foo')
self.assertEqual(ret, ports['foo'])

# Test with specific container name and filtering on port
ret = docker_mod.port('foo', private_port='5555/tcp')
self.assertEqual(ret, {'5555/tcp': ports['foo']['5555/tcp']})

# Test using pattern expression
ret = docker_mod.port('ba*')
self.assertEqual(ret, {'bar': ports['bar'], 'baz': ports['baz']})
ret = docker_mod.port('ba?')
self.assertEqual(ret, {'bar': ports['bar'], 'baz': ports['baz']})
ret = docker_mod.port('ba[rz]')
self.assertEqual(ret, {'bar': ports['bar'], 'baz': ports['baz']})

# Test using pattern expression and port filtering
ret = docker_mod.port('ba*', private_port='6666/tcp')
self.assertEqual(
ret,
{'bar': {'6666/tcp': ports['bar']['6666/tcp']}, 'baz': {}}
)
ret = docker_mod.port('ba?', private_port='6666/tcp')
self.assertEqual(
ret,
{'bar': {'6666/tcp': ports['bar']['6666/tcp']}, 'baz': {}}
)
ret = docker_mod.port('ba[rz]', private_port='6666/tcp')
self.assertEqual(
ret,
{'bar': {'6666/tcp': ports['bar']['6666/tcp']}, 'baz': {}}
)
ret = docker_mod.port('*')
self.assertEqual(ret, ports)
ret = docker_mod.port('*', private_port='5555/tcp')
self.assertEqual(
ret,
{'foo': {'5555/tcp': ports['foo']['5555/tcp']},
'bar': {'5555/tcp': ports['bar']['5555/tcp']},
'baz': {'5555/tcp': ports['baz']['5555/tcp']}}
)
ret = docker_mod.port('*', private_port=6666)
self.assertEqual(
ret,
{'foo': {'6666/tcp': ports['foo']['6666/tcp']},
'bar': {'6666/tcp': ports['bar']['6666/tcp']},
'baz': {'6666/udp': ports['baz']['6666/udp']}}
)
ret = docker_mod.port('*', private_port='6666/tcp')
self.assertEqual(
ret,
{'foo': {'6666/tcp': ports['foo']['6666/tcp']},
'bar': {'6666/tcp': ports['bar']['6666/tcp']},
'baz': {}}
)

0 comments on commit 0d02cee

Please sign in to comment.