From 7d1ede8c91f13b45ca287247d06b13fb4c21653f Mon Sep 17 00:00:00 2001 From: Albin Parou Date: Wed, 3 Jul 2024 08:17:51 +0200 Subject: [PATCH] releases: Add support for gitlab --- docs/configuration.md | 6 +- internal/feed/{github.go => git_forge.go} | 117 +++++++++++++++++++++- internal/widget/releases.go | 7 +- 3 files changed, 127 insertions(+), 3 deletions(-) rename internal/feed/{github.go => git_forge.go} (67%) diff --git a/docs/configuration.md b/docs/configuration.md index 2dffb2e..393e4c4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1008,6 +1008,7 @@ Preview: | token | string | no | | | limit | integer | no | 10 | | collapse-after | integer | no | 5 | +| source | string | no | github | ##### `repositories` A list of repositores for which to fetch the latest release for. Only the name/repo is required, not the full URL. @@ -1039,7 +1040,10 @@ This way you can safely check your `glance.yml` in version control without expos The maximum number of releases to show. #### `collapse-after` -How many releases are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse. +how many releases are visible before the "show more" button appears. set to `-1` to never collapse. + +#### `source` +Either `github` or `gitlab`. Wether to retrieve the releases from github repositories or gitlab repositories. ### Repository Display general information about a repository as well as a list of the latest open pull requests and issues. diff --git a/internal/feed/github.go b/internal/feed/git_forge.go similarity index 67% rename from internal/feed/github.go rename to internal/feed/git_forge.go index 4d7dc73..e46ecc5 100644 --- a/internal/feed/github.go +++ b/internal/feed/git_forge.go @@ -1,9 +1,11 @@ package feed import ( + "errors" "fmt" "log/slog" "net/http" + "net/url" "sync" "time" ) @@ -17,6 +19,19 @@ type githubReleaseLatestResponseJson struct { } `json:"reactions"` } +type gitlabReleaseResponseJson struct { + TagName string `json:"tag_name"` + PublishedAt string `json:"created_at"` + Links struct { + Self string `json:"self"` + } `json:"_links"` + Draft bool `json:"draft"` + PreRelease bool `json:"prerelease"` + Reactions struct { + Downvotes int `json:"-1"` + } `json:"reactions"` +} + func parseGithubTime(t string) time.Time { parsedTime, err := time.Parse("2006-01-02T15:04:05Z", t) @@ -27,7 +42,107 @@ func parseGithubTime(t string) time.Time { return parsedTime } -func FetchLatestReleasesFromGithub(repositories []string, token string) (AppReleases, error) { +func FetchLatestReleasesFromGitForge(repositories []string, token string, source string) (AppReleases, error) { + switch source { + case "github": + return fetchLatestReleasesFromGithub(repositories, token) + case "gitlab": + return fetchLatestReleasesFromGitlab(repositories, token) + default: + return nil, errors.New(fmt.Sprintf("Release source %s is invalid", source)) + } +} + +func fetchLatestReleasesFromGitlab(repositories []string, token string) (AppReleases, error) { + appReleases := make(AppReleases, 0, len(repositories)) + + if len(repositories) == 0 { + return appReleases, nil + } + + requests := make([]*http.Request, len(repositories)) + + for i, repository := range repositories { + request, _ := http.NewRequest("GET", fmt.Sprintf("https://gitlab.com/api/v4/projects/%s/releases/", url.QueryEscape(repository)), nil) + + if token != "" { + request.Header.Add("PRIVATE-TOKEN", token) + } + + requests[i] = request + } + + task := decodeJsonFromRequestTask[[]gitlabReleaseResponseJson](defaultClient) + job := newJob(task, requests).withWorkers(15) + responses, errs, err := workerPoolDo(job) + + if err != nil { + return nil, err + } + + var failed int + + for i := range responses { + if errs[i] != nil { + failed++ + slog.Error("Failed to fetch or parse gitlab release", "error", errs[i], "url", requests[i].URL) + continue + } + + releases := responses[i] + + if len(releases) < 1 { + failed++ + slog.Error("No releases found", "repository", repositories[i], "url", requests[i].URL) + continue + } + + var liveRelease *gitlabReleaseResponseJson + + for i := range releases { + release := &releases[i] + + if !release.Draft && !release.PreRelease { + liveRelease = release + break + } + } + + if liveRelease == nil { + slog.Error("No live release found", "repository", repositories[i], "url", requests[i].URL) + continue + } + + version := liveRelease.TagName + + if version[0] != 'v' { + version = "v" + version + } + + appReleases = append(appReleases, AppRelease{ + Name: repositories[i], + Version: version, + NotesUrl: liveRelease.Links.Self, + TimeReleased: parseGithubTime(liveRelease.PublishedAt), + Downvotes: liveRelease.Reactions.Downvotes, + }) + } + + if len(appReleases) == 0 { + return nil, ErrNoContent + } + + appReleases.SortByNewest() + + if failed > 0 { + return appReleases, fmt.Errorf("%w: could not get %d releases", ErrPartialContent, failed) + } + + return appReleases, nil +} + + +func fetchLatestReleasesFromGithub(repositories []string, token string) (AppReleases, error) { appReleases := make(AppReleases, 0, len(repositories)) if len(repositories) == 0 { diff --git a/internal/widget/releases.go b/internal/widget/releases.go index 77fe103..cd28aaf 100644 --- a/internal/widget/releases.go +++ b/internal/widget/releases.go @@ -16,6 +16,7 @@ type Releases struct { Token OptionalEnvString `yaml:"token"` Limit int `yaml:"limit"` CollapseAfter int `yaml:"collapse-after"` + Source string `yaml:"source"` } func (widget *Releases) Initialize() error { @@ -29,11 +30,15 @@ func (widget *Releases) Initialize() error { widget.CollapseAfter = 5 } + if widget.Source == "" { + widget.Source = "github" + } + return nil } func (widget *Releases) Update(ctx context.Context) { - releases, err := feed.FetchLatestReleasesFromGithub(widget.Repositories, string(widget.Token)) + releases, err := feed.FetchLatestReleasesFromGitForge(widget.Repositories, string(widget.Token), widget.Source) if !widget.canContinueUpdateAfterHandlingErr(err) { return