| # Copyright 2015 Google Inc. 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 docker layer tar file from a list of inputs.""" |
| |
| import os |
| import os.path |
| import sys |
| import tarfile |
| import tempfile |
| |
| from tools.build_defs.docker 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_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') |
| |
| FLAGS = gflags.FLAGS |
| |
| |
| class DockerLayer(object): |
| """A class to generates a Docker layer.""" |
| |
| class DebError(Exception): |
| pass |
| |
| def __init__(self, output, directory): |
| self.directory = directory |
| self.output = output |
| |
| def __enter__(self): |
| self.tarfile = archive.TarFileWriter(self.output) |
| return self |
| |
| def __exit__(self, t, v, traceback): |
| self.tarfile.close() |
| |
| def add_file(self, f, destfile): |
| """Add a file to the layer. |
| |
| Args: |
| f: the file to add to the layer |
| destfile: the name of the file in the layer |
| |
| `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 |
| self.tarfile.add_file(dest, file_content=f) |
| |
| def add_tar(self, tar): |
| """Add a tar file to the layer. |
| |
| All files presents in that tar will be added to the layer under |
| the same paths. The current user uid and gid will be replaced |
| by 0 (to make like we are running as root) and no user name nor |
| group names will be added to the layer. |
| |
| Args: |
| tar: the tar file to add to the layer |
| """ |
| self.tarfile.add_tar(tar, |
| rootuid=os.getuid(), |
| rootgid=os.getgid(), |
| numeric=True) |
| |
| def add_link(self, symlink, destination): |
| """Add a symbolic link pointing to `destination` in the layer. |
| |
| 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 layer. |
| |
| All files presents in that debian package will be added to the |
| layer under the same paths. No user name nor group names will |
| be added to the layer. |
| |
| Args: |
| deb: the tar file to add to the layer |
| |
| Raises: |
| DebError: if the format of the deb archive is incorrect. |
| |
| This method does not support LZMA (data.tar.xz or data.tar.lzma) |
| for the data in the deb package. Using Python 3 would fix it. |
| """ |
| 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): |
| with DockerLayer(FLAGS.output, FLAGS.directory) as layer: |
| for f in FLAGS.file: |
| (inf, tof) = f.split('=', 1) |
| layer.add_file(inf, tof) |
| for tar in FLAGS.tar: |
| layer.add_tar(tar) |
| for deb in FLAGS.deb: |
| layer.add_deb(deb) |
| for link in FLAGS.link: |
| l = link.split(':', 1) |
| layer.add_link(l[0], l[1]) |
| |
| if __name__ == '__main__': |
| main(FLAGS(sys.argv)) |