@@ -722,10 +722,17 @@ pub async fn sandbox_create(
722722 Err ( status) => return Err ( miette:: miette!( status. to_string( ) ) ) ,
723723 } ;
724724 let response = response. into_inner ( ) ;
725+ let application_service_urls = response. application_service_urls ;
725726 let service_urls = response
726727 . service_urls
727728 . into_iter ( )
728- . map ( |( service, url) | ( service, service_url_for_gateway ( & url, & effective_server) ) )
729+ . map ( |( service, url) | {
730+ let url = application_service_urls
731+ . get ( & service)
732+ . cloned ( )
733+ . unwrap_or_else ( || service_url_for_gateway ( & url, & effective_server) ) ;
734+ ( service, url)
735+ } )
729736 . collect :: < HashMap < _ , _ > > ( ) ;
730737 let sandbox = response
731738 . sandbox
@@ -1114,10 +1121,7 @@ pub async fn sandbox_create(
11141121 "\u{2713} " . green( ) . bold( ) ,
11151122 ) ;
11161123 if let Some ( url) = service_urls. get ( "" ) . filter ( |url| !url. is_empty ( ) ) {
1117- eprintln ! (
1118- " Access at: {}" ,
1119- service_url_for_gateway( url, & effective_server)
1120- ) ;
1124+ eprintln ! ( " Access at: {url}" ) ;
11211125 }
11221126 }
11231127
@@ -4260,7 +4264,7 @@ pub async fn service_expose(
42604264 ) ;
42614265 }
42624266 if !response. url . is_empty ( ) {
4263- let url = service_url_for_gateway ( & response. url , server) ;
4267+ let url = preferred_service_url ( & response, server) ;
42644268 println ! ( " URL: {}" , url. cyan( ) ) ;
42654269 }
42664270 Ok ( ( ) )
@@ -4473,7 +4477,7 @@ fn print_service_endpoint_table(
44734477 let url = if response. url . is_empty ( ) {
44744478 String :: new ( )
44754479 } else {
4476- service_url_for_gateway ( & response. url , gateway_endpoint)
4480+ preferred_service_url ( response, gateway_endpoint)
44774481 } ;
44784482 Some ( (
44794483 workspace. to_string ( ) ,
@@ -4564,7 +4568,7 @@ fn service_endpoint_to_json(
45644568 let url = if response. url . is_empty ( ) {
45654569 String :: new ( )
45664570 } else {
4567- service_url_for_gateway ( & response. url , gateway_endpoint)
4571+ preferred_service_url ( response, gateway_endpoint)
45684572 } ;
45694573
45704574 Some ( serde_json:: json!( {
@@ -4574,6 +4578,8 @@ fn service_endpoint_to_json(
45744578 "target_port" : endpoint. target_port,
45754579 "authorization_mode" : service_authorization_mode_name( endpoint. authorization_mode) ,
45764580 "url" : url,
4581+ "application_url" : response. application_url,
4582+ "gateway_url" : service_url_for_gateway( & response. gateway_url, gateway_endpoint) ,
45774583 } ) )
45784584}
45794585
@@ -4588,13 +4594,16 @@ fn service_authorization_mode_name(mode: i32) -> &'static str {
45884594 }
45894595}
45904596
4591- /// Read gcloud Application Default Credentials from disk.
4592- ///
4593- /// Returns `(client_id, client_secret, refresh_token)`.
4594- ///
4595- /// Checks `GOOGLE_APPLICATION_CREDENTIALS` first; falls back to
4596- /// `$CLOUDSDK_CONFIG/application_default_credentials.json` when set, then to
4597- /// `~/.config/gcloud/application_default_credentials.json`.
4597+ /// Application origins are independent of the gateway transport. Only legacy
4598+ /// gateway URLs inherit a caller's external gateway port.
4599+ fn preferred_service_url ( response : & ServiceEndpointResponse , gateway : & str ) -> String {
4600+ if response. application_url . is_empty ( ) {
4601+ service_url_for_gateway ( & response. url , gateway)
4602+ } else {
4603+ response. application_url . clone ( )
4604+ }
4605+ }
4606+
45984607fn service_url_for_gateway ( service_url : & str , gateway_endpoint : & str ) -> String {
45994608 let ( Ok ( mut service_url) , Ok ( gateway_endpoint) ) = (
46004609 url:: Url :: parse ( service_url) ,
@@ -7188,6 +7197,8 @@ mod tests {
71887197 #[ test]
71897198 fn service_endpoint_json_has_raw_fields_and_normalized_url ( ) {
71907199 let response = ServiceEndpointResponse {
7200+ application_url : String :: new ( ) ,
7201+ gateway_url : String :: new ( ) ,
71917202 endpoint : Some ( ServiceEndpoint {
71927203 metadata : Some ( ObjectMeta {
71937204 workspace : "team-a" . to_string ( ) ,
@@ -7213,6 +7224,8 @@ mod tests {
72137224 "target_port" : 8080 ,
72147225 "authorization_mode" : "bearer_passthrough" ,
72157226 "url" : "https://api.openshell.localhost:17670/" ,
7227+ "application_url" : "" ,
7228+ "gateway_url" : "" ,
72167229 } )
72177230 ) ;
72187231 assert ! ( service_endpoint_to_json( & ServiceEndpointResponse :: default ( ) , "unused" ) . is_none( ) ) ;
@@ -7972,6 +7985,20 @@ mod tests {
79727985 assert ! ( rootfs_tar_sources_supported_for_gateway( None ) ) ;
79737986 }
79747987
7988+ #[ test]
7989+ fn application_origin_is_preserved_when_gateway_uses_a_port_forward ( ) {
7990+ let response = ServiceEndpointResponse {
7991+ application_url : "https://default--demo--web.services.example.com:10443/" . into ( ) ,
7992+ gateway_url : "https://default--demo--web.api.example.com:8080/" . into ( ) ,
7993+ url : "https://default--demo--web.services.example.com:10443/" . into ( ) ,
7994+ ..Default :: default ( )
7995+ } ;
7996+ assert_eq ! (
7997+ super :: preferred_service_url( & response, "https://localhost:29092" ) ,
7998+ response. application_url
7999+ ) ;
8000+ }
8001+
79758002 #[ test]
79768003 fn service_url_for_gateway_uses_external_gateway_port ( ) {
79778004 assert_eq ! (
0 commit comments