Exemplo n.º 1
0
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
Exemplo n.º 2
0
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)