def _do_basic_auth_check(request: Request) -> Union[None, bool]: if "download_" not in request.match_info.route.name: return auth = None auth_header = request.headers.get(hdrs.AUTHORIZATION) if auth_header is not None: try: auth = BasicAuth.decode(auth_header=auth_header) except ValueError: pass if auth is None: try: auth = BasicAuth.from_url(request.url) except ValueError: pass if not auth: return Response( body=b"", status=401, reason="UNAUTHORIZED", headers={hdrs.WWW_AUTHENTICATE: 'Basic realm=""'}, ) if auth.login is None or auth.password is None: return if (auth.login != request.app["username"] or auth.password != request.app["password"]): return return True
async def main(argv=None): parser = argparse.ArgumentParser( prog="janitor-pull-worker", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "--base-url", type=str, help="Base URL", default="https://janitor.debian.net/api/", ) parser.add_argument("--output-directory", type=str, help="Output directory", default=".") parser.add_argument("--credentials", help="Path to credentials file (JSON).", type=str, default=None) parser.add_argument("--vcs-location", help="Override VCS location.", type=str) parser.add_argument( "--debug", help="Print out API communication", action="store_true", default=False, ) parser.add_argument("--prometheus", type=str, help="Prometheus push gateway to export to.") parser.add_argument('--port', type=int, default=0, help="Port to use for diagnostics web server") # Unused, here for backwards compatibility. parser.add_argument('--build-command', help=argparse.SUPPRESS, type=str) parser.add_argument("--gcp-logging", action="store_true") parser.add_argument("--listen-address", type=str, default="127.0.0.1") args = parser.parse_args(argv) if args.gcp_logging: import google.cloud.logging client = google.cloud.logging.Client() client.get_default_handler() client.setup_logging() else: if args.debug: log_level = logging.DEBUG else: log_level = logging.INFO logging.basicConfig(level=log_level, format="[%(asctime)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") global_config = GlobalStack() global_config.set("branch.fetch_tags", True) base_url = yarl.URL(args.base_url) if args.credentials: with open(args.credentials) as f: creds = json.load(f) auth = BasicAuth(login=creds["login"], password=creds["password"]) elif 'WORKER_NAME' in os.environ and 'WORKER_PASSWORD' in os.environ: auth = BasicAuth(login=os.environ["WORKER_NAME"], password=os.environ["WORKER_PASSWORD"]) else: auth = BasicAuth.from_url(base_url) if auth is not None: class WorkerCredentialStore(PlainTextCredentialStore): def get_credentials(self, protocol, host, port=None, user=None, path=None, realm=None): if host == base_url.host: return { "user": auth.login, "password": auth.password, "protocol": protocol, "port": port, "host": host, "realm": realm, "verify_certificates": True, } return None credential_store_registry.register("janitor-worker", WorkerCredentialStore, fallback=True) if any( filter( os.environ.__contains__, ["BUILD_URL", "EXECUTOR_NUMBER", "BUILD_ID", "BUILD_NUMBER"], )): jenkins_metadata = { "build_url": os.environ.get("BUILD_URL"), "executor_number": os.environ.get("EXECUTOR_NUMBER"), "build_id": os.environ.get("BUILD_ID"), "build_number": os.environ.get("BUILD_NUMBER"), } else: jenkins_metadata = None node_name = os.environ.get("NODE_NAME") if not node_name: node_name = socket.gethostname() async with ClientSession(auth=auth) as session: try: assignment = await get_assignment( session, args.base_url, node_name, jenkins_metadata=jenkins_metadata) except asyncio.TimeoutError as e: logging.fatal("timeout while retrieving assignment: %s", e) return 1 logging.debug("Got back assignment: %r", assignment) watchdog_petter = WatchdogPetter(args.base_url, auth, assignment['id'], queue_id=assignment['queue_id']) watchdog_petter.start() suite = assignment["suite"] branch_url = assignment["branch"]["url"] vcs_type = assignment["branch"]["vcs_type"] subpath = assignment["branch"].get("subpath", "") or "" if assignment["resume"]: resume_result = assignment["resume"].get("result") resume_branch_url = assignment["resume"]["branch_url"].rstrip("/") resume_branches = [ (role, name, base.encode("utf-8"), revision.encode("utf-8")) for (role, name, base, revision) in assignment["resume"]["branches"] ] else: resume_result = None resume_branch_url = None resume_branches = None cached_branch_url = assignment["branch"].get("cached_url") command = assignment["command"] if isinstance(command, str): command = shlex.split(command) target = assignment["build"]["target"] build_environment = assignment["build"].get("environment", {}) if args.vcs_location: vcs_manager = LocalVcsManager(args.vcs_location) else: vcs_manager = RemoteVcsManager(assignment["vcs_manager"]) run_id = assignment["id"] possible_transports = [] env = assignment["env"] vendor = build_environment.get('DEB_VENDOR', 'debian') os.environ.update(env) os.environ.update(build_environment) metadata = {"queue_id": assignment["queue_id"]} if jenkins_metadata: metadata["jenkins"] = jenkins_metadata with TemporaryDirectory(prefix='janitor') as output_directory: loop = asyncio.get_running_loop() loop.add_signal_handler(signal.SIGINT, handle_sigterm, session, args.base_url, run_id) loop.add_signal_handler(signal.SIGTERM, handle_sigterm, session, args.base_url, run_id) app = web.Application() app['directory'] = output_directory app['assignment'] = assignment app['metadata'] = metadata app.router.add_get('/', handle_index, name='index') app.router.add_get('/assignment', handle_assignment, name='assignment') app.router.add_get('/logs/', handle_log_index, name='log-index') app.router.add_get('/logs/{filename}', handle_log, name='log') app.router.add_get('/health', handle_health, name='health') setup_metrics(app) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, args.listen_address, args.port) await site.start() (site_addr, site_port) = site._server.sockets[0].getsockname() logging.info('Diagnostics available at http://%s:%d/', site_addr, site_port) watchdog_petter.track_log_directory(output_directory) start_time = datetime.utcnow() main_task = loop.run_in_executor( None, partial( run_worker, branch_url, run_id, subpath, vcs_type, os.environ, command, output_directory, metadata, vcs_manager, vendor, suite, target=target, resume_branch_url=resume_branch_url, resume_branches=resume_branches, cached_branch_url=cached_branch_url, resume_subworker_result=resume_result, possible_transports=possible_transports, ), ) watchdog_petter.kill = main_task.cancel metadata["start_time"] = start_time.isoformat() try: result = await main_task except WorkerFailure as e: metadata.update(e.json()) logging.info("Worker failed (%s): %s", e.code, e.description) # This is a failure for the worker, but returning 0 will cause # jenkins to mark the job having failed, which is not really # true. We're happy if we get to successfully POST to /finish return 0 except OSError as e: if e.errno == errno.ENOSPC: metadata["code"] = "no-space-on-device" metadata["description"] = str(e) else: metadata["code"] = "worker-exception" metadata["description"] = str(e) raise except BaseException as e: metadata["code"] = "worker-failure" metadata["description"] = ''.join( traceback.format_exception_only(type(e), e)).rstrip('\n') raise else: metadata["code"] = None metadata.update(result.json()) logging.info("%s", result.description) return 0 finally: finish_time = datetime.utcnow() metadata["finish_time"] = finish_time.isoformat() logging.info("Elapsed time: %s", finish_time - start_time) try: result = await upload_results( session, args.base_url, assignment["id"], metadata, output_directory, ) except ResultUploadFailure as e: sys.stderr.write(str(e)) sys.exit(1) logging.info('Results uploaded') if args.debug: logging.debug("Result: %r", result) if args.prometheus: push_to_gateway(args.prometheus, job="janitor.pull_worker", registry=REGISTRY)