client: remove unused multi-database functionality

We currently do not support running govulncheck with multiple databases.
Remove that functionality for initializing the client.

Change-Id: I005fe7733a9692e35373c9162b519e9c73b341ec
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/474222
Run-TryBot: Julie Qiu <julieqiu@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/client/client.go b/internal/client/client.go
index 528ee2a..58c915d 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -31,17 +31,12 @@
 import (
 	"context"
 	"fmt"
-	"net/http"
 	"net/url"
-	"os"
-	"sort"
 	"strings"
 	"time"
 
 	"golang.org/x/mod/module"
 	"golang.org/x/vuln/internal"
-	"golang.org/x/vuln/internal/derrors"
-	"golang.org/x/vuln/internal/web"
 	"golang.org/x/vuln/osv"
 )
 
@@ -126,132 +121,18 @@
 	return t
 }
 
-func NewClient(sources []string, opts Options) (_ Client, err error) {
-	defer derrors.Wrap(&err, "NewClient(%v, opts)", sources)
-	c := &client{}
-	for _, source := range sources {
-		source = strings.TrimRight(source, "/") // TODO: why?
-		uri, err := url.Parse(source)
-		if err != nil {
-			return nil, err
-		}
-		switch uri.Scheme {
-		case "http", "https":
-			hs := &httpSource{url: uri.String()}
-			hs.dbName = uri.Hostname()
-			if opts.HTTPCache != nil {
-				hs.cache = opts.HTTPCache
-			}
-			if opts.HTTPClient != nil {
-				hs.c = opts.HTTPClient
-			} else {
-				hs.c = new(http.Client)
-			}
-			c.sources = append(c.sources, hs)
-		case "file":
-			dir, err := web.URLToFilePath(uri)
-			if err != nil {
-				return nil, err
-			}
-			fi, err := os.Stat(dir)
-			if err != nil {
-				return nil, err
-			}
-			if !fi.IsDir() {
-				return nil, fmt.Errorf("%s is not a directory", dir)
-			}
-			c.sources = append(c.sources, &localSource{dir: dir})
-		default:
-			return nil, fmt.Errorf("source %q has unsupported scheme", uri)
-		}
+func NewClient(source string, opts Options) (_ Client, err error) {
+	source = strings.TrimRight(source, "/")
+	uri, err := url.Parse(source)
+	if err != nil {
+		return nil, err
 	}
-	return c, nil
-}
-
-func (*client) unexported() {}
-
-func (c *client) GetByModule(ctx context.Context, module string) (_ []*osv.Entry, err error) {
-	defer derrors.Wrap(&err, "GetByModule(%q)", module)
-	return c.unionEntries(ctx, func(c Client) ([]*osv.Entry, error) {
-		return c.GetByModule(ctx, module)
-	})
-}
-
-func (c *client) GetByAlias(ctx context.Context, alias string) (entries []*osv.Entry, err error) {
-	defer derrors.Wrap(&err, "GetByAlias(%q)", alias)
-	return c.unionEntries(ctx, func(c Client) ([]*osv.Entry, error) {
-		return c.GetByAlias(ctx, alias)
-	})
-}
-
-// unionEntries returns the union of all entries obtained by calling get on the client's sources.
-func (c *client) unionEntries(_ context.Context, get func(Client) ([]*osv.Entry, error)) ([]*osv.Entry, error) {
-	var entries []*osv.Entry
-	// probably should be parallelized
-	seen := map[string]bool{}
-	for _, s := range c.sources {
-		es, err := get(s)
-		if err != nil {
-			return nil, err // be failure tolerant?
-		}
-		for _, e := range es {
-			if !seen[e.ID] {
-				entries = append(entries, e)
-				seen[e.ID] = true
-			}
-		}
+	switch uri.Scheme {
+	case "http", "https":
+		return newHTTPClient(uri, opts)
+	case "file":
+		return newFileClient(uri)
+	default:
+		return nil, fmt.Errorf("source %q has unsupported scheme", uri)
 	}
-	return entries, nil
-}
-
-func (c *client) GetByID(ctx context.Context, id string) (_ *osv.Entry, err error) {
-	defer derrors.Wrap(&err, "GetByID(%q)", id)
-	for _, s := range c.sources {
-		entry, err := s.GetByID(ctx, id)
-		if err != nil {
-			return nil, err // be failure tolerant?
-		}
-		if entry != nil {
-			return entry, nil
-		}
-	}
-	return nil, nil
-}
-
-// ListIDs returns the union of the IDs from all sources,
-// sorted lexically.
-func (c *client) ListIDs(ctx context.Context) (_ []string, err error) {
-	defer derrors.Wrap(&err, "ListIDs()")
-	idSet := map[string]bool{}
-	for _, s := range c.sources {
-		ids, err := s.ListIDs(ctx)
-		if err != nil {
-			return nil, err
-		}
-		for _, id := range ids {
-			idSet[id] = true
-		}
-	}
-	var ids []string
-	for id := range idSet {
-		ids = append(ids, id)
-	}
-	sort.Strings(ids)
-	return ids, nil
-}
-
-// LastModifiedTime returns the latest modified time of all the sources.
-func (c *client) LastModifiedTime(ctx context.Context) (_ time.Time, err error) {
-	defer derrors.Wrap(&err, "LastModifiedTime()")
-	var lmt time.Time
-	for _, s := range c.sources {
-		t, err := s.LastModifiedTime(ctx)
-		if err != nil {
-			return time.Time{}, err
-		}
-		if t.After(lmt) {
-			lmt = t
-		}
-	}
-	return lmt, nil
 }
diff --git a/internal/client/client_test.go b/internal/client/client_test.go
index b11f8e2..4a6de92 100644
--- a/internal/client/client_test.go
+++ b/internal/client/client_test.go
@@ -141,7 +141,7 @@
 			cache: nil, detailPrefix: detailStartLowercase, wantVulns: 4},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			client, err := NewClient([]string{test.source}, Options{HTTPCache: test.cache})
+			client, err := NewClient(test.source, Options{HTTPCache: test.cache})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -192,11 +192,11 @@
 	// List of modules to query, some are repeated to exercise cache hits.
 	modulePaths := []string{"github.com/BeeGo/beego", "github.com/tidwall/gjson", "net/http", "abc.xyz", "github.com/BeeGo/beego"}
 	for _, cache := range []Cache{newTestCache(), nil} {
-		clt, err := NewClient([]string{srv.URL}, Options{HTTPCache: cache})
+		clt, err := NewClient(srv.URL, Options{HTTPCache: cache})
 		if err != nil {
 			t.Fatal(err)
 		}
-		hs := clt.(*client).sources[0].(*httpSource)
+		hs := clt.(*httpSource)
 		for _, modulePath := range modulePaths {
 			indexCalls := hs.indexCalls
 			httpCalls := hs.httpCalls
@@ -238,7 +238,7 @@
 		{"http", srv.URL},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			client, err := NewClient([]string{test.source}, Options{})
+			client, err := NewClient(test.source, Options{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -307,7 +307,7 @@
 	}
 	cache.WriteEntries(url.Hostname(), "a", []*osv.Entry{e})
 
-	client, err := NewClient([]string{ts.URL}, Options{HTTPCache: cache})
+	client, err := NewClient(ts.URL, Options{HTTPCache: cache})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -349,7 +349,7 @@
 		{name: "http", in: "NO-SUCH-VULN", source: localURL, want: nil},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			client, err := NewClient([]string{test.source}, Options{})
+			client, err := NewClient(test.source, Options{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -393,7 +393,7 @@
 		{name: "file", source: localURL},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			client, err := NewClient([]string{test.source}, Options{})
+			client, err := NewClient(test.source, Options{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -425,7 +425,7 @@
 		{name: "file", source: localURL},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			client, err := NewClient([]string{test.source}, Options{})
+			client, err := NewClient(test.source, Options{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -461,7 +461,7 @@
 		{name: "file", source: localURL},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			client, err := NewClient([]string{test.source}, Options{})
+			client, err := NewClient(test.source, Options{})
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/internal/client/file.go b/internal/client/file.go
index e82136a..32a97fc 100644
--- a/internal/client/file.go
+++ b/internal/client/file.go
@@ -7,12 +7,15 @@
 import (
 	"context"
 	"encoding/json"
+	"fmt"
+	"net/url"
 	"os"
 	"path/filepath"
 	"time"
 
 	"golang.org/x/vuln/internal"
 	"golang.org/x/vuln/internal/derrors"
+	"golang.org/x/vuln/internal/web"
 	"golang.org/x/vuln/osv"
 )
 
@@ -20,6 +23,21 @@
 	dir string
 }
 
+func newFileClient(uri *url.URL) (_ *localSource, err error) {
+	dir, err := web.URLToFilePath(uri)
+	if err != nil {
+		return nil, err
+	}
+	fi, err := os.Stat(dir)
+	if err != nil {
+		return nil, err
+	}
+	if !fi.IsDir() {
+		return nil, fmt.Errorf("%s is not a directory", dir)
+	}
+	return &localSource{dir: dir}, nil
+}
+
 func (*localSource) unexported() {}
 
 func (ls *localSource) GetByModule(ctx context.Context, modulePath string) (_ []*osv.Entry, err error) {
diff --git a/internal/client/http.go b/internal/client/http.go
index 66e17e0..80130ff 100644
--- a/internal/client/http.go
+++ b/internal/client/http.go
@@ -10,6 +10,7 @@
 	"fmt"
 	"io"
 	"net/http"
+	"net/url"
 	"path"
 	"time"
 
@@ -33,6 +34,20 @@
 	httpCalls  int
 }
 
+func newHTTPClient(uri *url.URL, opts Options) (_ *httpSource, err error) {
+	hs := &httpSource{url: uri.String()}
+	hs.dbName = uri.Hostname()
+	if opts.HTTPCache != nil {
+		hs.cache = opts.HTTPCache
+	}
+	if opts.HTTPClient != nil {
+		hs.c = opts.HTTPClient
+	} else {
+		hs.c = new(http.Client)
+	}
+	return hs, nil
+}
+
 func (hs *httpSource) Index(ctx context.Context) (_ DBIndex, err error) {
 	hs.indexCalls++ // for testing privacy properties
 	defer derrors.Wrap(&err, "Index()")
@@ -228,10 +243,6 @@
 	return io.ReadAll(resp.Body)
 }
 
-type client struct {
-	sources []Client
-}
-
 type Options struct {
 	HTTPClient *http.Client
 	HTTPCache  Cache
diff --git a/internal/govulncheck/scan.go b/internal/govulncheck/scan.go
index da626ee..f0354a5 100644
--- a/internal/govulncheck/scan.go
+++ b/internal/govulncheck/scan.go
@@ -110,13 +110,11 @@
 	ctx := context.Background()
 	dir := filepath.FromSlash(c.dir)
 
-	dbs := []string{c.db}
 	cache, err := DefaultCache()
 	if err != nil {
 		return err
 	}
-
-	dbClient, err := client.NewClient(dbs, client.Options{
+	dbClient, err := client.NewClient(c.db, client.Options{
 		HTTPCache: cache,
 	})
 	if err != nil {