2016-03-29 19:46:36 -07:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import hashlib
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import re
|
2016-05-09 20:31:33 +00:00
|
|
|
import subprocess
|
2016-03-29 19:46:36 -07:00
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='iconograph fetcher')
|
2016-03-30 16:47:30 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--default-rollout',
|
|
|
|
|
dest='default_rollout',
|
|
|
|
|
action='store',
|
|
|
|
|
type=int,
|
|
|
|
|
default=0)
|
2016-03-29 19:46:36 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--image-dir',
|
|
|
|
|
dest='image_dir',
|
|
|
|
|
action='store',
|
|
|
|
|
required=True)
|
2016-04-02 12:10:43 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--max-images',
|
|
|
|
|
dest='max_images',
|
|
|
|
|
action='store',
|
|
|
|
|
type=int,
|
|
|
|
|
default=0)
|
2016-03-29 20:07:55 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--old-manifest',
|
|
|
|
|
dest='old_manifest',
|
|
|
|
|
action='store')
|
2016-03-29 19:46:36 -07:00
|
|
|
FLAGS = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ManifestBuilder(object):
|
|
|
|
|
|
2016-03-31 17:28:25 -07:00
|
|
|
_FILE_REGEX = re.compile('^(?P<timestamp>\d+)\.iso$')
|
2016-05-09 20:31:33 +00:00
|
|
|
_VOLUME_ID_REGEX = re.compile(b'^Volume id: (?P<volume_id>.+)$', re.MULTILINE)
|
2016-03-29 19:46:36 -07:00
|
|
|
_BUF_SIZE = 2 ** 16
|
|
|
|
|
|
2016-03-31 17:28:25 -07:00
|
|
|
def __init__(self, image_dir, old_manifest):
|
2016-03-29 19:46:36 -07:00
|
|
|
self._image_dir = image_dir
|
2016-03-29 20:07:55 -07:00
|
|
|
self._old_manifest = old_manifest
|
|
|
|
|
|
2016-04-09 00:06:49 +00:00
|
|
|
def _OldImages(self):
|
2016-03-29 20:07:55 -07:00
|
|
|
if not self._old_manifest:
|
|
|
|
|
return {}
|
|
|
|
|
try:
|
|
|
|
|
with open(self._old_manifest, 'r') as fh:
|
|
|
|
|
parsed = json.load(fh)
|
|
|
|
|
return dict(
|
2016-04-09 00:06:49 +00:00
|
|
|
(image['timestamp'], image)
|
2016-03-29 20:07:55 -07:00
|
|
|
for image in parsed['images'])
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
return {}
|
2016-03-29 19:46:36 -07:00
|
|
|
|
2016-05-09 20:31:33 +00:00
|
|
|
def _GetVolumeID(self, path):
|
|
|
|
|
isoinfo = subprocess.check_output([
|
|
|
|
|
'isoinfo',
|
|
|
|
|
'-d',
|
|
|
|
|
'-i', path,
|
|
|
|
|
])
|
|
|
|
|
match = self._VOLUME_ID_REGEX.search(isoinfo)
|
|
|
|
|
return match.group('volume_id').decode('ascii')
|
|
|
|
|
|
2016-03-29 19:46:36 -07:00
|
|
|
def BuildManifest(self):
|
|
|
|
|
ret = {
|
|
|
|
|
'timestamp': int(time.time()),
|
|
|
|
|
'images': [],
|
|
|
|
|
}
|
2016-04-09 00:06:49 +00:00
|
|
|
old_images = self._OldImages()
|
2016-03-29 19:46:36 -07:00
|
|
|
for filename in os.listdir(self._image_dir):
|
2016-03-31 17:28:25 -07:00
|
|
|
match = self._FILE_REGEX.match(filename)
|
2016-03-29 19:46:36 -07:00
|
|
|
if not match:
|
|
|
|
|
continue
|
2016-03-29 20:07:55 -07:00
|
|
|
timestamp = int(match.group('timestamp'))
|
2016-03-29 19:46:36 -07:00
|
|
|
image = {
|
2016-03-29 20:07:55 -07:00
|
|
|
'timestamp': timestamp,
|
2016-04-09 00:06:49 +00:00
|
|
|
'rollout_‱': FLAGS.default_rollout,
|
2016-03-29 19:46:36 -07:00
|
|
|
}
|
2016-04-09 00:06:49 +00:00
|
|
|
ret['images'].append(image)
|
|
|
|
|
|
|
|
|
|
old_image = old_images.get(timestamp)
|
|
|
|
|
if old_image:
|
|
|
|
|
image.update(old_image)
|
|
|
|
|
continue
|
|
|
|
|
|
2016-05-09 20:31:33 +00:00
|
|
|
image_path = os.path.join(self._image_dir, filename)
|
|
|
|
|
|
|
|
|
|
image['volume_id'] = self._GetVolumeID(image_path)
|
|
|
|
|
|
|
|
|
|
with open(image_path, 'rb') as fh:
|
2016-03-29 19:46:36 -07:00
|
|
|
hash_obj = hashlib.sha256()
|
|
|
|
|
while True:
|
|
|
|
|
data = fh.read(self._BUF_SIZE)
|
|
|
|
|
if not data:
|
|
|
|
|
break
|
|
|
|
|
hash_obj.update(data)
|
|
|
|
|
image['hash'] = hash_obj.hexdigest()
|
2016-04-09 00:06:49 +00:00
|
|
|
|
2016-03-29 20:07:55 -07:00
|
|
|
ret['images'].sort(key=lambda x: x['timestamp'], reverse=True)
|
2016-03-29 19:46:36 -07:00
|
|
|
return ret
|
|
|
|
|
|
2016-04-02 12:10:43 -07:00
|
|
|
def DeleteOldImages(self, manifest, max_images):
|
|
|
|
|
if not max_images:
|
|
|
|
|
return
|
|
|
|
|
for image in manifest['images'][max_images:]:
|
|
|
|
|
filename = '%d.iso' % image['timestamp']
|
|
|
|
|
print('Deleting old image:', filename, file=sys.stderr)
|
|
|
|
|
path = os.path.join(self._image_dir, filename)
|
|
|
|
|
os.unlink(path)
|
|
|
|
|
manifest['images'] = manifest['images'][:max_images]
|
|
|
|
|
|
2016-03-29 19:46:36 -07:00
|
|
|
|
2016-03-29 20:11:37 -07:00
|
|
|
def main():
|
2016-03-31 17:28:25 -07:00
|
|
|
builder = ManifestBuilder(FLAGS.image_dir, FLAGS.old_manifest)
|
2016-03-29 20:11:37 -07:00
|
|
|
manifest = builder.BuildManifest()
|
2016-04-02 12:10:43 -07:00
|
|
|
builder.DeleteOldImages(manifest, FLAGS.max_images)
|
2016-03-29 20:11:37 -07:00
|
|
|
json.dump(manifest, sys.stdout, sort_keys=True, indent=4)
|
|
|
|
|
sys.stdout.write('\n')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|