Skip to content

Commit

Permalink
feat: configure the proxy with environment variables (#1514)
Browse files Browse the repository at this point in the history
Fixes #225.
  • Loading branch information
enocom authored Nov 7, 2022
1 parent 34537e6 commit 2a9d9a2
Show file tree
Hide file tree
Showing 5 changed files with 574 additions and 115 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The Cloud SQL Auth proxy has support for:
- [HTTP Healthchecks][health-check-example]
- Service account impersonation
- Separate Dialer functionality released as the [Cloud SQL Go Connector][go connector]
- Configuration with environment variables
- Fully POSIX-compliant flags

If you're using Go, Java, or Python, consider using the corresponding Cloud SQL
Expand Down
138 changes: 109 additions & 29 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import (
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.opencensus.io/trace"
)

Expand Down Expand Up @@ -207,10 +209,66 @@ Service Account Impersonation
SERVICE_ACCOUNT_3 which impersonates SERVICE_ACCOUNT_2 which then
impersonates the target SERVICE_ACCOUNT_1.
Configuration using environment variables
Instead of using CLI flags, the proxy may be configured using environment
variables. Each environment variable uses "CSQL_PROXY" as a prefix and is
the uppercase version of the flag using underscores as word delimiters. For
example, the --auto-iam-authn flag may be set with the environment variable
CSQL_PROXY_AUTO_IAM_AUTHN. An invocation of the proxy using environment
variables would look like the following:
CSQL_PROXY_AUTO_IAM_AUTHN=true \
./cloud-sql-proxy my-project:us-central1:my-db-server
In addition to CLI flags, instance connection names may also be specified
with environment variables. If invoking the proxy with only one instance
connection name, use CSQL_PROXY_INSTANCE_CONNECTION_NAME. For example:
CSQL_PROXY_INSTANCE_CONNECTION_NAME=my-project:us-central1:my-db-server \
./cloud-sql-proxy
If multiple instance connection names are used, add the index of the
instance connection name as a suffix. For example:
CSQL_PROXY_INSTANCE_CONNECTION_NAME_0=my-project:us-central1:my-db-server \
CSQL_PROXY_INSTANCE_CONNECTION_NAME_1=my-other-project:us-central1:my-other-server \
./cloud-sql-proxy
(*) indicates a flag that may be used as a query parameter
`

const envPrefix = "CSQL_PROXY"

func instanceFromEnv(args []string) []string {
// This supports naming the first instance first with:
// INSTANCE_CONNECTION_NAME
// or if that's not defined, with:
// INSTANCE_CONNECTION_NAME_0
inst := os.Getenv(fmt.Sprintf("%s_INSTANCE_CONNECTION_NAME", envPrefix))
if inst == "" {
inst = os.Getenv(fmt.Sprintf("%s_INSTANCE_CONNECTION_NAME_0", envPrefix))
if inst == "" {
return nil
}
}
args = append(args, inst)

i := 1
for {
instN := os.Getenv(fmt.Sprintf("%s_INSTANCE_CONNECTION_NAME_%d", envPrefix, i))
// if the next instance connection name is not defined, stop checking
// environment variables.
if instN == "" {
break
}
args = append(args, instN)
i++
}
return args
}

// NewCommand returns a Command object representing an invocation of the proxy.
func NewCommand(opts ...Option) *Command {
cmd := &cobra.Command{
Expand All @@ -234,6 +292,10 @@ func NewCommand(opts ...Option) *Command {
}

cmd.Args = func(cmd *cobra.Command, args []string) error {
// If args is not already populated, try to read from the environment.
if len(args) == 0 {
args = instanceFromEnv(args)
}
// Handle logger separately from config
if c.conf.StructuredLogs {
c.logger, c.cleanup = log.NewStructuredLogger()
Expand All @@ -254,74 +316,92 @@ func NewCommand(opts ...Option) *Command {

cmd.RunE = func(*cobra.Command, []string) error { return runSignalWrapper(c) }

pflags := cmd.PersistentFlags()

// Override Cobra's default messages.
cmd.PersistentFlags().BoolP("help", "h", false, "Display help information for cloud-sql-proxy")
cmd.PersistentFlags().BoolP("version", "v", false, "Print the cloud-sql-proxy version")
pflags.BoolP("help", "h", false, "Display help information for cloud-sql-proxy")
pflags.BoolP("version", "v", false, "Print the cloud-sql-proxy version")

// Global-only flags
cmd.PersistentFlags().StringVarP(&c.conf.Token, "token", "t", "",
pflags.StringVarP(&c.conf.Token, "token", "t", "",
"Use bearer token as a source of IAM credentials.")
cmd.PersistentFlags().StringVarP(&c.conf.CredentialsFile, "credentials-file", "c", "",
pflags.StringVarP(&c.conf.CredentialsFile, "credentials-file", "c", "",
"Use service account key file as a source of IAM credentials.")
cmd.PersistentFlags().StringVarP(&c.conf.CredentialsJSON, "json-credentials", "j", "",
pflags.StringVarP(&c.conf.CredentialsJSON, "json-credentials", "j", "",
"Use service account key JSON as a source of IAM credentials.")
cmd.PersistentFlags().BoolVarP(&c.conf.GcloudAuth, "gcloud-auth", "g", false,
pflags.BoolVarP(&c.conf.GcloudAuth, "gcloud-auth", "g", false,
"Use gcloud's user credentials as a source of IAM credentials.")
cmd.PersistentFlags().BoolVarP(&c.conf.StructuredLogs, "structured-logs", "l", false,
pflags.BoolVarP(&c.conf.StructuredLogs, "structured-logs", "l", false,
"Enable structured logging with LogEntry format")
cmd.PersistentFlags().Uint64Var(&c.conf.MaxConnections, "max-connections", 0,
pflags.Uint64Var(&c.conf.MaxConnections, "max-connections", 0,
"Limit the number of connections. Default is no limit.")
cmd.PersistentFlags().DurationVar(&c.conf.WaitOnClose, "max-sigterm-delay", 0,
pflags.DurationVar(&c.conf.WaitOnClose, "max-sigterm-delay", 0,
"Maximum number of seconds to wait for connections to close after receiving a TERM signal.")
cmd.PersistentFlags().StringVar(&c.telemetryProject, "telemetry-project", "",
pflags.StringVar(&c.telemetryProject, "telemetry-project", "",
"Enable Cloud Monitoring and Cloud Trace with the provided project ID.")
cmd.PersistentFlags().BoolVar(&c.disableTraces, "disable-traces", false,
pflags.BoolVar(&c.disableTraces, "disable-traces", false,
"Disable Cloud Trace integration (used with --telemetry-project)")
cmd.PersistentFlags().IntVar(&c.telemetryTracingSampleRate, "telemetry-sample-rate", 10_000,
pflags.IntVar(&c.telemetryTracingSampleRate, "telemetry-sample-rate", 10_000,
"Set the Cloud Trace sample rate. A smaller number means more traces.")
cmd.PersistentFlags().BoolVar(&c.disableMetrics, "disable-metrics", false,
pflags.BoolVar(&c.disableMetrics, "disable-metrics", false,
"Disable Cloud Monitoring integration (used with --telemetry-project)")
cmd.PersistentFlags().StringVar(&c.telemetryPrefix, "telemetry-prefix", "",
pflags.StringVar(&c.telemetryPrefix, "telemetry-prefix", "",
"Prefix for Cloud Monitoring metrics.")
cmd.PersistentFlags().BoolVar(&c.prometheus, "prometheus", false,
pflags.BoolVar(&c.prometheus, "prometheus", false,
"Enable Prometheus HTTP endpoint /metrics on localhost")
cmd.PersistentFlags().StringVar(&c.prometheusNamespace, "prometheus-namespace", "",
pflags.StringVar(&c.prometheusNamespace, "prometheus-namespace", "",
"Use the provided Prometheus namespace for metrics")
cmd.PersistentFlags().StringVar(&c.httpAddress, "http-address", "localhost",
pflags.StringVar(&c.httpAddress, "http-address", "localhost",
"Address for Prometheus and health check server")
cmd.PersistentFlags().StringVar(&c.httpPort, "http-port", "9090",
pflags.StringVar(&c.httpPort, "http-port", "9090",
"Port for Prometheus and health check server")
cmd.PersistentFlags().BoolVar(&c.healthCheck, "health-check", false,
pflags.BoolVar(&c.healthCheck, "health-check", false,
"Enables health check endpoints /startup, /liveness, and /readiness on localhost.")
cmd.PersistentFlags().StringVar(&c.conf.APIEndpointURL, "sqladmin-api-endpoint", "",
pflags.StringVar(&c.conf.APIEndpointURL, "sqladmin-api-endpoint", "",
"API endpoint for all Cloud SQL Admin API requests. (default: https://sqladmin.googleapis.com)")
cmd.PersistentFlags().StringVar(&c.conf.QuotaProject, "quota-project", "",
pflags.StringVar(&c.conf.QuotaProject, "quota-project", "",
`Specifies the project to use for Cloud SQL Admin API quota tracking.
The IAM principal must have the "serviceusage.services.use" permission
for the given project. See https://cloud.google.com/service-usage/docs/overview and
https://cloud.google.com/storage/docs/requester-pays`)
cmd.PersistentFlags().StringVar(&c.conf.FUSEDir, "fuse", "",
pflags.StringVar(&c.conf.FUSEDir, "fuse", "",
"Mount a directory at the path using FUSE to access Cloud SQL instances.")
cmd.PersistentFlags().StringVar(&c.conf.FUSETempDir, "fuse-tmp-dir",
pflags.StringVar(&c.conf.FUSETempDir, "fuse-tmp-dir",
filepath.Join(os.TempDir(), "csql-tmp"),
"Temp dir for Unix sockets created with FUSE")
cmd.PersistentFlags().StringVar(&c.impersonationChain, "impersonate-service-account", "",
pflags.StringVar(&c.impersonationChain, "impersonate-service-account", "",
`Comma separated list of service accounts to impersonate. Last value
is the target account.`)
cmd.PersistentFlags().BoolVar(&c.quiet, "quiet", false, "Log error messages only")

// Global and per instance flags
cmd.PersistentFlags().StringVarP(&c.conf.Addr, "address", "a", "127.0.0.1",
pflags.StringVarP(&c.conf.Addr, "address", "a", "127.0.0.1",
"(*) Address to bind Cloud SQL instance listeners.")
cmd.PersistentFlags().IntVarP(&c.conf.Port, "port", "p", 0,
pflags.IntVarP(&c.conf.Port, "port", "p", 0,
"(*) Initial port for listeners. Subsequent listeners increment from this value.")
cmd.PersistentFlags().StringVarP(&c.conf.UnixSocket, "unix-socket", "u", "",
pflags.StringVarP(&c.conf.UnixSocket, "unix-socket", "u", "",
`(*) Enables Unix sockets for all listeners with the provided directory.`)
cmd.PersistentFlags().BoolVarP(&c.conf.IAMAuthN, "auto-iam-authn", "i", false,
pflags.BoolVarP(&c.conf.IAMAuthN, "auto-iam-authn", "i", false,
"(*) Enables Automatic IAM Authentication for all instances")
cmd.PersistentFlags().BoolVar(&c.conf.PrivateIP, "private-ip", false,
pflags.BoolVar(&c.conf.PrivateIP, "private-ip", false,
"(*) Connect to the private ip address for all instances")

v := viper.NewWithOptions(viper.EnvKeyReplacer(strings.NewReplacer("-", "_")))
v.SetEnvPrefix(envPrefix)
v.AutomaticEnv()
// Ignoring the error here since its only occurence is if one of the pflags
// is nil which is never the case here.
_ = v.BindPFlags(pflags)

pflags.VisitAll(func(f *pflag.Flag) {
// Override any unset flags with Viper values to use the pflags
// object as a single source of truth.
if !f.Changed && v.IsSet(f.Name) {
val := v.Get(f.Name)
pflags.Set(f.Name, fmt.Sprintf("%v", val))
}
})

return c
}

Expand Down
Loading

0 comments on commit 2a9d9a2

Please sign in to comment.