Merge "Introduce merge and preserve_properties for config_files"
This commit is contained in:
commit
98e4a4bf56
@ -57,25 +57,42 @@ class ConfigFileBadState(ExitingException):
|
||||
|
||||
class ConfigFile(object):
|
||||
|
||||
def __init__(self, source, dest, owner, perm, optional=False):
|
||||
def __init__(self, source, dest, owner=None, perm=None, optional=False,
|
||||
preserve_properties=False, merge=False):
|
||||
self.source = source
|
||||
self.dest = dest
|
||||
self.owner = owner
|
||||
self.perm = perm
|
||||
self.optional = optional
|
||||
self.merge = merge
|
||||
self.preserve_properties = preserve_properties
|
||||
|
||||
def __str__(self):
|
||||
return '<ConfigFile source:"{}" dest:"{}">'.format(self.source,
|
||||
self.dest)
|
||||
|
||||
def _copy_dir(self, source, dest):
|
||||
LOG.info('Copying dir from %s to %s', source, dest)
|
||||
shutil.copytree(source, dest)
|
||||
for root, dirs, files in os.walk(dest):
|
||||
for dir_ in dirs:
|
||||
self._set_permission(os.path.join(root, dir_))
|
||||
for file_ in files:
|
||||
self._set_permission(os.path.join(root, file_))
|
||||
def _copy_file(self, source, dest):
|
||||
self._delete_path(dest)
|
||||
# dest endswith / means copy the <source> to <dest> folder
|
||||
LOG.info('Copying file from %s to %s', source, dest)
|
||||
shutil.copy(source, dest)
|
||||
self._set_properties(source, dest)
|
||||
|
||||
def _merge_directories(self, source, dest):
|
||||
LOG.info('Copying %s to %s', source, dest)
|
||||
if os.path.isdir(source):
|
||||
if os.path.exists(dest) and not os.path.isdir(dest):
|
||||
self._delete_path(dest)
|
||||
if not os.path.isdir(dest):
|
||||
os.makedirs(dest)
|
||||
self._set_properties(source, dest)
|
||||
|
||||
dir_content = os.listdir(source)
|
||||
for to_copy in dir_content:
|
||||
self._merge_directories(os.path.join(source, to_copy),
|
||||
os.path.join(dest, to_copy))
|
||||
else:
|
||||
self._copy_file(source, dest)
|
||||
|
||||
def _delete_path(self, path):
|
||||
if not os.path.exists(path):
|
||||
@ -92,13 +109,18 @@ class ConfigFile(object):
|
||||
if not os.path.exists(parent_path):
|
||||
os.makedirs(parent_path)
|
||||
|
||||
def _copy_file(self, source, dest):
|
||||
# dest endswith / means copy the <source> to <dest> folder
|
||||
LOG.info('Coping file from %s to %s', source, dest)
|
||||
shutil.copy(source, dest)
|
||||
self._set_permission(dest)
|
||||
def _set_properties(self, source, dest):
|
||||
if self.preserve_properties:
|
||||
self._set_properties_from_file(source, dest)
|
||||
else:
|
||||
self._set_properties_from_conf(dest)
|
||||
|
||||
def _set_permission(self, path):
|
||||
def _set_properties_from_file(self, source, dest):
|
||||
shutil.copystat(source, dest)
|
||||
stat = os.stat(source)
|
||||
os.chown(dest, stat.st_uid, stat.st_gid)
|
||||
|
||||
def _set_properties_from_conf(self, path):
|
||||
handle_permissions({'owner': self.owner, 'path': path,
|
||||
'perm': self.perm})
|
||||
|
||||
@ -118,12 +140,10 @@ class ConfigFile(object):
|
||||
# otherwise means copy the source to dest
|
||||
if dest.endswith(os.sep):
|
||||
dest = os.path.join(dest, os.path.basename(source))
|
||||
self._delete_path(dest)
|
||||
if not self.merge:
|
||||
self._delete_path(dest)
|
||||
self._create_parent_dirs(dest)
|
||||
if os.path.isdir(source):
|
||||
self._copy_dir(source, dest)
|
||||
else:
|
||||
self._copy_file(source, dest)
|
||||
self._merge_directories(source, dest)
|
||||
|
||||
def _cmp_file(self, source, dest):
|
||||
# check exsit
|
||||
@ -203,7 +223,7 @@ class ConfigFile(object):
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
required_keys = {'source', 'dest', 'owner', 'perm'}
|
||||
required_keys = {'source', 'dest'}
|
||||
|
||||
if 'command' not in config:
|
||||
raise InvalidConfig('Config is missing required "command" key')
|
||||
@ -214,6 +234,10 @@ def validate_config(config):
|
||||
if not data.viewkeys() >= required_keys:
|
||||
message = 'Config is missing required keys: %s' % required_keys
|
||||
raise InvalidConfig(message)
|
||||
if ('owner' not in data or 'perm' not in data) \
|
||||
and not data.get('preserve_properties', False):
|
||||
raise InvalidConfig(
|
||||
'Config needs preserve_properties or owner and perm')
|
||||
|
||||
|
||||
def validate_source(data):
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- New `merge` option to config_files, that copies all the files recursively
|
||||
inside the container without removing the existing files.
|
||||
- New `preserve_properties` option to config_files, that keeps all of the
|
||||
files and directories attributes (permissions, ownership, timestamps) when
|
||||
copying them in the container.
|
@ -115,14 +115,34 @@ class ConfigFileTest(base.BaseTestCase):
|
||||
mock_isdir.assert_called_with(config_file.dest)
|
||||
mock_remove.assert_called_with(config_file.dest)
|
||||
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch.object(set_configs, 'handle_permissions')
|
||||
def test_set_permission(self,
|
||||
mock_handle_permissions,
|
||||
mock_chmod):
|
||||
@mock.patch('shutil.copystat')
|
||||
@mock.patch('os.stat')
|
||||
@mock.patch('os.chown')
|
||||
def test_set_properties_from_file(self,
|
||||
mock_chown,
|
||||
mock_stat,
|
||||
mock_copystat):
|
||||
|
||||
stat_result = mock.MagicMock()
|
||||
mock_stat.return_value = stat_result
|
||||
|
||||
config_file = copy.deepcopy(FAKE_CONFIG_FILE)
|
||||
config_file._set_permission(config_file.dest)
|
||||
config_file._set_properties_from_file(config_file.source,
|
||||
config_file.dest)
|
||||
|
||||
mock_copystat.assert_called_with(config_file.source, config_file.dest)
|
||||
mock_stat.assert_called_with(config_file.source)
|
||||
mock_chown.assert_called_with(config_file.dest, stat_result.st_uid,
|
||||
stat_result.st_gid)
|
||||
|
||||
@mock.patch('os.chmod')
|
||||
@mock.patch.object(set_configs, 'handle_permissions')
|
||||
def test_set_properties_from_conf(self,
|
||||
mock_handle_permissions,
|
||||
mock_chmod):
|
||||
|
||||
config_file = copy.deepcopy(FAKE_CONFIG_FILE)
|
||||
config_file._set_properties_from_conf(config_file.dest)
|
||||
mock_handle_permissions.assert_called_with({'owner': 'user1',
|
||||
'path': config_file.dest,
|
||||
'perm': '0644'})
|
||||
@ -167,14 +187,14 @@ class ConfigFileTest(base.BaseTestCase):
|
||||
mock_copy_file.assert_called_with(config_file.source,
|
||||
config_file.dest)
|
||||
|
||||
@mock.patch.object(set_configs.ConfigFile, '_copy_dir')
|
||||
@mock.patch.object(set_configs.ConfigFile, '_merge_directories')
|
||||
@mock.patch('os.path.isdir', return_value=True)
|
||||
@mock.patch.object(set_configs.ConfigFile, '_create_parent_dirs')
|
||||
@mock.patch.object(set_configs.ConfigFile, '_delete_path')
|
||||
@mock.patch('glob.glob')
|
||||
def test_copy_one_source_dir(self, mock_glob, mock_delete_path,
|
||||
mock_create_parent_dirs, mock_isdir,
|
||||
mock_copy_dir):
|
||||
mock_merge_directories):
|
||||
config_file = copy.deepcopy(FAKE_CONFIG_FILE)
|
||||
|
||||
mock_glob.return_value = [config_file.source]
|
||||
@ -184,9 +204,8 @@ class ConfigFileTest(base.BaseTestCase):
|
||||
mock_glob.assert_called_with(config_file.source)
|
||||
mock_delete_path.assert_called_with(config_file.dest)
|
||||
mock_create_parent_dirs.assert_called_with(config_file.dest)
|
||||
mock_isdir.assert_called_with(config_file.source)
|
||||
mock_copy_dir.assert_called_with(config_file.source,
|
||||
config_file.dest)
|
||||
mock_merge_directories.assert_called_with(config_file.source,
|
||||
config_file.dest)
|
||||
|
||||
@mock.patch.object(set_configs.ConfigFile, '_copy_file')
|
||||
@mock.patch('os.path.isdir', return_value=False)
|
||||
|
Loading…
Reference in New Issue
Block a user