Skip to content

Commit

Permalink
Copy file with correct permissions and owner (#58)
Browse files Browse the repository at this point in the history
* Copy file with correct permissions and owner

* Patch Set #2

- move function to operatingsystem module to be consistent with
  os.stat
- add function get_file_stats
- add positive and negative tests

* Patch Set #3

- fix tests

* fix

* Patch Set #4

- change function names
- get octal permissions from stat

* Patch Set #5

- fix tests
- decode all output to UTF-8

* Patch Set #6

- remove decode of output

* Patch Set #7

- fix functions names under copy_to function

* Patch Set #8

- give possbility to change permission and ownership
  of the file, when file copied from other resource
I believe it no need to check if ownership is a tuple,
because we explicitly define it under docstring

* Patch Set #9

- fix call to function fs.chown
  • Loading branch information
cynepco3hahue authored and lukas-bednar committed Jun 20, 2016
1 parent ede0c96 commit 4326f0e
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 24 deletions.
6 changes: 4 additions & 2 deletions rrmngmnt/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from rrmngmnt.service import Service

from rrmngmnt import errors
from rrmngmnt.service import Service


class FileSystem(Service):
Expand All @@ -10,11 +11,12 @@ class FileSystem(Service):
"""
def _exec_command(self, cmd):
host_executor = self.host.executor()
rc, _, err = host_executor.run_cmd(cmd)
rc, out, err = host_executor.run_cmd(cmd)
if rc:
raise errors.CommandExecutionFailure(
cmd=cmd, executor=host_executor, rc=rc, err=err
)
return out

def _exec_file_test(self, op, path):
return self.host.executor().run_cmd(
Expand Down
27 changes: 18 additions & 9 deletions rrmngmnt/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
It should hold methods / properties which returns you Instance of specific
Service hosted on that Host.
"""
import os
import copy
import os
import socket
import netaddr
import warnings

from rrmngmnt import ssh
import netaddr

from rrmngmnt import errors
from rrmngmnt import power_manager
from rrmngmnt import ssh
from rrmngmnt.common import fqdn2ip
from rrmngmnt.network import Network
from rrmngmnt.storage import NFSService, LVMService
from rrmngmnt.service import Systemd, SysVinit, InitCtl
from rrmngmnt.resource import Resource
from rrmngmnt.filesystem import FileSystem
from rrmngmnt.package_manager import PackageManagerProxy
from rrmngmnt.network import Network
from rrmngmnt.operatingsystem import OperatingSystem
from rrmngmnt.package_manager import PackageManagerProxy
from rrmngmnt.resource import Resource
from rrmngmnt.service import Systemd, SysVinit, InitCtl
from rrmngmnt.storage import NFSService, LVMService


class Host(Resource):
Expand Down Expand Up @@ -243,7 +244,7 @@ def run_command(
)
return rc, out, err

def copy_to(self, resource, src, dst):
def copy_to(self, resource, src, dst, mode=None, ownership=None):
"""
Copy to host from another resource
Expand All @@ -253,12 +254,20 @@ def copy_to(self, resource, src, dst):
:type src: str
:param dst: path to destination
:type dst: str
:param mode: file permissions
:type mode: str
:param ownership: file ownership(ex. ('root', 'root'))
:type ownership: tuple
"""
with resource.executor().session() as resource_session:
with self.executor().session() as host_session:
with resource_session.open_file(src, 'rb') as resource_file:
with host_session.open_file(dst, 'wb') as host_file:
host_file.write(resource_file.read())
if mode:
self.fs.chmod(path=dst, mode=mode)
if ownership:
self.fs.chown(dst, *ownership)

def _create_service(self, name, timeout):
for provider in self.default_service_providers:
Expand Down
124 changes: 111 additions & 13 deletions rrmngmnt/operatingsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@ def __init__(self, host):
self._release_info = None
self._dist = None

def get_release_str(self):
cmd = ['cat', '/etc/system-release']
executor = self.host.executor()
rc, out, err = executor.run_cmd(cmd)
def _exec_command(self, cmd, err_msg=None):
host_executor = self.host.executor()
rc, out, err = host_executor.run_cmd(cmd)
if err_msg:
err = "{err_msg}: {err}".format(err_msg=err_msg, err=err)
if rc:
raise errors.CommandExecutionFailure(
executor, cmd, rc,
"Failed to obtain release string: {0}".format(err)
executor=host_executor, cmd=cmd, rc=rc, err=err
)
return out

def get_release_str(self):
cmd = ['cat', '/etc/system-release']
out = self._exec_command(
cmd=cmd, err_msg="Failed to obtain release string"
)
return out.strip()

@property
Expand Down Expand Up @@ -92,13 +99,9 @@ def get_distribution(self):
"python", "-c",
"import platform;print(','.join(platform.linux_distribution()))"
]
executor = self.host.executor()
rc, out, err = executor.run_cmd(cmd)
if rc:
raise errors.CommandExecutionFailure(
executor, cmd, rc,
"Failed to obtain release info: {0}".format(err)
)
out = self._exec_command(
cmd=cmd, err_msg="Failed to obtain release info"
)
Distribution = namedtuple('Distribution', values)
return Distribution(*[i.strip() for i in out.split(",")])

Expand All @@ -107,3 +110,98 @@ def distribution(self):
if not self._dist:
self._dist = self.get_distribution()
return self._dist

def stat(self, path):
"""
Get file or directory stats
:return: file stats
:rtype: collections.namedtuple
"""
type_map = {
'st_mode': ('0x%f', lambda x: int(x, 16)),
'st_ino': ('%i', int),
'st_dev': ('%d', int),
'st_nlink': ('%h', int),
'st_uid': ('%u', int),
'st_gid': ('%g', int),
'st_size': ('%s', int),
'st_atime': ('%X', int),
'st_mtime': ('%Y', int),
'st_ctime': ('%W', int),
'st_blocks': ('%b', int),
'st_blksize': ('%o', int),
'st_rdev': ('%t', int),
}
posix_stat_result = namedtuple(
"posix_stat_result", type_map.keys()
)

cmd = [
"stat",
"-c",
",".join(["%s=%s" % (k, v[0]) for k, v in type_map.items()]),
path
]
out = self._exec_command(cmd=cmd)
out = out.strip().split(',')

data = {}

for pair in out:
key, value = pair.split('=')
data[key] = type_map[key][1](value)

return posix_stat_result(**data)

def get_file_permissions(self, path):
"""
Get file permissions
:return: file permission in octal form(example 0644)
:rtype: str
"""
cmd = ["stat", "-c", "%a", path]
return self._exec_command(cmd=cmd).strip()

def get_file_owner(self, path):
"""
Get file user and group owner name
:return: file user and group owner names(example ['root', 'root'])
:rtype: list
"""
cmd = ["stat", "-c", "%U %G", path]
return self._exec_command(cmd=cmd).split()

def user_exists(self, user_name):
"""
Check if user exist on system
:param user_name: user name
:type user_name: str
:return: True, if user exist, otherwise False
:rtype: bool
"""
try:
cmd = ["id", "-u", user_name]
self._exec_command(cmd=cmd)
except errors.CommandExecutionFailure:
return False
return True

def group_exists(self, group_name):
""""
Check if group exist on system
:param group_name: group name
:type group_name: str
:return: True, if group exist, otherwise False
:rtype: bool
"""
try:
cmd = ["id", "-g", group_name]
self._exec_command(cmd=cmd)
except errors.CommandExecutionFailure:
return False
return True
151 changes: 151 additions & 0 deletions tests/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,154 @@ def test_get_release_info(self):
info = self.get_host().os.release_info
assert 'VERSION_ID' not in info
assert len(info) == 4


type_map = {
'st_mode': ('0x%f', lambda x: int(x, 16)),
'st_ino': ('%i', int),
'st_dev': ('%d', int),
'st_nlink': ('%h', int),
'st_uid': ('%u', int),
'st_gid': ('%g', int),
'st_size': ('%s', int),
'st_atime': ('%X', int),
'st_mtime': ('%Y', int),
'st_ctime': ('%W', int),
'st_blocks': ('%b', int),
'st_blksize': ('%o', int),
'st_rdev': ('%t', int),
}


class TestFileStats(object):
data = {
'stat -c %s /tmp/test' %
','.join(["%s=%s" % (k, v[0]) for k, v in type_map.items()]): (
0,
(
'st_ctime=0,'
'st_rdev=0,'
'st_blocks=1480,'
'st_nlink=1,'
'st_gid=0,'
'st_dev=2051,'
'st_ino=11804680,'
'st_mode=0x81a4,'
'st_mtime=1463487739,'
'st_blksize=4096,'
'st_size=751764,'
'st_uid=0,'
'st_atime=1463487196'
),
''
),
'stat -c "%U %G" /tmp/test': (
0,
'root root',
''
),
'stat -c %a /tmp/test': (
0,
'644\n',
''
),
'id -u root': (
0,
'',
''
),
'id -g root': (
0,
'',
''
)
}
files = {}

@classmethod
def setup_class(cls):
fake_cmd_data(cls.data, cls.files)

def get_host(self, ip='1.1.1.1'):
return Host(ip)

def test_get_file_stats(self):
file_stats = self.get_host().os.stat('/tmp/test')
assert (
file_stats.st_mode == 33188 and
file_stats.st_uid == 0 and
file_stats.st_gid == 0
)

def test_get_file_owner(self):
file_user, file_group = self.get_host().os.get_file_owner('/tmp/test')
assert file_user == 'root' and file_group == 'root'

def test_get_file_permissions(self):
assert self.get_host().os.get_file_permissions('/tmp/test') == '644'

def test_user_exists(self):
assert self.get_host().os.user_exists('root')

def test_group_exists(self):
assert self.get_host().os.group_exists('root')


class TestFileStatsNegative(object):
data = {
'stat -c %s /tmp/negative_test' %
','.join(["%s=%s" % (k, v[0]) for k, v in type_map.items()]): (
1,
'',
'cannot stat ‘/tmp/negative_test’: No such file or directory'
),
'stat -c "%U %G" /tmp/negative_test': (
1,
'',
'cannot stat ‘/tmp/negative_test’: No such file or directory'
),
'stat -c %a /tmp/negative_test': (
1,
'',
'cannot stat ‘/tmp/negative_test’: No such file or directory'
),
'id -u test': (
1,
'',
''
),
'id -g test': (
1,
'',
''
)
}
files = {}

@classmethod
def setup_class(cls):
fake_cmd_data(cls.data, cls.files)

def get_host(self, ip='1.1.1.1'):
return Host(ip)

def test_get_file_stats(self):
with pytest.raises(errors.CommandExecutionFailure) as ex_info:
self.get_host().os.stat('/tmp/negative_test')
assert "No such file" in str(ex_info.value)

def test_get_file_owner(self):
with pytest.raises(errors.CommandExecutionFailure) as ex_info:
self.get_host().os.get_file_owner('/tmp/negative_test')
assert "No such file" in str(ex_info.value)

def test_get_file_permissions(self):
with pytest.raises(errors.CommandExecutionFailure) as ex_info:
self.get_host().os.get_file_permissions('/tmp/negative_test')
assert "No such file" in str(ex_info.value)

def test_user_exists(self):
assert not self.get_host().os.user_exists('test')

def test_group_exists(self):
assert not self.get_host().os.group_exists('test')

0 comments on commit 4326f0e

Please sign in to comment.