Skip to content

Commit

Permalink
feat: configure the proxy with environment variables
Browse files Browse the repository at this point in the history
Fixes #225.
  • Loading branch information
enocom committed Oct 26, 2022
1 parent 4d34156 commit 666d684
Show file tree
Hide file tree
Showing 4 changed files with 665 additions and 75 deletions.
139 changes: 110 additions & 29 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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 @@ -70,6 +72,7 @@ type Command struct {
conf *proxy.Config
logger cloudsql.Logger
dialer cloudsql.Dialer
v *viper.Viper

cleanup func() error
disableTraces bool
Expand Down Expand Up @@ -205,10 +208,67 @@ 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 "CLOUD_SQL" 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
CLOUD_SQL_AUTO_IAM_AUTHN. An invocation of the proxy using environment
variables would look like the following:
CLOUD_SQL_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 CLOUD_SQL_INSTANCE_CONNECTION_NAME. For example:
CLOUD_SQL_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:
CLOUD_SQL_INSTANCE_CONNECTION_NAME_0=my-project:us-central1:my-db-server \
CLOUD_SQL_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 = "CLOUD_SQL"

func instanceConnectionNames(args []string) []string {
// If args is already populated from a CLI invocation, ignore any instance
// connection name env vars and return the CLI args.
if len(args) > 0 {
return args
}
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 @@ -232,6 +292,7 @@ func NewCommand(opts ...Option) *Command {
}

cmd.Args = func(cmd *cobra.Command, args []string) error {
args = instanceConnectionNames(args)
// Handle logger separately from config
if c.conf.StructuredLogs {
c.logger, c.cleanup = log.NewStructuredLogger()
Expand All @@ -249,73 +310,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.`)

// 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()
if err := v.BindPFlags(pflags); err != nil {
panic(err)
}

pflags.VisitAll(func(f *pflag.Flag) {
// When the flag has not been set, but there is a Viper value, set the
// flag to the Viper value. This removes the need to manually assign
// Viper values into the proxy.Config for each value.
if !f.Changed && v.IsSet(f.Name) {
val := v.Get(f.Name)
pflags.Set(f.Name, fmt.Sprintf("%v", val))
}
})

return c
}

Expand Down Expand Up @@ -487,6 +567,7 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error {
}

conf.Instances = ics
cmd.conf = conf
return nil
}

Expand Down
Loading

0 comments on commit 666d684

Please sign in to comment.