@@ -7,8 +7,6 @@ use serde::Deserialize;
77
88use crate :: schema;
99
10- const POSTGRES_DATABASE_URL_REF : & str = "keyring:gobby:postgres_database_url" ;
11- const DAEMON_POSTGRES_DATABASE_URL_REF : & str = "daemon:gobby:postgres_database_url" ;
1210const GCODE_DATABASE_URL_ENV : & str = "GCODE_DATABASE_URL" ;
1311const GOBBY_POSTGRES_DSN_ENV : & str = "GOBBY_POSTGRES_DSN" ;
1412const GCODE_CONFIG_FILENAME : & str = "gcode.yaml" ;
@@ -24,7 +22,6 @@ struct BrokerDatabaseUrlResponse {
2422struct BootstrapDatabase {
2523 hub_backend : String ,
2624 database_url : Option < String > ,
27- database_url_ref : Option < String > ,
2825}
2926
3027/// Return Gobby home, respecting `GOBBY_HOME` when the daemon was configured with it.
@@ -43,9 +40,8 @@ pub fn bootstrap_path() -> anyhow::Result<PathBuf> {
4340
4441/// Resolve the PostgreSQL hub DSN from explicit overrides or Gobby bootstrap config.
4542///
46- /// gcode intentionally has no local database fallback. Keyring-backed bootstraps resolve
47- /// through the long-lived daemon broker so short-lived gcode processes never touch the OS
48- /// keyring directly.
43+ /// gcode intentionally has no local database fallback. It asks the long-lived daemon
44+ /// broker first, then falls back to explicit DSN sources for daemonless operation.
4945pub fn resolve_database_url ( ) -> anyhow:: Result < String > {
5046 let home = gobby_home ( ) ?;
5147 resolve_database_url_from_sources (
@@ -83,7 +79,7 @@ fn resolve_database_url_from_sources(
8379 )
8480 } ) ?;
8581 let bootstrap = parse_bootstrap_database ( & contents) ?;
86- resolve_database_url_from_bootstrap ( & bootstrap, || broker_resolver ( & path ) )
82+ resolve_database_url_from_bootstrap ( & bootstrap)
8783}
8884
8985fn resolve_database_url_from_env (
@@ -144,18 +140,21 @@ fn parse_bootstrap_database(contents: &str) -> anyhow::Result<BootstrapDatabase>
144140 }
145141 } ;
146142
143+ let database_url_ref = get_string ( "database_url_ref" ) ?;
144+ if database_url_ref. is_some ( ) {
145+ bail ! (
146+ "database_url_ref is no longer supported in bootstrap.yaml; store the local PostgreSQL DSN in database_url"
147+ ) ;
148+ }
149+
147150 Ok ( BootstrapDatabase {
148151 hub_backend : get_string ( "hub_backend" ) ?
149152 . context ( "bootstrap.yaml must include `hub_backend: postgres`" ) ?,
150153 database_url : get_string ( "database_url" ) ?,
151- database_url_ref : get_string ( "database_url_ref" ) ?,
152154 } )
153155}
154156
155- fn resolve_database_url_from_bootstrap (
156- bootstrap : & BootstrapDatabase ,
157- broker_resolver : impl Fn ( ) -> anyhow:: Result < String > ,
158- ) -> anyhow:: Result < String > {
157+ fn resolve_database_url_from_bootstrap ( bootstrap : & BootstrapDatabase ) -> anyhow:: Result < String > {
159158 if bootstrap. hub_backend != "postgres" {
160159 bail ! (
161160 "gcode requires `hub_backend: postgres` in bootstrap.yaml. Current hub_backend is `{}`. Configure the Gobby PostgreSQL hub before running gcode." ,
@@ -167,23 +166,7 @@ fn resolve_database_url_from_bootstrap(
167166 return Ok ( database_url. to_string ( ) ) ;
168167 }
169168
170- if let Some ( database_url_ref) = bootstrap. database_url_ref . as_deref ( ) {
171- parse_database_url_ref ( database_url_ref) ?;
172- let database_url = broker_resolver ( ) . with_context ( || {
173- format ! (
174- "failed to resolve database_url_ref {database_url_ref} through the local Gobby daemon broker. gcode does not read the OS keyring directly; start the Gobby daemon or use inline database_url in bootstrap.yaml for daemonless/manual setups"
175- )
176- } ) ?;
177- let database_url = database_url. trim ( ) . to_string ( ) ;
178- if database_url. is_empty ( ) {
179- bail ! ( "database_url broker response for {database_url_ref} was empty" ) ;
180- }
181- return Ok ( database_url) ;
182- }
183-
184- bail ! (
185- "hub_backend=postgres requires `database_url_ref: {POSTGRES_DATABASE_URL_REF}`, `database_url_ref: {DAEMON_POSTGRES_DATABASE_URL_REF}`, or an inline `database_url`"
186- )
169+ bail ! ( "hub_backend=postgres requires `database_url` in bootstrap.yaml" )
187170}
188171
189172fn non_empty_trimmed ( value : Option < String > ) -> Option < String > {
@@ -232,16 +215,6 @@ fn request_broker_database_url(daemon_url: &str, token: &str) -> anyhow::Result<
232215 Ok ( database_url)
233216}
234217
235- fn parse_database_url_ref ( database_url_ref : & str ) -> anyhow:: Result < ( ) > {
236- let parts: Vec < & str > = database_url_ref. splitn ( 3 , ':' ) . collect ( ) ;
237- match parts. as_slice ( ) {
238- [ "keyring" | "daemon" , "gobby" , "postgres_database_url" ] => Ok ( ( ) ) ,
239- _ => bail ! (
240- "database_url_ref must be {POSTGRES_DATABASE_URL_REF} or {DAEMON_POSTGRES_DATABASE_URL_REF}"
241- ) ,
242- }
243- }
244-
245218pub fn connect_readwrite ( database_url : & str ) -> anyhow:: Result < Client > {
246219 connect ( database_url)
247220}
@@ -281,15 +254,10 @@ mod tests {
281254 use std:: net:: TcpListener ;
282255 use std:: thread;
283256
284- fn bootstrap (
285- hub_backend : & str ,
286- database_url : Option < & str > ,
287- database_url_ref : Option < & str > ,
288- ) -> BootstrapDatabase {
257+ fn bootstrap ( hub_backend : & str , database_url : Option < & str > ) -> BootstrapDatabase {
289258 BootstrapDatabase {
290259 hub_backend : hub_backend. to_string ( ) ,
291260 database_url : database_url. map ( str:: to_string) ,
292- database_url_ref : database_url_ref. map ( str:: to_string) ,
293261 }
294262 }
295263
@@ -413,126 +381,47 @@ mod tests {
413381 }
414382
415383 #[ test]
416- fn postgres_bootstrap_resolves_keyring_ref_through_broker ( ) {
417- let resolved = resolve_database_url_from_bootstrap (
418- & bootstrap ( "postgres" , None , Some ( POSTGRES_DATABASE_URL_REF ) ) ,
419- || Ok ( "postgresql://broker/db" . to_string ( ) ) ,
420- )
421- . expect ( "resolve ref" ) ;
422-
423- assert_eq ! ( resolved, "postgresql://broker/db" ) ;
424- }
425-
426- #[ test]
427- fn postgres_bootstrap_resolves_daemon_ref_through_broker ( ) {
428- let resolved = resolve_database_url_from_bootstrap (
429- & bootstrap ( "postgres" , None , Some ( DAEMON_POSTGRES_DATABASE_URL_REF ) ) ,
430- || Ok ( "postgresql://broker/db" . to_string ( ) ) ,
431- )
432- . expect ( "resolve ref" ) ;
384+ fn postgres_bootstrap_accepts_inline_url ( ) {
385+ let resolved = resolve_database_url_from_bootstrap ( & bootstrap (
386+ "postgres" ,
387+ Some ( "postgresql://inline/db" ) ,
388+ ) )
389+ . expect ( "resolve inline url" ) ;
433390
434- assert_eq ! ( resolved, "postgresql://broker /db" ) ;
391+ assert_eq ! ( resolved, "postgresql://inline /db" ) ;
435392 }
436393
437394 #[ test]
438- fn postgres_bootstrap_ref_fails_clearly_when_broker_fails ( ) {
439- let err = resolve_database_url_from_bootstrap (
440- & bootstrap ( "postgres" , None , Some ( POSTGRES_DATABASE_URL_REF ) ) ,
441- || bail ! ( "broker unavailable" ) ,
395+ fn postgres_bootstrap_rejects_database_url_ref ( ) {
396+ let err = parse_bootstrap_database (
397+ "hub_backend: postgres \n \
398+ database_url_ref: deprecated \n " ,
442399 )
443- . expect_err ( "broker failure must fail" ) ;
400+ . expect_err ( "database_url_ref must fail" ) ;
444401
445402 let message = err. to_string ( ) ;
446- assert ! ( message. contains( "local Gobby daemon broker" ) ) ;
447- assert ! ( message. contains( "does not read the OS keyring directly" ) ) ;
448- }
449-
450- #[ test]
451- fn postgres_bootstrap_fails_when_broker_token_is_missing ( ) {
452- let home = tempfile:: tempdir ( ) . expect ( "temp home" ) ;
453- let bootstrap_path = write_bootstrap ( home. path ( ) , 60887 ) ;
454-
455- let err = resolve_database_url_from_bootstrap (
456- & bootstrap ( "postgres" , None , Some ( POSTGRES_DATABASE_URL_REF ) ) ,
457- || resolve_brokered_database_url_at ( home. path ( ) , & bootstrap_path) ,
458- )
459- . expect_err ( "missing broker token must fail" ) ;
460-
461- assert ! ( err. to_string( ) . contains( "local Gobby daemon broker" ) ) ;
462- }
463-
464- #[ test]
465- fn postgres_bootstrap_fails_when_broker_is_unreachable ( ) {
466- let home = tempfile:: tempdir ( ) . expect ( "temp home" ) ;
467- std:: fs:: write ( home. path ( ) . join ( LOCAL_CLI_TOKEN_FILENAME ) , "token\n " ) . expect ( "write token" ) ;
468- let bootstrap_path = write_bootstrap ( home. path ( ) , unused_local_port ( ) ) ;
469-
470- let err = resolve_database_url_from_bootstrap (
471- & bootstrap ( "postgres" , None , Some ( POSTGRES_DATABASE_URL_REF ) ) ,
472- || resolve_brokered_database_url_at ( home. path ( ) , & bootstrap_path) ,
473- )
474- . expect_err ( "unreachable broker must fail" ) ;
475-
476- assert ! ( err. to_string( ) . contains( "local Gobby daemon broker" ) ) ;
477- }
478-
479- #[ test]
480- fn postgres_bootstrap_fails_after_broker_401 ( ) {
481- assert_broker_http_failure_fails ( "401 Unauthorized" , r#"{"error":"bad token"}"# ) ;
482- }
483-
484- #[ test]
485- fn postgres_bootstrap_fails_after_broker_403 ( ) {
486- assert_broker_http_failure_fails ( "403 Forbidden" , r#"{"error":"forbidden"}"# ) ;
487- }
488-
489- #[ test]
490- fn postgres_bootstrap_fails_after_broker_non_2xx ( ) {
491- assert_broker_http_failure_fails ( "503 Service Unavailable" , r#"{"error":"unavailable"}"# ) ;
492- }
493-
494- #[ test]
495- fn postgres_bootstrap_fails_after_broker_invalid_json ( ) {
496- assert_broker_http_failure_fails ( "200 OK" , "not json" ) ;
497- }
498-
499- #[ test]
500- fn postgres_bootstrap_fails_after_broker_empty_database_url ( ) {
501- assert_broker_http_failure_fails ( "200 OK" , r#"{"database_url":" "}"# ) ;
502- }
503-
504- #[ test]
505- fn postgres_bootstrap_prefers_inline_url_over_ref ( ) {
506- let resolved = resolve_database_url_from_bootstrap (
507- & bootstrap (
508- "postgres" ,
509- Some ( "postgresql://inline/db" ) ,
510- Some ( POSTGRES_DATABASE_URL_REF ) ,
511- ) ,
512- || unreachable ! ( "broker should not be used when inline url is present" ) ,
513- )
514- . expect ( "resolve inline url" ) ;
515-
516- assert_eq ! ( resolved, "postgresql://inline/db" ) ;
403+ assert ! ( message. contains( "database_url_ref is no longer supported" ) ) ;
404+ assert ! ( message. contains( "database_url" ) ) ;
517405 }
518406
519407 #[ test]
520- fn postgres_bootstrap_accepts_inline_url ( ) {
521- let resolved = resolve_database_url_from_bootstrap (
522- & bootstrap ( "postgres" , Some ( "postgresql://inline/db" ) , None ) ,
523- || unreachable ! ( "broker should not be used" ) ,
408+ fn postgres_bootstrap_rejects_database_url_ref_even_with_inline_url ( ) {
409+ let err = parse_bootstrap_database (
410+ "hub_backend: postgres\n \
411+ database_url: postgresql://inline/db\n \
412+ database_url_ref: deprecated\n ",
524413 )
525- . expect ( "resolve inline url ") ;
414+ . expect_err ( "database_url_ref must fail ") ;
526415
527- assert_eq ! ( resolved, "postgresql://inline/db" ) ;
416+ assert ! (
417+ err. to_string( )
418+ . contains( "database_url_ref is no longer supported" )
419+ ) ;
528420 }
529421
530422 #[ test]
531423 fn non_postgres_bootstrap_fails_clearly ( ) {
532- let err =
533- resolve_database_url_from_bootstrap ( & bootstrap ( "legacy-local" , None , None ) , || {
534- unreachable ! ( "broker should not be used" )
535- } )
424+ let err = resolve_database_url_from_bootstrap ( & bootstrap ( "legacy-local" , None ) )
536425 . expect_err ( "non-postgres backend must fail" ) ;
537426
538427 let message = err. to_string ( ) ;
@@ -550,41 +439,24 @@ mod tests {
550439
551440 #[ test]
552441 fn missing_postgres_dsn_fails_clearly ( ) {
553- let err = resolve_database_url_from_bootstrap ( & bootstrap ( "postgres" , None , None ) , || {
554- unreachable ! ( "broker should not be used" )
555- } )
556- . expect_err ( "missing dsn must fail" ) ;
442+ let err = resolve_database_url_from_bootstrap ( & bootstrap ( "postgres" , None ) )
443+ . expect_err ( "missing dsn must fail" ) ;
557444
558- assert ! ( err. to_string( ) . contains( "database_url_ref" ) ) ;
559- }
560-
561- #[ test]
562- fn unsupported_database_url_ref_fails_clearly ( ) {
563- let err = resolve_database_url_from_bootstrap (
564- & bootstrap (
565- "postgres" ,
566- None ,
567- Some ( "keyring:other:postgres_database_url" ) ,
568- ) ,
569- || unreachable ! ( "broker should not be used for unsupported refs" ) ,
570- )
571- . expect_err ( "unsupported ref must fail" ) ;
572-
573- assert ! ( err. to_string( ) . contains( POSTGRES_DATABASE_URL_REF ) ) ;
445+ assert ! ( err. to_string( ) . contains( "database_url" ) ) ;
574446 }
575447
576448 #[ test]
577449 fn parse_bootstrap_database_reads_postgres_fields ( ) {
578450 let parsed = parse_bootstrap_database (
579451 "hub_backend: postgres\n \
580- database_url_ref: daemon:gobby:postgres_database_url \n ",
452+ database_url: postgresql://inline/db \n ",
581453 )
582454 . expect ( "parse bootstrap" ) ;
583455
584456 assert_eq ! ( parsed. hub_backend, "postgres" ) ;
585457 assert_eq ! (
586- parsed. database_url_ref . as_deref( ) ,
587- Some ( DAEMON_POSTGRES_DATABASE_URL_REF )
458+ parsed. database_url . as_deref( ) ,
459+ Some ( "postgresql://inline/db" )
588460 ) ;
589461 }
590462
@@ -760,22 +632,4 @@ mod tests {
760632 } ) ;
761633 ( format ! ( "http://{addr}" ) , handle)
762634 }
763-
764- fn assert_broker_http_failure_fails ( status : & str , body : & str ) {
765- let ( daemon_url, request) = spawn_http_response ( http_response ( status, body) ) ;
766-
767- let err = resolve_database_url_from_bootstrap (
768- & bootstrap ( "postgres" , None , Some ( POSTGRES_DATABASE_URL_REF ) ) ,
769- || request_broker_database_url ( & daemon_url, "token" ) ,
770- )
771- . expect_err ( "broker failure must fail" ) ;
772- let _ = request. join ( ) . expect ( "read request" ) ;
773-
774- assert ! ( err. to_string( ) . contains( "local Gobby daemon broker" ) ) ;
775- }
776-
777- fn unused_local_port ( ) -> u16 {
778- let listener = TcpListener :: bind ( "127.0.0.1:0" ) . expect ( "bind unused local port" ) ;
779- listener. local_addr ( ) . expect ( "local addr" ) . port ( )
780- }
781635}
0 commit comments