Files
pmg/pkg/registry/client.go
T

111 lines
3.0 KiB
Go

package registry
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/safedep/pmg/pkg/models"
)
// RegistryClient defines the interface for making requests to a registry
type RegistryClient interface {
// FetchPackageInfo fetches metadata for a specific package version
FetchPackageInfo(ctx context.Context, pkg models.Package) (*models.PackageInfo, error)
// GetLatestVersion fetches the latest version for a package
GetLatestVersion(ctx context.Context, packageName string) (string, error)
}
// HttpRegistryClient is a basic HTTP client for registry APIs
type HttpRegistryClient struct {
httpClient *http.Client
urlFormat string
parser func([]byte) (*models.PackageInfo, error)
}
// NewHttpRegistryClient creates a new HTTP registry client
func NewHttpRegistryClient(
timeout time.Duration,
urlFormat string,
parser func([]byte) (*models.PackageInfo, error),
) *HttpRegistryClient {
return &HttpRegistryClient{
httpClient: &http.Client{Timeout: timeout},
urlFormat: urlFormat,
parser: parser,
}
}
// FetchPackageInfo fetches package metadata from the registry
func (c *HttpRegistryClient) FetchPackageInfo(ctx context.Context, pkg models.Package) (*models.PackageInfo, error) {
url := fmt.Sprintf(c.urlFormat, pkg.Name, pkg.Version)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("registry returned status: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
return c.parser(body)
}
// GetLatestVersion fetches the latest version for an NPM package
func (c *HttpRegistryClient) GetLatestVersion(ctx context.Context, packageName string) (string, error) {
// For NPM, we can get latest version by querying the base package URL
url := fmt.Sprintf("https://registry.npmjs.org/%s", packageName)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", fmt.Errorf("creating request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("registry returned status: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("reading response body: %w", err)
}
// Parse the response to get the latest version
var pkgData struct {
DistTags struct {
Latest string `json:"latest"`
} `json:"dist-tags"`
}
if err := json.Unmarshal(body, &pkgData); err != nil {
return "", fmt.Errorf("parsing package info: %w", err)
}
if pkgData.DistTags.Latest == "" {
return "", fmt.Errorf("no latest version found for package %s", packageName)
}
return pkgData.DistTags.Latest, nil
}