Commit 55b356b1 authored by Tarek Ziadé's avatar Tarek Ziadé

#6516 added owner/group support for tarfiles in Distutils

parent 4c666278
...@@ -14,15 +14,55 @@ from distutils.spawn import spawn ...@@ -14,15 +14,55 @@ from distutils.spawn import spawn
from distutils.dir_util import mkpath from distutils.dir_util import mkpath
from distutils import log from distutils import log
def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0): try:
from pwd import getpwnam
except AttributeError:
getpwnam = None
try:
from grp import getgrnam
except AttributeError:
getgrnam = None
def _get_gid(name):
"""Returns a gid, given a group name."""
if getgrnam is None or name is None:
return None
try:
result = getgrnam(name)
except KeyError:
result = None
if result is not None:
return result[2]
return None
def _get_uid(name):
"""Returns an uid, given a user name."""
if getpwnam is None or name is None:
return None
try:
result = getpwnam(name)
except KeyError:
result = None
if result is not None:
return result[2]
return None
def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
owner=None, group=None):
"""Create a (possibly compressed) tar file from all the files under """Create a (possibly compressed) tar file from all the files under
'base_dir'. 'base_dir'.
'compress' must be "gzip" (the default), "compress", "bzip2", or None. 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
Both "tar" and the compression utility named by 'compress' must be on (compress will be deprecated in Python 3.2)
the default program search path, so this is probably Unix-specific.
'owner' and 'group' can be used to define an owner and a group for the
archive that is being built. If not provided, the current owner and group
will be used.
The output tar file will be named 'base_dir' + ".tar", possibly plus The output tar file will be named 'base_dir' + ".tar", possibly plus
the appropriate compression extension (".gz", ".bz2" or ".Z"). the appropriate compression extension (".gz", ".bz2" or ".Z").
Returns the output filename. Returns the output filename.
""" """
tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''} tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
...@@ -44,10 +84,23 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0): ...@@ -44,10 +84,23 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0):
import tarfile # late import so Python build itself doesn't break import tarfile # late import so Python build itself doesn't break
log.info('Creating tar archive') log.info('Creating tar archive')
uid = _get_uid(owner)
gid = _get_gid(group)
def _set_uid_gid(tarinfo):
if gid is not None:
tarinfo.gid = gid
tarinfo.gname = group
if uid is not None:
tarinfo.uid = uid
tarinfo.uname = owner
return tarinfo
if not dry_run: if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress]) tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
try: try:
tar.add(base_dir) tar.add(base_dir, filter=_set_uid_gid)
finally: finally:
tar.close() tar.close()
...@@ -138,7 +191,7 @@ def check_archive_formats(formats): ...@@ -138,7 +191,7 @@ def check_archive_formats(formats):
return None return None
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
dry_run=0): dry_run=0, owner=None, group=None):
"""Create an archive file (eg. zip or tar). """Create an archive file (eg. zip or tar).
'base_name' is the name of the file to create, minus any format-specific 'base_name' is the name of the file to create, minus any format-specific
...@@ -151,6 +204,9 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, ...@@ -151,6 +204,9 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
ie. 'base_dir' will be the common prefix of all files and ie. 'base_dir' will be the common prefix of all files and
directories in the archive. 'root_dir' and 'base_dir' both default directories in the archive. 'root_dir' and 'base_dir' both default
to the current directory. Returns the name of the archive file. to the current directory. Returns the name of the archive file.
'owner' and 'group' are used when creating a tar archive. By default,
uses the current owner and group.
""" """
save_cwd = os.getcwd() save_cwd = os.getcwd()
if root_dir is not None: if root_dir is not None:
...@@ -172,8 +228,12 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, ...@@ -172,8 +228,12 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
func = format_info[0] func = format_info[0]
for arg, val in format_info[1]: for arg, val in format_info[1]:
kwargs[arg] = val kwargs[arg] = val
filename = apply(func, (base_name, base_dir), kwargs)
if format != 'zip':
kwargs['owner'] = owner
kwargs['group'] = group
filename = apply(func, (base_name, base_dir), kwargs)
if root_dir is not None: if root_dir is not None:
log.debug("changing back to '%s'", save_cwd) log.debug("changing back to '%s'", save_cwd)
os.chdir(save_cwd) os.chdir(save_cwd)
......
...@@ -385,10 +385,11 @@ class Command: ...@@ -385,10 +385,11 @@ class Command:
from distutils.spawn import spawn from distutils.spawn import spawn
spawn(cmd, search_path, dry_run= self.dry_run) spawn(cmd, search_path, dry_run= self.dry_run)
def make_archive (self, base_name, format, def make_archive(self, base_name, format, root_dir=None, base_dir=None,
root_dir=None, base_dir=None): owner=None, group=None):
return archive_util.make_archive( return archive_util.make_archive(base_name, format, root_dir,
base_name, format, root_dir, base_dir, dry_run=self.dry_run) base_dir, dry_run=self.dry_run,
owner=owner, group=group)
def make_file(self, infiles, outfile, func, args, def make_file(self, infiles, outfile, func, args,
exec_msg=None, skip_msg=None, level=1): exec_msg=None, skip_msg=None, level=1):
......
...@@ -40,6 +40,12 @@ class bdist(Command): ...@@ -40,6 +40,12 @@ class bdist(Command):
"[default: dist]"), "[default: dist]"),
('skip-build', None, ('skip-build', None,
"skip rebuilding everything (for testing/debugging)"), "skip rebuilding everything (for testing/debugging)"),
('owner=', 'u',
"Owner name used when creating a tar file"
" [default: current user]"),
('group=', 'g',
"Group name used when creating a tar file"
" [default: current group]"),
] ]
boolean_options = ['skip-build'] boolean_options = ['skip-build']
...@@ -81,6 +87,8 @@ class bdist(Command): ...@@ -81,6 +87,8 @@ class bdist(Command):
self.formats = None self.formats = None
self.dist_dir = None self.dist_dir = None
self.skip_build = 0 self.skip_build = 0
self.group = None
self.owner = None
def finalize_options(self): def finalize_options(self):
# have to finalize 'plat_name' before 'bdist_base' # have to finalize 'plat_name' before 'bdist_base'
...@@ -126,6 +134,11 @@ class bdist(Command): ...@@ -126,6 +134,11 @@ class bdist(Command):
if cmd_name not in self.no_format_option: if cmd_name not in self.no_format_option:
sub_cmd.format = self.formats[i] sub_cmd.format = self.formats[i]
# passing the owner and group names for tar archiving
if cmd_name == 'bdist_dumb':
sub_cmd.owner = self.owner
sub_cmd.group = self.group
# If we're going to need to run this command again, tell it to # If we're going to need to run this command again, tell it to
# keep its temporary files around so subsequent runs go faster. # keep its temporary files around so subsequent runs go faster.
if cmd_name in commands[i+1:]: if cmd_name in commands[i+1:]:
......
...@@ -36,6 +36,12 @@ class bdist_dumb (Command): ...@@ -36,6 +36,12 @@ class bdist_dumb (Command):
('relative', None, ('relative', None,
"build the archive using relative paths" "build the archive using relative paths"
"(default: false)"), "(default: false)"),
('owner=', 'u',
"Owner name used when creating a tar file"
" [default: current user]"),
('group=', 'g',
"Group name used when creating a tar file"
" [default: current group]"),
] ]
boolean_options = ['keep-temp', 'skip-build', 'relative'] boolean_options = ['keep-temp', 'skip-build', 'relative']
...@@ -53,6 +59,8 @@ class bdist_dumb (Command): ...@@ -53,6 +59,8 @@ class bdist_dumb (Command):
self.dist_dir = None self.dist_dir = None
self.skip_build = 0 self.skip_build = 0
self.relative = 0 self.relative = 0
self.owner = None
self.group = None
def finalize_options(self): def finalize_options(self):
if self.bdist_dir is None: if self.bdist_dir is None:
...@@ -71,7 +79,7 @@ class bdist_dumb (Command): ...@@ -71,7 +79,7 @@ class bdist_dumb (Command):
('dist_dir', 'dist_dir'), ('dist_dir', 'dist_dir'),
('plat_name', 'plat_name')) ('plat_name', 'plat_name'))
def run (self): def run(self):
if not self.skip_build: if not self.skip_build:
self.run_command('build') self.run_command('build')
...@@ -110,7 +118,8 @@ class bdist_dumb (Command): ...@@ -110,7 +118,8 @@ class bdist_dumb (Command):
# Make the archive # Make the archive
filename = self.make_archive(pseudoinstall_root, filename = self.make_archive(pseudoinstall_root,
self.format, root_dir=archive_root) self.format, root_dir=archive_root,
owner=self.owner, group=self.group)
if self.distribution.has_ext_modules(): if self.distribution.has_ext_modules():
pyversion = get_python_version() pyversion = get_python_version()
else: else:
......
...@@ -74,6 +74,10 @@ class sdist(Command): ...@@ -74,6 +74,10 @@ class sdist(Command):
('medata-check', None, ('medata-check', None,
"Ensure that all required elements of meta-data " "Ensure that all required elements of meta-data "
"are supplied. Warn if any missing. [default]"), "are supplied. Warn if any missing. [default]"),
('owner=', 'u',
"Owner name used when creating a tar file [default: current user]"),
('group=', 'g',
"Group name used when creating a tar file [default: current group]"),
] ]
boolean_options = ['use-defaults', 'prune', boolean_options = ['use-defaults', 'prune',
...@@ -113,6 +117,8 @@ class sdist(Command): ...@@ -113,6 +117,8 @@ class sdist(Command):
self.archive_files = None self.archive_files = None
self.metadata_check = 1 self.metadata_check = 1
self.owner = None
self.group = None
def finalize_options(self): def finalize_options(self):
if self.manifest is None: if self.manifest is None:
...@@ -455,7 +461,8 @@ class sdist(Command): ...@@ -455,7 +461,8 @@ class sdist(Command):
self.formats.append(self.formats.pop(self.formats.index('tar'))) self.formats.append(self.formats.pop(self.formats.index('tar')))
for fmt in self.formats: for fmt in self.formats:
file = self.make_archive(base_name, fmt, base_dir=base_dir) file = self.make_archive(base_name, fmt, base_dir=base_dir,
owner=self.owner, group=self.group)
archive_files.append(file) archive_files.append(file)
self.distribution.dist_files.append(('sdist', '', file)) self.distribution.dist_files.append(('sdist', '', file))
......
...@@ -13,6 +13,13 @@ from distutils.spawn import find_executable, spawn ...@@ -13,6 +13,13 @@ from distutils.spawn import find_executable, spawn
from distutils.tests import support from distutils.tests import support
from test.test_support import check_warnings from test.test_support import check_warnings
try:
import grp
import pwd
UID_GID_SUPPORT = True
except ImportError:
UID_GID_SUPPORT = False
try: try:
import zipfile import zipfile
ZIP_SUPPORT = True ZIP_SUPPORT = True
...@@ -30,7 +37,7 @@ class ArchiveUtilTestCase(support.TempdirManager, ...@@ -30,7 +37,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
support.LoggingSilencer, support.LoggingSilencer,
unittest.TestCase): unittest.TestCase):
@unittest.skipUnless(zlib, "Requires zlib") @unittest.skipUnless(zlib, "requires zlib")
def test_make_tarball(self): def test_make_tarball(self):
# creating something to tar # creating something to tar
tmpdir = self.mkdtemp() tmpdir = self.mkdtemp()
...@@ -41,7 +48,7 @@ class ArchiveUtilTestCase(support.TempdirManager, ...@@ -41,7 +48,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
tmpdir2 = self.mkdtemp() tmpdir2 = self.mkdtemp()
unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0], unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0],
"Source and target should be on same drive") "source and target should be on same drive")
base_name = os.path.join(tmpdir2, 'archive') base_name = os.path.join(tmpdir2, 'archive')
...@@ -202,6 +209,58 @@ class ArchiveUtilTestCase(support.TempdirManager, ...@@ -202,6 +209,58 @@ class ArchiveUtilTestCase(support.TempdirManager,
base_name = os.path.join(tmpdir, 'archive') base_name = os.path.join(tmpdir, 'archive')
self.assertRaises(ValueError, make_archive, base_name, 'xxx') self.assertRaises(ValueError, make_archive, base_name, 'xxx')
def test_make_archive_owner_group(self):
# testing make_archive with owner and group, with various combinations
# this works even if there's not gid/uid support
if UID_GID_SUPPORT:
group = grp.getgrgid(0)[0]
owner = pwd.getpwuid(0)[0]
else:
group = owner = 'root'
base_dir, root_dir, base_name = self._create_files()
base_name = os.path.join(self.mkdtemp() , 'archive')
res = make_archive(base_name, 'zip', root_dir, base_dir, owner=owner,
group=group)
self.assertTrue(os.path.exists(res))
res = make_archive(base_name, 'zip', root_dir, base_dir)
self.assertTrue(os.path.exists(res))
res = make_archive(base_name, 'tar', root_dir, base_dir,
owner=owner, group=group)
self.assertTrue(os.path.exists(res))
res = make_archive(base_name, 'tar', root_dir, base_dir,
owner='kjhkjhkjg', group='oihohoh')
self.assertTrue(os.path.exists(res))
@unittest.skipUnless(zlib, "Requires zlib")
@unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
def test_tarfile_root_owner(self):
tmpdir, tmpdir2, base_name = self._create_files()
old_dir = os.getcwd()
os.chdir(tmpdir)
group = grp.getgrgid(0)[0]
owner = pwd.getpwuid(0)[0]
try:
archive_name = make_tarball(base_name, 'dist', compress=None,
owner=owner, group=group)
finally:
os.chdir(old_dir)
# check if the compressed tarball was created
self.assertTrue(os.path.exists(archive_name))
# now checks the rights
archive = tarfile.open(archive_name)
try:
for member in archive.getmembers():
self.assertEquals(member.uid, 0)
self.assertEquals(member.gid, 0)
finally:
archive.close()
def test_suite(): def test_suite():
return unittest.makeSuite(ArchiveUtilTestCase) return unittest.makeSuite(ArchiveUtilTestCase)
......
...@@ -3,6 +3,7 @@ import os ...@@ -3,6 +3,7 @@ import os
import unittest import unittest
import shutil import shutil
import zipfile import zipfile
import tarfile
# zlib is not used here, but if it's not available # zlib is not used here, but if it's not available
# the tests that use zipfile may fail # the tests that use zipfile may fail
...@@ -11,6 +12,13 @@ try: ...@@ -11,6 +12,13 @@ try:
except ImportError: except ImportError:
zlib = None zlib = None
try:
import grp
import pwd
UID_GID_SUPPORT = True
except ImportError:
UID_GID_SUPPORT = False
from os.path import join from os.path import join
import sys import sys
import tempfile import tempfile
...@@ -288,6 +296,52 @@ class SDistTestCase(PyPIRCCommandTestCase): ...@@ -288,6 +296,52 @@ class SDistTestCase(PyPIRCCommandTestCase):
cmd.formats = 'supazipa' cmd.formats = 'supazipa'
self.assertRaises(DistutilsOptionError, cmd.finalize_options) self.assertRaises(DistutilsOptionError, cmd.finalize_options)
@unittest.skipUnless(zlib, "requires zlib")
@unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
def test_make_distribution_owner_group(self):
# check if tar and gzip are installed
if (find_executable('tar') is None or
find_executable('gzip') is None):
return
# now building a sdist
dist, cmd = self.get_cmd()
# creating a gztar and specifying the owner+group
cmd.formats = ['gztar']
cmd.owner = pwd.getpwuid(0)[0]
cmd.group = grp.getgrgid(0)[0]
cmd.ensure_finalized()
cmd.run()
# making sure we have the good rights
archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
archive = tarfile.open(archive_name)
try:
for member in archive.getmembers():
self.assertEquals(member.uid, 0)
self.assertEquals(member.gid, 0)
finally:
archive.close()
# building a sdist again
dist, cmd = self.get_cmd()
# creating a gztar
cmd.formats = ['gztar']
cmd.ensure_finalized()
cmd.run()
# making sure we have the good rights
archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
archive = tarfile.open(archive_name)
try:
for member in archive.getmembers():
self.assertEquals(member.uid, os.getuid())
self.assertEquals(member.gid, os.getgid())
finally:
archive.close()
def test_suite(): def test_suite():
return unittest.makeSuite(SDistTestCase) return unittest.makeSuite(SDistTestCase)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment