mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #29300 from ticosax/dockerng-volumes
[dockerng] Add support for volume management in dockerng
This commit is contained in:
commit
5ec7947595
4 changed files with 396 additions and 6 deletions
|
@ -1421,10 +1421,14 @@ def _validate_input(kwargs,
|
|||
raise SaltInvocationError(err)
|
||||
|
||||
if not os.path.isabs(host_path):
|
||||
raise SaltInvocationError(
|
||||
'Host path {0} in bind {1} is not absolute'
|
||||
.format(host_path, bind)
|
||||
)
|
||||
if os.path.sep in host_path:
|
||||
raise SaltInvocationError(
|
||||
'Host path {0} in bind {1} is not absolute'
|
||||
.format(container_path, bind)
|
||||
)
|
||||
log.warn('Host path {0} in bind {1} is not absolute,'
|
||||
' assuming it is a docker volume.'.format(host_path,
|
||||
bind))
|
||||
if not os.path.isabs(container_path):
|
||||
raise SaltInvocationError(
|
||||
'Container path {0} in bind {1} is not absolute'
|
||||
|
@ -4304,6 +4308,7 @@ def networks(names=None, ids=None):
|
|||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def create_network(name, driver=None):
|
||||
'''
|
||||
Create a new network
|
||||
|
@ -4419,6 +4424,107 @@ def disconnect_container_from_network(container, network_id):
|
|||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
# Volume Management
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def volumes(filters=None):
|
||||
'''
|
||||
List existing volumes
|
||||
|
||||
.. versionadded:: 2015.8.4
|
||||
|
||||
filters
|
||||
There is one available filter: dangling=true
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.volumes filters="{'dangling': True}"
|
||||
'''
|
||||
response = _client_wrapper('volumes', filters=filters)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def create_volume(name, driver=None, driver_opts=None):
|
||||
'''
|
||||
Create a new volume
|
||||
|
||||
.. versionadded:: 2015.8.4
|
||||
|
||||
name
|
||||
name of volume
|
||||
|
||||
driver
|
||||
Driver of the volume
|
||||
|
||||
driver_opts
|
||||
Options for the driver volume
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.create_volume my_volume driver=local
|
||||
'''
|
||||
response = _client_wrapper('create_volume', name, driver=driver,
|
||||
driver_opts=driver_opts)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def remove_volume(name):
|
||||
'''
|
||||
Remove a volume
|
||||
|
||||
.. versionadded:: 2015.8.4
|
||||
|
||||
name
|
||||
Name of volume
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.remove_volume my_volume
|
||||
'''
|
||||
response = _client_wrapper('remove_volume', name)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def inspect_volume(name):
|
||||
'''
|
||||
Inspect Volume
|
||||
|
||||
.. versionadded:: 2015.8.4
|
||||
|
||||
name
|
||||
Name of volume
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.inspect_volume my_volume
|
||||
'''
|
||||
response = _client_wrapper('inspect_volume', name)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
# Functions to manage container state
|
||||
@_refresh_mine_cache
|
||||
|
|
|
@ -1969,7 +1969,7 @@ def network_present(name, driver=None, containers=None):
|
|||
Ensure that a network is present.
|
||||
|
||||
name
|
||||
Name of the netwotk
|
||||
Name of the network
|
||||
|
||||
driver
|
||||
Type of driver for that network.
|
||||
|
@ -2045,7 +2045,7 @@ def network_absent(name, driver=None):
|
|||
Ensure that a network is absent.
|
||||
|
||||
name
|
||||
Name of the netwotk
|
||||
Name of the network
|
||||
|
||||
Usage Examples:
|
||||
|
||||
|
@ -2081,6 +2081,132 @@ def network_absent(name, driver=None):
|
|||
return ret
|
||||
|
||||
|
||||
def volume_present(name, driver=None, driver_opts=None):
|
||||
'''
|
||||
Ensure that a volume is present.
|
||||
|
||||
.. versionadded:: 2015.8.4
|
||||
|
||||
name
|
||||
Name of the volume
|
||||
|
||||
driver
|
||||
Type of driver for that volume.
|
||||
|
||||
driver_opts
|
||||
Option for tha volume driver
|
||||
|
||||
Usage Examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
volume_foo:
|
||||
dockerng.volume_present
|
||||
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
volume_bar:
|
||||
dockerng.volume_present
|
||||
- name: bar
|
||||
- driver: local
|
||||
- driver_opts:
|
||||
foo: bar
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
volume_bar:
|
||||
dockerng.volume_present
|
||||
- name: bar
|
||||
- driver: local
|
||||
- driver_opts:
|
||||
- foo: bar
|
||||
- option: value
|
||||
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': ''}
|
||||
if salt.utils.is_dictlist(driver_opts):
|
||||
driver_opts = salt.utils.repack_dictlist(driver_opts)
|
||||
volumes = [v for v in __salt__['dockerng.volumes']()['Volumes'] if v['Name'] == name]
|
||||
if not volumes:
|
||||
try:
|
||||
ret['changes']['created'] = __salt__['dockerng.create_volume'](
|
||||
name, driver=driver, driver_opts=driver_opts)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to create volume \'{0}\': {1}'
|
||||
.format(name, exc))
|
||||
return ret
|
||||
else:
|
||||
result = True
|
||||
ret['result'] = result
|
||||
return ret
|
||||
# volume exits, check if driver is the same.
|
||||
volume = volumes[0]
|
||||
if volume['Driver'] != driver:
|
||||
try:
|
||||
ret['changes']['removed'] = __salt__['dockerng.remove_volume'](name)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to remove volume \'{0}\': {1}'
|
||||
.format(name, exc))
|
||||
return ret
|
||||
else:
|
||||
try:
|
||||
ret['changes']['created'] = __salt__['dockerng.create_volume'](
|
||||
name, driver=driver, driver_opts=driver_opts)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to create volume \'{0}\': {1}'
|
||||
.format(name, exc))
|
||||
return ret
|
||||
else:
|
||||
result = True
|
||||
ret['result'] = result
|
||||
return ret
|
||||
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Volume \'{0}\' already exists.'.format(name)
|
||||
return ret
|
||||
|
||||
|
||||
def volume_absent(name, driver=None):
|
||||
'''
|
||||
Ensure that a volume is absent.
|
||||
|
||||
.. versionadded:: 2015.8.4
|
||||
|
||||
name
|
||||
Name of the volume
|
||||
|
||||
Usage Examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
volume_foo:
|
||||
dockerng.volume_absent
|
||||
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': ''}
|
||||
|
||||
volumes = [v for v in __salt__['dockerng.volumes']()['Volumes'] if v['Name'] == name]
|
||||
if not volumes:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Volume \'{0}\' already absent'.format(name)
|
||||
return ret
|
||||
|
||||
try:
|
||||
ret['changes']['removed'] = __salt__['dockerng.remove_volume'](name)
|
||||
ret['result'] = True
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to remove volume \'{0}\': {1}'
|
||||
.format(name, exc))
|
||||
return ret
|
||||
|
||||
|
||||
def mod_watch(name, sfun=None, **kwargs):
|
||||
if sfun == 'running':
|
||||
watch_kwargs = copy.deepcopy(kwargs)
|
||||
|
|
|
@ -421,6 +421,94 @@ class DockerngTestCase(TestCase):
|
|||
client.disconnect_container_from_network.assert_called_once_with(
|
||||
'container', 'foo')
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_list_volumes(self, *args):
|
||||
'''
|
||||
test list volumes.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.volumes(
|
||||
filters={'dangling': [True]},
|
||||
)
|
||||
client.volumes.assert_called_once_with(
|
||||
filters={'dangling': [True]},
|
||||
)
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_create_volume(self, *args):
|
||||
'''
|
||||
test create volume.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.create_volume(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
driver_opts={},
|
||||
)
|
||||
client.create_volume.assert_called_once_with(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
driver_opts={},
|
||||
)
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_remove_volume(self, *args):
|
||||
'''
|
||||
test remove volume.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.remove_volume('foo')
|
||||
client.remove_volume.assert_called_once_with('foo')
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_inspect_volume(self, *args):
|
||||
'''
|
||||
test inspect volume.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.inspect_volume('foo')
|
||||
client.inspect_volume.assert_called_once_with('foo')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
|
|
|
@ -502,6 +502,76 @@ class DockerngTestCase(TestCase):
|
|||
'removed': 'removed'},
|
||||
'result': True})
|
||||
|
||||
def test_volume_present(self):
|
||||
'''
|
||||
Test dockerng.volume_present
|
||||
'''
|
||||
dockerng_create_volume = Mock(return_value='created')
|
||||
__salt__ = {'dockerng.create_volume': dockerng_create_volume,
|
||||
'dockerng.volumes': Mock(return_value={'Volumes': []}),
|
||||
}
|
||||
with patch.dict(dockerng_state.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
ret = dockerng_state.volume_present(
|
||||
'volume_foo',
|
||||
)
|
||||
dockerng_create_volume.assert_called_with('volume_foo',
|
||||
driver=None,
|
||||
driver_opts=None)
|
||||
self.assertEqual(ret, {'name': 'volume_foo',
|
||||
'comment': '',
|
||||
'changes': {'created': 'created'},
|
||||
'result': True})
|
||||
|
||||
def test_volume_present_with_another_driver(self):
|
||||
'''
|
||||
Test dockerng.volume_present
|
||||
'''
|
||||
dockerng_create_volume = Mock(return_value='created')
|
||||
dockerng_remove_volume = Mock(return_value='removed')
|
||||
__salt__ = {'dockerng.create_volume': dockerng_create_volume,
|
||||
'dockerng.remove_volume': dockerng_remove_volume,
|
||||
'dockerng.volumes': Mock(return_value={
|
||||
'Volumes': [{'Name': 'volume_foo',
|
||||
'Driver': 'foo'}]}),
|
||||
}
|
||||
with patch.dict(dockerng_state.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
ret = dockerng_state.volume_present(
|
||||
'volume_foo',
|
||||
driver='bar'
|
||||
)
|
||||
dockerng_remove_volume.assert_called_with('volume_foo')
|
||||
dockerng_create_volume.assert_called_with('volume_foo',
|
||||
driver='bar',
|
||||
driver_opts=None)
|
||||
self.assertEqual(ret, {'name': 'volume_foo',
|
||||
'comment': '',
|
||||
'changes': {'created': 'created',
|
||||
'removed': 'removed'},
|
||||
'result': True})
|
||||
|
||||
def test_volume_absent(self):
|
||||
'''
|
||||
Test dockerng.volume_absent
|
||||
'''
|
||||
dockerng_remove_volume = Mock(return_value='removed')
|
||||
__salt__ = {'dockerng.remove_volume': dockerng_remove_volume,
|
||||
'dockerng.volumes': Mock(return_value={
|
||||
'Volumes': [{'Name': 'volume_foo'}]}),
|
||||
}
|
||||
with patch.dict(dockerng_state.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
ret = dockerng_state.volume_absent(
|
||||
'volume_foo',
|
||||
)
|
||||
dockerng_remove_volume.assert_called_with('volume_foo')
|
||||
self.assertEqual(ret, {'name': 'volume_foo',
|
||||
'comment': '',
|
||||
'changes': {'removed': 'removed'},
|
||||
'result': True})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(DockerngTestCase, needs_daemon=False)
|
||||
|
|
Loading…
Add table
Reference in a new issue