From ca95e876eb916b4bcf6fec95a477f5263b8679a1 Mon Sep 17 00:00:00 2001 From: CT Wu Date: Tue, 8 Apr 2025 17:48:40 +0800 Subject: [PATCH 1/6] Enhance Bitbucket provider functionality and update secret configuration template. --- pr_agent/git_providers/bitbucket_provider.py | 47 +++++++++++++------- pr_agent/settings/.secrets_template.toml | 6 ++- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 969ddf9e..74de2407 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -29,17 +29,26 @@ class BitbucketProvider(GitProvider): self, pr_url: Optional[str] = None, incremental: Optional[bool] = False ): s = requests.Session() - try: - self.bearer_token = bearer = context.get("bitbucket_bearer_token", None) - if not bearer and get_settings().get("BITBUCKET.BEARER_TOKEN", None): - self.bearer_token = bearer = get_settings().get("BITBUCKET.BEARER_TOKEN", None) - s.headers["Authorization"] = f"Bearer {bearer}" - except Exception: - self.bearer_token = get_settings().get("BITBUCKET.BEARER_TOKEN", None) - s.headers[ - "Authorization" - ] = f'Bearer {self.bearer_token}' s.headers["Content-Type"] = "application/json" + + try: + auth_type = context.get("bitbucket_auth_type", None) or get_settings().get("BITBUCKET.AUTH_TYPE", "bearer") + + if auth_type == "basic": + self.basic_token = context.get("bitbucket_basic_token", None) or get_settings().get("BITBUCKET.BASIC_TOKEN", None) + if not self.basic_token: + raise ValueError("Basic auth requires a token") + s.headers["Authorization"] = f"Basic {self.basic_token}" + else: # default to bearer + self.bearer_token = context.get("bitbucket_bearer_token", None) or get_settings().get("BITBUCKET.BEARER_TOKEN", None) + if not self.bearer_token: + raise ValueError("Bearer token is required for bearer auth") + s.headers["Authorization"] = f"Bearer {self.bearer_token}" + + except Exception as e: + get_logger().exception(f"Failed to initialize Bitbucket authentication: {e}") + raise + self.headers = s.headers self.bitbucket_client = Cloud(session=s) self.max_comment_length = 31000 @@ -608,16 +617,20 @@ class BitbucketProvider(GitProvider): if "bitbucket.org" not in repo_url_to_clone: get_logger().error("Repo URL is not a valid bitbucket URL.") return None - bearer_token = self.bearer_token - if not bearer_token: - get_logger().error("No bearer token provided. Returning None") - return None - #For example: For repo: https://bitbucket.org/codiumai/pr-agent-tests.git - #clone url will be: https://x-token-auth:@bitbucket.org/codiumai/pr-agent-tests.git (scheme, base_url) = repo_url_to_clone.split("bitbucket.org") if not all([scheme, base_url]): get_logger().error(f"repo_url_to_clone: {repo_url_to_clone} is not a valid bitbucket URL.") return None - clone_url = f"{scheme}x-token-auth:{bearer_token}@bitbucket.org{base_url}" + + if hasattr(self, 'basic_token'): + # Basic auth with token + clone_url = f"{scheme}x-token-auth:{self.basic_token}@bitbucket.org{base_url}" + elif hasattr(self, 'bearer_token'): + # Bearer token + clone_url = f"{scheme}x-token-auth:{self.bearer_token}@bitbucket.org{base_url}" + else: + get_logger().error("No valid authentication method provided. Returning None") + return None + return clone_url diff --git a/pr_agent/settings/.secrets_template.toml b/pr_agent/settings/.secrets_template.toml index 42946da5..845f9833 100644 --- a/pr_agent/settings/.secrets_template.toml +++ b/pr_agent/settings/.secrets_template.toml @@ -66,8 +66,12 @@ personal_access_token = "" shared_secret = "" # webhook secret [bitbucket] -# For Bitbucket personal/repository bearer token +# For Bitbucket authentication +auth_type = "bearer" # "bearer" or "basic" +# For bearer token authentication bearer_token = "" +# For basic authentication (uses token only) +basic_token = "" [bitbucket_server] # For Bitbucket Server bearer token From c0c307503f4088d880a8b80d1701f6b48c8f5a22 Mon Sep 17 00:00:00 2001 From: ChunTing Wu Date: Fri, 11 Apr 2025 14:55:06 +0800 Subject: [PATCH 2/6] Update pr_agent/git_providers/bitbucket_provider.py Co-authored-by: Prateek <110811408+Prateikx@users.noreply.github.com> --- pr_agent/git_providers/bitbucket_provider.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 74de2407..3a0d20be 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -32,18 +32,20 @@ class BitbucketProvider(GitProvider): s.headers["Content-Type"] = "application/json" try: - auth_type = context.get("bitbucket_auth_type", None) or get_settings().get("BITBUCKET.AUTH_TYPE", "bearer") + self.auth_type = context.get("bitbucket_auth_type", None) or get_settings().get("BITBUCKET.AUTH_TYPE", "bearer") - if auth_type == "basic": + if self.auth_type == "basic": self.basic_token = context.get("bitbucket_basic_token", None) or get_settings().get("BITBUCKET.BASIC_TOKEN", None) if not self.basic_token: raise ValueError("Basic auth requires a token") s.headers["Authorization"] = f"Basic {self.basic_token}" - else: # default to bearer + elif self.auth_type == "bearer": self.bearer_token = context.get("bitbucket_bearer_token", None) or get_settings().get("BITBUCKET.BEARER_TOKEN", None) if not self.bearer_token: raise ValueError("Bearer token is required for bearer auth") s.headers["Authorization"] = f"Bearer {self.bearer_token}" + else: + raise ValueError(f"Unsupported auth_type: {self.auth_type}") except Exception as e: get_logger().exception(f"Failed to initialize Bitbucket authentication: {e}") From 0cbf65dab6e66a35f4a61e36a31e266ae2080592 Mon Sep 17 00:00:00 2001 From: ChunTing Wu Date: Fri, 11 Apr 2025 14:59:27 +0800 Subject: [PATCH 3/6] Update pr_agent/git_providers/bitbucket_provider.py Co-authored-by: Prateek <110811408+Prateikx@users.noreply.github.com> --- pr_agent/git_providers/bitbucket_provider.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 3a0d20be..50d84abc 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -625,14 +625,15 @@ class BitbucketProvider(GitProvider): get_logger().error(f"repo_url_to_clone: {repo_url_to_clone} is not a valid bitbucket URL.") return None - if hasattr(self, 'basic_token'): + if self.auth_type == "basic": # Basic auth with token clone_url = f"{scheme}x-token-auth:{self.basic_token}@bitbucket.org{base_url}" - elif hasattr(self, 'bearer_token'): + elif self.auth_type == "bearer": # Bearer token clone_url = f"{scheme}x-token-auth:{self.bearer_token}@bitbucket.org{base_url}" else: - get_logger().error("No valid authentication method provided. Returning None") + # This case should ideally not be reached if __init__ validates auth_type + get_logger().error(f"Unsupported or uninitialized auth_type: {getattr(self, 'auth_type', 'N/A')}. Returning None") return None return clone_url From 5f2d4d400e8427a7f8d863692d2e10d7a0ca91b7 Mon Sep 17 00:00:00 2001 From: Chunting Wu Date: Fri, 11 Apr 2025 16:20:28 +0800 Subject: [PATCH 4/6] Extract repeated token retrieval logic into a helper function to reduce code duplication --- pr_agent/git_providers/bitbucket_provider.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 50d84abc..4c27e898 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -34,15 +34,17 @@ class BitbucketProvider(GitProvider): try: self.auth_type = context.get("bitbucket_auth_type", None) or get_settings().get("BITBUCKET.AUTH_TYPE", "bearer") + def get_token(token_name, auth_type_name): + token = context.get(f"bitbucket_{token_name}", None) or get_settings().get(f"BITBUCKET.{token_name.upper()}", None) + if not token: + raise ValueError(f"{auth_type_name} auth requires a token") + return token + if self.auth_type == "basic": - self.basic_token = context.get("bitbucket_basic_token", None) or get_settings().get("BITBUCKET.BASIC_TOKEN", None) - if not self.basic_token: - raise ValueError("Basic auth requires a token") + self.basic_token = get_token("basic_token", "Basic") s.headers["Authorization"] = f"Basic {self.basic_token}" elif self.auth_type == "bearer": - self.bearer_token = context.get("bitbucket_bearer_token", None) or get_settings().get("BITBUCKET.BEARER_TOKEN", None) - if not self.bearer_token: - raise ValueError("Bearer token is required for bearer auth") + self.bearer_token = get_token("bearer_token", "Bearer") s.headers["Authorization"] = f"Bearer {self.bearer_token}" else: raise ValueError(f"Unsupported auth_type: {self.auth_type}") From 3c8ad9eac8a2e311b7fe05c074d0325fe6bfeb30 Mon Sep 17 00:00:00 2001 From: Chunting Wu Date: Fri, 11 Apr 2025 16:55:32 +0800 Subject: [PATCH 5/6] Update doc for auth_type and basic_token --- docs/docs/installation/bitbucket.md | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/docs/installation/bitbucket.md b/docs/docs/installation/bitbucket.md index a15da9f1..507e88ae 100644 --- a/docs/docs/installation/bitbucket.md +++ b/docs/docs/installation/bitbucket.md @@ -1,33 +1,33 @@ ## Run as a Bitbucket Pipeline - You can use the Bitbucket Pipeline system to run PR-Agent on every pull request open or update. 1. Add the following file in your repository bitbucket-pipelines.yml ```yaml pipelines: - pull-requests: - '**': - - step: - name: PR Agent Review - image: python:3.12 - services: - - docker - script: - - docker run -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=https://bitbucket.org/$BITBUCKET_WORKSPACE/$BITBUCKET_REPO_SLUG/pull-requests/$BITBUCKET_PR_ID review + pull-requests: + "**": + - step: + name: PR Agent Review + image: python:3.12 + services: + - docker + script: + - docker run -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=https://bitbucket.org/$BITBUCKET_WORKSPACE/$BITBUCKET_REPO_SLUG/pull-requests/$BITBUCKET_PR_ID review ``` 2. Add the following secure variables to your repository under Repository settings > Pipelines > Repository variables. -OPENAI_API_KEY: `` -BITBUCKET_BEARER_TOKEN: `` + OPENAI_API_KEY: `` + BITBUCKET.AUTH_TYPE: `basic` or `bearer` (default is `bearer`) + BITBUCKET.BEARER_TOKEN: `` (required when auth_type is bearer) + BITBUCKET.BASIC_TOKEN: `` (required when auth_type is basic) You can get a Bitbucket token for your repository by following Repository Settings -> Security -> Access Tokens. +For basic auth, you can generate a base64 encoded token from your username:password combination. Note that comments on a PR are not supported in Bitbucket Pipeline. - - ## Bitbucket Server and Data Center Login into your on-prem instance of Bitbucket with your service account username and password. @@ -48,6 +48,7 @@ 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 ``` @@ -55,6 +56,7 @@ python cli.py --pr_url https://git.onpreminstanceofbitbucket.com/projects/PROJEC ### 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 From 7a32faf64f74be7ab7c0dc0465bd85218884084b Mon Sep 17 00:00:00 2001 From: Chunting Wu Date: Fri, 11 Apr 2025 17:44:46 +0800 Subject: [PATCH 6/6] Fix Bearer backward compatibility logic --- pr_agent/git_providers/bitbucket_provider.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 4c27e898..73db18b9 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -31,11 +31,11 @@ class BitbucketProvider(GitProvider): s = requests.Session() s.headers["Content-Type"] = "application/json" - try: - self.auth_type = context.get("bitbucket_auth_type", None) or get_settings().get("BITBUCKET.AUTH_TYPE", "bearer") + self.auth_type = get_settings().get("BITBUCKET.AUTH_TYPE", "bearer") + try: def get_token(token_name, auth_type_name): - token = context.get(f"bitbucket_{token_name}", None) or get_settings().get(f"BITBUCKET.{token_name.upper()}", None) + token = get_settings().get(f"BITBUCKET.{token_name.upper()}", None) if not token: raise ValueError(f"{auth_type_name} auth requires a token") return token @@ -44,7 +44,13 @@ class BitbucketProvider(GitProvider): self.basic_token = get_token("basic_token", "Basic") s.headers["Authorization"] = f"Basic {self.basic_token}" elif self.auth_type == "bearer": - self.bearer_token = get_token("bearer_token", "Bearer") + try: + self.bearer_token = context.get("bitbucket_bearer_token", None) + except: + self.bearer_token = None + + if not self.bearer_token: + get_token("bearer_token", "Bearer") s.headers["Authorization"] = f"Bearer {self.bearer_token}" else: raise ValueError(f"Unsupported auth_type: {self.auth_type}")