130 lines
3.7 KiB
Python
Executable File
130 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
|
|
from contextlib import closing
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def parse_args():
|
|
p = argparse.ArgumentParser()
|
|
|
|
p.add_argument('--ignore-errors', '-i',
|
|
action='store_true',
|
|
help='Ignore OS errors when extracting files')
|
|
p.add_argument('--output', '-o',
|
|
default='.',
|
|
help='Output directory (defaults to ".")')
|
|
p.add_argument('--verbose', '-v',
|
|
action='store_const',
|
|
const=logging.INFO,
|
|
dest='loglevel')
|
|
p.add_argument('--debug', '-d',
|
|
action='store_const',
|
|
const=logging.DEBUG,
|
|
dest='loglevel')
|
|
p.add_argument('--layers',
|
|
action='store_true',
|
|
help='List layers in an image')
|
|
p.add_argument('--list', '--ls',
|
|
action='store_true',
|
|
help='List images/tags contained in archive')
|
|
p.add_argument('--layer', '-l',
|
|
action='append',
|
|
help='Extract only the specified layer')
|
|
p.add_argument('image', nargs='?')
|
|
|
|
p.set_defaults(level=logging.WARN)
|
|
return p.parse_args()
|
|
|
|
|
|
def find_layers(img, id):
|
|
with closing(img.extractfile('%s/json' % id)) as fd:
|
|
info = json.load(fd)
|
|
|
|
LOG.debug('layer = %s', id)
|
|
for k in ['os', 'architecture', 'author', 'created']:
|
|
if k in info:
|
|
LOG.debug('%s = %s', k, info[k])
|
|
|
|
yield id
|
|
if 'parent' in info:
|
|
pid = info['parent']
|
|
for layer in find_layers(img, pid):
|
|
yield layer
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
logging.basicConfig(level=args.loglevel)
|
|
|
|
with tempfile.NamedTemporaryFile() as fd:
|
|
while True:
|
|
data = sys.stdin.read(8192)
|
|
if not data:
|
|
break
|
|
fd.write(data)
|
|
fd.seek(0)
|
|
with tarfile.TarFile(fileobj=fd) as img:
|
|
repos = img.extractfile('repositories')
|
|
repos = json.load(repos)
|
|
|
|
if args.list:
|
|
for name, tags in repos.items():
|
|
print '%s: %s' % (
|
|
name,
|
|
' '.join(tags))
|
|
sys.exit(0)
|
|
|
|
if not args.image:
|
|
if len(repos) == 1:
|
|
args.image = repos.keys()[0]
|
|
else:
|
|
LOG.error('No image name specified and multiple '
|
|
'images contained in archive')
|
|
sys.exit(1)
|
|
try:
|
|
name, tag = args.image.split(':', 1)
|
|
except ValueError:
|
|
name, tag = args.image, 'latest'
|
|
|
|
try:
|
|
top = repos[name][tag]
|
|
except KeyError:
|
|
LOG.error('failed to find image %s with tag %s',
|
|
name,
|
|
tag)
|
|
sys.exit(1)
|
|
|
|
LOG.info('extracting image %s (%s)', name, top)
|
|
layers = list(find_layers(img, top))
|
|
|
|
if args.layers:
|
|
print '\n'.join(reversed(layers))
|
|
sys.exit(0)
|
|
|
|
if not os.path.isdir(args.output):
|
|
os.mkdir(args.output)
|
|
|
|
for id in reversed(layers):
|
|
if args.layer and not id in args.layer:
|
|
continue
|
|
|
|
LOG.info('extracting layer %s', id)
|
|
with tarfile.TarFile(
|
|
fileobj=img.extractfile('%s/layer.tar' % id),
|
|
errorlevel=(0 if args.ignore_errors else 1)) as layer:
|
|
layer.extractall(path=args.output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|