-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupload.rs
More file actions
166 lines (142 loc) · 4.65 KB
/
upload.rs
File metadata and controls
166 lines (142 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
mod current_system;
mod path_info;
use std::{fmt, io::Read, str::FromStr};
use clap::Args;
use miette::{Context, IntoDiagnostic, bail, miette};
use models::{
StorePath,
dvf::{FileSize, StrictSlug},
};
use self::{current_system::CurrentSystem, path_info::PathInfo};
use crate::{Action, app_state::AppState, authenticate::AuthenticateCommand};
#[derive(Clone, Debug)]
pub(crate) struct Installable(String);
impl FromStr for Installable {
type Err = !;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Installable(s.to_owned()))
}
}
impl fmt::Display for Installable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }
}
#[derive(Args, Debug)]
pub(crate) struct UploadCommand {
/// The flake installable to upload.
installable: Installable,
/// The store to store the uploaded entries in.
#[arg(long, short)]
store: String,
/// The caches to upload to.
#[arg(required = true, num_args = 1..)]
caches: Vec<String>,
}
impl Action for UploadCommand {
type Error = miette::Report;
type Output = ();
async fn execute(
self,
app_state: &AppState,
) -> Result<Self::Output, Self::Error> {
// get the pathinfo
let pathinfo_result = PathInfo::calculate(&self.installable)
.await
.context(format!(
"failed to get path-info for installable `{}`",
self.installable
))?;
let Some(pathinfo) = pathinfo_result.get().as_ref() else {
bail!(
"specified installable has not been built or fetched on this system: \
`{installable}`",
installable = self.installable
);
};
let store_path = pathinfo_result.store_path();
tracing::debug!(%store_path, "got path-info");
let deriver_store_path: StorePath<String> = StorePath::from_absolute_path(
pathinfo
.deriver()
.strip_suffix(".drv")
.ok_or(miette!(
"deriver path from `nix path-info` did not have \".drv\" suffix"
))?
.as_bytes(),
)
.into_diagnostic()
.context(
"failed to parse deriver path from `nix path-info` as a store path",
)?;
let current_system = CurrentSystem::calculate()
.await
.context("failed to determine current system")?;
let cache_list = self
.caches
.into_iter()
.map(|c| (c.clone(), StrictSlug::new(c)))
.inspect(|(o, n)| {
if *o != n.clone().into_inner() {
tracing::warn!("coercing cache name `{o}` into `{n}`")
}
})
.map(|(_, s)| s.to_string())
.collect::<Vec<_>>()
.join(",");
tracing::debug!(%cache_list, "using cache list");
let target_store = StrictSlug::new(self.store.clone());
if self.store != target_store.clone().into_inner() {
tracing::warn!(
"coercing store name `{original}` into `{new}`",
original = self.store,
new = target_store
);
}
tracing::debug!(%target_store, "using target store");
// authenticate with origin. session cookie gets saved in client.
let _creds = (AuthenticateCommand {})
.execute(app_state)
.await
.context("failed to authenticate")?;
tracing::debug!(%store_path, "building NAR");
let mut nar_reader = pathinfo_result
.nar_encoder()
.into_diagnostic()
.context("failed to pack nix store path as a NAR")?;
let mut buffer = Vec::new();
nar_reader
.read_to_end(&mut buffer)
.into_diagnostic()
.context("failed to read bytes from nar encoder")?;
tracing::debug!("buffered {} of NAR", FileSize::new(buffer.len() as _));
let nar_belt = belt::Belt::from_bytes(bytes::Bytes::from(buffer), None);
let client = app_state.http_client();
let url = format!("{}/upload", app_state.api_url_base());
let req = client
.post(url)
.query(&[
("caches", cache_list),
("store_path", store_path.to_string()),
("target_store", target_store.to_string()),
("deriver_store_path", deriver_store_path.to_string()),
("deriver_system", current_system.to_string()),
])
.body(reqwest::Body::wrap_stream(nar_belt));
tracing::debug!("sending upload request");
let resp = req
.send()
.await
.into_diagnostic()
.context("failed to send upload request")?;
let text_resp = resp
.text()
.await
.into_diagnostic()
.context("failed to read response body")?;
tracing::debug!(body = text_resp, "got upload response");
let json_resp: serde_json::Value = serde_json::from_str(&text_resp)
.into_diagnostic()
.context("failed to deserialize response body as JSON")?;
tracing::debug!(body = ?json_resp, "parsed upload response");
Ok(())
}
}