@@ -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
@@ -4361,7 +4365,7 @@ pub async fn service_expose(
43614365 ) ;
43624366 }
43634367 if !response. url . is_empty ( ) {
4364- let url = service_url_for_gateway ( & response. url , server) ;
4368+ let url = preferred_service_url ( & response, server) ;
43654369 println ! ( " URL: {}" , url. cyan( ) ) ;
43664370 }
43674371 Ok ( ( ) )
@@ -4574,7 +4578,7 @@ fn print_service_endpoint_table(
45744578 let url = if response. url . is_empty ( ) {
45754579 String :: new ( )
45764580 } else {
4577- service_url_for_gateway ( & response. url , gateway_endpoint)
4581+ preferred_service_url ( response, gateway_endpoint)
45784582 } ;
45794583 Some ( (
45804584 workspace. to_string ( ) ,
@@ -4665,7 +4669,7 @@ fn service_endpoint_to_json(
46654669 let url = if response. url . is_empty ( ) {
46664670 String :: new ( )
46674671 } else {
4668- service_url_for_gateway ( & response. url , gateway_endpoint)
4672+ preferred_service_url ( response, gateway_endpoint)
46694673 } ;
46704674
46714675 Some ( serde_json:: json!( {
@@ -4675,6 +4679,8 @@ fn service_endpoint_to_json(
46754679 "target_port" : endpoint. target_port,
46764680 "authorization_mode" : service_authorization_mode_name( endpoint. authorization_mode) ,
46774681 "url" : url,
4682+ "application_url" : response. application_url,
4683+ "gateway_url" : service_url_for_gateway( & response. gateway_url, gateway_endpoint) ,
46784684 } ) )
46794685}
46804686
@@ -4689,13 +4695,16 @@ fn service_authorization_mode_name(mode: i32) -> &'static str {
46894695 }
46904696}
46914697
4692- /// Read gcloud Application Default Credentials from disk.
4693- ///
4694- /// Returns `(client_id, client_secret, refresh_token)`.
4695- ///
4696- /// Checks `GOOGLE_APPLICATION_CREDENTIALS` first; falls back to
4697- /// `$CLOUDSDK_CONFIG/application_default_credentials.json` when set, then to
4698- /// `~/.config/gcloud/application_default_credentials.json`.
4698+ /// Application origins are independent of the gateway transport. Only legacy
4699+ /// gateway URLs inherit a caller's external gateway port.
4700+ fn preferred_service_url ( response : & ServiceEndpointResponse , gateway : & str ) -> String {
4701+ if response. application_url . is_empty ( ) {
4702+ service_url_for_gateway ( & response. url , gateway)
4703+ } else {
4704+ response. application_url . clone ( )
4705+ }
4706+ }
4707+
46994708fn service_url_for_gateway ( service_url : & str , gateway_endpoint : & str ) -> String {
47004709 let ( Ok ( mut service_url) , Ok ( gateway_endpoint) ) = (
47014710 url:: Url :: parse ( service_url) ,
@@ -7324,6 +7333,8 @@ mod tests {
73247333 #[ test]
73257334 fn service_endpoint_json_has_raw_fields_and_normalized_url ( ) {
73267335 let response = ServiceEndpointResponse {
7336+ application_url : String :: new ( ) ,
7337+ gateway_url : String :: new ( ) ,
73277338 endpoint : Some ( ServiceEndpoint {
73287339 metadata : Some ( ObjectMeta {
73297340 workspace : "team-a" . to_string ( ) ,
@@ -7349,6 +7360,8 @@ mod tests {
73497360 "target_port" : 8080 ,
73507361 "authorization_mode" : "bearer_passthrough" ,
73517362 "url" : "https://api.openshell.localhost:17670/" ,
7363+ "application_url" : "" ,
7364+ "gateway_url" : "" ,
73527365 } )
73537366 ) ;
73547367 assert ! ( service_endpoint_to_json( & ServiceEndpointResponse :: default ( ) , "unused" ) . is_none( ) ) ;
@@ -8108,6 +8121,20 @@ mod tests {
81088121 assert ! ( rootfs_tar_sources_supported_for_gateway( None ) ) ;
81098122 }
81108123
8124+ #[ test]
8125+ fn application_origin_is_preserved_when_gateway_uses_a_port_forward ( ) {
8126+ let response = ServiceEndpointResponse {
8127+ application_url : "https://default--demo--web.services.example.com:10443/" . into ( ) ,
8128+ gateway_url : "https://default--demo--web.api.example.com:8080/" . into ( ) ,
8129+ url : "https://default--demo--web.services.example.com:10443/" . into ( ) ,
8130+ ..Default :: default ( )
8131+ } ;
8132+ assert_eq ! (
8133+ super :: preferred_service_url( & response, "https://localhost:29092" ) ,
8134+ response. application_url
8135+ ) ;
8136+ }
8137+
81118138 #[ test]
81128139 fn service_url_for_gateway_uses_external_gateway_port ( ) {
81138140 assert_eq ! (
0 commit comments