blob: 0e5bbde5ae4509521d78f4d588beb6e526b5d236 [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.
// Package config resolves shared configuration for services, and
// provides functions to access this configuration.
package config
import (
mrpb ""
// Config holds configuration information for the worker server.
type Config struct {
// ProjectID is the Google Cloud ProjectID where the resources live.
ProjectID string
// Identifier for the version currently running.
// We do not use the version ID from Cloud Run (see
// Instead, we use the DOCKER_IMAGE environment variable, set
// in the Cloud Build deploy file.
VersionID string
// LocationID is the location for the GCP project.
LocationID string
// ServiceID names the Cloud Run service.
ServiceID string
// StaticPath is the directory containing static files.
StaticPath template.TrustedSource
// ServiceAccount is the email of the service account that this process
// is running as when on GCP.
ServiceAccount string
// UseErrorReporting determines whether errors go to the Error Reporting API.
UseErrorReporting bool
// BigQuery dataset to write results to.
BigQueryDataset string
// QueueName is the name of the Cloud Tasks queue.
QueueName string
// QueueURL is the URL that the Cloud Tasks queue should send requests to.
// It should be used when the worker is not on AppEngine.
QueueURL string
// LocalQueueWorkers is the number of concurrent requests to the fetch service, when running locally.
LocalQueueWorkers int
// MonitoredResource represents the resource that is running the current binary.
// It might be a Google AppEngine app, a Cloud Run service, or a Kubernetes pod.
// See for more
// details:
// "An object representing a resource that can be used for monitoring, logging,
// billing, or other purposes. Examples include virtual machine instances,
// databases, and storage devices such as disks.""
MonitoredResource *mrpb.MonitoredResource
// DevMode indicates whether the server is running in development mode.
DevMode bool
// VulnDBBucketProjectID is the project ID for the vuln DB bucket and its
// associated load balancer.
VulnDBBucketProjectID string
// BinaryBucket holds binaries for govulncheck scanning.
BinaryBucket string
// BinaryDir is the local directory for binaries.
BinaryDir string
// The host, port and user of the pkgsite database used to find
// modules to scan.
PkgsiteDBHost string
PkgsiteDBPort string
PkgsiteDBName string
PkgsiteDBUser string
// The name of the Secret Manager secret holding the DB password.
PkgsiteDBSecret string
// Run analysis binaries without sandbox.
Insecure bool
// ProxyURL is the url for the Go module proxy.
ProxyURL string
// VulnDBURL is the url for the Go vulnerability database.
VulnDBURL string
// Init resolves all configuration values provided by the config package. It
// must be called before any configuration values are used.
func Init(ctx context.Context) (_ *Config, err error) {
defer derrors.Wrap(&err, "config.Init(ctx)")
// Build a Config from the execution environment, loading some values
// from environment variables.
var ts template.TrustedSource
if f := flag.Lookup("static"); f != nil {
ts = template.TrustedSourceFromFlag(f.Value)
cfg := &Config{
ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
ServiceID: os.Getenv("GO_ECOSYSTEM_SERVICE_ID"),
VersionID: os.Getenv("DOCKER_IMAGE"),
LocationID: "us-central1",
StaticPath: ts,
BigQueryDataset: GetEnv("GO_ECOSYSTEM_BIGQUERY_DATASET", "disable"),
QueueName: os.Getenv("GO_ECOSYSTEM_QUEUE_NAME"),
BinaryBucket: os.Getenv("GO_ECOSYSTEM_BINARY_BUCKET"),
BinaryDir: GetEnv("GO_ECOSYSTEM_BINARY_DIR", "/tmp/binaries"),
PkgsiteDBHost: GetEnv("GO_ECOSYSTEM_PKGSITE_DB_HOST", "localhost"),
PkgsiteDBPort: GetEnv("GO_ECOSYSTEM_PKGSITE_DB_PORT", "5432"),
PkgsiteDBName: GetEnv("GO_ECOSYSTEM_PKGSITE_DB_NAME", "discovery-db"),
PkgsiteDBUser: GetEnv("GO_ECOSYSTEM_PKGSITE_DB_USER", "postgres"),
ProxyURL: GetEnv("GO_MODULE_PROXY_URL", ""),
VulnDBURL: GetEnv("GO_VULNDB_URL", ""),
if OnCloudRun() {
sa, err := gceMetadata(ctx, "instance/service-accounts/default/email")
if err != nil {
return nil, err
cfg.ServiceAccount = sa
cfg.MonitoredResource = &mrpb.MonitoredResource{
Type: "cloud_run_revision",
Labels: map[string]string{
"project_id": cfg.ProjectID,
"service_name": cfg.ServiceID,
"revision_name": cfg.VersionID,
"configuration_name": os.Getenv("K_CONFIGURATION"),
cfg.UseErrorReporting = true
} else { // running locally, perhaps
cfg.MonitoredResource = &mrpb.MonitoredResource{
Type: "global",
Labels: map[string]string{"project_id": cfg.ProjectID},
return cfg, nil
// OnCloudRun reports whether the current process is running on Cloud Run.
func OnCloudRun() bool {
// Use the presence of the environment variables provided by Cloud Run.
// See
for _, ev := range []string{"K_SERVICE", "K_REVISION", "K_CONFIGURATION"} {
if os.Getenv(ev) == "" {
return false
return true
func (c *Config) Validate() error {
if c.ProjectID == "" {
return errors.New("missing project")
if c.BigQueryDataset == "" {
return errors.New("missing dataset")
return nil
// Dump outputs the current config information to the given Writer.
func (c *Config) Dump(w io.Writer) error {
fmt.Fprint(w, "config: ")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(c)
// GetEnv looks up the given key from the environment, returning its value if
// it exists, and otherwise returning the given fallback value.
func GetEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
return fallback
// GetEnvInt performs GetEnv(key, fallback) and parses the
// result as int. If parsing fails, returns errVal.
func GetEnvInt(key, fallback string, errVal int) int {
v := GetEnv(key, fallback)
i, err := strconv.Atoi(v)
if err != nil {
return errVal
return i
// gceMetadata reads a metadata value from GCE.
// For the possible values of name, see
func gceMetadata(ctx context.Context, name string) (_ string, err error) {
// See
// (This documentation doesn't exist for Golang, but it seems to work).
defer derrors.Wrap(&err, "gceMetadata(ctx, %q)", name)
const metadataURL = ""
req, err := http.NewRequest("GET", metadataURL+name, nil)
if err != nil {
return "", fmt.Errorf("http.NewRequest: %v", err)
req.Header.Set("Metadata-Flavor", "Google")
resp, err := ctxhttp.Do(ctx, nil, req)
if err != nil {
return "", fmt.Errorf("ctxhttp.Do: %v", err)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("bad status: %s", resp.Status)
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("io.ReadAll: %v", err)
return string(bytes), nil