2023-05-11 10:21:21 +00:00
|
|
|
package mr
|
|
|
|
|
|
|
|
import (
|
2023-05-11 16:07:15 +00:00
|
|
|
"context"
|
2023-05-11 10:21:21 +00:00
|
|
|
"encoding/json"
|
2023-05-11 11:54:35 +00:00
|
|
|
"errors"
|
2023-05-11 10:21:21 +00:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2023-05-11 21:15:29 +00:00
|
|
|
"strings"
|
2023-05-11 16:07:15 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"golang.org/x/time/rate"
|
2023-05-11 10:21:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const apiURL = "https://snapshot.debian.org/mr"
|
|
|
|
|
2023-05-11 21:15:29 +00:00
|
|
|
var (
|
2023-05-17 06:05:37 +00:00
|
|
|
limiterTimeout time.Duration = time.Second / 20
|
2023-05-28 20:36:20 +00:00
|
|
|
limiterMaxTimeout time.Duration = time.Second * 2
|
2023-05-28 12:31:52 +00:00
|
|
|
limiterBurst int = 1
|
2023-05-28 17:22:16 +00:00
|
|
|
limiterUpdateDelay time.Duration = time.Second
|
2023-05-11 21:15:29 +00:00
|
|
|
|
|
|
|
Limiter = rate.NewLimiter(rate.Every(limiterTimeout), limiterBurst)
|
|
|
|
)
|
|
|
|
|
|
|
|
func lowerLimit() {
|
|
|
|
limiterTimeout = limiterTimeout * 2
|
2023-05-28 12:16:05 +00:00
|
|
|
if limiterTimeout > limiterMaxTimeout {
|
|
|
|
limiterTimeout = limiterMaxTimeout
|
|
|
|
}
|
2023-05-11 21:15:29 +00:00
|
|
|
log.Info().Msgf("limiter timeout set to %v", limiterTimeout)
|
2023-05-17 06:05:37 +00:00
|
|
|
Limiter.SetLimitAt(
|
|
|
|
time.Now().Add(limiterUpdateDelay),
|
|
|
|
rate.Every(limiterTimeout),
|
|
|
|
)
|
2023-05-28 12:42:01 +00:00
|
|
|
log.Info().Msgf("wait %v", limiterUpdateDelay)
|
2023-05-26 13:10:54 +00:00
|
|
|
time.Sleep(limiterUpdateDelay)
|
2023-05-11 21:15:29 +00:00
|
|
|
}
|
2023-05-11 16:07:15 +00:00
|
|
|
|
|
|
|
// Retries in case of 5xx errors
|
|
|
|
var Retries = 10
|
|
|
|
|
2023-05-11 10:21:21 +00:00
|
|
|
// https://salsa.debian.org/snapshot-team/snapshot/blob/master/API
|
|
|
|
|
|
|
|
// /mr/package/<package>/
|
|
|
|
type Package struct {
|
|
|
|
Comment string `json:"_comment"`
|
|
|
|
Package string `json:"package"`
|
|
|
|
Result []struct {
|
|
|
|
Version string `json:"version"`
|
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// /mr/package/<package>/<version>/binpackages
|
|
|
|
type Binpackages struct {
|
|
|
|
Comment string `json:"_comment"`
|
|
|
|
Package string `json:"package"`
|
|
|
|
Result []struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
} `json:"result"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// /mr/binary/<binary>/
|
|
|
|
type Binary struct {
|
|
|
|
Comment string `json:"_comment"`
|
|
|
|
Binary string `json:"binary"`
|
|
|
|
Result []struct {
|
|
|
|
BinaryVersion string `json:"binary_version"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Source string `json:"source"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// /mr/binary/<binpkg>/<binversion>/binfiles
|
|
|
|
type Binfiles struct {
|
|
|
|
Comment string `json:"_comment"`
|
|
|
|
Binary string `json:"binary"`
|
|
|
|
BinaryVersion string `json:"binary_version"`
|
|
|
|
Result []struct {
|
|
|
|
Architecture string `json:"architecture"`
|
|
|
|
Hash string `json:"hash"`
|
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Fileinfo struct {
|
|
|
|
ArchiveName string `json:"archive_name"`
|
|
|
|
FirstSeen string `json:"first_seen"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
Size int `json:"size"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// /mr/file/<hash>/info
|
|
|
|
type Info struct {
|
|
|
|
Comment string `json:"_comment"`
|
|
|
|
Hash string `json:"hash"`
|
|
|
|
Result []Fileinfo `json:"result"`
|
|
|
|
}
|
|
|
|
|
2023-05-11 21:59:44 +00:00
|
|
|
var ErrNotFound = errors.New("404 not found")
|
|
|
|
|
2023-05-11 16:07:15 +00:00
|
|
|
func getJson(query string, target interface{}) (err error) {
|
|
|
|
flog := log.With().Str("url", query).Logger()
|
|
|
|
|
|
|
|
var resp *http.Response
|
|
|
|
for i := Retries; i > 0; i-- {
|
|
|
|
flog.Trace().Msg("wait")
|
|
|
|
Limiter.Wait(context.Background())
|
|
|
|
|
|
|
|
flog.Trace().Msg("start")
|
|
|
|
resp, err = http.Get(query)
|
|
|
|
if err != nil {
|
2023-05-17 05:39:24 +00:00
|
|
|
if strings.Contains(err.Error(), "reset by peer") ||
|
|
|
|
strings.Contains(err.Error(), "connection refused") {
|
2023-05-11 21:15:29 +00:00
|
|
|
flog.Debug().Err(err).Msg("")
|
|
|
|
lowerLimit()
|
|
|
|
continue
|
|
|
|
}
|
2023-05-11 16:07:15 +00:00
|
|
|
flog.Error().Err(err).Msg("")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
2023-05-11 21:59:44 +00:00
|
|
|
|
2023-05-11 16:07:15 +00:00
|
|
|
flog.Debug().Msgf("%s", resp.Status)
|
|
|
|
|
2023-05-11 21:59:44 +00:00
|
|
|
if resp.StatusCode == 404 {
|
|
|
|
err = ErrNotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-05-11 16:07:15 +00:00
|
|
|
if resp.StatusCode < 500 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
flog.Debug().Msgf("retry (%d left)", i)
|
2023-05-11 10:21:21 +00:00
|
|
|
}
|
|
|
|
|
2023-05-11 12:54:54 +00:00
|
|
|
if resp.StatusCode >= 400 {
|
2023-05-11 16:07:15 +00:00
|
|
|
err = fmt.Errorf("%d (%s)", resp.StatusCode, query)
|
2023-05-11 12:54:54 +00:00
|
|
|
}
|
2023-05-11 10:21:21 +00:00
|
|
|
return json.NewDecoder(resp.Body).Decode(target)
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetPackage(name string) (pkg Package, err error) {
|
2023-05-11 19:43:42 +00:00
|
|
|
query := fmt.Sprintf("%s/package/%s/", apiURL, name)
|
2023-05-11 10:21:21 +00:00
|
|
|
err = getJson(query, &pkg)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetBinpackages(name, version string) (binpkgs Binpackages, err error) {
|
2023-05-11 19:43:42 +00:00
|
|
|
query := fmt.Sprintf("%s/package/%s/%s/binpackages",
|
|
|
|
apiURL, name, version)
|
2023-05-11 10:21:21 +00:00
|
|
|
err = getJson(query, &binpkgs)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetBinary(pkg string) (binary Binary, err error) {
|
2023-05-11 19:43:42 +00:00
|
|
|
query := fmt.Sprintf("%s/binary/%s/", apiURL, pkg)
|
2023-05-11 10:21:21 +00:00
|
|
|
err = getJson(query, &binary)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetBinfiles(binpkg, binversion string) (binfiles Binfiles, err error) {
|
2023-05-11 19:43:42 +00:00
|
|
|
query := fmt.Sprintf("%s/binary/%s/%s/binfiles",
|
|
|
|
apiURL, binpkg, binversion)
|
2023-05-11 10:21:21 +00:00
|
|
|
err = getJson(query, &binfiles)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetInfo(hash string) (info Info, err error) {
|
2023-05-11 19:43:42 +00:00
|
|
|
query := fmt.Sprintf("%s/file/%s/info", apiURL, hash)
|
2023-05-11 11:54:35 +00:00
|
|
|
|
2023-05-11 10:21:21 +00:00
|
|
|
err = getJson(query, &info)
|
2023-05-11 11:54:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-05-11 21:29:15 +00:00
|
|
|
if len(info.Result) == 0 {
|
|
|
|
err = errors.New("empty response")
|
2023-05-11 11:54:35 +00:00
|
|
|
}
|
2023-05-11 10:21:21 +00:00
|
|
|
return
|
|
|
|
}
|