11/*
2- * Copyright 2024 Oxide Computer Company
2+ * Copyright 2025 Oxide Computer Company
33 */
44
55use crate :: { App , FlushOut , FlushState } ;
66use anyhow:: { bail, Result } ;
77use buildomat_client:: types:: { DependSubmit , JobOutput } ;
88use buildomat_common:: * ;
9+ use buildomat_download:: PotentialRange ;
910use buildomat_github_database:: { types:: * , Database } ;
1011use buildomat_jobsh:: variety:: basic:: { output_sse, output_table, BasicConfig } ;
1112use chrono:: SecondsFormat ;
@@ -19,6 +20,7 @@ use serde::{Deserialize, Serialize};
1920use slog:: { debug, error, info, o, trace, warn, Logger } ;
2021use std:: collections:: { HashMap , VecDeque } ;
2122use std:: sync:: Arc ;
23+ use std:: time:: Duration ;
2224use tokio:: io:: { AsyncSeekExt , AsyncWriteExt } ;
2325
2426const KILOBYTE : f64 = 1024.0 ;
@@ -939,6 +941,7 @@ pub(crate) async fn artefact(
939941 output : & str ,
940942 name : & str ,
941943 format : Option < & str > ,
944+ pr : Option < PotentialRange > ,
942945) -> Result < Option < hyper:: Response < Body > > > {
943946 let p: BasicPrivate = cr. get_private ( ) ?;
944947
@@ -951,10 +954,6 @@ pub(crate) async fn artefact(
951954 if let Some ( id) = & p. buildomat_id {
952955 let bm = app. buildomat ( & app. db . load_repository ( cs. repo ) ?) ;
953956
954- let backend =
955- bm. job_output_download ( ) . job ( id) . output ( output) . send ( ) . await ?;
956- let cl = backend. content_length ( ) . unwrap ( ) ;
957-
958957 /*
959958 * To try and help out the browser in deciding whether to display or
960959 * immediately download a particular file, we'll try to guess the
@@ -974,6 +973,10 @@ pub(crate) async fn artefact(
974973 bail ! ( "cannot reformat a file that is not plain text" ) ;
975974 }
976975
976+ let backend =
977+ bm. job_output_download ( ) . job ( id) . output ( output) . send ( ) . await ?;
978+ let cl = backend. content_length ( ) . unwrap ( ) ;
979+
977980 if cl > MAX_RENDERED_LOG {
978981 bail ! ( "file too large for reformat" ) ;
979982 }
@@ -1048,15 +1051,69 @@ pub(crate) async fn artefact(
10481051 ) ) ;
10491052 }
10501053
1051- return Ok ( Some (
1052- hyper:: Response :: builder ( )
1053- . status ( hyper:: StatusCode :: OK )
1054- . header ( hyper:: header:: CONTENT_TYPE , ct)
1055- . header ( hyper:: header:: CONTENT_LENGTH , cl)
1056- . body ( Body :: wrap ( StreamBody :: new (
1057- backend. into_inner_stream ( ) . map_ok ( |b| Frame :: data ( b) ) ,
1058- ) ) ) ?,
1059- ) ) ;
1054+ /*
1055+ * To improve efficiency, we can try to fetch the file directly from the
1056+ * object store instead of forwarding the request through the API
1057+ * server.
1058+ */
1059+ let url = bm
1060+ . job_output_signed_url ( )
1061+ . job ( id)
1062+ . output ( output)
1063+ . body_map ( |body| body. content_type ( ct. clone ( ) ) . expiry_seconds ( 120 ) )
1064+ . send ( )
1065+ . await ?;
1066+ if url. available {
1067+ let client = reqwest:: ClientBuilder :: new ( )
1068+ . timeout ( Duration :: from_secs ( 3600 ) )
1069+ . tcp_keepalive ( Duration :: from_secs ( 60 ) )
1070+ . connect_timeout ( Duration :: from_secs ( 15 ) )
1071+ . build ( ) ?;
1072+
1073+ /*
1074+ * The file is available for streaming directly from the object
1075+ * store.
1076+ */
1077+ match buildomat_download:: stream_from_url (
1078+ & app. log ,
1079+ format ! ( "pre-signed job output: job {id}, output {output}" ) ,
1080+ & client,
1081+ url. url . clone ( ) ,
1082+ pr,
1083+ false ,
1084+ ct,
1085+ Some ( url. size ) ,
1086+ )
1087+ . await
1088+ {
1089+ Ok ( res) => return Ok ( res) ,
1090+ Err ( e) => {
1091+ bail ! (
1092+ "pre-signed job output: \
1093+ job {id} output {output}: {e:?}"
1094+ ) ;
1095+ }
1096+ }
1097+ } else {
1098+ /*
1099+ * Fall back to a regular request through the core API server.
1100+ * XXX There is currently no good way to pass the range request to
1101+ * the backend here.
1102+ */
1103+ let backend =
1104+ bm. job_output_download ( ) . job ( id) . output ( output) . send ( ) . await ?;
1105+ let cl = backend. content_length ( ) . unwrap ( ) ;
1106+
1107+ return Ok ( Some (
1108+ hyper:: Response :: builder ( )
1109+ . status ( hyper:: StatusCode :: OK )
1110+ . header ( hyper:: header:: CONTENT_TYPE , ct)
1111+ . header ( hyper:: header:: CONTENT_LENGTH , cl)
1112+ . body ( Body :: wrap ( StreamBody :: new (
1113+ backend. into_inner_stream ( ) . map_ok ( |b| Frame :: data ( b) ) ,
1114+ ) ) ) ?,
1115+ ) ) ;
1116+ }
10601117 }
10611118
10621119 Ok ( None )
0 commit comments