|
5 | 5 | import tarfile
|
6 | 6 |
|
7 | 7 | WHITEOUT = '.wh.'
|
| 8 | +WHITEOUT_OPAQUE = '.wh..wh..opq' |
8 | 9 |
|
9 | 10 | def flatten(image, output):
|
10 | 11 | manifest = json.load(image.extractfile('manifest.json'))
|
11 | 12 | assert len(manifest) == 1
|
12 | 13 | manifest = manifest[0]
|
| 14 | + |
13 | 15 | entries = {}
|
14 | 16 | layers = [tarfile.open(fileobj=image.extractfile(layer)) for layer in manifest['Layers']]
|
15 | 17 | for layer in layers:
|
| 18 | + real_members = [] |
| 19 | + # process whiteout files |
16 | 20 | for info in layer.getmembers():
|
17 | 21 | info.name = './' + info.name
|
18 | 22 | dirname, sep, basename = info.name.rpartition('/')
|
19 |
| - if basename.startswith(WHITEOUT): |
| 23 | + |
| 24 | + if basename == WHITEOUT_OPAQUE: |
| 25 | + for key in entries.keys(): |
| 26 | + if key.startswith(dirname+sep): |
| 27 | + del entries[key] |
| 28 | + elif basename.startswith(WHITEOUT): |
20 | 29 | del entries[dirname+sep+basename[len(WHITEOUT):]]
|
21 | 30 | else:
|
22 |
| - entries[info.name] = layer, info |
| 31 | + real_members.append(info) |
| 32 | + continue |
| 33 | + |
| 34 | + # real files |
| 35 | + for info in real_members: |
| 36 | + entries[info.name] = layer, info |
| 37 | + |
23 | 38 | # need a root entry
|
24 | 39 | if './' not in entries:
|
25 | 40 | info = tarfile.TarInfo('./')
|
26 | 41 | info.type = tarfile.DIRTYPE
|
27 | 42 | info.mode = 0o755
|
28 | 43 | entries[info.name] = None, info
|
| 44 | + |
29 | 45 | for layer, info in entries.values():
|
30 | 46 | fileobj = layer.extractfile(info) if info.isfile() else None
|
31 |
| - info.mtime = 0 |
| 47 | + info.mtime = 0 # make reproducible |
32 | 48 | output.addfile(info, fileobj)
|
| 49 | + |
33 | 50 | for layer in layers:
|
34 | 51 | layer.close()
|
35 | 52 |
|
|
0 commit comments