Files
artifactapi/internal/provider/puppet/puppet.go
T
unkin-agent ca269c5046
ci/woodpecker/tag/docker Pipeline was successful
Forward module and pagination params on puppet release listings (#161)
A puppet remote drops the query string on `/v3/releases`, so `?module=<slug>` reaches the Forge unfiltered and clients get releases of every module instead of the one they asked for.

- forward validated `module`, `limit` (capped at 100) and `offset` on `v3/releases` via `QueryForwarder`
- document the forwarded params and the Forge API `base_url` in `docs/puppet/remote.md`

Reviewed-on: #161
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-20 22:19:23 +11:00

86 lines
2.3 KiB
Go

package puppet
import (
"context"
"net/http"
"strconv"
"strings"
"git.unkin.net/unkin/artifactapi/internal/auth"
"git.unkin.net/unkin/artifactapi/internal/provider"
"git.unkin.net/unkin/artifactapi/pkg/models"
)
func init() {
provider.Register(&Provider{})
}
type Provider struct{}
func (p *Provider) Type() models.PackageType { return models.PackagePuppet }
func (p *Provider) Classify(path string) provider.Mutability {
if strings.HasPrefix(path, "v3/modules/") || strings.HasPrefix(path, "v3/releases") {
return provider.Mutable
}
return provider.Immutable
}
func (p *Provider) ContentType(path string) string {
if strings.HasSuffix(path, ".tar.gz") {
return "application/gzip"
}
if strings.HasPrefix(path, "v3/") {
return "application/json"
}
return "application/octet-stream"
}
func (p *Provider) UpstreamURL(remote models.Remote, path string) string {
return strings.TrimRight(remote.BaseURL, "/") + "/" + strings.TrimLeft(path, "/")
}
// ForwardedQuery keeps the module filter and pagination on release listings.
func (p *Provider) ForwardedQuery(path string) map[string]provider.QueryValidator {
if path != "v3/releases" {
return nil
}
return map[string]provider.QueryValidator{"module": moduleParam, "limit": pageLimit, "offset": pageOffset}
}
func moduleParam(v string) (string, bool) {
v = NormalizeSlug(v)
return v, ValidSlug(v)
}
func pageLimit(v string) (string, bool) {
n, err := strconv.ParseUint(v, 10, 32)
if err != nil || n == 0 {
return "", false
}
return strconv.FormatUint(min(n, 100), 10), true
}
func pageOffset(v string) (string, bool) {
n, err := strconv.ParseUint(v, 10, 32)
return strconv.FormatUint(n, 10), err == nil
}
func (p *Provider) RewritesResponse() {}
func (p *Provider) RewriteResponse(body []byte, remote models.Remote, proxyBaseURL string) ([]byte, error) {
if proxyBaseURL == "" {
return nil, nil
}
content := string(body)
proxyURL := strings.TrimRight(proxyBaseURL, "/") + "/api/v1/remote/" + remote.Name
content = strings.ReplaceAll(content, `"/v3/files/`, `"`+proxyURL+`/v3/files/`)
baseURL := strings.TrimRight(remote.BaseURL, "/")
content = strings.ReplaceAll(content, baseURL, proxyURL)
return []byte(content), nil
}
func (p *Provider) AuthHeaders(_ context.Context, remote models.Remote) (http.Header, error) {
return auth.BasicHeaders(remote), nil
}