blob: daa50b2c8cb99e9a7e9b39b5b4f3a5289dceed13 [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Fetches a package.far as specified in the artifact_lock.json file."""
import argparse
import json
import os
import subprocess
import shutil
import sys
import tempfile
import urllib.error
import urllib.request
META_FAR_FILE_RELATIVE_PATH = 'meta.far'
CONTENTS_FILE_RELATIVE_PATH = 'meta/contents'
BLOB_SERVER = 'fuchsia-blobs.googleusercontent.com'
def extract_far(far_tool, meta_far_path, extract_dir):
"""Invokes the far tool to extract the meta_far."""
args = [
far_tool, 'extract', '--archive=' + meta_far_path,
'--output=' + extract_dir
]
subprocess.check_output(args, stderr=subprocess.STDOUT)
def get_merkle(merkleroot_tool, path):
"""Run the merkleroot tool to get the merkle."""
result = subprocess.run(
[merkleroot_tool, path],
check=True,
capture_output=True,
text=True,
)
return result.stdout.split()[0]
def compare_merkle(merkleroot_tool, meta_far_path, meta_far_merkle):
"""Compares the saved merkle to see if download is needed."""
if not os.path.isfile(meta_far_path):
return False
return meta_far_merkle == get_merkle(merkleroot_tool, meta_far_path)
def parse_args():
"""Parses arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--far-tool', help='Path to far tool', required=True)
parser.add_argument(
'--merkleroot-tool', help='Path to merkleroot tool', required=True)
parser.add_argument(
'--lock-file', help='Path to saved lock file', required=True)
parser.add_argument(
'--outdir', help='Path to target output directory', required=True)
parser.add_argument(
'--local-dir', help='Path to local artifact_store root', required=False)
parser.add_argument(
'--show-progress', dest='progress', action='store_true',
help='Show progress during execution',
required=False)
return parser.parse_args()
def fetch_file(artifact_store, merkle, relative_path, name, args):
"""Fetch the merkle contents from the artifact store.
In the case of local store the output relative path and name
are used for input since the in/out directory structures are
identical.
"""
# Make sure the output path exists
path = os.path.join(args.outdir, relative_path)
os.makedirs(path, exist_ok=True)
filename = os.path.join(path, name)
if args.progress:
print(" - %s" % filename)
type_ = artifact_store['type']
if type_ == 'tuf':
url = 'https://%s/%s' % (BLOB_SERVER, merkle)
try:
urllib.request.urlretrieve(url, filename)
except urllib.error.HTTPError as ex:
raise ValueError('Failed to download meta far or blob') from ex
elif type_ == 'local':
local_filename = os.path.join(args.local_dir, relative_path, name)
shutil.copyfile(local_filename, filename)
else:
raise TypeError('Unknown artifact store type: ' + type_)
def fetch_artifact(artifact, args):
"""Fetch the meta-far and blobs."""
merkle = artifact['id']
artifact_store = artifact['artifact_store']
artifact_name = artifact['name'].replace('/0', '')
artifact_type = artifact['type']
# meta.far is saved under artifact_stores/<artifact_store_name>/<artifact_name>
relative_path = os.path.join('artifact_stores', artifact_store['name'])
meta_far_filename = os.path.join(args.outdir, relative_path, artifact_name)
if compare_merkle(args.merkleroot_tool, meta_far_filename, merkle):
return
fetch_file(artifact_store, merkle, relative_path, artifact_name, args)
if artifact_type == 'package':
with tempfile.TemporaryDirectory() as tmpdir:
extract_far(args.far_tool, meta_far_filename, tmpdir)
with open(os.path.join(tmpdir, CONTENTS_FILE_RELATIVE_PATH),
'r') as content_file:
for content in content_file.readlines():
merkle = content.strip().split('=')[1]
fetch_file(artifact_store, merkle, 'blobs', merkle, args)
def main():
"""Main for the fetch tool."""
args = parse_args()
os.makedirs(args.outdir, exist_ok=True)
with open(args.lock_file, 'r') as lock_file:
try:
artifact_lock = json.load(lock_file)
except ValueError as ex:
raise ValueError(
'Malformed %s: %s\n%s\n' %
(args.lock_file, lock_file.read(),
'Execute artifact_update.py to refresh merkles.')) from ex
for artifact in artifact_lock['artifacts']:
fetch_artifact(artifact, args)
if __name__ == '__main__':
sys.exit(main())