mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
92 lines
2.9 KiB
Go
92 lines
2.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
homebrewFormulaPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9@+._-]*$`)
|
|
homebrewVersionPattern = regexp.MustCompile(`^[0-9A-Za-z][0-9A-Za-z._+-]*$`)
|
|
)
|
|
|
|
// homebrewMaxFormulaJSON bounds one formula's JSON API document, a few KB.
|
|
const homebrewMaxFormulaJSON = 1 << 20
|
|
|
|
// homebrewFormula is the part of a formula's JSON API document that names
|
|
// its bottles' image tag.
|
|
type homebrewFormula struct {
|
|
Versions struct {
|
|
Stable string `json:"stable"`
|
|
} `json:"versions"`
|
|
Revision int `json:"revision"`
|
|
Bottle struct {
|
|
Stable struct {
|
|
Rebuild int `json:"rebuild"`
|
|
Files map[string]any `json:"files"`
|
|
} `json:"stable"`
|
|
} `json:"bottle"`
|
|
}
|
|
|
|
// BottleImage returns the homebrew/core image and tag for a formula version.
|
|
// Only the current version gets its revision and rebuild from the JSON API;
|
|
// any other version is used as the tag as given.
|
|
func (h *HomebrewHandler) BottleImage(ctx context.Context, formula, version string) (ImageRef, error) {
|
|
if !homebrewFormulaPattern.MatchString(formula) {
|
|
return ImageRef{}, fmt.Errorf("invalid Homebrew formula name %q", formula)
|
|
}
|
|
if !homebrewVersionPattern.MatchString(version) {
|
|
return ImageRef{}, fmt.Errorf("invalid Homebrew version %q", version)
|
|
}
|
|
f, err := h.fetchFormula(ctx, formula)
|
|
if err != nil {
|
|
return ImageRef{}, err
|
|
}
|
|
tag := version
|
|
if version == f.Versions.Stable {
|
|
if len(f.Bottle.Stable.Files) == 0 {
|
|
return ImageRef{}, fmt.Errorf("homebrew formula %s %s has no bottles", formula, version)
|
|
}
|
|
if f.Revision > 0 {
|
|
tag += "_" + strconv.Itoa(f.Revision)
|
|
}
|
|
if f.Bottle.Stable.Rebuild > 0 {
|
|
tag += "-" + strconv.Itoa(f.Bottle.Stable.Rebuild)
|
|
}
|
|
}
|
|
name := strings.NewReplacer("@", "/", "+", "x").Replace(formula)
|
|
return ImageRef{Name: homebrewArtifactRepository + "/" + name, References: []string{tag}}, nil
|
|
}
|
|
|
|
func (h *HomebrewHandler) fetchFormula(ctx context.Context, formula string) (*homebrewFormula, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, h.apiUpstream+"/formula/"+formula+".json", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := h.proxy.HTTPClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading the Homebrew formula: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
switch {
|
|
case resp.StatusCode == http.StatusNotFound:
|
|
return nil, fmt.Errorf("homebrew/core has no formula %q", formula)
|
|
case resp.StatusCode != http.StatusOK:
|
|
return nil, fmt.Errorf("reading the Homebrew formula: upstream answered %d", resp.StatusCode)
|
|
}
|
|
var f homebrewFormula
|
|
if err := json.NewDecoder(io.LimitReader(resp.Body, homebrewMaxFormulaJSON)).Decode(&f); err != nil {
|
|
return nil, fmt.Errorf("reading the Homebrew formula: %w", err)
|
|
}
|
|
if f.Versions.Stable == "" {
|
|
return nil, errors.New("reading the Homebrew formula: it has no stable version")
|
|
}
|
|
return &f, nil
|
|
}
|