@@ -38,6 +38,8 @@ import (
3838 "github.com/GoogleCloudPlatform/alloydb-auth-proxy/internal/log"
3939 "github.com/GoogleCloudPlatform/alloydb-auth-proxy/internal/proxy"
4040 "github.com/spf13/cobra"
41+ "github.com/spf13/pflag"
42+ "github.com/spf13/viper"
4143 "go.opencensus.io/trace"
4244)
4345
@@ -243,8 +245,66 @@ Service Account Impersonation
243245 In this example, the environment's IAM principal impersonates
244246 SERVICE_ACCOUNT_3 which impersonates SERVICE_ACCOUNT_2 which then
245247 impersonates the target SERVICE_ACCOUNT_1.
248+
249+ Configuration using environment variables
250+
251+ Instead of using CLI flags, the proxy may be configured using environment
252+ variables. Each environment variable uses "ALLOYDB_PROXY" as a prefix and
253+ is the uppercase version of the flag using underscores as word delimiters.
254+ For example, the --structured-logs flag may be set with the environment
255+ variable ALLOYDB_PROXY_STRUCTURED_LOGS. An invocation of the proxy using
256+ environment variables would look like the following:
257+
258+ ALLOYDB_PROXY_STRUCTURED_LOGS=true \
259+ ./alloydb-auth-proxy \
260+ projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE
261+
262+ In addition to CLI flags, instance URIs may also be specified with
263+ environment variables. If invoking the proxy with only one instance URI,
264+ use ALLOYDB_PROXY_INSTANCE_URI. For example:
265+
266+ ALLOYDB_PROXY_INSTANCE_URI=projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE \
267+ ./alloydb-auth-proxy
268+
269+ If multiple instance URIs are used, add the index of the instance URI as a
270+ suffix. For example:
271+
272+ ALLOYDB_PROXY_INSTANCE_URI_0=projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE1 \
273+ ALLOYDB_PROXY_INSTANCE_URI_1=projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE2 \
274+ ./alloydb-auth-proxy
275+
246276`
247277
278+ const envPrefix = "ALLOYDB_PROXY"
279+
280+ func instanceFromEnv (args []string ) []string {
281+ // This supports naming the first instance first with:
282+ // INSTANCE_URI
283+ // or if that's not defined, with:
284+ // INSTANCE_URI_0
285+ inst := os .Getenv (fmt .Sprintf ("%s_INSTANCE_URI" , envPrefix ))
286+ if inst == "" {
287+ inst = os .Getenv (fmt .Sprintf ("%s_INSTANCE_URI_0" , envPrefix ))
288+ if inst == "" {
289+ return nil
290+ }
291+ }
292+ args = append (args , inst )
293+
294+ i := 1
295+ for {
296+ instN := os .Getenv (fmt .Sprintf ("%s_INSTANCE_URI_%d" , envPrefix , i ))
297+ // if the next instance connection name is not defined, stop checking
298+ // environment variables.
299+ if instN == "" {
300+ break
301+ }
302+ args = append (args , instN )
303+ i ++
304+ }
305+ return args
306+ }
307+
248308// NewCommand returns a Command object representing an invocation of the proxy.
249309func NewCommand (opts ... Option ) * Command {
250310 cmd := & cobra.Command {
@@ -268,6 +328,10 @@ func NewCommand(opts ...Option) *Command {
268328 }
269329
270330 cmd .Args = func (cmd * cobra.Command , args []string ) error {
331+ // If args is not already populated, try to read from the environment.
332+ if len (args ) == 0 {
333+ args = instanceFromEnv (args )
334+ }
271335 // Handle logger separately from config
272336 if c .conf .StructuredLogs {
273337 c .logger , c .cleanup = log .NewStructuredLogger ()
@@ -288,69 +352,87 @@ func NewCommand(opts ...Option) *Command {
288352
289353 cmd .RunE = func (* cobra.Command , []string ) error { return runSignalWrapper (c ) }
290354
355+ pflags := cmd .PersistentFlags ()
356+
291357 // Global-only flags
292- cmd . PersistentFlags () .StringVarP (& c .conf .Token , "token" , "t" , "" ,
358+ pflags .StringVarP (& c .conf .Token , "token" , "t" , "" ,
293359 "Bearer token used for authorization." )
294- cmd . PersistentFlags () .StringVarP (& c .conf .CredentialsFile , "credentials-file" , "c" , "" ,
360+ pflags .StringVarP (& c .conf .CredentialsFile , "credentials-file" , "c" , "" ,
295361 "Path to a service account key to use for authentication." )
296- cmd . PersistentFlags () .StringVarP (& c .conf .CredentialsJSON , "json-credentials" , "j" , "" ,
362+ pflags .StringVarP (& c .conf .CredentialsJSON , "json-credentials" , "j" , "" ,
297363 "Use service account key JSON as a source of IAM credentials." )
298- cmd . PersistentFlags () .BoolVarP (& c .conf .GcloudAuth , "gcloud-auth" , "g" , false ,
364+ pflags .BoolVarP (& c .conf .GcloudAuth , "gcloud-auth" , "g" , false ,
299365 "Use gcloud's user configuration to retrieve a token for authentication." )
300- cmd . PersistentFlags () .BoolVarP (& c .conf .StructuredLogs , "structured-logs" , "l" , false ,
366+ pflags .BoolVarP (& c .conf .StructuredLogs , "structured-logs" , "l" , false ,
301367 "Enable structured logs using the LogEntry format" )
302- cmd . PersistentFlags () .Uint64Var (& c .conf .MaxConnections , "max-connections" , 0 ,
368+ pflags .Uint64Var (& c .conf .MaxConnections , "max-connections" , 0 ,
303369 `Limits the number of connections by refusing any additional connections.
304370When this flag is not set, there is no limit.` )
305- cmd . PersistentFlags () .DurationVar (& c .conf .WaitOnClose , "max-sigterm-delay" , 0 ,
371+ pflags .DurationVar (& c .conf .WaitOnClose , "max-sigterm-delay" , 0 ,
306372 `Maximum amount of time to wait after for any open connections
307373to close after receiving a TERM signal. The proxy will shut
308374down when the number of open connections reaches 0 or when
309375the maximum time has passed. Defaults to 0s.` )
310- cmd . PersistentFlags () .StringVar (& c .conf .APIEndpointURL , "alloydbadmin-api-endpoint" ,
376+ pflags .StringVar (& c .conf .APIEndpointURL , "alloydbadmin-api-endpoint" ,
311377 "https://alloydb.googleapis.com/v1beta" ,
312378 "When set, the proxy uses this host as the base API path." )
313- cmd . PersistentFlags () .StringVar (& c .conf .FUSEDir , "fuse" , "" ,
379+ pflags .StringVar (& c .conf .FUSEDir , "fuse" , "" ,
314380 "Mount a directory at the path using FUSE to access AlloyDB instances." )
315- cmd . PersistentFlags () .StringVar (& c .conf .FUSETempDir , "fuse-tmp-dir" ,
381+ pflags .StringVar (& c .conf .FUSETempDir , "fuse-tmp-dir" ,
316382 filepath .Join (os .TempDir (), "csql-tmp" ),
317383 "Temp dir for Unix sockets created with FUSE" )
318- cmd . PersistentFlags () .StringVar (& c .impersonationChain , "impersonate-service-account" , "" ,
384+ pflags .StringVar (& c .impersonationChain , "impersonate-service-account" , "" ,
319385 `Comma separated list of service accounts to impersonate. Last value
320386+is the target account.` )
321387 cmd .PersistentFlags ().BoolVar (& c .quiet , "quiet" , false , "Log error messages only" )
322388
323- cmd . PersistentFlags () .StringVar (& c .telemetryProject , "telemetry-project" , "" ,
389+ pflags .StringVar (& c .telemetryProject , "telemetry-project" , "" ,
324390 "Enable Cloud Monitoring and Cloud Trace integration with the provided project ID." )
325- cmd . PersistentFlags () .BoolVar (& c .disableTraces , "disable-traces" , false ,
391+ pflags .BoolVar (& c .disableTraces , "disable-traces" , false ,
326392 "Disable Cloud Trace integration (used with telemetry-project)" )
327- cmd . PersistentFlags () .IntVar (& c .telemetryTracingSampleRate , "telemetry-sample-rate" , 10_000 ,
393+ pflags .IntVar (& c .telemetryTracingSampleRate , "telemetry-sample-rate" , 10_000 ,
328394 "Configure the denominator of the probabilistic sample rate of traces sent to Cloud Trace\n (e.g., 10,000 traces 1/10,000 calls)." )
329- cmd . PersistentFlags () .BoolVar (& c .disableMetrics , "disable-metrics" , false ,
395+ pflags .BoolVar (& c .disableMetrics , "disable-metrics" , false ,
330396 "Disable Cloud Monitoring integration (used with telemetry-project)" )
331- cmd . PersistentFlags () .StringVar (& c .telemetryPrefix , "telemetry-prefix" , "" ,
397+ pflags .StringVar (& c .telemetryPrefix , "telemetry-prefix" , "" ,
332398 "Prefix to use for Cloud Monitoring metrics." )
333- cmd . PersistentFlags () .BoolVar (& c .prometheus , "prometheus" , false ,
399+ pflags .BoolVar (& c .prometheus , "prometheus" , false ,
334400 "Enable Prometheus HTTP endpoint /metrics" )
335- cmd . PersistentFlags () .StringVar (& c .prometheusNamespace , "prometheus-namespace" , "" ,
401+ pflags .StringVar (& c .prometheusNamespace , "prometheus-namespace" , "" ,
336402 "Use the provided Prometheus namespace for metrics" )
337- cmd . PersistentFlags () .StringVar (& c .httpAddress , "http-address" , "localhost" ,
403+ pflags .StringVar (& c .httpAddress , "http-address" , "localhost" ,
338404 "Address for Prometheus and health check server" )
339- cmd . PersistentFlags () .StringVar (& c .httpPort , "http-port" , "9090" ,
405+ pflags .StringVar (& c .httpPort , "http-port" , "9090" ,
340406 "Port for the Prometheus server to use" )
341- cmd . PersistentFlags () .BoolVar (& c .healthCheck , "health-check" , false ,
407+ pflags .BoolVar (& c .healthCheck , "health-check" , false ,
342408 `Enables HTTP endpoints /startup, /liveness, and /readiness
343409that report on the proxy's health. Endpoints are available on localhost
344410only. Uses the port specified by the http-port flag.` )
345411
346412 // Global and per instance flags
347- cmd . PersistentFlags () .StringVarP (& c .conf .Addr , "address" , "a" , "127.0.0.1" ,
413+ pflags .StringVarP (& c .conf .Addr , "address" , "a" , "127.0.0.1" ,
348414 "Address on which to bind AlloyDB instance listeners." )
349- cmd . PersistentFlags () .IntVarP (& c .conf .Port , "port" , "p" , 5432 ,
415+ pflags .IntVarP (& c .conf .Port , "port" , "p" , 5432 ,
350416 "Initial port to use for listeners. Subsequent listeners increment from this value." )
351- cmd . PersistentFlags () .StringVarP (& c .conf .UnixSocket , "unix-socket" , "u" , "" ,
417+ pflags .StringVarP (& c .conf .UnixSocket , "unix-socket" , "u" , "" ,
352418 `Enables Unix sockets for all listeners using the provided directory.` )
353419
420+ v := viper .NewWithOptions (viper .EnvKeyReplacer (strings .NewReplacer ("-" , "_" )))
421+ v .SetEnvPrefix (envPrefix )
422+ v .AutomaticEnv ()
423+ // Ignoring the error here since its only occurence is if one of the pflags
424+ // is nil which is never the case here.
425+ _ = v .BindPFlags (pflags )
426+
427+ pflags .VisitAll (func (f * pflag.Flag ) {
428+ // Override any unset flags with Viper values to use the pflags
429+ // object as a single source of truth.
430+ if ! f .Changed && v .IsSet (f .Name ) {
431+ val := v .Get (f .Name )
432+ pflags .Set (f .Name , fmt .Sprintf ("%v" , val ))
433+ }
434+ })
435+
354436 return c
355437}
356438
0 commit comments