1#!/usr/bin/env vpython3 2# Copyright 2022 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Implements commands for serving a TUF repository.""" 6 7import argparse 8import contextlib 9import sys 10 11from typing import Iterator, Optional 12 13from common import REPO_ALIAS, catch_sigterm, register_device_args, \ 14 run_ffx_command, wait_for_sigterm 15 16_REPO_NAME = 'chromium-test-package-server' 17 18 19def _stop_serving(repo_name: str, target: Optional[str]) -> None: 20 """Stop serving a repository.""" 21 22 # Attempt to clean up. 23 run_ffx_command( 24 cmd=['target', 'repository', 'deregister', '-r', repo_name], 25 target_id=target, 26 check=False) 27 run_ffx_command(cmd=['repository', 'remove', repo_name], check=False) 28 run_ffx_command(cmd=['repository', 'server', 'stop'], check=False) 29 30 31def _start_serving(repo_dir: str, repo_name: str, 32 target: Optional[str]) -> None: 33 """Start serving a repository to a target device. 34 35 Args: 36 repo_dir: directory the repository is served from. 37 repo_name: repository name. 38 target: Fuchsia device the repository is served to. 39 """ 40 41 run_ffx_command(cmd=('config', 'set', 'repository.server.mode', '\"ffx\"')) 42 43 run_ffx_command(cmd=['repository', 'server', 'start']) 44 run_ffx_command( 45 cmd=['repository', 'add-from-pm', repo_dir, '-r', repo_name]) 46 run_ffx_command(cmd=[ 47 'target', 'repository', 'register', '-r', repo_name, '--alias', 48 REPO_ALIAS 49 ], 50 target_id=target) 51 52 53def register_serve_args(arg_parser: argparse.ArgumentParser) -> None: 54 """Register common arguments for repository serving.""" 55 56 serve_args = arg_parser.add_argument_group('serve', 57 'repo serving arguments') 58 serve_args.add_argument('--serve-repo', 59 dest='repo', 60 help='Directory the repository is served from.') 61 serve_args.add_argument('--repo-name', 62 default=_REPO_NAME, 63 help='Name of the repository.') 64 65 66def run_serve_cmd(cmd: str, args: argparse.Namespace) -> None: 67 """Helper for running serve commands.""" 68 69 if cmd == 'start': 70 _start_serving(args.repo, args.repo_name, args.target_id) 71 elif cmd == 'stop': 72 _stop_serving(args.repo_name, args.target_id) 73 else: 74 assert cmd == 'run' 75 catch_sigterm() 76 with serve_repository(args): 77 # Clients can assume the repo is up and running once the repo-name 78 # is printed out. 79 print(args.repo_name, flush=True) 80 wait_for_sigterm('shutting down the repo server.') 81 82 83@contextlib.contextmanager 84def serve_repository(args: argparse.Namespace) -> Iterator[None]: 85 """Context manager for serving a repository.""" 86 run_serve_cmd('start', args) 87 try: 88 yield None 89 finally: 90 run_serve_cmd('stop', args) 91 92 93def main(): 94 """Stand-alone function for serving a repository.""" 95 96 parser = argparse.ArgumentParser() 97 parser.add_argument('cmd', 98 choices=['start', 'stop', 'run'], 99 help='Choose to start|stop|run repository serving. ' \ 100 '"start" command will start the repo and exit; ' \ 101 '"run" command will start the repo and wait ' \ 102 'until ctrl-c or sigterm.') 103 register_device_args(parser) 104 register_serve_args(parser) 105 args = parser.parse_args() 106 if (args.cmd == 'start' or args.cmd == 'run') and not args.repo: 107 raise ValueError('Directory the repository is serving from needs ' 108 'to be specified.') 109 110 run_serve_cmd(args.cmd, args) 111 112 113if __name__ == '__main__': 114 sys.exit(main()) 115