athena_cli/commands/inspect/
download.rs1use anyhow::{anyhow, Context, Result};
2use aws_sdk_s3::Client;
3use std::fs::{self, File};
4use std::io::Write;
5use std::path::{Path, PathBuf};
6use url::Url;
7
8pub async fn download_from_s3(
10 s3_client: &Client,
11 s3_url: &str,
12 output_dir: &str,
13 _query_id: &str,
14) -> Result<PathBuf> {
15 println!("Downloading query results from S3: {}", s3_url);
16
17 let url = Url::parse(s3_url).context(format!("Failed to parse S3 URL: {}", s3_url))?;
19
20 println!(
22 "URL scheme: {}, host: {:?}, path: {}",
23 url.scheme(),
24 url.host_str(),
25 url.path()
26 );
27
28 let host = url
29 .host_str()
30 .ok_or_else(|| anyhow!("Invalid S3 URL: no host in {}", s3_url))?;
31
32 let (bucket, key) = if let Some(stripped) = s3_url.strip_prefix("s3://") {
34 let parts: Vec<&str> = stripped.splitn(2, '/').collect();
36
37 if parts.len() < 2 {
38 return Err(anyhow!("Invalid S3 URL format (s3://): {}", s3_url));
39 }
40
41 (parts[0].to_string(), parts[1].to_string())
42 } else if host.ends_with(".amazonaws.com") {
43 let bucket_name = host
45 .split('.')
46 .next()
47 .ok_or_else(|| anyhow!("Invalid S3 URL: cannot extract bucket from host: {}", host))?;
48
49 let object_key = url.path().strip_prefix('/').unwrap_or(url.path());
51
52 (bucket_name.to_string(), object_key.to_string())
53 } else {
54 let path_segments = url
56 .path_segments()
57 .ok_or_else(|| anyhow!("Invalid S3 URL: no path in {}", s3_url))?
58 .collect::<Vec<_>>();
59
60 if path_segments.is_empty() {
61 return Err(anyhow!("Invalid S3 URL: empty path in {}", s3_url));
62 }
63
64 let bucket_name = path_segments[0];
65 let object_key = path_segments[1..].join("/");
66
67 (bucket_name.to_string(), object_key)
68 };
69
70 println!("Extracted bucket: {}, key: {}", bucket, key);
71
72 fs::create_dir_all(output_dir)
74 .context(format!("Failed to create output directory: {}", output_dir))?;
75
76 let filename_from_key = Path::new(&key)
78 .file_name()
79 .ok_or_else(|| anyhow!("Could not extract filename from S3 key: {}", key))?
80 .to_string_lossy()
81 .to_string();
82
83 let output_path = Path::new(output_dir).join(&filename_from_key);
85 println!("Will save to: {}", output_path.display());
86
87 println!("Requesting object from S3...");
89 let resp = s3_client
90 .get_object()
91 .bucket(&bucket)
92 .key(&key)
93 .send()
94 .await
95 .context(format!(
96 "Failed to download file from S3 bucket: {}, key: {}",
97 bucket, key
98 ))?;
99
100 println!(
101 "S3 response received, content length: {:?}",
102 resp.content_length()
103 );
104
105 let data = resp
107 .body
108 .collect()
109 .await
110 .context("Failed to read S3 object data stream")?;
111 let bytes = data.into_bytes();
112
113 println!("Downloaded {} bytes from S3", bytes.len());
114
115 let mut file = File::create(&output_path).context(format!(
117 "Failed to create output file: {}",
118 output_path.display()
119 ))?;
120 file.write_all(&bytes).context(format!(
121 "Failed to write data to file: {}",
122 output_path.display()
123 ))?;
124
125 println!(
126 "Successfully downloaded {} bytes to {}",
127 bytes.len(),
128 output_path.display()
129 );
130
131 Ok(output_path)
132}