Skip to content

Commit c590e61

Browse files
ronodnnrnascimentocgrindel
authored
Conditionally preserve file permissions when archiving through pkg_tar (#951)
* preserve file permission through an option * fix parsing and handling of new preserve_mode argument * restore version * add tests * use an existing test file, as git does not preserve file perms * delete test file * cover windows testing * record test subject file perms * use pythonic elif Co-authored-by: Chuck Grindel <[email protected]> * buildifier * trim trailing whitespaces --------- Co-authored-by: rnascimento <[email protected]> Co-authored-by: Chuck Grindel <[email protected]>
1 parent 106429f commit c590e61

File tree

4 files changed

+54
-5
lines changed

4 files changed

+54
-5
lines changed

pkg/private/tar/build_tar.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import argparse
1717
import os
18+
import stat
1819
import tarfile
1920
import tempfile
2021

@@ -43,7 +44,7 @@ class DebError(Exception):
4344
pass
4445

4546
def __init__(self, output, directory, compression, compressor, create_parents,
46-
allow_dups_from_deps, default_mtime, compression_level):
47+
allow_dups_from_deps, default_mtime, compression_level, preserve_mode):
4748
# Directory prefix on all output paths
4849
d = directory.strip('/')
4950
self.directory = (d + '/') if d else None
@@ -54,6 +55,7 @@ def __init__(self, output, directory, compression, compressor, create_parents,
5455
self.create_parents = create_parents
5556
self.allow_dups_from_deps = allow_dups_from_deps
5657
self.compression_level = compression_level
58+
self.preserve_mode = preserve_mode
5759

5860
def __enter__(self):
5961
self.tarfile = tar_writer.TarFileWriter(
@@ -101,9 +103,13 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
101103
copied to `self.directory/destfile` in the layer.
102104
"""
103105
dest = self.normalize_path(destfile)
104-
# If mode is unspecified, derive the mode from the file's mode.
105-
if mode is None:
106-
mode = 0o755 if os.access(f, os.X_OK) else 0o644
106+
# If preserve_mode is enabled, set mode by extracting the permission bits
107+
# from the file's mode attribute. Note: the mode argument is ignored.
108+
# Otherwise; if mode is unspecified, derive the mode from the file's mode.
109+
if self.preserve_mode is True:
110+
mode = stat.S_IMODE(os.stat(f).st_mode)
111+
elif mode is None:
112+
mode = 0o755 if os.access(f, os.X_OK) else 0o644
107113
if ids is None:
108114
ids = (0, 0)
109115
if names is None:
@@ -402,6 +408,10 @@ def main():
402408
parser.add_argument('--allow_dups_from_deps',
403409
action='store_true',
404410
help='')
411+
parser.add_argument(
412+
'--preserve_mode', default='False',
413+
action='store_true',
414+
help='Preserve original file permissions in the archive. Mode argument is ignored.')
405415
parser.add_argument(
406416
'--compression_level', default=-1,
407417
help='Specify the numeric compress level in gzip mode; may be 0-9 or -1 (default to 6).')
@@ -461,7 +471,8 @@ def main():
461471
default_mtime=default_mtime,
462472
create_parents=options.create_parents,
463473
allow_dups_from_deps=options.allow_dups_from_deps,
464-
compression_level = compression_level) as output:
474+
compression_level = compression_level,
475+
preserve_mode = options.preserve_mode) as output:
465476

466477
def file_attributes(filename):
467478
if filename.startswith('/'):

pkg/private/tar/tar.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ def _pkg_tar_impl(ctx):
181181
if ctx.attr.allow_duplicates_from_deps:
182182
args.add("--allow_dups_from_deps")
183183

184+
if ctx.attr.preserve_mode:
185+
args.add("--preserve_mode")
186+
184187
inputs = depset(
185188
direct = ctx.files.deps + files,
186189
transitive = mapping_context.file_deps,
@@ -294,6 +297,10 @@ Such behaviour is always incorrect, but we provide a flag to support it in case
294297
builds were accidentally doing it. Never explicitly set this to true for new code.
295298
""",
296299
),
300+
"preserve_mode": attr.bool(
301+
default = False,
302+
doc = """If true, will add file to archive with preserved file permissions.""",
303+
),
297304
"stamp": attr.int(
298305
doc = """Enable file time stamping. Possible values:
299306
<li>stamp = 1: Use the time of the build as the modification time of each file in the archive.

tests/tar/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ py_test(
474474
":test-tar-files_dict.tar",
475475
":test-tar-long-filename",
476476
":test-tar-mtime.tar",
477+
":test-tar-preserve_mode-False.tar",
478+
":test-tar-preserve_mode-True.tar",
477479
":test-tar-repackaging-long-filename.tar",
478480
":test-tar-strip_prefix-dot.tar",
479481
":test-tar-strip_prefix-empty.tar",
@@ -801,3 +803,14 @@ verify_archive_test(
801803
6,
802804
9,
803805
]]
806+
807+
[pkg_tar(
808+
name = "test-tar-preserve_mode-%s" % state,
809+
srcs = [
810+
"//tests:testdata/hello.txt", # rw- r-- r--
811+
],
812+
preserve_mode = state,
813+
) for state in [
814+
True,
815+
False,
816+
]]

tests/tar/pkg_tar_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,5 +306,23 @@ def test_compression_level(self):
306306
file_size = os.stat(file_path).st_size
307307
self.assertEqual(file_size, expected_size, 'size error for ' + file_name)
308308

309+
def test_preserve_mode(self):
310+
if os.name == 'nt':
311+
expected_mode = [
312+
('test-tar-preserve_mode-False.tar', "0o555"), # chmod 555 = r-x r-x r-x
313+
('test-tar-preserve_mode-True.tar', "0o666"), # chmod 666 = rw- rw- rw-
314+
]
315+
else:
316+
expected_mode = [
317+
('test-tar-preserve_mode-False.tar', "0o555"), # chmod 555 = r-x r-x r-x
318+
('test-tar-preserve_mode-True.tar', "0o644"), # chmod 644 = rw- r-- r--
319+
]
320+
for file_name, expected_mode in expected_mode:
321+
file_path = runfiles.Create().Rlocation('rules_pkg/tests/tar/' + file_name)
322+
with tarfile.open(file_path, 'r') as f:
323+
for member in f.getmembers():
324+
self.assertEqual(member.name, "hello.txt", "unexpected file name for " + file_name)
325+
self.assertEqual(member.mode, int(expected_mode, 0), 'file mode not preserved for ' + file_name)
326+
309327
if __name__ == '__main__':
310328
unittest.main()

0 commit comments

Comments
 (0)