Export artifact bytes explicitly

This commit is contained in:
Michel Paulissen 2026-07-03 23:43:45 +02:00
parent 2a86900e57
commit 22fa4fc675
9 changed files with 346 additions and 15 deletions

View file

@ -6,6 +6,7 @@ license.workspace = true
repository.workspace = true
[dependencies]
base64.workspace = true
disasmer-core = { path = "../disasmer-core" }
postgres.workspace = true
serde.workspace = true

View file

@ -3,6 +3,7 @@ use std::io::{BufRead, BufReader, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
use disasmer_core::{
Actor, AgentId, ArtifactId, ArtifactRegistry, Authorization, Capability, CapabilityReportError,
CredentialKind, DataPlaneObject, DataPlaneScope, DefaultScheduler, Digest,
@ -673,11 +674,17 @@ pub enum CoordinatorResponse {
link: DownloadLink,
streamed_bytes: u64,
charged_download_bytes: u64,
content_bytes_available: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
content_base64: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
content_source: Option<String>,
},
ArtifactExportPlan {
plan: DirectBulkTransferPlan,
source_node: NodeId,
receiver_node: NodeId,
artifact_size_bytes: u64,
},
Error {
message: String,
@ -2163,10 +2170,16 @@ impl CoordinatorService {
} => {
let _ = token_nonce;
let context = user_context(tenant, project, actor_user);
let artifact = ArtifactId::new(artifact);
let policy = DownloadPolicy { max_bytes };
let downloadable_size = self
.artifact_registry
.downloadable_size(&context, &artifact, &policy)?;
let chunk_bytes = chunk_bytes.min(downloadable_size);
let mut stream = self.artifact_registry.open_download_stream(
&context,
&ArtifactId::new(artifact),
&DownloadPolicy { max_bytes },
&artifact,
&policy,
&token_digest,
now_epoch_seconds,
&self.download_limits,
@ -2181,10 +2194,18 @@ impl CoordinatorService {
)?;
let charged_download_bytes =
self.download_meter.used(&LimitKind::ArtifactDownloadBytes);
let content_base64 =
self.download_stream_content_base64(&context, &artifact, stream.streamed_bytes);
let content_bytes_available = content_base64.is_some();
let content_source =
content_bytes_available.then(|| "coordinator_complete_stdout_tail".to_owned());
Ok(CoordinatorResponse::ArtifactDownloadStream {
link: stream.link,
streamed_bytes: stream.streamed_bytes,
charged_download_bytes,
content_bytes_available,
content_base64,
content_source,
})
}
CoordinatorRequest::RevokeArtifactDownloadLink {
@ -2254,11 +2275,39 @@ impl CoordinatorService {
plan,
source_node,
receiver_node,
artifact_size_bytes: metadata.size,
})
}
}
}
fn download_stream_content_base64(
&self,
context: &disasmer_core::AuthContext,
artifact: &ArtifactId,
streamed_bytes: u64,
) -> Option<String> {
self.task_events
.iter()
.rev()
.find(|event| {
event.tenant == context.tenant
&& event.project == context.project
&& event.status_code == Some(0)
&& !event.stdout_truncated
&& event.stdout_bytes == event.stdout_tail.as_bytes().len() as u64
&& event.artifact_size_bytes == Some(event.stdout_bytes)
&& event.artifact_size_bytes == Some(streamed_bytes)
&& event
.artifact_path
.as_ref()
.map(artifact_id_from_path)
.as_ref()
== Some(artifact)
})
.map(|event| BASE64_STANDARD.encode(event.stdout_tail.as_bytes()))
}
fn record_debug_audit_event(
&mut self,
tenant: TenantId,