44 "context"
55 "errors"
66 "fmt"
7- "io"
87 "os"
98 "os/exec"
109 "time"
@@ -61,7 +60,17 @@ Examples:
6160 return err
6261 }
6362
64- connectionString , err := buildConnectionString (service , dbConnectionStringPooled , dbConnectionStringRole , dbConnectionStringWithPassword , cmd .ErrOrStderr ())
63+ passwordMode := password .PasswordExclude
64+ if dbConnectionStringWithPassword {
65+ passwordMode = password .PasswordRequired
66+ }
67+
68+ connectionString , err := password .BuildConnectionString (service , password.ConnectionStringOptions {
69+ Pooled : dbConnectionStringPooled ,
70+ Role : dbConnectionStringRole ,
71+ PasswordMode : passwordMode ,
72+ WarnWriter : cmd .ErrOrStderr (),
73+ })
6574 if err != nil {
6675 return fmt .Errorf ("failed to build connection string: %w" , err )
6776 }
@@ -133,8 +142,12 @@ Examples:
133142 return fmt .Errorf ("psql client not found. Please install PostgreSQL client tools" )
134143 }
135144
136- // Get connection string using existing logic
137- connectionString , err := buildConnectionString (service , dbConnectPooled , dbConnectRole , false , cmd .ErrOrStderr ())
145+ connectionString , err := password .BuildConnectionString (service , password.ConnectionStringOptions {
146+ Pooled : dbConnectPooled ,
147+ Role : dbConnectRole ,
148+ PasswordMode : password .PasswordExclude ,
149+ WarnWriter : cmd .ErrOrStderr (),
150+ })
138151 if err != nil {
139152 return fmt .Errorf ("failed to build connection string: %w" , err )
140153 }
@@ -192,8 +205,13 @@ Examples:
192205 return exitWithCode (ExitInvalidParameters , err )
193206 }
194207
195- // Build connection string for testing
196- connectionString , err := buildConnectionString (service , dbTestConnectionPooled , dbTestConnectionRole , false , cmd .ErrOrStderr ())
208+ // Build connection string for testing with password (if available)
209+ connectionString , err := password .BuildConnectionString (service , password.ConnectionStringOptions {
210+ Pooled : dbTestConnectionPooled ,
211+ Role : dbTestConnectionRole ,
212+ PasswordMode : password .PasswordOptional ,
213+ WarnWriter : cmd .ErrOrStderr (),
214+ })
197215 if err != nil {
198216 return exitWithCode (ExitInvalidParameters , fmt .Errorf ("failed to build connection string: %w" , err ))
199217 }
@@ -204,7 +222,7 @@ Examples:
204222 }
205223
206224 // Test the connection
207- return testDatabaseConnection (connectionString , dbTestConnectionTimeout , service , cmd )
225+ return testDatabaseConnection (cmd . Context (), connectionString , dbTestConnectionTimeout , cmd )
208226 },
209227 }
210228
@@ -254,62 +272,6 @@ func getServicePassword(service api.Service) (string, error) {
254272 return passwd , nil
255273}
256274
257- // buildConnectionString creates a PostgreSQL connection string from service details
258- func buildConnectionString (service api.Service , pooled bool , role string , withPassword bool , output io.Writer ) (string , error ) {
259- if service .Endpoint == nil {
260- return "" , fmt .Errorf ("service endpoint not available" )
261- }
262-
263- var endpoint * api.Endpoint
264- var host string
265- var port int
266-
267- // Use pooler endpoint if requested and available, otherwise use direct endpoint
268- if pooled && service .ConnectionPooler != nil && service .ConnectionPooler .Endpoint != nil {
269- endpoint = service .ConnectionPooler .Endpoint
270- } else {
271- // If pooled was requested but no pooler is available, warn the user
272- if pooled {
273- fmt .Fprintf (output , "⚠️ Warning: Connection pooler not available for this service, using direct connection\n " )
274- }
275- endpoint = service .Endpoint
276- }
277-
278- if endpoint .Host == nil {
279- return "" , fmt .Errorf ("endpoint host not available" )
280- }
281- host = * endpoint .Host
282-
283- if endpoint .Port != nil {
284- port = * endpoint .Port
285- } else {
286- port = 5432 // Default PostgreSQL port
287- }
288-
289- // Database is always "tsdb" for TimescaleDB/PostgreSQL services
290- database := "tsdb"
291-
292- // Build connection string in PostgreSQL URI format
293- var connectionString string
294- if withPassword {
295- // Get password from storage if requested
296- passwd , err := getServicePassword (service )
297- if err != nil {
298- return "" , err
299- }
300-
301- // Include password in connection string
302- connectionString = fmt .Sprintf ("postgresql://%s:%s@%s:%d/%s?sslmode=require" , role , passwd , host , port , database )
303- } else {
304- // Build connection string without password (default behavior)
305- // Password is handled separately via PGPASSWORD env var or ~/.pgpass file
306- // This ensures credentials are never visible in process arguments
307- connectionString = fmt .Sprintf ("postgresql://%s@%s:%d/%s?sslmode=require" , role , host , port , database )
308- }
309-
310- return connectionString , nil
311- }
312-
313275// getServiceDetails is a helper that handles common service lookup logic and returns the service details
314276func getServiceDetails (cmd * cobra.Command , args []string ) (api.Service , error ) {
315277 // Get config
@@ -350,7 +312,7 @@ func getServiceDetails(cmd *cobra.Command, args []string) (api.Service, error) {
350312 }
351313
352314 // Fetch service details
353- ctx , cancel := context .WithTimeout (context . Background (), 30 * time .Second )
315+ ctx , cancel := context .WithTimeout (cmd . Context (), 30 * time .Second )
354316 defer cancel ()
355317
356318 resp , err := client .GetProjectsProjectIdServicesServiceIdWithResponse (ctx , projectID , serviceID )
@@ -437,50 +399,18 @@ func buildPsqlCommand(connectionString, psqlPath string, additionalFlags []strin
437399 return psqlCmd
438400}
439401
440- // buildConnectionConfig creates a pgx connection config with proper password handling
441- func buildConnectionConfig (connectionString string , service api.Service ) (* pgx.ConnConfig , error ) {
442- // Parse the connection string first to validate it
443- config , err := pgx .ParseConfig (connectionString )
444- if err != nil {
445- return nil , err
446- }
447-
448- // Set password from keyring storage if available
449- // pgpass storage works automatically since pgx checks ~/.pgpass file
450- storage := password .GetPasswordStorage ()
451- if _ , isKeyring := storage .(* password.KeyringStorage ); isKeyring {
452- if password , err := storage .Get (service ); err == nil && password != "" {
453- config .Password = password
454- }
455- // Note: If keyring password retrieval fails, we let pgx try without it
456- // This allows fallback to other authentication methods
457- }
458-
459- return config , nil
460- }
461-
462402// testDatabaseConnection tests the database connection and returns appropriate exit codes
463- func testDatabaseConnection (connectionString string , timeout time.Duration , service api. Service , cmd * cobra.Command ) error {
403+ func testDatabaseConnection (ctx context. Context , connectionString string , timeout time.Duration , cmd * cobra.Command ) error {
464404 // Create context with timeout if specified
465- var ctx context.Context
466405 var cancel context.CancelFunc
467-
468406 if timeout > 0 {
469- ctx , cancel = context .WithTimeout (context . Background () , timeout )
407+ ctx , cancel = context .WithTimeout (ctx , timeout )
470408 defer cancel ()
471- } else {
472- ctx = context .Background ()
473- }
474-
475- // Build connection config with proper password handling
476- config , err := buildConnectionConfig (connectionString , service )
477- if err != nil {
478- fmt .Fprintf (cmd .ErrOrStderr (), "Failed to build connection config: %v\n " , err )
479- return exitWithCode (ExitInvalidParameters , err )
480409 }
481410
482411 // Attempt to connect to the database
483- conn , err := pgx .ConnectConfig (ctx , config )
412+ // The connection string already includes the password (if available) thanks to PasswordOptional mode
413+ conn , err := pgx .Connect (ctx , connectionString )
484414 if err != nil {
485415 // Determine the appropriate exit code based on error type
486416 if isContextDeadlineExceeded (err ) {
0 commit comments