Skip to content

Commit 146c9b9

Browse files
authored
Merge pull request #97 from mattmoor/fork-tar
Fork build_tar.py
2 parents 79aa5de + 0dcc072 commit 146c9b9

File tree

3 files changed

+252
-1
lines changed

3 files changed

+252
-1
lines changed

docker/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,14 @@ filegroup(
134134
srcs = ["incremental_load.sh.tpl"],
135135
visibility = ["//visibility:public"],
136136
)
137+
138+
py_binary(
139+
name = "build_tar",
140+
srcs = ["build_tar.py"],
141+
srcs_version = "PY2AND3",
142+
visibility = ["//visibility:public"],
143+
deps = [
144+
"@bazel_tools//tools/build_defs/pkg:archive",
145+
"@bazel_tools//third_party/py/gflags",
146+
],
147+
)

docker/build.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ docker_build_ = rule(
256256
),
257257
"label_file_strings": attr.string_list(),
258258
"build_layer": attr.label(
259-
default = Label("@bazel_tools//tools/build_defs/pkg:build_tar"),
259+
default = Label("//docker:build_tar"),
260260
cfg = "host",
261261
executable = True,
262262
allow_files = True,

docker/build_tar.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Copyright 2015 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""This tool build tar files from a list of inputs."""
15+
16+
import os
17+
import os.path
18+
import sys
19+
import tarfile
20+
import tempfile
21+
22+
from tools.build_defs.pkg import archive
23+
from third_party.py import gflags
24+
25+
gflags.DEFINE_string('output', None, 'The output file, mandatory')
26+
gflags.MarkFlagAsRequired('output')
27+
28+
gflags.DEFINE_multistring('file', [], 'A file to add to the layer')
29+
30+
gflags.DEFINE_string(
31+
'mode', None, 'Force the mode on the added files (in octal).')
32+
33+
gflags.DEFINE_multistring('tar', [], 'A tar file to add to the layer')
34+
35+
gflags.DEFINE_multistring('deb', [], 'A debian package to add to the layer')
36+
37+
gflags.DEFINE_multistring(
38+
'link', [],
39+
'Add a symlink a inside the layer ponting to b if a:b is specified')
40+
gflags.RegisterValidator(
41+
'link',
42+
lambda l: all(value.find(':') > 0 for value in l),
43+
message='--link value should contains a : separator')
44+
45+
gflags.DEFINE_string(
46+
'directory', None, 'Directory in which to store the file inside the layer')
47+
48+
gflags.DEFINE_string(
49+
'compression', None, 'Compression (`gz` or `bz2`), default is none.')
50+
51+
gflags.DEFINE_multistring(
52+
'modes', None,
53+
'Specific mode to apply to specific file (from the file argument),'
54+
' e.g., path/to/file=0455.')
55+
56+
gflags.DEFINE_multistring('owners', None,
57+
'Specify the numeric owners of individual files, '
58+
'e.g. path/to/file=0.0.')
59+
60+
gflags.DEFINE_string('owner', '0.0',
61+
'Specify the numeric default owner of all files,'
62+
' e.g., 0.0')
63+
64+
gflags.DEFINE_string('owner_name', None,
65+
'Specify the owner name of all files, e.g. root.root.')
66+
67+
gflags.DEFINE_multistring('owner_names', None,
68+
'Specify the owner names of individual files, e.g. '
69+
'path/to/file=root.root.')
70+
71+
FLAGS = gflags.FLAGS
72+
73+
74+
class TarFile(object):
75+
"""A class to generates a Docker layer."""
76+
77+
class DebError(Exception):
78+
pass
79+
80+
def __init__(self, output, directory, compression):
81+
self.directory = directory
82+
self.output = output
83+
self.compression = compression
84+
85+
def __enter__(self):
86+
self.tarfile = archive.TarFileWriter(self.output, self.compression)
87+
return self
88+
89+
def __exit__(self, t, v, traceback):
90+
self.tarfile.close()
91+
92+
def add_file(self, f, destfile, mode=None, ids=None, names=None):
93+
"""Add a file to the tar file.
94+
95+
Args:
96+
f: the file to add to the layer
97+
destfile: the name of the file in the layer
98+
mode: force to set the specified mode, by
99+
default the value from the source is taken.
100+
ids: (uid, gid) for the file to set ownership
101+
names: (username, groupname) for the file to set ownership.
102+
`f` will be copied to `self.directory/destfile` in the layer.
103+
"""
104+
dest = destfile.lstrip('/') # Remove leading slashes
105+
if self.directory and self.directory != '/':
106+
dest = self.directory.lstrip('/') + '/' + dest
107+
# If mode is unspecified, derive the mode from the file's mode.
108+
if mode is None:
109+
mode = 0o755 if os.access(f, os.X_OK) else 0o644
110+
if ids is None:
111+
ids = (0, 0)
112+
if names is None:
113+
names = ('', '')
114+
dest = os.path.normpath(dest)
115+
self.tarfile.add_file(
116+
dest,
117+
file_content=f,
118+
mode=mode,
119+
uid=ids[0],
120+
gid=ids[1],
121+
uname=names[0],
122+
gname=names[1])
123+
124+
def add_tar(self, tar):
125+
"""Merge a tar file into the destination tar file.
126+
127+
All files presents in that tar will be added to the output file
128+
under self.directory/path. No user name nor group name will be
129+
added to the output.
130+
131+
Args:
132+
tar: the tar file to add
133+
"""
134+
root = None
135+
if self.directory and self.directory != '/':
136+
root = self.directory
137+
self.tarfile.add_tar(tar, numeric=True, root=root)
138+
139+
def add_link(self, symlink, destination):
140+
"""Add a symbolic link pointing to `destination`.
141+
142+
Args:
143+
symlink: the name of the symbolic link to add.
144+
destination: where the symbolic link point to.
145+
"""
146+
symlink = os.path.normpath(symlink)
147+
self.tarfile.add_file(symlink, tarfile.SYMTYPE, link=destination)
148+
149+
def add_deb(self, deb):
150+
"""Extract a debian package in the output tar.
151+
152+
All files presents in that debian package will be added to the
153+
output tar under the same paths. No user name nor group names will
154+
be added to the output.
155+
156+
Args:
157+
deb: the tar file to add
158+
159+
Raises:
160+
DebError: if the format of the deb archive is incorrect.
161+
"""
162+
with archive.SimpleArFile(deb) as arfile:
163+
current = arfile.next()
164+
while current and not current.filename.startswith('data.'):
165+
current = arfile.next()
166+
if not current:
167+
raise self.DebError(deb + ' does not contains a data file!')
168+
tmpfile = tempfile.mkstemp(suffix=os.path.splitext(current.filename)[-1])
169+
with open(tmpfile[1], 'wb') as f:
170+
f.write(current.data)
171+
self.add_tar(tmpfile[1])
172+
os.remove(tmpfile[1])
173+
174+
175+
def main(unused_argv):
176+
# Parse modes arguments
177+
default_mode = None
178+
if FLAGS.mode:
179+
# Convert from octal
180+
default_mode = int(FLAGS.mode, 8)
181+
182+
mode_map = {}
183+
if FLAGS.modes:
184+
for filemode in FLAGS.modes:
185+
(f, mode) = filemode.split('=', 1)
186+
if f[0] == '/':
187+
f = f[1:]
188+
mode_map[f] = int(mode, 8)
189+
190+
default_ownername = ('', '')
191+
if FLAGS.owner_name:
192+
default_ownername = FLAGS.owner_name.split('.', 1)
193+
names_map = {}
194+
if FLAGS.owner_names:
195+
for file_owner in FLAGS.owner_names:
196+
(f, owner) = file_owner.split('=', 1)
197+
(user, group) = owner.split('.', 1)
198+
if f[0] == '/':
199+
f = f[1:]
200+
names_map[f] = (user, group)
201+
202+
default_ids = FLAGS.owner.split('.', 1)
203+
default_ids = (int(default_ids[0]), int(default_ids[1]))
204+
ids_map = {}
205+
if FLAGS.owners:
206+
for file_owner in FLAGS.owners:
207+
(f, owner) = file_owner.split('=', 1)
208+
(user, group) = owner.split('.', 1)
209+
if f[0] == '/':
210+
f = f[1:]
211+
ids_map[f] = (int(user), int(group))
212+
213+
# Add objects to the tar file
214+
with TarFile(FLAGS.output, FLAGS.directory, FLAGS.compression) as output:
215+
for f in FLAGS.file:
216+
(inf, tof) = f.split('=', 1)
217+
mode = default_mode
218+
ids = default_ids
219+
names = default_ownername
220+
map_filename = tof
221+
if tof[0] == '/':
222+
map_filename = tof[1:]
223+
if map_filename in mode_map:
224+
mode = mode_map[map_filename]
225+
if map_filename in ids_map:
226+
ids = ids_map[map_filename]
227+
if map_filename in names_map:
228+
names = names_map[map_filename]
229+
output.add_file(inf, tof, mode, ids, names)
230+
for tar in FLAGS.tar:
231+
output.add_tar(tar)
232+
for deb in FLAGS.deb:
233+
output.add_deb(deb)
234+
for link in FLAGS.link:
235+
l = link.split(':', 1)
236+
output.add_link(l[0], l[1])
237+
238+
239+
if __name__ == '__main__':
240+
main(FLAGS(sys.argv))

0 commit comments

Comments
 (0)