Skip to content

Commit 2ef4788

Browse files
committed
feat: Support self-hosted GitHub Enterprise servers
1 parent a1196a7 commit 2ef4788

File tree

13 files changed

+194
-95
lines changed

13 files changed

+194
-95
lines changed

assets/chezmoi.io/docs/reference/templates/github-functions/gitHubLatestRelease.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# `gitHubLatestRelease` *owner-repo*
1+
# `gitHubLatestRelease` *host-owner-repo*
22

33
`gitHubLatestRelease` calls the GitHub API to retrieve the latest release about
4-
the given *owner-repo*, returning structured data as defined by the [GitHub Go
5-
API
4+
the given *host-owner-repo*, returning structured data as defined by the [GitHub
5+
Go API
66
bindings](https://pkg.go.dev/github.com/google/go-github/v57/github#RepositoryRelease).
77

88
Calls to `gitHubLatestRelease` are cached so calling `gitHubLatestRelease` with
9-
the same *owner-repo* will only result in one call to the GitHub API.
9+
the same *host-owner-repo* will only result in one call to the GitHub API.
1010

1111
!!! example
1212

assets/chezmoi.io/docs/reference/templates/github-functions/gitHubLatestReleaseAssetURL.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# `gitHubLatestReleaseAssetURL` *owner-repo* *pattern*
1+
# `gitHubLatestReleaseAssetURL` *host-owner-repo* *pattern*
22

33
`gitHubLatestReleaseAssetURL` calls the GitHub API to retrieve the latest
4-
release about the given *owner-repo*, returning structured data as defined by
5-
the [GitHub Go API
4+
release about the given *host-owner-repo*, returning structured data as defined
5+
by the [GitHub Go API
66
bindings](https://pkg.go.dev/github.com/google/go-github/v61/github#RepositoryRelease).
77
It then iterates through all the release's assets, returning the first one that
88
matches *pattern*. *pattern* is a shell pattern as [described in
99
`path.Match`](https://pkg.go.dev/path#Match).
1010

1111
Calls to `gitHubLatestReleaseAssetURL` are cached so calling
12-
`gitHubLatestReleaseAssetURL` with the same *owner-repo* will only result in one
13-
call to the GitHub API.
12+
`gitHubLatestReleaseAssetURL` with the same *host-owner-repo* will only result
13+
in one call to the GitHub API.
1414

1515
!!! example
1616

assets/chezmoi.io/docs/reference/templates/github-functions/gitHubLatestTag.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# `gitHubLatestTag` *owner-repo*
1+
# `gitHubLatestTag` *host-owner-repo*
22

33
`gitHubLatestTag` calls the GitHub API to retrieve the latest tag for the given
4-
*owner-repo*, returning structured data as defined by the [GitHub Go API
4+
*host-owner-repo*, returning structured data as defined by the [GitHub Go API
55
bindings](https://pkg.go.dev/github.com/google/go-github/v57/github#RepositoryTag).
66

77
Calls to `gitHubLatestTag` are cached the same as [`githubTags`](gitHubTags.md),
8-
so calling `gitHubLatestTag` with the same *owner-repo* will only result in one
9-
call to the GitHub API.
8+
so calling `gitHubLatestTag` with the same *host-owner-repo* will only result in
9+
one call to the GitHub API.
1010

1111
!!! example
1212

assets/chezmoi.io/docs/reference/templates/github-functions/gitHubReleases.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# `gitHubReleases` *owner-repo*
1+
# `gitHubReleases` *host-owner-repo*
22

33
`gitHubReleases` calls the GitHub API to retrieve the first page of releases for
4-
the given *owner-repo*, returning structured data as defined by the [GitHub Go
5-
API
4+
the given *host-owner-repo*, returning structured data as defined by the [GitHub
5+
Go API
66
bindings](https://pkg.go.dev/github.com/google/go-github/v57/github#RepositoryRelease).
77

88
Calls to `gitHubReleases` are cached so calling `gitHubReleases` with the same
9-
*owner-repo* will only result in one call to the GitHub API.
9+
*host-owner-repo* will only result in one call to the GitHub API.
1010

1111
!!! example
1212

assets/chezmoi.io/docs/reference/templates/github-functions/gitHubTags.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# `gitHubTags` *owner-repo*
1+
# `gitHubTags` *host-owner-repo*
22

3-
`gitHubTags` calls the GitHub API to retrieve the first page of tags for
4-
the given *owner-repo*, returning structured data as defined by the [GitHub Go
3+
`gitHubTags` calls the GitHub API to retrieve the first page of tags for the
4+
given *host-owner-repo*, returning structured data as defined by the [GitHub Go
55
API
66
bindings](https://pkg.go.dev/github.com/google/go-github/v57/github#RepositoryTag).
77

88
Calls to `gitHubTags` are cached so calling `gitHubTags` with the
9-
same *owner-repo* will only result in one call to the GitHub API.
9+
same *host-owner-repo* will only result in one call to the GitHub API.
1010

1111
!!! example
1212

assets/chezmoi.io/docs/reference/templates/github-functions/index.md

+19-4
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,32 @@
22

33
The `gitHub*` template functions return data from the GitHub API.
44

5+
All functions take a *host-owner-repo* argument of the form:
6+
7+
[host/]owner/repo
8+
9+
The optional `host` specifies the host and defaults to `github.com` if omitted.
10+
`owner` and `repo` specify the repository owner and name respectively.
11+
512
By default, chezmoi makes anonymous GitHub API requests, which are subject to
613
[GitHub's rate
714
limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting)
815
(currently 60 requests per hour per source IP address). chezmoi caches results
916
from identical GitHub API requests for the period defined in
1017
`gitHub.refreshPeriod` (default one minute).
1118

12-
If any of the environment variables `$CHEZMOI_GITHUB_ACCESS_TOKEN`,
13-
`$GITHUB_ACCESS_TOKEN`, or `$GITHUB_TOKEN` are found, then the first one found
14-
will be used to authenticate the GitHub API requests which have a higher rate
15-
limit (currently 5,000 requests per hour per user).
19+
For `github.com` repos, if any of the environment variables
20+
`$CHEZMOI_GITHUB_ACCESS_TOKEN`, `$CHEZMOI_GITHUB_TOKEN`, `$GITHUB_ACCESS_TOKEN`,
21+
or `$GITHUB_TOKEN` are found, then the first one found will be used to
22+
authenticate the GitHub API requests which have a higher rate limit (currently
23+
5,000 requests per hour per user).
24+
25+
For non-`github.com` repos, i.e. self-host GitHub enterprise repos, chezmoi will
26+
use an access token from the `$CHEZMOI_<HOST>_ACCESS_TOKEN` environment
27+
variable, if set, where `<HOST>` is the host converted to uppercase and with all
28+
non-letter characters replaced with underscores. For example, given the host
29+
`git.example.com`, chezmoi look for a `$CHEZMOI_GIT_EXAMPLE_COM_ACCESS_TOKEN`
30+
environment variable.
1631

1732
In practice, GitHub API rate limits are high enough chezmoi's caching of results
1833
mean that you should rarely need to set a token, unless you are sharing a source

internal/chezmoi/github.go

+32-7
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,8 @@ import (
1111

1212
// NewGitHubClient returns a new github.Client configured with an access token
1313
// and a http client, if available.
14-
func NewGitHubClient(ctx context.Context, httpClient *http.Client) *github.Client {
15-
for _, key := range []string{
16-
"CHEZMOI_GITHUB_ACCESS_TOKEN",
17-
"CHEZMOI_GITHUB_TOKEN",
18-
"GITHUB_ACCESS_TOKEN",
19-
"GITHUB_TOKEN",
20-
} {
14+
func NewGitHubClient(ctx context.Context, httpClient *http.Client, host string) *github.Client {
15+
for _, key := range accessTokenEnvKeys(host) {
2116
if accessToken := os.Getenv(key); accessToken != "" {
2217
httpClient = oauth2.NewClient(
2318
context.WithValue(ctx, oauth2.HTTPClient, httpClient),
@@ -29,3 +24,33 @@ func NewGitHubClient(ctx context.Context, httpClient *http.Client) *github.Clien
2924
}
3025
return github.NewClient(httpClient)
3126
}
27+
28+
func accessTokenEnvKeys(host string) []string {
29+
if host == "github.com" {
30+
return []string{
31+
"CHEZMOI_GITHUB_ACCESS_TOKEN",
32+
"CHEZMOI_GITHUB_TOKEN",
33+
"GITHUB_ACCESS_TOKEN",
34+
"GITHUB_TOKEN",
35+
}
36+
}
37+
hostKey := makeHostKey(host)
38+
return []string{
39+
"CHEZMOI_" + hostKey + "_ACCESS_TOKEN",
40+
}
41+
}
42+
43+
func makeHostKey(host string) string {
44+
hostKey := make([]byte, 0, len(host))
45+
for _, b := range []byte(host) {
46+
switch {
47+
case 'A' <= b && b <= 'Z':
48+
hostKey = append(hostKey, b)
49+
case 'a' <= b && b <= 'z':
50+
hostKey = append(hostKey, b-'a'+'A')
51+
default:
52+
hostKey = append(hostKey, '_')
53+
}
54+
}
55+
return string(hostKey)
56+
}

internal/chezmoi/github_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package chezmoi
2+
3+
import (
4+
"testing"
5+
6+
"github.com/alecthomas/assert/v2"
7+
)
8+
9+
func TestAccessTokenEnvKeys(t *testing.T) {
10+
for _, tc := range []struct {
11+
host string
12+
expected []string
13+
}{
14+
{
15+
host: "github.com",
16+
expected: []string{
17+
"CHEZMOI_GITHUB_ACCESS_TOKEN",
18+
"CHEZMOI_GITHUB_TOKEN",
19+
"GITHUB_ACCESS_TOKEN",
20+
"GITHUB_TOKEN",
21+
},
22+
},
23+
{
24+
host: "git.example.com",
25+
expected: []string{
26+
"CHEZMOI_GIT_EXAMPLE_COM_ACCESS_TOKEN",
27+
},
28+
},
29+
} {
30+
t.Run(tc.host, func(t *testing.T) {
31+
assert.Equal(t, tc.expected, accessTokenEnvKeys(tc.host))
32+
})
33+
}
34+
}

internal/cmd/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ func newConfig(options ...configOption) (*Config, error) {
340340
homeDir: userHomeDir,
341341
templateFuncs: sprig.TxtFuncMap(),
342342

343+
// Password manager data.
344+
gitHub: gitHubData{
345+
clientsByHost: make(map[string]gitHubClientResult),
346+
},
347+
343348
// Command configurations.
344349
apply: applyCmdConfig{
345350
filter: chezmoi.NewEntryTypeFilter(chezmoi.EntryTypesAll, chezmoi.EntryTypesNone),

internal/cmd/doctorcmd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ func (c *latestVersionCheck) Run(system chezmoi.System, homeDirAbsPath chezmoi.A
659659

660660
ctx := context.Background()
661661

662-
gitHubClient := chezmoi.NewGitHubClient(ctx, c.httpClient)
662+
gitHubClient := chezmoi.NewGitHubClient(ctx, c.httpClient, "github.com")
663663
rr, _, err := gitHubClient.Repositories.GetLatestRelease(ctx, "twpayne", "chezmoi")
664664
var rateLimitErr *github.RateLimitError
665665
var abuseRateLimitErr *github.AbuseRateLimitError

0 commit comments

Comments
 (0)