diff --git a/INSTALL.md b/INSTALL.md index d0298033..9efb4aa3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -415,4 +415,41 @@ You can get a Bitbucket token for your repository by following Repository Settin Please contact if you're interested in a hosted BitBucket app solution that provides full functionality including PR reviews and comment handling. It's based on the [bitbucket_app.py](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/git_providers/bitbucket_provider.py) implmentation. +### Bitbucket Server and Data Center + +Login into your on-prem instance of Bitbucket with your service account username and password. +Navigate to `Manage account`, `HTTP Access tokens`, `Create Token`. +Generate the token and add it to .secret.toml under `bitbucket_server` section + +```toml +[bitbucket_server] +bearer_token = "" +``` + +#### Run it as CLI + +Modify `configuration.toml`: + +```toml +git_provider="bitbucket_server" +``` + +and pass the Pull request URL: +```shell +python cli.py --pr_url https://git.onpreminstanceofbitbucket.com/projects/PROJECT/repos/REPO/pull-requests/1 review +``` + +#### Run it as service + +To run pr-agent as webhook, build the docker image: +``` +docker build . -t codiumai/pr-agent:bitbucket_server_webhook --target bitbucket_server_webhook -f docker/Dockerfile +docker push codiumai/pr-agent:bitbucket_server_webhook # Push to your Docker repository +``` + +Navigate to `Projects` or `Repositories`, `Settings`, `Webhooks`, `Create Webhook`. +Fill the name and URL, Authentication None select the Pull Request Opened checkbox to receive that event as webhook. + +The url should be ends with `/webhook`, example: https://domain.com/webhook + ======= diff --git a/docker/Dockerfile b/docker/Dockerfile index 951f846c..0f669e89 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,6 +14,10 @@ FROM base as bitbucket_app ADD pr_agent pr_agent CMD ["python", "pr_agent/servers/bitbucket_app.py"] +FROM base as bitbucket_server_webhook +ADD pr_agent pr_agent +CMD ["python", "pr_agent/servers/bitbucket_server_webhook.py"] + FROM base as github_polling ADD pr_agent pr_agent CMD ["python", "pr_agent/servers/github_polling.py"] diff --git a/pr_agent/git_providers/bitbucket_server_provider.py b/pr_agent/git_providers/bitbucket_server_provider.py index ed0a7a19..44347850 100644 --- a/pr_agent/git_providers/bitbucket_server_provider.py +++ b/pr_agent/git_providers/bitbucket_server_provider.py @@ -1,6 +1,4 @@ -import difflib import json -import re from typing import Optional, Tuple from urllib.parse import urlparse @@ -26,7 +24,7 @@ class BitbucketServerProvider(GitProvider): except Exception: s.headers[ "Authorization" - ] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' + ] = f'Bearer {get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN", None)}' s.headers["Content-Type"] = "application/json" self.headers = s.headers @@ -44,7 +42,7 @@ class BitbucketServerProvider(GitProvider): self.bitbucket_server_url = self._parse_bitbucket_server(url=pr_url) self.bitbucket_client = Bitbucket(url=self.bitbucket_server_url, - token=get_settings().get("BITBUCKET.BEARER_TOKEN", None)) + token=get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN", None)) if pr_url: self.set_pr(pr_url) @@ -114,7 +112,7 @@ class BitbucketServerProvider(GitProvider): return False def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels', 'gfm_markdown']: + if capability in ['get_issue_comments', 'get_labels', 'gfm_markdown']: return False return True diff --git a/pr_agent/servers/bitbucket_server_webhook.py b/pr_agent/servers/bitbucket_server_webhook.py new file mode 100644 index 00000000..c6ce8353 --- /dev/null +++ b/pr_agent/servers/bitbucket_server_webhook.py @@ -0,0 +1,64 @@ +import json + +import uvicorn +from fastapi import APIRouter, FastAPI +from fastapi.encoders import jsonable_encoder +from starlette import status +from starlette.background import BackgroundTasks +from starlette.middleware import Middleware +from starlette.requests import Request +from starlette.responses import JSONResponse +from starlette_context.middleware import RawContextMiddleware + +from pr_agent.agent.pr_agent import PRAgent +from pr_agent.config_loader import get_settings +from pr_agent.log import get_logger + +router = APIRouter() + + +def handle_request(background_tasks: BackgroundTasks, url: str, body: str, log_context: dict): + log_context["action"] = body + log_context["event"] = "pull_request" if body == "review" else "comment" + log_context["api_url"] = url + with get_logger().contextualize(**log_context): + background_tasks.add_task(PRAgent().handle_request, url, body) + + +@router.post("/webhook") +async def handle_webhook(background_tasks: BackgroundTasks, request: Request): + log_context = {"server_type": "bitbucket_server"} + data = await request.json() + get_logger().info(json.dumps(data)) + + pr_id = data['pullRequest']['id'] + repository_name = data['pullRequest']['toRef']['repository']['slug'] + project_name = data['pullRequest']['toRef']['repository']['project']['key'] + bitbucket_server = get_settings().get("BITBUCKET_SERVER.URL") + pr_url = f"{bitbucket_server}/projects/{project_name}/repos/{repository_name}/pull-requests/{pr_id}" + + log_context["api_url"] = pr_url + log_context["event"] = "pull_request" + + handle_request(background_tasks, pr_url, "review", log_context) + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"})) + + +@router.get("/") +async def root(): + return {"status": "ok"} + + +def start(): + bitbucket_server_url = get_settings().get("BITBUCKET_SERVER.URL", None) + if not bitbucket_server_url: + raise ValueError("BITBUCKET_SERVER.URL is not set") + get_settings().config.git_provider = "bitbucket_server" + middleware = [Middleware(RawContextMiddleware)] + app = FastAPI(middleware=middleware) + app.include_router(router) + uvicorn.run(app, host="0.0.0.0", port=3000) + + +if __name__ == '__main__': + start()