@@ -723,10 +723,17 @@ pub async fn sandbox_create(
723723 Err ( status) => return Err ( miette:: miette!( status. to_string( ) ) ) ,
724724 } ;
725725 let response = response. into_inner ( ) ;
726+ let application_service_urls = response. application_service_urls ;
726727 let service_urls = response
727728 . service_urls
728729 . into_iter ( )
729- . map ( |( service, url) | ( service, service_url_for_gateway ( & url, & effective_server) ) )
730+ . map ( |( service, url) | {
731+ let url = application_service_urls
732+ . get ( & service)
733+ . cloned ( )
734+ . unwrap_or_else ( || service_url_for_gateway ( & url, & effective_server) ) ;
735+ ( service, url)
736+ } )
730737 . collect :: < HashMap < _ , _ > > ( ) ;
731738 let sandbox = response
732739 . sandbox
@@ -1115,10 +1122,7 @@ pub async fn sandbox_create(
11151122 "\u{2713} " . green( ) . bold( ) ,
11161123 ) ;
11171124 if let Some ( url) = service_urls. get ( "" ) . filter ( |url| !url. is_empty ( ) ) {
1118- eprintln ! (
1119- " Access at: {}" ,
1120- service_url_for_gateway( url, & effective_server)
1121- ) ;
1125+ eprintln ! ( " Access at: {url}" ) ;
11221126 }
11231127 }
11241128
@@ -4369,7 +4373,7 @@ pub async fn service_expose(
43694373 ) ;
43704374 }
43714375 if !response. url . is_empty ( ) {
4372- let url = service_url_for_gateway ( & response. url , server) ;
4376+ let url = preferred_service_url ( & response, server) ;
43734377 println ! ( " URL: {}" , url. cyan( ) ) ;
43744378 }
43754379 Ok ( ( ) )
@@ -4582,7 +4586,7 @@ fn print_service_endpoint_table(
45824586 let url = if response. url . is_empty ( ) {
45834587 String :: new ( )
45844588 } else {
4585- service_url_for_gateway ( & response. url , gateway_endpoint)
4589+ preferred_service_url ( response, gateway_endpoint)
45864590 } ;
45874591 Some ( (
45884592 workspace. to_string ( ) ,
@@ -4673,7 +4677,7 @@ fn service_endpoint_to_json(
46734677 let url = if response. url . is_empty ( ) {
46744678 String :: new ( )
46754679 } else {
4676- service_url_for_gateway ( & response. url , gateway_endpoint)
4680+ preferred_service_url ( response, gateway_endpoint)
46774681 } ;
46784682
46794683 Some ( serde_json:: json!( {
@@ -4683,6 +4687,8 @@ fn service_endpoint_to_json(
46834687 "target_port" : endpoint. target_port,
46844688 "authorization_mode" : service_authorization_mode_name( endpoint. authorization_mode) ,
46854689 "url" : url,
4690+ "application_url" : response. application_url,
4691+ "gateway_url" : service_url_for_gateway( & response. gateway_url, gateway_endpoint) ,
46864692 } ) )
46874693}
46884694
@@ -4697,13 +4703,16 @@ fn service_authorization_mode_name(mode: i32) -> &'static str {
46974703 }
46984704}
46994705
4700- /// Read gcloud Application Default Credentials from disk.
4701- ///
4702- /// Returns `(client_id, client_secret, refresh_token)`.
4703- ///
4704- /// Checks `GOOGLE_APPLICATION_CREDENTIALS` first; falls back to
4705- /// `$CLOUDSDK_CONFIG/application_default_credentials.json` when set, then to
4706- /// `~/.config/gcloud/application_default_credentials.json`.
4706+ /// Application origins are independent of the gateway transport. Only legacy
4707+ /// gateway URLs inherit a caller's external gateway port.
4708+ fn preferred_service_url ( response : & ServiceEndpointResponse , gateway : & str ) -> String {
4709+ if response. application_url . is_empty ( ) {
4710+ service_url_for_gateway ( & response. url , gateway)
4711+ } else {
4712+ response. application_url . clone ( )
4713+ }
4714+ }
4715+
47074716fn service_url_for_gateway ( service_url : & str , gateway_endpoint : & str ) -> String {
47084717 let ( Ok ( mut service_url) , Ok ( gateway_endpoint) ) = (
47094718 url:: Url :: parse ( service_url) ,
@@ -7332,6 +7341,8 @@ mod tests {
73327341 #[ test]
73337342 fn service_endpoint_json_has_raw_fields_and_normalized_url ( ) {
73347343 let response = ServiceEndpointResponse {
7344+ application_url : String :: new ( ) ,
7345+ gateway_url : String :: new ( ) ,
73357346 endpoint : Some ( ServiceEndpoint {
73367347 metadata : Some ( ObjectMeta {
73377348 workspace : "team-a" . to_string ( ) ,
@@ -7357,6 +7368,8 @@ mod tests {
73577368 "target_port" : 8080 ,
73587369 "authorization_mode" : "bearer_passthrough" ,
73597370 "url" : "https://api.openshell.localhost:17670/" ,
7371+ "application_url" : "" ,
7372+ "gateway_url" : "" ,
73607373 } )
73617374 ) ;
73627375 assert ! ( service_endpoint_to_json( & ServiceEndpointResponse :: default ( ) , "unused" ) . is_none( ) ) ;
@@ -8116,6 +8129,20 @@ mod tests {
81168129 assert ! ( rootfs_tar_sources_supported_for_gateway( None ) ) ;
81178130 }
81188131
8132+ #[ test]
8133+ fn application_origin_is_preserved_when_gateway_uses_a_port_forward ( ) {
8134+ let response = ServiceEndpointResponse {
8135+ application_url : "https://default--demo--web.services.example.com:10443/" . into ( ) ,
8136+ gateway_url : "https://default--demo--web.api.example.com:8080/" . into ( ) ,
8137+ url : "https://default--demo--web.services.example.com:10443/" . into ( ) ,
8138+ ..Default :: default ( )
8139+ } ;
8140+ assert_eq ! (
8141+ super :: preferred_service_url( & response, "https://localhost:29092" ) ,
8142+ response. application_url
8143+ ) ;
8144+ }
8145+
81198146 #[ test]
81208147 fn service_url_for_gateway_uses_external_gateway_port ( ) {
81218148 assert_eq ! (
0 commit comments