Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(applicationset): reuse repo-creds for an existing GitHub App #10092

Merged
merged 1 commit into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions applicationset/generators/pull_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/gosimple/slug"

"github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/applicationset/v1alpha1"
"github.com/gosimple/slug"
)

var _ Generator = (*PullRequestGenerator)(nil)
Expand All @@ -24,11 +25,13 @@ const (
type PullRequestGenerator struct {
client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
auth SCMAuthProviders
}

func NewPullRequestGenerator(client client.Client) Generator {
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders) Generator {
g := &PullRequestGenerator{
client: client,
auth: auth,
}
g.selectServiceProviderFunc = g.selectServiceProvider
return g
Expand Down Expand Up @@ -101,12 +104,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
// selectServiceProvider selects the provider to get pull requests from the configuration
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
if generatorConfig.Github != nil {
providerConfig := generatorConfig.Github
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGithubService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Labels)
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
}
if generatorConfig.GitLab != nil {
providerConfig := generatorConfig.GitLab
Expand Down Expand Up @@ -139,6 +137,24 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
return nil, fmt.Errorf("no Pull Request provider implementation configured")
}

func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
// use an app if it was configured
if cfg.AppSecretName != "" {
auth, err := g.auth.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
if err != nil {
return nil, fmt.Errorf("error getting GitHub App secret: %v", err)
}
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
}

// always default to token, even if not set (public access)
token, err := g.getSecretRef(ctx, cfg.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
}

// getSecretRef gets the value of the key for the specified Secret resource.
func (g *PullRequestGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
if ref == nil {
Expand Down
44 changes: 36 additions & 8 deletions applicationset/generators/scm_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/applicationset/v1alpha1"
Expand All @@ -24,10 +25,18 @@ type SCMProviderGenerator struct {
client client.Client
// Testing hooks.
overrideProvider scm_provider.SCMProviderService
SCMAuthProviders
}

func NewSCMProviderGenerator(client client.Client) Generator {
return &SCMProviderGenerator{client: client}
type SCMAuthProviders struct {
GitHubApps github_app_auth.Credentials
}

func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders) Generator {
return &SCMProviderGenerator{
client: client,
SCMAuthProviders: providers,
}
}

// Testing generator
Expand Down Expand Up @@ -66,13 +75,10 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if g.overrideProvider != nil {
provider = g.overrideProvider
} else if providerConfig.Github != nil {
token, err := g.getSecretRef(ctx, providerConfig.Github.TokenRef, applicationSetInfo.Namespace)
var err error
provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo)
if err != nil {
return nil, fmt.Errorf("error fetching Github token: %v", err)
}
provider, err = scm_provider.NewGithubProvider(ctx, providerConfig.Github.Organization, token, providerConfig.Github.API, providerConfig.Github.AllBranches)
if err != nil {
return nil, fmt.Errorf("error initializing Github service: %v", err)
return nil, fmt.Errorf("scm provider: %w", err)
}
} else if providerConfig.Gitlab != nil {
token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace)
Expand Down Expand Up @@ -169,3 +175,25 @@ func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojio
}
return string(tokenBytes), nil
}

func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) {
if github.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
if err != nil {
return nil, fmt.Errorf("error fetching Github app secret: %v", err)
}

return scm_provider.NewGithubAppProviderFor(
*auth,
github.Organization,
github.API,
github.AllBranches,
)
}

token, err := g.getSecretRef(ctx, github.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Github token: %v", err)
}
return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
}
19 changes: 19 additions & 0 deletions applicationset/services/github_app_auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package github_app_auth

import "context"

// Authentication has the authentication information required to access the GitHub API and repositories.
type Authentication struct {
// Id specifies the ID of the GitHub app used to access the repo
Id int64
// InstallationId specifies the installation ID of the GitHub App used to access the repo
InstallationId int64
// EnterpriseBaseURL specifies the base URL of GitHub Enterprise installation. If empty will default to https://api.github.com
EnterpriseBaseURL string
// PrivateKey in PEM format.
PrivateKey string
}

type Credentials interface {
GetAuthSecret(ctx context.Context, secretName string) (*Authentication, error)
}
33 changes: 33 additions & 0 deletions applicationset/services/internal/github_app/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package github_app

import (
"fmt"
"net/http"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v35/github"

"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
)

// Client builds a github client for the given app authentication.
func Client(g github_app_auth.Authentication, url string) (*github.Client, error) {
iamnoah marked this conversation as resolved.
Show resolved Hide resolved
rt, err := ghinstallation.New(http.DefaultTransport, g.Id, g.InstallationId, []byte(g.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to create github app install: %w", err)
}
if url == "" {
url = g.EnterpriseBaseURL
}
var client *github.Client
httpClient := http.Client{Transport: rt}
if url == "" {
client = github.NewClient(&httpClient)
} else {
client, err = github.NewEnterpriseClient(url, url, &httpClient)
if err != nil {
return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
}
}
return client, nil
}
2 changes: 1 addition & 1 deletion applicationset/services/pull_request/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
for {
pulls, resp, err := g.client.PullRequests.List(ctx, g.owner, g.repo, opts)
if err != nil {
return nil, fmt.Errorf("error listing pull requests for %s/%s: %v", g.owner, g.repo, err)
return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", g.owner, g.repo, err)
}
for _, pull := range pulls {
if !containLabels(g.labels, pull.Labels) {
Expand Down
19 changes: 19 additions & 0 deletions applicationset/services/pull_request/github_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package pull_request

import (
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/internal/github_app"
)

func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string) (PullRequestService, error) {
client, err := github_app.Client(g, url)
if err != nil {
return nil, err
}
return &GithubService{
client: client,
owner: owner,
repo: repo,
labels: labels,
}, nil
}
4 changes: 2 additions & 2 deletions applicationset/services/scm_provider/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (g *GithubProvider) GetBranches(ctx context.Context, repo *Repository) ([]*
repos := []*Repository{}
branches, err := g.listBranches(ctx, repo)
if err != nil {
return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err)
return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
}

for _, branch := range branches {
Expand All @@ -72,7 +72,7 @@ func (g *GithubProvider) ListRepos(ctx context.Context, cloneProtocol string) ([
for {
githubRepos, resp, err := g.client.Repositories.ListByOrg(ctx, g.organization, opt)
if err != nil {
return nil, fmt.Errorf("error listing repositories for %s: %v", g.organization, err)
return nil, fmt.Errorf("error listing repositories for %s: %w", g.organization, err)
}
for _, githubRepo := range githubRepos {
var url string
Expand Down
14 changes: 14 additions & 0 deletions applicationset/services/scm_provider/github_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package scm_provider

import (
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/internal/github_app"
)

func NewGithubAppProviderFor(g github_app_auth.Authentication, organization string, url string, allBranches bool) (*GithubProvider, error) {
client, err := github_app.Client(g, url)
if err != nil {
return nil, err
}
return &GithubProvider{client: client, organization: organization, allBranches: allBranches}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/reposerver/askpass"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/github_app"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -136,13 +137,16 @@ func NewCommand() *cobra.Command {
argoCDDB := db.NewDB(namespace, argoSettingsMgr, k8sClient)

askPassServer := askpass.NewServer()
scmAuth := generators.SCMAuthProviders{
GitHubApps: github_app.NewAuthCredentials(argoCDDB.(db.RepoCredsDB)),
}
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(mgr.GetClient(), ctx, k8sClient, namespace),
"Git": generators.NewGitGenerator(services.NewArgoCDService(argoCDDB, askPassServer, getSubmoduleEnabled())),
"SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient()),
"SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient(), scmAuth),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": generators.NewPullRequestGenerator(mgr.GetClient()),
"PullRequest": generators.NewPullRequestGenerator(mgr.GetClient(), scmAuth),
}

nestedGenerators := map[string]generators.Generator{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ spec:
tokenRef:
secretName: github-token
key: token
# (optional) use a GitHub App to access the API instead of a PAT.
appSecretName: github-app-repo-creds
# Labels is used to filter the PRs that you want to target. (optional)
labels:
- preview
Expand All @@ -57,6 +59,9 @@ spec:
* `api`: If using GitHub Enterprise, the URL to access it. (Optional)
* `tokenRef`: A `Secret` name and key containing the GitHub access token to use for requests. If not specified, will make anonymous requests which have a lower rate limit and can only see public repositories. (Optional)
* `labels`: Labels is used to filter the PRs that you want to target. (Optional)
* `appSecretName`: A `Secret` name containing a GitHub App secret in [repo-creds format][repo-creds].

[repo-creds]: ../declarative-setup.md#repository-credentials

## GitLab

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ spec:
tokenRef:
secretName: github-token
key: token
# (optional) use a GitHub App to access the API instead of a PAT.
appSecretName: gh-app-repo-creds
template:
# ...
```
Expand All @@ -56,6 +58,9 @@ spec:
* `api`: If using GitHub Enterprise, the URL to access it.
* `allBranches`: By default (false) the template will only be evaluated for the default branch of each repo. If this is true, every branch of every repository will be passed to the filters. If using this flag, you likely want to use a `branchMatch` filter.
* `tokenRef`: A `Secret` name and key containing the GitHub access token to use for requests. If not specified, will make anonymous requests which have a lower rate limit and can only see public repositories.
* `appSecretName`: A `Secret` name containing a GitHub App secret in [repo-creds format][repo-creds].

[repo-creds]: ../declarative-setup.md#repository-credentials

For label filtering, the repository topics are used.

Expand Down
12 changes: 12 additions & 0 deletions manifests/core-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4527,6 +4527,8 @@ spec:
properties:
api:
type: string
appSecretName:
type: string
labels:
items:
type: string
Expand Down Expand Up @@ -4964,6 +4966,8 @@ spec:
type: boolean
api:
type: string
appSecretName:
type: string
organization:
type: string
tokenRef:
Expand Down Expand Up @@ -6753,6 +6757,8 @@ spec:
properties:
api:
type: string
appSecretName:
type: string
labels:
items:
type: string
Expand Down Expand Up @@ -7190,6 +7196,8 @@ spec:
type: boolean
api:
type: string
appSecretName:
type: string
organization:
type: string
tokenRef:
Expand Down Expand Up @@ -7844,6 +7852,8 @@ spec:
properties:
api:
type: string
appSecretName:
type: string
labels:
items:
type: string
Expand Down Expand Up @@ -8281,6 +8291,8 @@ spec:
type: boolean
api:
type: string
appSecretName:
type: string
organization:
type: string
tokenRef:
Expand Down
Loading