Skip to content

Conversation

rahulbollisetty
Copy link

@rahulbollisetty rahulbollisetty commented Nov 28, 2024

Fixes #18968

PR Summary:

This PR addresses an issue with the argocd.argoproj.io/manifest-generate-paths Manifest Paths Annotation in Argo CD when used with Bitbucket repositories.

Key Changes:

  • Prevents spurious refreshes in cases where multiple Applications are using the same Git monorepo stored in Bitbucket.
  • Ensures that the manifest-generate-paths annotation is handled correctly, refreshing the app only when the files changed match with the paths in manifest-generate-paths .

Steps to Implement:

  1. Add Bitbucket Repository to ArgoCD:
    Use the following command to add the Bitbucket repository to ArgoCD with authentication:

    argocd repo add <REPO_URL> --username <BITBUCKET_USERNAME> --password <BITBUCKET_APP_PASSWORD>
    • <REPO_URL>: Replace with your Bitbucket repository URL.
    • <BITBUCKET_USERNAME>: Your Bitbucket account username.
    • <BITBUCKET_APP_PASSWORD>: An app password generated in Bitbucket (can be found in your Bitbucket account setting
  2. Generate an Access Token for the Repository in Bitbucket:

    • Create an access token from your Bitbucket repository.
    • Use the access token in your repository's secret.
  3. Store the Access Token as a Secret:
    Convert the generated access token to Base64 format. You can use the following command:

    echo "ACCESS_TOKEN" | base64 -w0
    • Replace ACCESS_TOKEN with your actual Bitbucket access token.
    • Use the access token generated in the previous step to add a Kubernetes secret for the repository added in Step 1.Store the access token in the secret under the key data.accessToken

Checklist:

  • Either (a) I've created an enhancement proposal and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
  • The title of the PR states what changed and the related issues number (used for the release note).
  • The title of the PR conforms to the Toolchain Guide
  • I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
  • I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
  • Does this PR require documentation updates?
  • I've updated documentation as required by this PR.
  • I have signed off all my commits as required by DCO
  • I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
  • My build is green (troubleshooting builds).
  • My new feature complies with the feature status guidelines.
  • I have added a brief description of why this PR is necessary and/or what this PR solves.
  • Optional. My organization is added to USERS.md.
  • Optional. For bug fixes, I've indicated what older releases this fix should be cherry-picked into (this may or may not happen depending on risk/complexity).

…#18968

Signed-off-by: B Rahul <rahul20bollisetty@gmail.com>
@rahulbollisetty rahulbollisetty requested a review from a team as a code owner November 28, 2024 16:21
Copy link

bunnyshell bot commented Nov 28, 2024

❌ Preview Environment deleted from Bunnyshell

Available commands (reply to this comment):

  • 🚀 /bns:deploy to deploy the environment

@rahulbollisetty rahulbollisetty changed the title Fixed manifest-generate-paths monorepo support for Bitbucket #18968 feat: Fixed manifest-generate-paths monorepo support for Bitbucket #18968 Nov 28, 2024
Copy link

codecov bot commented Nov 28, 2024

Codecov Report

Attention: Patch coverage is 12.08791% with 80 lines in your changes missing coverage. Please review.

Project coverage is 54.98%. Comparing base (9a14d7f) to head (c4ba9ad).
Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
util/webhook/webhook.go 15.27% 59 Missing and 2 partials ⚠️
util/settings/settings.go 0.00% 19 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #20993      +/-   ##
==========================================
- Coverage   55.07%   54.98%   -0.10%     
==========================================
  Files         324      324              
  Lines       55466    55552      +86     
==========================================
- Hits        30547    30543       -4     
- Misses      22303    22388      +85     
- Partials     2616     2621       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

if secretRepoURL == repoURL {
if tokenBytes, exists := secret.Data["accessToken"]; exists {
return string(tokenBytes), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit uncomfortable with using selector and processing multiple items. What if somebody hijacks the flow by adding a new secret with a different/wrong token?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate a bit more about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just query a specific secret?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well see, if we are adding a private repo the secret that are created are of the format repo-178100904 , so you cannot directly query a specific secret, you have to iterate all the repository type secret and the select the one that matches with the URL.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can have the same repository-access-token (RAT) used while creating the repository for doing a call from the webhook as well.

// Check if a handler was passed
var webhookHandler *ArgoCDWebhookHandler
if len(optionalHandler) > 0 {
if handler, ok := optionalHandler[0].(*ArgoCDWebhookHandler); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leads to a confusing function signature, which implies multiple optional handlers can be passed. Maybe use a pointer and have it be nil if the optional handler is not available?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed pls check

// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get

url := fmt.Sprintf("https://api.bitbucket.org/2.0/repositories/%s/%s/diffstat/%s",
workspace, repoSlug, spec)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix formatting, either one argument per line or all in one line.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed pls check

Signed-off-by: B Rahul <rahul20bollisetty@gmail.com>

if webhookHandler != nil {
spec := change.shaAfter + ".." + change.shaBefore
accessToken, err := webhookHandler.settingsSrc.GetRepositorySecretAccessToken(webURLs[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webURLs[0] doesn't read well here, i.e. what makes index 0 special?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls have a look at the webhook payload, you can see that the bitbucket includes multiple changes in a single event, so I have appended the URL of repo in webURL. So only one webURLs is present in case of bitbucket, whereas it is different for github, gitlab, etc. So it has to be a array, wherein only one item is present in case of bitbucket. That's why index 0 is used.

return nil, fmt.Errorf("error creating request: %w", err)
}

trimmedAccessToken := strings.TrimSpace(accessToken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to trim it? Why can't it be without spaces?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever fetching the accessToken from the secrets, a new line is added by default. When this accessToken is passed without removing the spaces/newline, the HTTP GET call fails, with error - invalid header field value for \"Authorization\""

refParts := strings.SplitN(ref, "/", 3)
return refParts[len(refParts)-1]
}

// affectedRevisionInfo examines a payload from a webhook event, and extracts the repo web URL,
// the revision, and whether or not this affected origin/HEAD (the default branch of the repository)
func affectedRevisionInfo(payloadIf interface{}) (webURLs []string, revision string, change changeInfo, touchedHead bool, changedFiles []string) {
func affectedRevisionInfo(payloadIf interface{}, optionalHandler *ArgoCDWebhookHandler) (webURLs []string, revision string, change changeInfo, touchedHead bool, changedFiles []string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just call this webhookHandler and use it directly without declaring extra vars?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed it, please check

Signed-off-by: B Rahul <rahul20bollisetty@gmail.com>
Copy link
Member

@nitishfy nitishfy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll spend some time taking a look at the code you've written so far. Meanwhile, can you briefly explain how did you solve this problem?

@rahulbollisetty
Copy link
Author

I'll spend some time taking a look at the code you've written so far. Meanwhile, can you briefly explain how did you solve this problem?

The webhook payload didn't consist of files changed. Bitbucket provides API, which gives the path of files that are changed. Using the webhook payload, I pull out the hash of new and old commit, workspace name and repoSlug, and then get the access token from repo secret (repo secret is created when a repo is added using argocd repo add command, then user has manually add the accessToken inside the data key of repo secret), using these I make a GET call. I have added a method (GetRepositorySecretAccessToken) in util/settings.go to get the secret, as it was the best possible option to get the secret from the added repo when called from util/webhook.go. To access the util/settings.go , I have modified the signature of affectedRevisionInfo in util/webhook.go specifically for bitbucket.

@jarlebh
Copy link

jarlebh commented Jan 10, 2025

We really need this fix, any timeline for getting this merged ?

@nitishfy
Copy link
Member

I'll start with reviewing this PR in depth now.

@nitishfy nitishfy self-assigned this Jan 16, 2025
Copy link
Member

@nitishfy nitishfy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works well. We need to update the docs to tell about the implementation which can be done in the same PR.

Comment on lines +277 to +278
// Getting the files changed from diff API:
// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Getting the files changed from diff API:
// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get
// fetchDiffstatFromBitbucket gets the files changed from diff API - https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get

Please add this as a function comment (at the top of function)

Co-authored-by: Nitish Kumar <justnitish06@gmail.com>
Signed-off-by: B Rahul <rahul20bollisetty@gmail.com>
@@ -244,14 +271,66 @@ func affectedRevisionInfo(payloadIf interface{}) (webURLs []string, revision str
return webURLs, revision, change, touchedHead, changedFiles
}

func fetchDiffstatFromBitbucket(workspace, repoSlug, spec, accessToken string) ([]string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about this. iirc our webhook handler isn't auth-protected. So anyone with API access could hammer the endpoint and trigger SSRF. I don't have time to give a thorough review right now, but requesting changes so this doesn't get merged without further conversation.

@anandf
Copy link
Member

anandf commented Feb 7, 2025

I have created an improved version of this PR. #21811
the improvements I have done in my branch are,

  1. Use existing repo secret for the webhook callback whereas the original one required a new kind of secret to store auth tokens for making calls from within the webhook handler.
  2. Use BitBucket APIs to do the REST call instead of using http client.

@nitishfy
Copy link
Member

Closing this in favour of #21811

@nitishfy nitishfy closed this Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

manifest-generate-paths monorepo support for Bitbucket
7 participants