blob: 824da2ad2c5a64e36fb0fb2515b628b352ffa7bf [file] [log] [blame]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !plan9
// Package pkgsitedb provides functionality for connecting to the pkgsite
// database.
package pkgsitedb
import (
"context"
"database/sql"
"fmt"
"regexp"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
smpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
_ "github.com/lib/pq"
)
type Config struct {
User string
PasswordSecret string
Password string
Host string
Port string
DBName string
}
// Open creates a connection to the pkgsite database.
func Open(ctx context.Context, cfg Config) (_ *sql.DB, err error) {
if cfg.Password == "" {
var err error
cfg.Password, err = getSecret(ctx, cfg.PasswordSecret)
if err != nil {
return nil, err
}
}
connString := fmt.Sprintf(
"user='%s' password='%s' host='%s' port=%s dbname='%s' sslmode='disable'",
cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName)
db, err := sql.Open("postgres", connString)
if err != nil {
return nil, err
}
if err := db.PingContext(ctx); err != nil {
return nil, err
}
return db, nil
}
type Module struct {
Path string
Packages []*Package
}
type Package struct {
Path string
Version string
NumImporters int
}
func QueryModule(ctx context.Context, db *sql.DB, modulePath string) (*Module, error) {
query := `
SELECT package_path, version, imported_by_count
FROM search_documents
WHERE module_path = $1
ORDER BY 3 DESC
`
rows, err := db.QueryContext(ctx, query, modulePath)
if err != nil {
return nil, err
}
defer rows.Close()
m := &Module{Path: modulePath}
for rows.Next() {
var p Package
if err := rows.Scan(&p.Path, &p.Version, &p.NumImporters); err != nil {
return nil, err
}
m.Packages = append(m.Packages, &p)
}
if err := rows.Err(); err != nil {
return nil, err
}
return m, nil
}
var passwordRegexp = regexp.MustCompile(`password=\S+`)
func redactPassword(dbinfo string) string {
return passwordRegexp.ReplaceAllLiteralString(dbinfo, "password=REDACTED")
}
// getSecret retrieves a secret from the GCP Secret Manager.
// secretFullName should be of the form "projects/PROJECT/secrets/NAME".
func getSecret(ctx context.Context, secretFullName string) (_ string, err error) {
client, err := secretmanager.NewClient(ctx)
if err != nil {
return "", err
}
defer client.Close()
result, err := client.AccessSecretVersion(ctx, &smpb.AccessSecretVersionRequest{
Name: secretFullName + "/versions/latest",
})
if err != nil {
return "", err
}
return string(result.Payload.Data), nil
}