Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions depsdev.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ import (
type DepsDevClient struct {
baseURL string
httpClient *http.Client
userAgent string
}

// NewDepsDevClient creates a client for the deps.dev API.
func NewDepsDevClient() *DepsDevClient {
return newDepsDevClient(defaultUserAgent)
}

func newDepsDevClient(userAgent string) *DepsDevClient {
return &DepsDevClient{
baseURL: "https://api.deps.dev",
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
userAgent: userAgent,
}
}

Expand Down Expand Up @@ -184,12 +190,13 @@ func (c *DepsDevClient) getPackage(ctx context.Context, system, name string) (*d
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.userAgent)

resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("deps.dev: %s", resp.Status)
Expand All @@ -210,12 +217,13 @@ func (c *DepsDevClient) getVersion(ctx context.Context, system, name, version st
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.userAgent)

resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("deps.dev: %s", resp.Status)
Expand Down
6 changes: 5 additions & 1 deletion ecosystems.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ type EcosystemsClient struct {

// NewEcosystemsClient creates a client that uses the ecosyste.ms API.
func NewEcosystemsClient() (*EcosystemsClient, error) {
client, err := ecosystems.NewClient("git-pkgs/1.0")
return newEcosystemsClient(defaultUserAgent)
}

func newEcosystemsClient(userAgent string) (*EcosystemsClient, error) {
client, err := ecosystems.NewClient(userAgent)
if err != nil {
return nil, err
}
Expand Down
61 changes: 57 additions & 4 deletions enrichment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func TestDepsDevGetVersions(t *testing.T) {
},
},
}
json.NewEncoder(w).Encode(resp)
_ = json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()

Expand Down Expand Up @@ -214,7 +214,7 @@ func TestDepsDevGetVersion(t *testing.T) {
{Label: "HOMEPAGE", URL: "https://lodash.com"},
},
}
json.NewEncoder(w).Encode(resp)
_ = json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()

Expand All @@ -236,6 +236,59 @@ func TestDepsDevGetVersion(t *testing.T) {
}
}

func TestDepsDevUserAgent(t *testing.T) {
var gotUA string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotUA = r.Header.Get("User-Agent")
_ = json.NewEncoder(w).Encode(depsdevPackageResponse{})
}))
defer srv.Close()

t.Run("default", func(t *testing.T) {
client := NewDepsDevClient()
client.baseURL = srv.URL
client.httpClient = srv.Client()
_, _ = client.GetVersions(context.Background(), "pkg:npm/lodash")
if gotUA != "enrichment" {
t.Errorf("default User-Agent = %q, want %q", gotUA, "enrichment")
}
})

t.Run("custom", func(t *testing.T) {
client := newDepsDevClient("git-pkgs/test")
client.baseURL = srv.URL
client.httpClient = srv.Client()
_, _ = client.GetVersions(context.Background(), "pkg:npm/lodash")
if gotUA != "git-pkgs/test" {
t.Errorf("custom User-Agent = %q, want %q", gotUA, "git-pkgs/test")
}
})
}

func TestRegistriesClientUserAgent(t *testing.T) {
client := newRegistriesClient("custom-agent")
if client.client.UserAgent != "custom-agent" {
t.Errorf("UserAgent = %q, want %q", client.client.UserAgent, "custom-agent")
}
}

func TestNewClientWithUserAgent(t *testing.T) {
t.Setenv("GIT_PKGS_DIRECT", "1")

client, err := NewClient(WithUserAgent("test-ua"))
if err != nil {
t.Fatalf("NewClient() error: %v", err)
}

rc, ok := client.(*RegistriesClient)
if !ok {
t.Fatalf("expected *RegistriesClient, got %T", client)
}
if rc.client.UserAgent != "test-ua" {
t.Errorf("UserAgent = %q, want %q", rc.client.UserAgent, "test-ua")
}
}

func TestDepsDevBulkLookup(t *testing.T) {
callCount := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -255,7 +308,7 @@ func TestDepsDevBulkLookup(t *testing.T) {
},
},
}
json.NewEncoder(w).Encode(resp)
_ = json.NewEncoder(w).Encode(resp)
} else {
// Version response
resp := depsdevVersionResponse{
Expand All @@ -273,7 +326,7 @@ func TestDepsDevBulkLookup(t *testing.T) {
{Label: "SOURCE_REPO", URL: "https://github.com/lodash/lodash"},
},
}
json.NewEncoder(w).Encode(resp)
_ = json.NewEncoder(w).Encode(resp)
}
}))
defer srv.Close()
Expand Down
31 changes: 28 additions & 3 deletions factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ import (
"strings"
)

const defaultUserAgent = "enrichment"

// Option configures an enrichment client.
type Option func(*options)

type options struct {
userAgent string
}

// WithUserAgent sets the User-Agent header for API requests.
func WithUserAgent(ua string) Option {
return func(o *options) {
o.userAgent = ua
}
}

func buildOptions(opts []Option) options {
o := options{userAgent: defaultUserAgent}
for _, opt := range opts {
opt(&o)
}
return o
}

// NewClient creates an enrichment client based on configuration.
//
// By default, uses a hybrid approach:
Expand All @@ -15,11 +39,12 @@ import (
// To skip ecosyste.ms and query all registries directly:
// - Set GIT_PKGS_DIRECT=1 environment variable, or
// - Set git config: git config --global pkgs.direct true
func NewClient() (Client, error) {
func NewClient(opts ...Option) (Client, error) {
o := buildOptions(opts)
if directMode() {
return NewRegistriesClient(), nil
return newRegistriesClient(o.userAgent), nil
}
return NewHybridClient()
return newHybridClient(o.userAgent)
}

// directMode checks if direct registry mode is enabled.
Expand Down
8 changes: 6 additions & 2 deletions hybrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ type HybridClient struct {

// NewHybridClient creates a client that routes based on PURL qualifiers.
func NewHybridClient() (*HybridClient, error) {
eco, err := NewEcosystemsClient()
return newHybridClient(defaultUserAgent)
}

func newHybridClient(userAgent string) (*HybridClient, error) {
eco, err := newEcosystemsClient(userAgent)
if err != nil {
return nil, err
}
return &HybridClient{
ecosystems: eco,
registries: NewRegistriesClient(),
registries: newRegistriesClient(userAgent),
}, nil
}

Expand Down
10 changes: 7 additions & 3 deletions registries.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ type RegistriesClient struct {

// NewRegistriesClient creates a client that queries registries directly.
func NewRegistriesClient() *RegistriesClient {
return &RegistriesClient{
client: registries.DefaultClient(),
}
return newRegistriesClient(defaultUserAgent)
}

func newRegistriesClient(userAgent string) *RegistriesClient {
c := registries.DefaultClient()
c.UserAgent = userAgent
return &RegistriesClient{client: c}
}

func (c *RegistriesClient) BulkLookup(ctx context.Context, purls []string) (map[string]*PackageInfo, error) {
Expand Down
11 changes: 9 additions & 2 deletions scorecard/scorecard.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,21 @@ type Check struct {
type Client struct {
baseURL string
httpClient *http.Client
userAgent string
}

// New creates a new scorecard client.
func New() *Client {
func New(userAgent ...string) *Client {
ua := "enrichment"
if len(userAgent) > 0 && userAgent[0] != "" {
ua = userAgent[0]
}
return &Client{
baseURL: "https://api.securityscorecards.dev",
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
userAgent: ua,
}
}

Expand All @@ -55,12 +61,13 @@ func (c *Client) GetScore(ctx context.Context, repoURL string) (*Result, error)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.userAgent)

resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("scorecard: %s", resp.Status)
Expand Down
40 changes: 38 additions & 2 deletions scorecard/scorecard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestGetScore(t *testing.T) {
{Name: "Vulnerabilities", Score: 0, Reason: "79 existing vulnerabilities detected"},
},
}
json.NewEncoder(w).Encode(resp)
_ = json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()

Expand Down Expand Up @@ -77,7 +77,7 @@ func TestGetScoreStripsScheme(t *testing.T) {
return
}
resp := scorecardResponse{Score: 5.0, Date: "2026-01-01"}
json.NewEncoder(w).Encode(resp)
_ = json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()

Expand All @@ -96,6 +96,42 @@ func TestGetScoreStripsScheme(t *testing.T) {
}
}

func TestDefaultUserAgent(t *testing.T) {
var gotUA string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotUA = r.Header.Get("User-Agent")
_ = json.NewEncoder(w).Encode(scorecardResponse{Score: 5.0})
}))
defer srv.Close()

client := New()
client.baseURL = srv.URL
client.httpClient = srv.Client()
_, _ = client.GetScore(context.Background(), "github.com/test/repo")

if gotUA != "enrichment" {
t.Errorf("default User-Agent = %q, want %q", gotUA, "enrichment")
}
}

func TestCustomUserAgent(t *testing.T) {
var gotUA string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotUA = r.Header.Get("User-Agent")
_ = json.NewEncoder(w).Encode(scorecardResponse{Score: 5.0})
}))
defer srv.Close()

client := New("git-pkgs/test")
client.baseURL = srv.URL
client.httpClient = srv.Client()
_, _ = client.GetScore(context.Background(), "github.com/test/repo")

if gotUA != "git-pkgs/test" {
t.Errorf("User-Agent = %q, want %q", gotUA, "git-pkgs/test")
}
}

func TestGetScoreNotFound(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
Expand Down