| // 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 |
| } |