mirror of
https://github.com/webinstall/webi-installers.git
synced 2026-04-07 10:56:50 +00:00
GitLab's API differs from GitHub: different URL pattern (/api/v4/projects/:id/releases), nested asset structure (sources + links), page/per_page pagination with X-Total-Pages header, and PRIVATE-TOKEN auth.
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
// Package gitlab fetches releases from a GitLab instance.
|
|
//
|
|
// GitLab's releases API differs from GitHub's in structure:
|
|
//
|
|
// GET /api/v4/projects/:id/releases
|
|
//
|
|
// Where :id is the URL-encoded project path (e.g. "group%2Frepo") or a
|
|
// numeric project ID. Assets are split into auto-generated source archives
|
|
// and manually attached links. Pagination uses page/per_page query params
|
|
// and X-Total-Pages response headers (not Link headers).
|
|
//
|
|
// This package handles pagination, authentication, and deserialization.
|
|
// It does not transform or normalize the data.
|
|
package gitlab
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"iter"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
// Release is one release from the GitLab releases API.
|
|
type Release struct {
|
|
TagName string `json:"tag_name"`
|
|
Name string `json:"name"`
|
|
ReleasedAt string `json:"released_at"` // "2025-10-22T13:00:26Z"
|
|
Assets Assets `json:"assets"`
|
|
}
|
|
|
|
// Assets holds both auto-generated source archives and attached links.
|
|
type Assets struct {
|
|
Sources []Source `json:"sources"`
|
|
Links []Link `json:"links"`
|
|
}
|
|
|
|
// Source is an auto-generated source archive (tar.gz, zip, etc.).
|
|
type Source struct {
|
|
Format string `json:"format"` // "zip", "tar.gz", "tar.bz2", "tar"
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// Link is a file attached to a release (binary, package, etc.).
|
|
type Link struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
DirectAssetPath string `json:"direct_asset_path"`
|
|
LinkType string `json:"link_type"` // "other", "runbook", "image", "package"
|
|
}
|
|
|
|
// Auth holds optional credentials for authenticated API access.
|
|
type Auth struct {
|
|
Token string // personal access token or deploy token
|
|
}
|
|
|
|
// Fetch retrieves releases from a GitLab instance, paginating automatically.
|
|
// Each yield is one page of releases.
|
|
//
|
|
// The baseURL should be the GitLab root (e.g. "https://gitlab.com").
|
|
// The project is identified by its path (e.g. "group/repo") — it will be
|
|
// URL-encoded automatically.
|
|
func Fetch(ctx context.Context, client *http.Client, baseURL, project string, auth *Auth) iter.Seq2[[]Release, error] {
|
|
return func(yield func([]Release, error) bool) {
|
|
encodedProject := url.PathEscape(project)
|
|
page := 1
|
|
|
|
for {
|
|
reqURL := fmt.Sprintf("%s/api/v4/projects/%s/releases?per_page=100&page=%d",
|
|
baseURL, encodedProject, page)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
|
if err != nil {
|
|
yield(nil, fmt.Errorf("gitlab: %w", err))
|
|
return
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
if auth != nil && auth.Token != "" {
|
|
req.Header.Set("PRIVATE-TOKEN", auth.Token)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
yield(nil, fmt.Errorf("gitlab: fetch %s: %w", reqURL, err))
|
|
return
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
resp.Body.Close()
|
|
yield(nil, fmt.Errorf("gitlab: fetch %s: %s", reqURL, resp.Status))
|
|
return
|
|
}
|
|
|
|
var releases []Release
|
|
err = json.NewDecoder(resp.Body).Decode(&releases)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
yield(nil, fmt.Errorf("gitlab: decode %s: %w", reqURL, err))
|
|
return
|
|
}
|
|
|
|
if !yield(releases, nil) {
|
|
return
|
|
}
|
|
|
|
// Check if there are more pages.
|
|
totalPages := 1
|
|
if tp := resp.Header.Get("X-Total-Pages"); tp != "" {
|
|
if n, err := strconv.Atoi(tp); err == nil {
|
|
totalPages = n
|
|
}
|
|
}
|
|
if page >= totalPages {
|
|
return
|
|
}
|
|
page++
|
|
}
|
|
}
|
|
}
|