diff --git a/e2e/depsdev_test.go b/e2e/depsdev_test.go new file mode 100644 index 00000000000..00323bf0781 --- /dev/null +++ b/e2e/depsdev_test.go @@ -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)) + }) + }) +}) diff --git a/internal/packageclient/depsdev.go b/internal/packageclient/depsdev.go new file mode 100644 index 00000000000..b816296c2fc --- /dev/null +++ b/internal/packageclient/depsdev.go @@ -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 +}