Skip to content

Commit

Permalink
switch fetcher to use maven-metadata.xml (#277)
Browse files Browse the repository at this point in the history
Instead of dealing w/ the rest API (and pagination), simply fetch the
maven-metadata.xml file and determine versions from there.
  • Loading branch information
pkwarren authored Dec 15, 2022
1 parent 7ffaf3d commit 205b630
Showing 1 changed file with 39 additions and 101 deletions.
140 changes: 39 additions & 101 deletions internal/fetchclient/fetchclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ package fetchclient
import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"net/url"
"os"
"strconv"
"strings"

"github.com/google/go-github/v48/github"
Expand All @@ -28,10 +25,7 @@ const (
dartFlutterAPIURL = "https://pub.dev/api/packages"
goProxyURL = "https://proxy.golang.org"
npmRegistryURL = "https://registry.npmjs.org"
// docs: https://central.sonatype.org/search/rest-api-guide/
mavenSearchURL = "https://search.maven.org/solrsearch/select"
mavenPageSize = 50
mavenMaxFetch = 1000
mavenURL = "https://repo1.maven.org/maven2"
)

// Client is a client used to fetch latest package version.
Expand Down Expand Up @@ -84,11 +78,7 @@ func (c *Client) fetch(ctx context.Context, config *source.Config) (string, erro
case config.Source.NPMRegistry != nil:
return c.fetchNPMRegistry(ctx, config.Source.NPMRegistry.Name)
case config.Source.Maven != nil:
results, err := c.fetchMaven(ctx, config.Source.Maven.Group, config.Source.Maven.Name)
if err != nil {
return "", err
}
return results.latestVersion, nil
return c.fetchMaven(ctx, config.Source.Maven.Group, config.Source.Maven.Name)
case config.Source.Crates != nil:
return c.fetchCrate(ctx, config.Source.Crates.CrateName)
}
Expand Down Expand Up @@ -232,106 +222,54 @@ func (c *Client) fetchNPMRegistry(ctx context.Context, name string) (string, err
return data.DistTags.Latest, nil
}

type mavenResults struct {
latestVersion string
latestPatchVersions map[string]string
}

func (c *Client) fetchMaven(ctx context.Context, group string, name string) (*mavenResults, error) {
targetURL, err := url.Parse(mavenSearchURL)
if err != nil {
return nil, err
}
results := &mavenResults{
latestPatchVersions: make(map[string]string),
}
numFound := math.MaxInt
for start := 0; start < numFound; start += mavenPageSize {
response, err := c.fetchMavenPage(ctx, targetURL, group, name, start)
if err != nil {
return nil, err
}
if numFound == math.MaxInt {
numFound = response.Response.NumFound
}
// Limit pagination in case API calls return a large number of versions.
if numFound > mavenMaxFetch {
numFound = mavenMaxFetch
}
if len(response.Response.Docs) == 0 {
break
}
for _, doc := range response.Response.Docs {
version := doc.Version
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
if !semver.IsValid(version) || semver.Prerelease(version) != "" {
continue
}
version = semver.Canonical(version)
if results.latestVersion == "" || semver.Compare(results.latestVersion, version) < 0 {
results.latestVersion = version
}
release := semver.MajorMinor(version)
if results.latestPatchVersions[release] == "" || semver.Compare(results.latestPatchVersions[release], version) < 0 {
results.latestPatchVersions[release] = version
}
}
}
if results.latestVersion == "" {
return nil, errors.New("failed to determine latest version from response docs")
}
return results, nil
}

type mavenResponse struct {
Response struct {
NumFound int `json:"numFound"` //nolint:tagliatelle
Start int `json:"start"`
Docs []struct {
ID string `json:"id"`
Group string `json:"g"`
Artifact string `json:"a"`
Version string `json:"v"`
Packaging string `json:"p"`
} `json:"docs"`
} `json:"response"`
}

func (c *Client) fetchMavenPage(ctx context.Context, targetURL *url.URL, group string, name string, start int) (*mavenResponse, error) {
q := url.Values{}
q.Set("wt", "json")
q.Set("rows", strconv.Itoa(mavenPageSize))
q.Set("core", "gav")
q.Set("start", strconv.Itoa(start))
q.Set("q", fmt.Sprintf("g:%s+AND+a:%s", group, name))
unescapedQuery, err := url.QueryUnescape(q.Encode())
func (c *Client) fetchMaven(ctx context.Context, group string, name string) (string, error) {
groupComponents := strings.Split(group, ".")
targetURL, err := url.JoinPath(mavenURL, append(groupComponents, name, "maven-metadata.xml")...)
if err != nil {
return nil, err
return "", err
}
targetURL.RawQuery = unescapedQuery
request, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL.String(), nil)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
if err != nil {
return nil, err
return "", err
}
response, err := c.httpClient.Do(request)
if err != nil {
return nil, err
return "", err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
if errorBody, err := io.ReadAll(response.Body); err != nil {
log.Println("error:", string(errorBody))
return "", fmt.Errorf("received status code %d retrieving %q", response.StatusCode, request.URL.String())
}
var metadata struct {
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Versioning struct {
Latest string `xml:"latest"`
Release string `xml:"release"`
Versions []string `xml:"versions>version"`
LastUpdated string `xml:"lastUpdated"`
} `xml:"versioning"`
}
if err := xml.NewDecoder(response.Body).Decode(&metadata); err != nil {
return "", err
}
latestVersion := ""
for _, version := range metadata.Versioning.Versions {
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
if !semver.IsValid(version) || semver.Prerelease(version) != "" {
continue
}
version = semver.Canonical(version)
if latestVersion == "" || semver.Compare(latestVersion, version) < 0 {
latestVersion = version
}
return nil, fmt.Errorf("received status code %d retrieving %q", response.StatusCode, request.URL.String())
}

var data mavenResponse
if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
return nil, err
if latestVersion == "" {
return "", errors.New("failed to determine latest version from metadata")
}
return &data, nil
return latestVersion, nil
}

func (c *Client) fetchGithub(ctx context.Context, owner string, repository string) (string, error) {
Expand Down

0 comments on commit 205b630

Please sign in to comment.