Skip to content

Commit

Permalink
feat: Add gitHubRelease and gitHubReleaseAssetURL template functions
Browse files Browse the repository at this point in the history
  • Loading branch information
pix authored and twpayne committed Aug 5, 2024
1 parent 73893c1 commit 48f873b
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `gitHubRelease` *owner-repo* *version*

`gitHubRelease` calls the GitHub API to retrieve the latest releases about
the given *owner-repo*, It iterates through all the versions of the release,
fetching the first entry equal to *version*

It then returns structured data as defined by the [GitHub Go API
bindings](https://pkg.go.dev/github.com/google/go-github/v63/github#RepositoryRelease).

Calls to `gitHubRelease` are cached so calling `gitHubRelease` with
the same *owner-repo* *version* will only result in one call to the GitHub API.

!!! example

```
{{ (gitHubRelease "docker/compose" "v2.29.1").TagName }}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# `gitHubReleaseAssetURL` *owner-repo* *version* *pattern*

`gitHubReleaseAssetURL` calls the GitHub API to retrieve the latest
releases about the given *owner-repo*, returning structured data as defined by
the [GitHub Go API
bindings](https://pkg.go.dev/github.com/google/go-github/v63/github#RepositoryRelease).
It iterates through all the versions of the release, returning the first entry equal to *version*.
It then iterates through all the release's assets, returning the first one that
matches *pattern*. *pattern* is a shell pattern as [described in
`path.Match`](https://pkg.go.dev/path#Match).

Calls to `gitHubReleaseAssetURL` are cached so calling
`gitHubReleaseAssetURL` with the same *owner-repo* will only result in one
call to the GitHub API.

!!! example

```
{{ gitHubReleaseAssetURL "FiloSottile/age" "age v1.2.0" (printf "age-*-%s-%s.tar.gz" .chezmoi.os .chezmoi.arch) }}
{{ gitHubReleaseAssetURL "twpayne/chezmoi" "v2.50.0" (printf "chezmoi-%s-%s" .chezmoi.os .chezmoi.arch) }}
```
2 changes: 2 additions & 0 deletions assets/chezmoi.io/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ nav:
- reference/templates/github-functions/index.md
- gitHubKeys: reference/templates/github-functions/gitHubKeys.md
- gitHubLatestRelease: reference/templates/github-functions/gitHubLatestRelease.md
- gitHubRelease: reference/templates/github-functions/gitHubRelease.md
- gitHubLatestReleaseAssetURL: reference/templates/github-functions/gitHubLatestReleaseAssetURL.md
- gitHubReleaseAssetURL: reference/templates/github-functions/gitHubReleaseAssetURL.md
- gitHubLatestTag: reference/templates/github-functions/gitHubLatestTag.md
- gitHubReleases: reference/templates/github-functions/gitHubReleases.md
- gitHubTags: reference/templates/github-functions/gitHubTags.md
Expand Down
2 changes: 2 additions & 0 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,9 @@ func newConfig(options ...configOption) (*Config, error) {
"fromYaml": c.fromYamlTemplateFunc,
"gitHubKeys": c.gitHubKeysTemplateFunc,
"gitHubLatestRelease": c.gitHubLatestReleaseTemplateFunc,
"gitHubRelease": c.gitHubReleaseTemplateFunc,
"gitHubLatestReleaseAssetURL": c.gitHubLatestReleaseAssetURLTemplateFunc,
"gitHubReleaseAssetURL": c.gitHubReleaseAssetURLTemplateFunc,
"gitHubLatestTag": c.gitHubLatestTagTemplateFunc,
"gitHubReleases": c.gitHubReleasesTemplateFunc,
"gitHubTags": c.gitHubTagsTemplateFunc,
Expand Down
109 changes: 94 additions & 15 deletions internal/cmd/githubtemplatefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ type gitHubTagsState struct {
}

var (
gitHubKeysStateBucket = []byte("gitHubLatestKeysState")
gitHubLatestReleaseStateBucket = []byte("gitHubLatestReleaseState")
gitHubReleasesStateBucket = []byte("gitHubReleasesState")
gitHubTagsStateBucket = []byte("gitHubTagsState")
gitHubKeysStateBucket = []byte("gitHubLatestKeysState")
gitHubVersionReleaseStateBucket = []byte("gitHubVersionReleaseState")
gitHubLatestReleaseStateBucket = []byte("gitHubLatestReleaseState")
gitHubReleasesStateBucket = []byte("gitHubReleasesState")
gitHubTagsStateBucket = []byte("gitHubTagsState")
)

type gitHubData struct {
client *github.Client
clientErr error
keysCache map[string][]*github.Key
latestReleaseCache map[string]map[string]*github.RepositoryRelease
releasesCache map[string]map[string][]*github.RepositoryRelease
tagsCache map[string]map[string][]*github.RepositoryTag
client *github.Client
clientErr error
keysCache map[string][]*github.Key
versionReleaseCache map[string]map[string]map[string]*github.RepositoryRelease
latestReleaseCache map[string]map[string]*github.RepositoryRelease
releasesCache map[string]map[string][]*github.RepositoryRelease
tagsCache map[string]map[string][]*github.RepositoryTag
}

func (c *Config) gitHubKeysTemplateFunc(user string) []*github.Key {
Expand Down Expand Up @@ -108,11 +110,7 @@ func (c *Config) gitHubKeysTemplateFunc(user string) []*github.Key {
return allKeys
}

func (c *Config) gitHubLatestReleaseAssetURLTemplateFunc(ownerRepo, pattern string) string {
release, err := c.gitHubLatestRelease(ownerRepo)
if err != nil {
panic(err)
}
func (c *Config) githubMatchingReleaseAssetURL(release *github.RepositoryRelease, pattern string) string {
for _, asset := range release.Assets {
if asset.Name == nil {
continue
Expand All @@ -127,6 +125,79 @@ func (c *Config) gitHubLatestReleaseAssetURLTemplateFunc(ownerRepo, pattern stri
return ""
}

func (c *Config) gitHubLatestReleaseAssetURLTemplateFunc(ownerRepo, pattern string) string {
release, err := c.gitHubLatestRelease(ownerRepo)
if err != nil {
panic(err)
}
return c.githubMatchingReleaseAssetURL(release, pattern)
}

func (c *Config) gitHubReleaseAssetURLTemplateFunc(ownerRepo, version, pattern string) string {
release, err := c.gitHubRelease(ownerRepo, version)
if err != nil {
panic(err)
}
return c.githubMatchingReleaseAssetURL(release, pattern)
}

func (c *Config) gitHubRelease(ownerRepo, version string) (*github.RepositoryRelease, error) {
owner, repo, err := gitHubSplitOwnerRepo(ownerRepo)
if err != nil {
return nil, err
}

if c.gitHub.versionReleaseCache == nil {
c.gitHub.versionReleaseCache = make(map[string]map[string]map[string]*github.RepositoryRelease)
}
if c.gitHub.versionReleaseCache[owner] == nil {
c.gitHub.versionReleaseCache[owner] = make(map[string]map[string]*github.RepositoryRelease)
}
if c.gitHub.versionReleaseCache[owner][repo] == nil {
c.gitHub.versionReleaseCache[owner][repo] = make(map[string]*github.RepositoryRelease)
}

if release := c.gitHub.versionReleaseCache[owner][repo][version]; release != nil {
return release, nil
}

now := time.Now()
gitHubVersionReleaseKey := []byte(owner + "/" + repo + "/" + version)
if c.GitHub.RefreshPeriod != 0 {
var gitHubVersionReleaseStateValue gitHubLatestReleaseState
switch ok, err := chezmoi.PersistentStateGet(c.persistentState, gitHubVersionReleaseStateBucket, gitHubVersionReleaseKey, &gitHubVersionReleaseStateValue); {
case err != nil:
return nil, err
case ok && now.Before(gitHubVersionReleaseStateValue.RequestedAt.Add(c.GitHub.RefreshPeriod)):
return gitHubVersionReleaseStateValue.Release, nil
}
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

gitHubClient, err := c.getGitHubClient(ctx)
if err != nil {
return nil, err
}

release, _, err := gitHubClient.Repositories.GetReleaseByTag(ctx, owner, repo, version)
if err != nil {
return nil, err
}

if err := chezmoi.PersistentStateSet(c.persistentState, gitHubVersionReleaseStateBucket, gitHubVersionReleaseKey, &gitHubLatestReleaseState{
RequestedAt: now,
Release: release,
}); err != nil {
return nil, err
}

c.gitHub.versionReleaseCache[owner][repo][version] = release

return release, nil
}

func (c *Config) gitHubLatestRelease(ownerRepo string) (*github.RepositoryRelease, error) {
owner, repo, err := gitHubSplitOwnerRepo(ownerRepo)
if err != nil {
Expand Down Expand Up @@ -188,6 +259,14 @@ func (c *Config) gitHubLatestReleaseTemplateFunc(ownerRepo string) *github.Repos
return release
}

func (c *Config) gitHubReleaseTemplateFunc(ownerRepo, version string) *github.RepositoryRelease {
release, err := c.gitHubRelease(ownerRepo, version)
if err != nil {
panic(err)
}
return release
}

func (c *Config) gitHubLatestTagTemplateFunc(ownerRepo string) *github.RepositoryTag {
tags, err := c.getGitHubTags(ownerRepo)
if err != nil {
Expand Down
17 changes: 9 additions & 8 deletions internal/cmd/statecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,15 @@ func (c *Config) runStateDeleteBucketCmd(cmd *cobra.Command, args []string) erro

func (c *Config) runStateDumpCmd(cmd *cobra.Command, args []string) error {
data, err := chezmoi.PersistentStateData(c.persistentState, map[string][]byte{
"configState": chezmoi.ConfigStateBucket,
"entryState": chezmoi.EntryStateBucket,
"gitHubKeysState": gitHubKeysStateBucket,
"gitHubLatestReleaseState": gitHubLatestReleaseStateBucket,
"gitHubReleasesState": gitHubReleasesStateBucket,
"gitHubTagsState": gitHubTagsStateBucket,
"gitRepoExternalState": chezmoi.GitRepoExternalStateBucket,
"scriptState": chezmoi.ScriptStateBucket,
"configState": chezmoi.ConfigStateBucket,
"entryState": chezmoi.EntryStateBucket,
"gitHubKeysState": gitHubKeysStateBucket,
"gitHubLatestReleaseState": gitHubLatestReleaseStateBucket,
"gitHubVersionReleaseState": gitHubVersionReleaseStateBucket,
"gitHubReleasesState": gitHubReleasesStateBucket,
"gitHubTagsState": gitHubTagsStateBucket,
"gitRepoExternalState": chezmoi.GitRepoExternalStateBucket,
"scriptState": chezmoi.ScriptStateBucket,
})
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/testdata/scripts/configstate.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ gitHubKeysState: {}
gitHubLatestReleaseState: {}
gitHubReleasesState: {}
gitHubTagsState: {}
gitHubVersionReleaseState: {}
gitRepoExternalState: {}
scriptState: {}
-- home/user/.local/share/chezmoi/.chezmoi.toml.tmpl --
Expand Down
8 changes: 8 additions & 0 deletions internal/cmd/testdata/scripts/githubtemplatefuncs.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ stdout ^ssh-rsa
exec chezmoi execute-template '{{ (gitHubLatestRelease "twpayne/chezmoi").TagName }}'
stdout ^v2\.

# test gitHubLatestRelease template function
exec chezmoi execute-template '{{ (gitHubLatestRelease "2.51.0" "twpayne/chezmoi").TagName }}'
stdout ^v2.51.0

# test gitHubLatestRelease template function
exec chezmoi execute-template '{{ (gitHubLatestRelease "2.49.0" "twpayne/chezmoi").TagName }}'
stdout ^v2.49.0

# test gitHubLatestTag template function
exec chezmoi execute-template '{{ (gitHubLatestTag "twpayne/chezmoi").Name }}'
stdout ^v2\.
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/testdata/scripts/state_unix.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ gitHubKeysState: {}
gitHubLatestReleaseState: {}
gitHubReleasesState: {}
gitHubTagsState: {}
gitHubVersionReleaseState: {}
gitRepoExternalState: {}
scriptState: {}
-- home/user/.local/share/chezmoi/run_once_script.sh --
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/testdata/scripts/state_windows.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ gitHubKeysState: {}
gitHubLatestReleaseState: {}
gitHubReleasesState: {}
gitHubTagsState: {}
gitHubVersionReleaseState: {}
gitRepoExternalState: {}
scriptState: {}
-- home/user/.local/share/chezmoi/run_once_script.cmd --
Expand Down

0 comments on commit 48f873b

Please sign in to comment.