serve_repo.py (4166B)
1 # Copyright 2022 The Chromium Authors 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 """Implements commands for serving a TUF repository.""" 5 6 import argparse 7 import contextlib 8 import json 9 import logging 10 from typing import Iterator, Optional 11 12 import monitors 13 14 from common import run_ffx_command, REPO_ALIAS 15 16 _REPO_NAME = 'chromium-test-package-server' 17 18 19 def _stop_serving(repo_name: str, target: Optional[str]) -> None: 20 """Stop serving a repository.""" 21 22 # Attempt to clean up. 23 with monitors.time_consumption('repository', 'deregister'): 24 run_ffx_command( 25 cmd=['target', 'repository', 'deregister', '-r', repo_name], 26 target_id=target, 27 check=False) 28 29 with monitors.time_consumption('repository', 'stop'): 30 run_ffx_command(cmd=['repository', 'server', 'stop', repo_name], 31 check=False) 32 33 34 def _start_serving(repo_dir: str, repo_name: str, 35 target: Optional[str]) -> None: 36 """Start serving a repository to a target device. 37 38 Args: 39 repo_dir: directory the repository is served from. 40 repo_name: repository name. 41 target: Fuchsia device the repository is served to. 42 """ 43 44 cmd = [ 45 'repository', 'server', 'start', '--background', 46 '--address', '[::]:0', 47 '--repository', repo_name, '--repo-path', repo_dir, '--no-device' 48 ] 49 50 with monitors.time_consumption('repository', 'start'): 51 start_cmd = run_ffx_command(cmd=cmd, check=False) 52 53 logging.warning('ffx repository server start returns %d: %s %s', 54 start_cmd.returncode, 55 start_cmd.stderr, start_cmd.stdout) 56 57 _assert_server_running(repo_name) 58 59 cmd = [ 60 'target', 'repository', 'register', '-r', repo_name, '--alias', 61 REPO_ALIAS 62 ] 63 with monitors.time_consumption('repository', 'register'): 64 run_ffx_command(cmd=cmd, target_id=target) 65 66 67 def _assert_server_running(repo_name: str) -> None: 68 """Raises RuntimeError if the repository server is not running.""" 69 70 with monitors.time_consumption('repository', 'list'): 71 list_cmd = run_ffx_command(cmd=[ 72 '--machine', 'json', 'repository', 'server', 'list', '--name', 73 repo_name 74 ], 75 check=False, 76 capture_output=True) 77 try: 78 response = json.loads(list_cmd.stdout.strip()) 79 if 'ok' in response and response['ok']['data']: 80 if response['ok']['data'][0]['name'] != repo_name: 81 raise RuntimeError( 82 'Repository server %s is not running. Output: %s stderr: %s' 83 % (repo_name, list_cmd.stdout, list_cmd.stderr)) 84 return 85 except json.decoder.JSONDecodeError as error: 86 # Log the json parsing error, but don't raise an exception since it 87 # does not have the full context of the error. 88 logging.error('Unexpected json string: %s, exception: %s, stderr: %s', 89 list_cmd.stdout, error, list_cmd.stderr) 90 raise RuntimeError( 91 'Repository server %s is not running. Output: %s stderr: %s' 92 % (repo_name, list_cmd.stdout, list_cmd.stderr)) 93 94 def register_serve_args(arg_parser: argparse.ArgumentParser) -> None: 95 """Register common arguments for repository serving.""" 96 97 serve_args = arg_parser.add_argument_group('serve', 98 'repo serving arguments') 99 serve_args.add_argument('--serve-repo', 100 dest='repo', 101 help='Directory the repository is served from.') 102 serve_args.add_argument('--repo-name', 103 default=_REPO_NAME, 104 help='Name of the repository.') 105 106 @contextlib.contextmanager 107 def serve_repository(args: argparse.Namespace) -> Iterator[None]: 108 """Context manager for serving a repository.""" 109 _start_serving(args.repo, args.repo_name, args.target_id) 110 try: 111 yield None 112 finally: 113 _stop_serving(args.repo_name, args.target_id)