🌱 Add ProjectPackageClient interface and deps.dev default client (#3954)

* Add ProjectPackageClient interface and deps.dev default client

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* v4 -> v5

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* Move to internal

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* move internal to higher-level w/ shared root

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* update

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

---------

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>
This commit is contained in:
Raghav Kaul 2024-05-08 16:56:36 -04:00 committed by GitHub
parent c92efe9bb2
commit f8422929cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 144 additions and 0 deletions

55
e2e/depsdev_test.go Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2024 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2e
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/ossf/scorecard/v5/internal/packageclient"
)
var _ = Describe("E2E TEST: depsdevclient.GetProjectPackageVersions", func() {
var client packageclient.ProjectPackageClient
Context("E2E TEST: Confirm ProjectPackageClient works", func() {
It("Should receive a non-empty response from deps.dev for existing projects", func() {
client = packageclient.CreateDepsDevClient()
versions, err := client.GetProjectPackageVersions(
context.Background(), "github.com", "ossf/scorecard",
)
Expect(err).Should(BeNil())
Expect(len(versions.Versions)).Should(BeNumerically(">", 0))
})
It("Should error from deps.dev for nonexistent projects", func() {
client = packageclient.CreateDepsDevClient()
versions, err := client.GetProjectPackageVersions(
context.Background(), "github.com", "ossf/scorecard-E2E-TEST-DOES-NOT-EXIST",
)
Expect(err).ShouldNot(BeNil())
Expect(versions).Should(BeNil())
})
It("Should receive a non-empty response from deps.dev for existing projects", func() {
client = packageclient.CreateDepsDevClient()
versions, err := client.GetProjectPackageVersions(
context.Background(), "gitlab.com", "libtiff/libtiff",
)
Expect(err).Should(BeNil())
Expect(len(versions.Versions)).Should(BeNumerically(">", 0))
})
})
})

View File

@ -0,0 +1,89 @@
// Copyright 2024 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package packageclient
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
// This interface lets Scorecard look up package manager metadata for a project.
type ProjectPackageClient interface {
GetProjectPackageVersions(ctx context.Context, host, project string) (*ProjectPackageVersions, error)
}
type depsDevClient struct {
client *http.Client
}
type ProjectPackageVersions struct {
// field alignment
//nolint:govet
Versions []struct {
VersionKey struct {
System string `json:"system"`
Name string `json:"name"`
Version string `json:"version"`
} `json:"versionKey"`
SLSAProvenances []struct {
SourceRepository string `json:"sourceRepository"`
Commit string `json:"commit"`
Verified bool `json:"verified"`
} `json:"slsaProvenances"`
RelationType string `json:"relationType"`
RelationProvenance string `json:"relationProvenance"`
} `json:"versions"`
}
func CreateDepsDevClient() ProjectPackageClient {
return depsDevClient{
client: &http.Client{},
}
}
func (d depsDevClient) GetProjectPackageVersions(
ctx context.Context, host, project string,
) (*ProjectPackageVersions, error) {
path := fmt.Sprintf("%s/%s", host, project)
query := fmt.Sprintf("https://api.deps.dev/v3/projects/%s:packageversions", url.QueryEscape(path))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, query, nil)
if err != nil {
return nil, fmt.Errorf("http.NewRequestWithContext: %w", err)
}
resp, err := d.client.Do(req)
if err != nil {
return nil, fmt.Errorf("deps.dev GetProjectPackageVersions: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("resp.Body.Read: %w", err)
}
var res ProjectPackageVersions
err = json.Unmarshal(body, &res)
if err != nil {
return nil, fmt.Errorf("json.Unmarshal from deps.dev: %w", err)
}
return &res, nil
}