From bb715387548f61e0f73b14ad9fed6d74cbc57bd2 Mon Sep 17 00:00:00 2001 From: Nicolas Zin Date: Sun, 2 Mar 2025 11:57:41 -0500 Subject: [PATCH] allow PAT --- CHANGELOG.md | 4 +++ docs/installation.md | 1 + docs/quick_start.md | 11 ++++++++ internal/config/env.go | 1 + internal/github/client.go | 55 ++++++++++++++++++++++++--------------- internal/goliac.go | 2 ++ internal/scaffold.go | 1 + 7 files changed, 54 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ad0c6..5a180fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Goliac v0.15.7 + +- allow to use a PAT (Personal Access Token) to run Goliac (in particular useful to scaffold) + ## Goliac v0.15.6 - new property to specify default branch for each repository diff --git a/docs/installation.md b/docs/installation.md index bfe0a3b..cf8212d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -231,6 +231,7 @@ You can run the goliac server as a service or a docker container. It needs sever | GOLIAC_GITHUB_APP_PRIVATE_KEY_FILE | | (mandatory) path to private key | | GOLIAC_GITHUB_TEAM_APP_ID | | (optional) dedicated app id of Goliac GitHub App for goliac teams repo (see security.md) | | GOLIAC_GITHUB_TEAM_APP_PRIVATE_KEY_FILE | | (optional) dedicated path to private key for goliac teams repo (see security.md) | +| GOLIAC_GITHUB_PERSONAL_ACCESS_TOKEN | | (optional) personal access token to use instead of the GitHub App | | GOLIAC_EMAIL | goliac@goliac-project.com | author name used by Goliac to commit (Codeowners) | | GOLIAC_GITHUB_CONCURRENT_THREADS | 5 | You can increase, like '10' | | GOLIAC_GITHUB_CACHE_TTL | 86400 | GitHub remote cache seconds retention | diff --git a/docs/quick_start.md b/docs/quick_start.md index d3bc6c8..408e104 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -28,6 +28,17 @@ As a Github org admin, in GitHub: - Go to the left tab "Install App" - Click on "Install" +### Alternative: use a personal access token + +If you don't have the possibility to create a Github App, you can use a personal access token. +If you only need to scaffold, you will need a personal access token with +- `read:org` scope (under `admin:org` category) + +If you want to use the full Goliac capabilities, you will need a personal access token with +- `read:org` and `write:org` scope (under `admin:org` category) + +You will need to export the `GOLIAC_GITHUB_PERSONAL_ACCESS_TOKEN` env variable (instead of `GOLIAC_GITHUB_APP_ID`, `GOLIAC_GITHUB_APP_PRIVATE_KEY_FILE`) in the following examples. + ### Get the Goliac binary ```shell diff --git a/internal/config/env.go b/internal/config/env.go index 0d44d9d..6a81445 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -15,6 +15,7 @@ var Config = struct { GithubAppPrivateKeyFile string `env:"GOLIAC_GITHUB_APP_PRIVATE_KEY_FILE" envDefault:"github-app-private-key.pem"` GithubTeamAppID int64 `env:"GOLIAC_GITHUB_TEAM_APP_ID"` GithubTeamAppPrivateKeyFile string `env:"GOLIAC_GITHUB_TEAM_APP_PRIVATE_KEY_FILE"` + GithubPersonalAccessToken string `env:"GOLIAC_GITHUB_PERSONAL_ACCESS_TOKEN"` GoliacEmail string `env:"GOLIAC_EMAIL" envDefault:"goliac@goliac-project.com"` GoliacTeamOwnerSuffix string `env:"GOLIAC_TEAM_OWNER_SUFFIX" envDefault:"-goliac-owners"` diff --git a/internal/github/client.go b/internal/github/client.go index 7cbeea9..00300d9 100644 --- a/internal/github/client.go +++ b/internal/github/client.go @@ -37,6 +37,7 @@ type GitHubClientImpl struct { installationID int64 appSlug string privateKey []byte + patToken string // if not "" we use the personal access token accessToken string httpClient *http.Client tokenExpiration time.Time @@ -50,6 +51,13 @@ type AuthorizedTransport struct { func (t *AuthorizedTransport) RoundTrip(req *http.Request) (*http.Response, error) { t.client.mu.Lock() + // Use the personal access token if available + if t.client.patToken != "" { + req.Header.Add("Authorization", "token "+t.client.patToken) + t.client.mu.Unlock() + return http.DefaultTransport.RoundTrip(req) + } + // Refresh the access token if necessary if t.client.accessToken == "" || time.Until(t.client.tokenExpiration) < 5*time.Minute { token, err := t.client.createJWT() @@ -90,7 +98,7 @@ func (t *AuthorizedTransport) RoundTrip(req *http.Request) (*http.Response, erro * "private-key.pem", * ) */ -func NewGitHubClientImpl(githubServer, organizationName string, appID int64, privateKeyFile string) (GitHubClient, error) { +func NewGitHubClientImpl(githubServer, organizationName string, appID int64, privateKeyFile string, patToken string) (GitHubClient, error) { privateKey, err := os.ReadFile(privateKeyFile) if err != nil { return nil, err @@ -100,32 +108,37 @@ func NewGitHubClientImpl(githubServer, organizationName string, appID int64, pri gitHubServer: githubServer, appID: appID, privateKey: privateKey, + patToken: patToken, } - // create JWT - token, err := client.createJWT() - if err != nil { - return nil, err - } + // If a personal access token is not provided, we need to find the installation ID + if client.patToken == "" { - // retrieve all installations for the authenticated app - installations, err := client.getInstallations(token) - if err != nil { - return nil, err - } + // create JWT + token, err := client.createJWT() + if err != nil { + return nil, err + } - // find the installation ID for the given organization - for _, installation := range installations { - logrus.Debugf("Found installation %s with id %d for organization: %s", installation.AppSlug, installation.ID, organizationName) - if strings.EqualFold(installation.Account.Login, organizationName) && installation.AppId == appID { - client.installationID = installation.ID - client.appSlug = installation.AppSlug - break + // retrieve all installations for the authenticated app + installations, err := client.getInstallations(token) + if err != nil { + return nil, err } - } - if client.installationID == 0 { - return nil, fmt.Errorf("installation not found for organization: %s", organizationName) + // find the installation ID for the given organization + for _, installation := range installations { + logrus.Debugf("Found installation %s with id %d for organization: %s", installation.AppSlug, installation.ID, organizationName) + if strings.EqualFold(installation.Account.Login, organizationName) && installation.AppId == appID { + client.installationID = installation.ID + client.appSlug = installation.AppSlug + break + } + } + + if client.installationID == 0 { + return nil, fmt.Errorf("installation not found for organization: %s", organizationName) + } } transport := &AuthorizedTransport{ diff --git a/internal/goliac.go b/internal/goliac.go index eeff02d..239be7d 100644 --- a/internal/goliac.go +++ b/internal/goliac.go @@ -65,6 +65,7 @@ func NewGoliacImpl() (Goliac, error) { config.Config.GithubAppOrganization, config.Config.GithubAppID, config.Config.GithubAppPrivateKeyFile, + config.Config.GithubPersonalAccessToken, ) if err != nil { return nil, err @@ -75,6 +76,7 @@ func NewGoliacImpl() (Goliac, error) { config.Config.GithubAppOrganization, config.Config.GithubTeamAppID, config.Config.GithubTeamAppPrivateKeyFile, + config.Config.GithubPersonalAccessToken, ) if err != nil { return nil, err diff --git a/internal/scaffold.go b/internal/scaffold.go index 077d903..66005d7 100644 --- a/internal/scaffold.go +++ b/internal/scaffold.go @@ -35,6 +35,7 @@ func NewScaffold() (*Scaffold, error) { config.Config.GithubAppOrganization, config.Config.GithubAppID, config.Config.GithubAppPrivateKeyFile, + config.Config.GithubPersonalAccessToken, ) if err != nil {