mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
Signed-off-by: pbhatnagar-oss <pbhatifiwork@gmail.com> Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
This commit is contained in:
@@ -243,9 +243,9 @@ func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alph
|
||||
}
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
|
||||
return pullrequest.NewGithubAppService(ctx, *auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
|
||||
}
|
||||
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
|
||||
return pullrequest.NewGithubAppService(ctx, *auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
|
||||
}
|
||||
|
||||
// always default to token, even if not set (public access)
|
||||
|
||||
@@ -296,9 +296,9 @@ func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argop
|
||||
}
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches, httpClient)
|
||||
return scm_provider.NewGithubAppProviderFor(ctx, *auth, github.Organization, github.API, github.AllBranches, httpClient)
|
||||
}
|
||||
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches)
|
||||
return scm_provider.NewGithubAppProviderFor(ctx, *auth, github.Organization, github.API, github.AllBranches)
|
||||
}
|
||||
|
||||
token, err := utils.GetSecretRef(ctx, g.client, github.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package github_app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -8,40 +10,65 @@ import (
|
||||
"github.com/google/go-github/v69/github"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v3/util/git"
|
||||
)
|
||||
|
||||
func getOptionalHTTPClientAndTransport(optionalHTTPClient ...*http.Client) (*http.Client, http.RoundTripper) {
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
if len(optionalHTTPClient) > 0 && optionalHTTPClient[0] != nil && optionalHTTPClient[0].Transport != nil {
|
||||
// will either use the provided custom httpClient and it's transport
|
||||
return httpClient, optionalHTTPClient[0].Transport
|
||||
}
|
||||
// or the default httpClient and transport
|
||||
return httpClient, http.DefaultTransport
|
||||
// getInstallationClient creates a new GitHub client with the specified installation ID.
|
||||
// It also returns a ghinstallation.Transport, which can be used for git requests.
|
||||
func getInstallationClient(g github_app_auth.Authentication, url string, httpClient ...*http.Client) (*github.Client, error) {
|
||||
if g.InstallationId <= 0 {
|
||||
return nil, errors.New("installation ID is required for github")
|
||||
}
|
||||
|
||||
// Client builds a github client for the given app authentication.
|
||||
func Client(g github_app_auth.Authentication, url string, optionalHTTPClient ...*http.Client) (*github.Client, error) {
|
||||
httpClient, transport := getOptionalHTTPClientAndTransport(optionalHTTPClient...)
|
||||
// Use provided HTTP client's transport or default
|
||||
var transport http.RoundTripper
|
||||
if len(httpClient) > 0 && httpClient[0] != nil && httpClient[0].Transport != nil {
|
||||
transport = httpClient[0].Transport
|
||||
} else {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
rt, err := ghinstallation.New(transport, g.Id, g.InstallationId, []byte(g.PrivateKey))
|
||||
itr, err := ghinstallation.New(transport, g.Id, g.InstallationId, []byte(g.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create github app install: %w", err)
|
||||
return nil, fmt.Errorf("failed to create GitHub installation transport: %w", err)
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
url = g.EnterpriseBaseURL
|
||||
}
|
||||
|
||||
var client *github.Client
|
||||
httpClient.Transport = rt
|
||||
if url == "" {
|
||||
client = github.NewClient(httpClient)
|
||||
} else {
|
||||
rt.BaseURL = url
|
||||
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
|
||||
client = github.NewClient(&http.Client{Transport: itr})
|
||||
return client, nil
|
||||
}
|
||||
|
||||
itr.BaseURL = url
|
||||
client, err = github.NewClient(&http.Client{Transport: itr}).WithEnterpriseURLs(url, url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create GitHub enterprise client: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Client builds a github client for the given app authentication.
|
||||
func Client(ctx context.Context, g github_app_auth.Authentication, url, org string, optionalHTTPClient ...*http.Client) (*github.Client, error) {
|
||||
if url == "" {
|
||||
url = g.EnterpriseBaseURL
|
||||
}
|
||||
|
||||
// If an installation ID is already provided, use it directly.
|
||||
if g.InstallationId != 0 {
|
||||
return getInstallationClient(g, url, optionalHTTPClient...)
|
||||
}
|
||||
|
||||
// Auto-discover installation ID using shared utility
|
||||
// Pass optional HTTP client for metrics tracking
|
||||
installationId, err := git.DiscoverGitHubAppInstallationID(ctx, g.Id, g.PrivateKey, url, org, optionalHTTPClient...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.InstallationId = installationId
|
||||
return getInstallationClient(g, url, optionalHTTPClient...)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package pull_request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
@@ -8,9 +9,9 @@ import (
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) {
|
||||
func NewGithubAppService(ctx context.Context, g github_app_auth.Authentication, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) {
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
client, err := github_app.Client(g, url, httpClient)
|
||||
client, err := github_app.Client(ctx, g, url, owner, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package scm_provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
@@ -8,9 +9,9 @@ import (
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
func NewGithubAppProviderFor(g github_app_auth.Authentication, organization string, url string, allBranches bool, optionalHTTPClient ...*http.Client) (*GithubProvider, error) {
|
||||
func NewGithubAppProviderFor(ctx context.Context, g github_app_auth.Authentication, organization string, url string, allBranches bool, optionalHTTPClient ...*http.Client) (*GithubProvider, error) {
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
client, err := github_app.Client(g, url, httpClient)
|
||||
client, err := github_app.Client(ctx, g, url, organization, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -77,6 +77,15 @@ func NewGenRepoSpecCommand() *cobra.Command {
|
||||
|
||||
# Add a private HTTP OCI repository named 'stable'
|
||||
argocd admin repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
|
||||
|
||||
# Add a private Git repository on GitHub.com via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd admin repo generate-spec https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd admin repo generate-spec https://ghe.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
|
||||
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
|
||||
argocd admin repo generate-spec https://source.developers.google.com/p/my-google-cloud-project/r/my-repo --gcp-service-account-key-path service-account-key.json
|
||||
`
|
||||
|
||||
command := &cobra.Command{
|
||||
|
||||
@@ -94,10 +94,10 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
# Add a private HTTP OCI repository named 'stable'
|
||||
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
|
||||
|
||||
# Add a private Git repository on GitHub.com via GitHub App
|
||||
# Add a private Git repository on GitHub.com via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repo add https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repo add https://ghe.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
|
||||
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
|
||||
|
||||
@@ -72,10 +72,10 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
# Add credentials with SSH private key authentication to use for all repositories under ssh://git@git.example.com/repos
|
||||
argocd repocreds add ssh://git@git.example.com/repos/ --ssh-private-key-path ~/.ssh/id_rsa
|
||||
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://github.com/repos
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://github.com/repos. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repocreds add https://github.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://ghe.example.com/repos
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://ghe.example.com/repos. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repocreds add https://ghe.example.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
|
||||
# Add credentials with helm oci registry so that these oci registry urls do not need to be added as repos individually.
|
||||
@@ -191,7 +191,7 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
|
||||
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key (must be PEM format)")
|
||||
command.Flags().Int64Var(&repo.GithubAppId, "github-app-id", 0, "id of the GitHub Application")
|
||||
command.Flags().Int64Var(&repo.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application")
|
||||
command.Flags().Int64Var(&repo.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application (optional, will be auto-discovered if not provided)")
|
||||
command.Flags().StringVar(&githubAppPrivateKeyPath, "github-app-private-key-path", "", "private key of the GitHub Application")
|
||||
command.Flags().StringVar(&repo.GitHubAppEnterpriseBaseURL, "github-app-enterprise-base-url", "", "base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3")
|
||||
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
|
||||
|
||||
@@ -45,7 +45,7 @@ func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
|
||||
command.Flags().BoolVar(&opts.EnableLfs, "enable-lfs", false, "enable git-lfs (Large File Support) on this repository")
|
||||
command.Flags().BoolVar(&opts.EnableOci, "enable-oci", false, "enable helm-oci (Helm OCI-Based Repository) (only valid for helm type repositories)")
|
||||
command.Flags().Int64Var(&opts.GithubAppId, "github-app-id", 0, "id of the GitHub Application")
|
||||
command.Flags().Int64Var(&opts.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application")
|
||||
command.Flags().Int64Var(&opts.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application (optional, will be auto-discovered if not provided)")
|
||||
command.Flags().StringVar(&opts.GithubAppPrivateKeyPath, "github-app-private-key-path", "", "private key of the GitHub Application")
|
||||
command.Flags().StringVar(&opts.GitHubAppEnterpriseBaseURL, "github-app-enterprise-base-url", "", "base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3")
|
||||
command.Flags().StringVar(&opts.Proxy, "proxy", "", "use proxy to access repository")
|
||||
|
||||
@@ -13,7 +13,7 @@ func getCredentialType(repo *v1alpha1.Repository) string {
|
||||
if repo.SSHPrivateKey != "" {
|
||||
return "ssh"
|
||||
}
|
||||
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 && repo.GithubAppInstallationId != 0 {
|
||||
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 { // Promoter MVP: remove github-app-installation-id check since it is no longer a required field
|
||||
return "github-app"
|
||||
}
|
||||
if repo.GCPServiceAccountKey != "" {
|
||||
|
||||
@@ -45,6 +45,15 @@ argocd admin repo generate-spec REPOURL [flags]
|
||||
# Add a private HTTP OCI repository named 'stable'
|
||||
argocd admin repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
|
||||
|
||||
# Add a private Git repository on GitHub.com via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd admin repo generate-spec https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd admin repo generate-spec https://ghe.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
|
||||
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
|
||||
argocd admin repo generate-spec https://source.developers.google.com/p/my-google-cloud-project/r/my-repo --gcp-service-account-key-path service-account-key.json
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
@@ -58,7 +67,7 @@ argocd admin repo generate-spec REPOURL [flags]
|
||||
--gcp-service-account-key-path string service account key for the Google Cloud Platform
|
||||
--github-app-enterprise-base-url string base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3
|
||||
--github-app-id int id of the GitHub Application
|
||||
--github-app-installation-id int installation id of the GitHub Application
|
||||
--github-app-installation-id int installation id of the GitHub Application (optional, will be auto-discovered if not provided)
|
||||
--github-app-private-key-path string private key of the GitHub Application
|
||||
-h, --help help for generate-spec
|
||||
--insecure-ignore-host-key disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)
|
||||
|
||||
6
docs/user-guide/commands/argocd_repo_add.md
generated
6
docs/user-guide/commands/argocd_repo_add.md
generated
@@ -47,10 +47,10 @@ argocd repo add REPOURL [flags]
|
||||
# Add a private HTTP OCI repository named 'stable'
|
||||
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
|
||||
|
||||
# Add a private Git repository on GitHub.com via GitHub App
|
||||
# Add a private Git repository on GitHub.com via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repo add https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repo add https://ghe.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
|
||||
# Add a private Git repository on Google Cloud Sources via GCP service account credentials
|
||||
@@ -69,7 +69,7 @@ argocd repo add REPOURL [flags]
|
||||
--gcp-service-account-key-path string service account key for the Google Cloud Platform
|
||||
--github-app-enterprise-base-url string base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3
|
||||
--github-app-id int id of the GitHub Application
|
||||
--github-app-installation-id int installation id of the GitHub Application
|
||||
--github-app-installation-id int installation id of the GitHub Application (optional, will be auto-discovered if not provided)
|
||||
--github-app-private-key-path string private key of the GitHub Application
|
||||
-h, --help help for add
|
||||
--insecure-ignore-host-key disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)
|
||||
|
||||
6
docs/user-guide/commands/argocd_repocreds_add.md
generated
6
docs/user-guide/commands/argocd_repocreds_add.md
generated
@@ -20,10 +20,10 @@ argocd repocreds add REPOURL [flags]
|
||||
# Add credentials with SSH private key authentication to use for all repositories under ssh://git@git.example.com/repos
|
||||
argocd repocreds add ssh://git@git.example.com/repos/ --ssh-private-key-path ~/.ssh/id_rsa
|
||||
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://github.com/repos
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://github.com/repos. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repocreds add https://github.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://ghe.example.com/repos
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://ghe.example.com/repos. github-app-installation-id is optional, if not provided, the installation id will be fetched from the GitHub API.
|
||||
argocd repocreds add https://ghe.example.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
|
||||
# Add credentials with helm oci registry so that these oci registry urls do not need to be added as repos individually.
|
||||
@@ -43,7 +43,7 @@ argocd repocreds add REPOURL [flags]
|
||||
--gcp-service-account-key-path string service account key for the Google Cloud Platform
|
||||
--github-app-enterprise-base-url string base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3
|
||||
--github-app-id int id of the GitHub Application
|
||||
--github-app-installation-id int installation id of the GitHub Application
|
||||
--github-app-installation-id int installation id of the GitHub Application (optional, will be auto-discovered if not provided)
|
||||
--github-app-private-key-path string private key of the GitHub Application
|
||||
-h, --help help for add
|
||||
--password string password to the repository
|
||||
|
||||
@@ -122,13 +122,16 @@ argocd repo add https://github.com/argoproj/argocd-example-apps.git --github-app
|
||||
> [!NOTE]
|
||||
> To add a private Git repository on GitHub Enterprise using the CLI add `--github-app-enterprise-base-url https://ghe.example.com/api/v3` flag.
|
||||
|
||||
> [!NOTE]
|
||||
> The `--github-app-installation-id` flag is optional. If omitted, Argo CD will automatically discover the installation ID based on the repository's organization.
|
||||
|
||||
Using the UI:
|
||||
|
||||
1. Navigate to `Settings/Repositories`
|
||||
|
||||

|
||||
|
||||
2. Click `Connect Repo using GitHub App` button, choose type: `GitHub` or `GitHub Enterprise`, enter the URL, App Id, Installation Id, and the app's private key.
|
||||
2. Click `Connect Repo using GitHub App` button, choose type: `GitHub` or `GitHub Enterprise`, enter the URL, App Id, Installation Id (optional), and the app's private key.
|
||||
|
||||
> [!NOTE]
|
||||
> Enter the GitHub Enterprise Base URL for type `GitHub Enterprise`.
|
||||
|
||||
@@ -62,7 +62,8 @@ stringData:
|
||||
url: "https://github.com/<your org or user>/<your repo>"
|
||||
type: "git"
|
||||
githubAppID: "<your app ID here>"
|
||||
githubAppInstallationID: "<your installation ID here>"
|
||||
# githubAppInstallationID is optional and will be auto-discovered if omitted
|
||||
githubAppInstallationID: "<your installation ID here>" # Optional
|
||||
githubAppPrivateKey: |
|
||||
<your private key here>
|
||||
---
|
||||
@@ -78,7 +79,8 @@ stringData:
|
||||
url: "https://github.com/<your org or user>/<your repo>"
|
||||
type: "git"
|
||||
githubAppID: "<your app ID here>"
|
||||
githubAppInstallationID: "<your installation ID here>"
|
||||
# githubAppInstallationID is optional and will be auto-discovered if omitted
|
||||
githubAppInstallationID: "<your installation ID here>" # Optional
|
||||
githubAppPrivateKey: |
|
||||
<your private key here>
|
||||
```
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/util/oci"
|
||||
|
||||
@@ -239,8 +241,33 @@ func (repo *Repository) GetGitCreds(store git.CredsStore) git.Creds {
|
||||
if repo.SSHPrivateKey != "" {
|
||||
return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure(), repo.Proxy)
|
||||
}
|
||||
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 && repo.GithubAppInstallationId != 0 {
|
||||
return git.NewGitHubAppCreds(repo.GithubAppId, repo.GithubAppInstallationId, repo.GithubAppPrivateKey, repo.GitHubAppEnterpriseBaseURL, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, repo.NoProxy, store)
|
||||
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 { // Promoter MVP: remove github-app-installation-id check since it is no longer a required field
|
||||
installationId := repo.GithubAppInstallationId
|
||||
|
||||
// Auto-discover installation ID if not provided
|
||||
if installationId == 0 {
|
||||
org, err := git.ExtractOrgFromRepoURL(repo.Repo)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to extract organization from repository URL %s for GitHub App auto-discovery: %v", repo.Repo, err)
|
||||
return git.NopCreds{}
|
||||
}
|
||||
if org != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
discoveredId, err := git.DiscoverGitHubAppInstallationID(ctx, repo.GithubAppId, repo.GithubAppPrivateKey, repo.GitHubAppEnterpriseBaseURL, org)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to auto-discover GitHub App installation ID for org %s: %v. Proceeding with installation ID 0.", org, err)
|
||||
} else {
|
||||
log.Infof("Auto-discovered GitHub App installation ID %d for org %s", discoveredId, org)
|
||||
installationId = discoveredId
|
||||
}
|
||||
} else {
|
||||
log.Warnf("Could not extract organization from repository URL %s for GitHub App auto-discovery", repo.Repo)
|
||||
}
|
||||
}
|
||||
|
||||
return git.NewGitHubAppCreds(repo.GithubAppId, installationId, repo.GithubAppPrivateKey, repo.GitHubAppEnterpriseBaseURL, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure(), repo.Proxy, repo.NoProxy, store)
|
||||
}
|
||||
if repo.GCPServiceAccountKey != "" {
|
||||
return git.NewGoogleCloudCreds(repo.GCPServiceAccountKey, store)
|
||||
|
||||
@@ -217,7 +217,6 @@ export const ReposList = ({match, location}: RouteComponentProps) => {
|
||||
return {
|
||||
url: (!githubAppValues.url && 'Repository URL is required') || (credsTemplate && !isHTTPOrHTTPSUrl(githubAppValues.url) && 'Not a valid HTTP/HTTPS URL'),
|
||||
githubAppId: !githubAppValues.githubAppId && 'GitHub App ID is required',
|
||||
githubAppInstallationId: !githubAppValues.githubAppInstallationId && 'GitHub App installation ID is required',
|
||||
githubAppPrivateKey: !githubAppValues.githubAppPrivateKey && 'GitHub App private Key is required'
|
||||
};
|
||||
case ConnectionMethod.GOOGLECLOUD:
|
||||
|
||||
@@ -9,13 +9,16 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
giturls "github.com/chainguard-dev/git-urls"
|
||||
"github.com/google/go-github/v69/github"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@@ -43,6 +46,10 @@ var (
|
||||
|
||||
// In memory cache for storing Azure tokens
|
||||
azureTokenCache *gocache.Cache
|
||||
|
||||
// installationIdCache caches installation IDs for organizations to avoid redundant API calls.
|
||||
githubInstallationIdCache *gocache.Cache
|
||||
githubInstallationIdCacheMutex sync.RWMutex // For bulk API call coordination
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -66,6 +73,7 @@ func init() {
|
||||
// oauth2.TokenSource handles fetching new Tokens once they are expired. The oauth2.TokenSource itself does not expire.
|
||||
googleCloudTokenSource = gocache.New(gocache.NoExpiration, 0)
|
||||
azureTokenCache = gocache.New(gocache.NoExpiration, 0)
|
||||
githubInstallationIdCache = gocache.New(60*time.Minute, 60*time.Minute)
|
||||
}
|
||||
|
||||
type NoopCredsStore struct{}
|
||||
@@ -576,6 +584,199 @@ func (g GitHubAppCreds) GetClientCertKey() string {
|
||||
return g.clientCertKey
|
||||
}
|
||||
|
||||
// GitHub App installation discovery cache and helper
|
||||
|
||||
// DiscoverGitHubAppInstallationID discovers the GitHub App installation ID for a given organization.
|
||||
// It queries the GitHub API to list all installations for the app and returns the installation ID
|
||||
// for the matching organization. Results are cached to avoid redundant API calls.
|
||||
// An optional HTTP client can be provided for custom transport (e.g., for metrics tracking).
|
||||
func DiscoverGitHubAppInstallationID(ctx context.Context, appId int64, privateKey, enterpriseBaseURL, org string, httpClient ...*http.Client) (int64, error) {
|
||||
domain, err := domainFromBaseURL(enterpriseBaseURL)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get domain from base URL: %w", err)
|
||||
}
|
||||
org = strings.ToLower(org)
|
||||
// Check cache first
|
||||
cacheKey := fmt.Sprintf("%s:%s:%d", strings.ToLower(org), domain, appId)
|
||||
if id, found := githubInstallationIdCache.Get(cacheKey); found {
|
||||
return id.(int64), nil
|
||||
}
|
||||
|
||||
// Use provided HTTP client or default
|
||||
var transport http.RoundTripper
|
||||
if len(httpClient) > 0 && httpClient[0] != nil && httpClient[0].Transport != nil {
|
||||
transport = httpClient[0].Transport
|
||||
} else {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
// Create GitHub App transport
|
||||
rt, err := ghinstallation.NewAppsTransport(transport, appId, []byte(privateKey))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to create GitHub app transport: %w", err)
|
||||
}
|
||||
|
||||
if enterpriseBaseURL != "" {
|
||||
rt.BaseURL = enterpriseBaseURL
|
||||
}
|
||||
|
||||
// Create GitHub client
|
||||
var client *github.Client
|
||||
clientTransport := &http.Client{Transport: rt}
|
||||
if enterpriseBaseURL == "" {
|
||||
client = github.NewClient(clientTransport)
|
||||
} else {
|
||||
client, err = github.NewClient(clientTransport).WithEnterpriseURLs(enterpriseBaseURL, enterpriseBaseURL)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to create GitHub enterprise client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// List all installations and cache them
|
||||
var allInstallations []*github.Installation
|
||||
opts := &github.ListOptions{PerPage: 100}
|
||||
|
||||
// Lock for the entire loop to avoid multiple concurrent API calls on startup
|
||||
githubInstallationIdCacheMutex.Lock()
|
||||
defer githubInstallationIdCacheMutex.Unlock()
|
||||
|
||||
// Check cache again inside the write lock in case another goroutine already fetched it
|
||||
if id, found := githubInstallationIdCache.Get(cacheKey); found {
|
||||
return id.(int64), nil
|
||||
}
|
||||
|
||||
for {
|
||||
installations, resp, err := client.Apps.ListInstallations(ctx, opts)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to list installations: %w", err)
|
||||
}
|
||||
|
||||
allInstallations = append(allInstallations, installations...)
|
||||
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
|
||||
// Cache all installation IDs
|
||||
for _, installation := range allInstallations {
|
||||
if installation.Account != nil && installation.Account.Login != nil && installation.ID != nil {
|
||||
githubInstallationIdCache.Set(cacheKey, *installation.ID, gocache.DefaultExpiration)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the installation ID for the requested org
|
||||
if id, found := githubInstallationIdCache.Get(cacheKey); found {
|
||||
return id.(int64), nil
|
||||
}
|
||||
return 0, fmt.Errorf("installation not found for org: %s", org)
|
||||
}
|
||||
|
||||
// domainFromBaseURL extracts the host (domain) from the given GitHub base URL.
|
||||
// Supports HTTP(S), SSH URLs, and git@host:org/repo forms.
|
||||
// Returns an error if a domain cannot be extracted.
|
||||
func domainFromBaseURL(baseURL string) (string, error) {
|
||||
if baseURL == "" {
|
||||
return "github.com", nil
|
||||
}
|
||||
|
||||
// --- 1. SSH-style Git URL: git@github.com:org/repo.git ---
|
||||
if strings.Contains(baseURL, "@") && strings.Contains(baseURL, ":") && !strings.Contains(baseURL, "://") {
|
||||
parts := strings.SplitN(baseURL, "@", 2)
|
||||
right := parts[len(parts)-1] // github.com:org/repo
|
||||
host := strings.SplitN(right, ":", 2)[0] // github.com
|
||||
if host != "" {
|
||||
return host, nil
|
||||
}
|
||||
return "", fmt.Errorf("failed to extract host from SSH-style URL: %q", baseURL)
|
||||
}
|
||||
|
||||
// --- 2. Ensure scheme so url.Parse works ---
|
||||
if !strings.HasPrefix(baseURL, "http://") &&
|
||||
!strings.HasPrefix(baseURL, "https://") &&
|
||||
!strings.HasPrefix(baseURL, "ssh://") {
|
||||
baseURL = "https://" + baseURL
|
||||
}
|
||||
|
||||
// --- 3. Standard URL parse ---
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse URL %q: %w", baseURL, err)
|
||||
}
|
||||
if parsed.Host == "" {
|
||||
return "", fmt.Errorf("URL %q parsed but host is empty", baseURL)
|
||||
}
|
||||
|
||||
host := parsed.Host
|
||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
||||
host = h
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// ExtractOrgFromRepoURL extracts the organization/owner name from a GitHub repository URL.
|
||||
// Supports formats:
|
||||
// - HTTPS: https://github.com/org/repo.git
|
||||
// - SSH: git@github.com:org/repo.git
|
||||
// - SSH with port: git@github.com:22/org/repo.git or ssh://git@github.com:22/org/repo.git
|
||||
func ExtractOrgFromRepoURL(repoURL string) (string, error) {
|
||||
if repoURL == "" {
|
||||
return "", errors.New("repo URL is empty")
|
||||
}
|
||||
|
||||
// Handle edge case: ssh://git@host:org/repo (malformed but used in practice)
|
||||
// This format mixes ssh:// prefix with colon notation instead of using a slash.
|
||||
// Convert it to git@host:org/repo which git-urls can parse correctly.
|
||||
// We distinguish this from the valid ssh://git@host:22/org/repo (with port number).
|
||||
if strings.HasPrefix(repoURL, "ssh://git@") {
|
||||
remainder := strings.TrimPrefix(repoURL, "ssh://")
|
||||
if colonIdx := strings.Index(remainder, ":"); colonIdx != -1 {
|
||||
afterColon := remainder[colonIdx+1:]
|
||||
slashIdx := strings.Index(afterColon, "/")
|
||||
|
||||
// Check if what follows the colon is a port number
|
||||
isPort := false
|
||||
if slashIdx > 0 {
|
||||
if _, err := strconv.Atoi(afterColon[:slashIdx]); err == nil {
|
||||
isPort = true
|
||||
}
|
||||
}
|
||||
|
||||
// If not a port, it's the malformed format - strip ssh:// prefix
|
||||
if !isPort && slashIdx != 0 {
|
||||
repoURL = remainder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use git-urls library to parse all Git URL formats
|
||||
parsed, err := giturls.Parse(repoURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse repository URL %q: %w", repoURL, err)
|
||||
}
|
||||
|
||||
// Clean the path: remove leading/trailing slashes and .git suffix
|
||||
path := strings.Trim(parsed.Path, "/")
|
||||
path = strings.TrimSuffix(path, ".git")
|
||||
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("repository URL %q does not contain a path", repoURL)
|
||||
}
|
||||
|
||||
// Extract the first path component (organization/owner)
|
||||
// Path format is typically "org/repo" or "org/repo/subpath"
|
||||
if idx := strings.Index(path, "/"); idx > 0 {
|
||||
org := path[:idx]
|
||||
// Normalize to lowercase for case-insensitive comparison
|
||||
return strings.ToLower(org), nil
|
||||
}
|
||||
|
||||
// If there's no slash, the entire path might be just the org (unusual but handle it)
|
||||
// This would fail validation later, but let's return it
|
||||
return "", fmt.Errorf("could not extract organization from repository URL %q: path %q does not contain org/repo format", repoURL, path)
|
||||
}
|
||||
|
||||
var _ Creds = GoogleCloudCreds{}
|
||||
|
||||
// GoogleCloudCreds to authenticate to Google Cloud Source repositories
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
@@ -515,3 +519,132 @@ func TestAzureWorkloadIdentityCreds_ReuseTokenIfExistingIsNotExpired(t *testing.
|
||||
func resetAzureTokenCache() {
|
||||
azureTokenCache = gocache.New(gocache.NoExpiration, 0)
|
||||
}
|
||||
|
||||
const fakeGitHubAppPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEA2KHkp2fe0ReHqAt9BimWEec2ryWZIyg9jvB3BdP3mzFf0bOt
|
||||
WlHm1FAETFxH4h5jYASUaaWEwRNNyGlT1GhTp+jOMC4xhOSb5/SnI2dt2EITkudQ
|
||||
FKsSFUdJAndqOzkjrP2pL4fi4b7JWhuLDO36ufAP4l2m3tnAseGSSTIccWvzLFFU
|
||||
s3wsHOHxOcJGCP1Z7rizxl6mTKYL/Z+GHqN17OJslDf901uPXsUeDCYL2iigGPhD
|
||||
Ao6k8POsfbpqLG7poCDTK50FLnS5qEocjxt+J4ZjBEWTU/DOFXWYstzfbhm8OZPQ
|
||||
pSSEiBCxpg+zjtkQfCyZxXB5RQ84CY78fXOI9QIDAQABAoIBAG8jL0FLIp62qZvm
|
||||
uO9ualUo/37/lP7aaCpq50UQJ9lwjS3yNh8+IWQO4QWj2iUBXg4mi1Vf2ymKk78b
|
||||
eixgkXp1D0Lcj/8ToYBwnUami04FKDGXhhf0Y8SS27vuM4vKlqjrQd7modkangYi
|
||||
V0X82UKHDD8fuLpfkGIxzXDLypfMzjMuVpSntnWaf2YX3VR/0/66yEp9GejftF2k
|
||||
wqhGoWM6r68pN5XuCqWd5PRluSoDy/o4BAFMhYCSfp9PjgZE8aoeWHgYzlZ3gUyn
|
||||
r+HaDDNWbibhobXk/9h8lwAJ6KCZ5RZ+HFfh0HuwIxmocT9OCFgy/S0g1p+o3m9K
|
||||
VNd5AMkCgYEA5fbS5UK7FBzuLoLgr1hktmbLJhpt8y8IPHNABHcUdE+O4/1xTQNf
|
||||
pMUwkKjGG1MtrGjLOIoMGURKKn8lR1GMZueOTSKY0+mAWUGvSzl6vwtJwvJruT8M
|
||||
otEO03o0tPnRKGxbFjqxkp2b6iqJ8MxCRZ3lSidc4mdi7PHzv9lwgvsCgYEA8Siq
|
||||
7weCri9N6y+tIdORAXgRzcW54BmJyqB147c72RvbMacb6rN28KXpM3qnRXyp3Llb
|
||||
yh81TW3FH10GqrjATws7BK8lP9kkAw0Z/7kNiS1NgH3pUbO+5H2kAa/6QW35nzRe
|
||||
Jw2lyfYGWqYO4hYXH14ML1kjgS1hgd3XHOQ64M8CgYAKcjDYSzS2UC4dnMJaFLjW
|
||||
dErsGy09a7iDDnUs/r/GHMsP3jZkWi/hCzgOiiwdl6SufUAl/FdaWnjH/2iRGco3
|
||||
7nLPXC/3CFdVNp+g2iaSQRADtAFis9N+HeL/hkCYq/RtUqa8lsP0NgacF3yWnKCy
|
||||
Ct8chDc67ZlXzBHXeCgdOwKBgHHGFPbWXUHeUW1+vbiyvrupsQSanznp8oclMtkv
|
||||
Dk48hSokw9fzuU6Jh77gw9/Vk7HtxS9Tj+squZA1bDrJFPl1u+9WzkUUJZhG6xgp
|
||||
bwhj1iejv5rrKUlVOTYOlwudXeJNa4oTNz9UEeVcaLMjZt9GmIsSC90a0uDZD26z
|
||||
AlAjAoGAEoqm2DcNN7SrH6aVFzj1EVOrNsHYiXj/yefspeiEmf27PSAslP+uF820
|
||||
SDpz4h+Bov5qTKkzcxuu1QWtA4M0K8Iy6IYLwb83DZEm1OsAf4i0pODz21PY/I+O
|
||||
VHzjB10oYgaInHZgMUdyb6F571UdiYSB6a/IlZ3ngj5touy3VIM=
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
func TestDiscoverGitHubAppInstallationID(t *testing.T) {
|
||||
t.Run("returns cached installation ID", func(t *testing.T) {
|
||||
// Setup: prepopulate cache
|
||||
org := "test-org"
|
||||
appId := int64(12345)
|
||||
domain := "github.com"
|
||||
expectedId := int64(98765)
|
||||
|
||||
// Clean up at both start and end to ensure test isolation
|
||||
cacheKey := fmt.Sprintf("%s:%s:%d", strings.ToLower(org), domain, appId)
|
||||
|
||||
githubInstallationIdCache.Set(cacheKey, expectedId, gocache.NoExpiration)
|
||||
|
||||
// Ensure cleanup even if test fails
|
||||
t.Cleanup(func() {
|
||||
githubInstallationIdCache.Delete(cacheKey)
|
||||
})
|
||||
|
||||
// Execute
|
||||
ctx := context.Background()
|
||||
actualId, err := DiscoverGitHubAppInstallationID(ctx, appId, "fake-key", "", org)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedId, actualId)
|
||||
})
|
||||
|
||||
t.Run("discovers installation ID from GitHub API", func(t *testing.T) {
|
||||
// Setup: mock GitHub API server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// GitHub Enterprise expects paths like /api/v3/app/installations
|
||||
// go-github's WithEnterpriseURLs adds this prefix automatically
|
||||
if strings.HasSuffix(r.URL.Path, "/app/installations") {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
//nolint:errcheck
|
||||
json.NewEncoder(w).Encode([]map[string]any{
|
||||
{"id": 98765, "account": map[string]any{"login": "test-org"}},
|
||||
})
|
||||
return
|
||||
}
|
||||
// Return 404 for any other path
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Clean up cache entry for this test on completion
|
||||
t.Cleanup(func() {
|
||||
// Extract domain from server URL for proper cache key
|
||||
domain, _ := domainFromBaseURL(server.URL)
|
||||
cacheKey := fmt.Sprintf("%s:%s:%d", strings.ToLower("test-org"), domain, 12345)
|
||||
githubInstallationIdCache.Delete(cacheKey)
|
||||
})
|
||||
|
||||
// Execute & Assert
|
||||
ctx := context.Background()
|
||||
// Pass the mock server URL as the enterpriseBaseURL so the GitHub client uses it
|
||||
// Note: The mock server will have a different domain (e.g., 127.0.0.1) than the first test (github.com),
|
||||
// so there's no cache collision between the two subtests.
|
||||
actualId, err := DiscoverGitHubAppInstallationID(ctx, 12345, fakeGitHubAppPrivateKey, server.URL, "test-org")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(98765), actualId)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractOrgFromRepoURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoURL string
|
||||
expected string
|
||||
expectError bool
|
||||
}{
|
||||
{"HTTPS URL", "https://github.com/argoproj/argo-cd", "argoproj", false},
|
||||
{"HTTPS URL with .git", "https://github.com/argoproj/argo-cd.git", "argoproj", false},
|
||||
{"HTTPS URL with port", "https://github.com:443/argoproj/argo-cd.git", "argoproj", false},
|
||||
{"SSH URL", "git@github.com:argoproj/argo-cd.git", "argoproj", false},
|
||||
{"SSH URL without .git", "git@github.com:argoproj/argo-cd", "argoproj", false},
|
||||
{"SSH URL with ssh:// prefix", "ssh://git@github.com:argoproj/argo-cd.git", "argoproj", false},
|
||||
{"SSH URL with port", "ssh://git@github.com:22/argoproj/argo-cd.git", "argoproj", false},
|
||||
{"GitHub Enterprise HTTPS", "https://github.example.com/myorg/myrepo.git", "myorg", false},
|
||||
{"GitHub Enterprise SSH", "git@github.example.com:myorg/myrepo.git", "myorg", false},
|
||||
{"Case insensitive", "https://github.com/ArgoPROJ/argo-cd", "argoproj", false}, // Test case sensitivity
|
||||
{"Invalid URL", "not-a-url", "", true},
|
||||
{"Empty string", "", "", true},
|
||||
{"URL without org/repo", "https://github.com", "", true},
|
||||
{"URL with only org", "https://github.com/argoproj", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, err := ExtractOrgFromRepoURL(tt.repoURL)
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
assert.Empty(t, actual)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,21 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/util/proxy"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/common"
|
||||
"github.com/argoproj/argo-cd/v3/test/fixture/log"
|
||||
"github.com/argoproj/argo-cd/v3/test/fixture/path"
|
||||
"github.com/argoproj/argo-cd/v3/test/fixture/test"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Ensure tests use non-cached proxy callback
|
||||
proxy.UseTestingProxyCallback()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestIsCommitSHA(t *testing.T) {
|
||||
assert.True(t, IsCommitSHA("9d921f65f3c5373b682e2eb4b37afba6592e8f8b"))
|
||||
assert.True(t, IsCommitSHA("9D921F65F3C5373B682E2EB4B37AFBA6592E8F8B"))
|
||||
|
||||
@@ -9,6 +9,19 @@ import (
|
||||
"golang.org/x/net/http/httpproxy"
|
||||
)
|
||||
|
||||
// DefaultProxyCallback is the default proxy callback function that reads from environment variables. http.ProxyFromEnvironment
|
||||
// is cached on first call, so we can't use it for tests. When writing a test that uses t.Setenv for some proxy env var,
|
||||
// call UseTestingProxyCallback.
|
||||
var DefaultProxyCallback = http.ProxyFromEnvironment
|
||||
|
||||
// UseTestingProxyCallback sets the DefaultProxyCallback to use httpproxy.FromEnvironment. This is useful for tests that
|
||||
// use t.Setenv to set proxy env variables.
|
||||
func UseTestingProxyCallback() {
|
||||
DefaultProxyCallback = func(r *http.Request) (*url.URL, error) {
|
||||
return httpproxy.FromEnvironment().ProxyFunc()(r.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// UpsertEnv removes the existing proxy env variables and adds the custom proxy variables
|
||||
func UpsertEnv(cmd *exec.Cmd, proxy string, noProxy string) []string {
|
||||
envs := []string{}
|
||||
@@ -42,7 +55,7 @@ func GetCallback(proxy string, noProxy string) func(*http.Request) (*url.URL, er
|
||||
}
|
||||
}
|
||||
// read proxy from env variable if custom proxy is missing
|
||||
return http.ProxyFromEnvironment
|
||||
return DefaultProxyCallback
|
||||
}
|
||||
|
||||
func httpProxy(url string) string {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
@@ -11,6 +12,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Ensure tests use non-cached proxy callback
|
||||
UseTestingProxyCallback()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestAddProxyEnvIfAbsent(t *testing.T) {
|
||||
t.Run("Existing proxy env variables", func(t *testing.T) {
|
||||
proxy := "https://proxy:5000"
|
||||
|
||||
Reference in New Issue
Block a user