aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/build_defs/pkg/build_tar.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/build_defs/pkg/build_tar.py')
-rw-r--r--tools/build_defs/pkg/build_tar.py179
1 files changed, 179 insertions, 0 deletions
diff --git a/tools/build_defs/pkg/build_tar.py b/tools/build_defs/pkg/build_tar.py
new file mode 100644
index 0000000000..75ad1610e8
--- /dev/null
+++ b/tools/build_defs/pkg/build_tar.py
@@ -0,0 +1,179 @@
+# Copyright 2015 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""This tool build tar files from a list of inputs."""
+
+import os
+import os.path
+import sys
+import tarfile
+import tempfile
+
+from tools.build_defs.pkg import archive
+from third_party.py import gflags
+
+gflags.DEFINE_string('output', None, 'The output file, mandatory')
+gflags.MarkFlagAsRequired('output')
+
+gflags.DEFINE_multistring('file', [], 'A file to add to the layer')
+
+gflags.DEFINE_string(
+ 'mode', None, 'Force the mode on the added files (in octal).')
+
+gflags.DEFINE_multistring('tar', [], 'A tar file to add to the layer')
+
+gflags.DEFINE_multistring('deb', [], 'A debian package to add to the layer')
+
+gflags.DEFINE_multistring(
+ 'link', [],
+ 'Add a symlink a inside the layer ponting to b if a:b is specified')
+gflags.RegisterValidator(
+ 'link',
+ lambda l: all(value.find(':') > 0 for value in l),
+ message='--link value should contains a : separator')
+
+gflags.DEFINE_string(
+ 'directory', None, 'Directory in which to store the file inside the layer')
+
+gflags.DEFINE_string(
+ 'compression', None, 'Compression (`gz` or `bz2`), default is none.')
+
+gflags.DEFINE_multistring(
+ 'modes', None,
+ 'Specific mode to apply to specific file (from the file argument),'
+ ' e.g., path/to/file=0455.')
+
+FLAGS = gflags.FLAGS
+
+
+class TarFile(object):
+ """A class to generates a Docker layer."""
+
+ class DebError(Exception):
+ pass
+
+ def __init__(self, output, directory, compression):
+ self.directory = directory
+ self.output = output
+ self.compression = compression
+
+ def __enter__(self):
+ self.tarfile = archive.TarFileWriter(self.output, self.compression)
+ return self
+
+ def __exit__(self, t, v, traceback):
+ self.tarfile.close()
+
+ def add_file(self, f, destfile, mode=None):
+ """Add a file to the tar file.
+
+ Args:
+ f: the file to add to the layer
+ destfile: the name of the file in the layer
+ mode: force to set the specified mode, by
+ default the value from the source is taken.
+ `f` will be copied to `self.directory/destfile` in the layer.
+ """
+ dest = destfile.lstrip('/') # Remove leading slashes
+ # TODO(mattmoor): Consider applying the working directory to all four
+ # options, not just files...
+ if self.directory and self.directory != '/':
+ dest = self.directory.lstrip('/') + '/' + dest
+ # If mode is unspecified, derive the mode from the file's mode.
+ if mode is None:
+ mode = 0755 if os.access(f, os.X_OK) else 0644
+ self.tarfile.add_file(dest, file_content=f, mode=mode)
+
+ def add_tar(self, tar):
+ """Merge a tar file into the destination tar file.
+
+ All files presents in that tar will be added to the output file
+ under the same paths. No user name nor group name will be added to
+ the output.
+
+ Args:
+ tar: the tar file to add
+ """
+ self.tarfile.add_tar(tar, numeric=True)
+
+ def add_link(self, symlink, destination):
+ """Add a symbolic link pointing to `destination`.
+
+ Args:
+ symlink: the name of the symbolic link to add.
+ destination: where the symbolic link point to.
+ """
+ self.tarfile.add_file(symlink, tarfile.SYMTYPE, link=destination)
+
+ def add_deb(self, deb):
+ """Extract a debian package in the output tar.
+
+ All files presents in that debian package will be added to the
+ output tar under the same paths. No user name nor group names will
+ be added to the output.
+
+ Args:
+ deb: the tar file to add
+
+ Raises:
+ DebError: if the format of the deb archive is incorrect.
+ """
+ with archive.SimpleArFile(deb) as arfile:
+ current = arfile.next()
+ while current and not current.filename.startswith('data.'):
+ current = arfile.next()
+ if not current:
+ raise self.DebError(deb + ' does not contains a data file!')
+ tmpfile = tempfile.mkstemp(suffix=os.path.splitext(current.filename)[-1])
+ with open(tmpfile[1], 'wb') as f:
+ f.write(current.data)
+ self.add_tar(tmpfile[1])
+ os.remove(tmpfile[1])
+
+
+def main(unused_argv):
+ # Parse modes arguments
+ default_mode = None
+ if FLAGS.mode:
+ # Convert from octal
+ default_mode = int(FLAGS.mode, 8)
+
+ mode_map = {}
+ if FLAGS.modes:
+ for filemode in FLAGS.modes:
+ (f, mode) = filemode.split('=', 1)
+ if f[0] == '/':
+ f = f[1:]
+ mode_map[f] = int(mode, 8)
+
+ # Add objects to the tar file
+ with TarFile(FLAGS.output, FLAGS.directory, FLAGS.compression) as output:
+ for f in FLAGS.file:
+ (inf, tof) = f.split('=', 1)
+ mode = default_mode
+ if tof[0] == '/' and (tof[1:] in mode_map):
+ mode = mode_map[tof[1:]]
+ elif tof in mode_map:
+ mode = mode_map[tof]
+ output.add_file(inf, tof, mode)
+ for tar in FLAGS.tar:
+ output.add_tar(tar)
+ for deb in FLAGS.deb:
+ output.add_deb(deb)
+ for link in FLAGS.link:
+ l = link.split(':', 1)
+ output.add_link(l[0], l[1])
+
+
+if __name__ == '__main__':
+ main(FLAGS(sys.argv))