1pub mod flight;
26pub use flight::FlightCtx;
27pub mod formation;
28pub use formation::{FormationCfgCtx, FormationCtx};
29pub mod metadata;
30pub use metadata::MetadataCtx;
31pub mod locks;
32pub use locks::LocksCtx;
33pub mod restrict;
34use std::path::{Path, PathBuf};
35
36use clap_complete::Shell;
37use once_cell::unsync::OnceCell;
38use reqwest::Url;
39pub use restrict::RestrictCtx;
40
41use crate::{
42 config::RawConfig,
43 error::{CliErrorKind, Context, Result},
44 fs::{self, FromDisk, ToDisk},
45 ops::{flight::Flights, formation::Formations},
46 printer::{ColorChoice, OutputFormat},
47};
48
49const FLIGHTS_FILE: &str = "flights.json";
50const FORMATIONS_FILE: &str = "formations.json";
51pub const DEFAULT_IMAGE_REGISTRY_URL: &str = "registry.cplane.cloud";
53
54#[derive(Debug, Default, Clone)]
55pub struct Args {
56 pub color: ColorChoice,
61
62 pub name_id: Option<String>,
64
65 pub overwrite: Vec<String>,
67
68 pub exact: bool,
70
71 pub all: bool,
73
74 pub third_party: bool,
76
77 pub shell: Option<Shell>,
79
80 pub out_format: OutputFormat,
82
83 pub force: bool,
85
86 pub stateless: bool,
88
89 pub api_key: Option<String>,
92
93 pub fetch: bool,
95}
96
97impl Args {
98 pub fn api_key(&self) -> Result<&str> {
99 self.api_key
100 .as_deref()
101 .ok_or_else(|| CliErrorKind::MissingApiKey.into_err())
102 }
103}
104
105#[derive(Debug)]
109pub struct Ctx {
110 data_dir: PathBuf,
112
113 pub flight_ctx: LateInit<FlightCtx>,
115
116 pub formation_ctx: LateInit<FormationCtx>,
118
119 pub md_ctx: LateInit<MetadataCtx>,
121
122 pub locks_ctx: LateInit<LocksCtx>,
124
125 pub restrict_ctx: LateInit<RestrictCtx>,
127
128 pub conf_files: Vec<PathBuf>,
130
131 pub args: Args,
133
134 pub db: Db,
136
137 pub internal_run: bool,
139
140 pub did_init: bool,
142
143 pub disable_pb: bool,
145
146 pub registry: String,
148
149 pub compute_url: Option<Url>,
151 pub identity_url: Option<Url>,
152 pub metadata_url: Option<Url>,
153 pub locks_url: Option<Url>,
154 pub insecure_urls: bool,
155 pub invalid_certs: bool,
156}
157
158impl Clone for Ctx {
159 fn clone(&self) -> Self {
160 Self {
161 data_dir: self.data_dir.clone(),
162 flight_ctx: if self.flight_ctx.get().is_some() {
163 let li = LateInit::default();
164 li.init(self.flight_ctx.get().cloned().unwrap());
165 li
166 } else {
167 LateInit::default()
168 },
169 formation_ctx: if self.formation_ctx.get().is_some() {
170 let li = LateInit::default();
171 li.init(self.formation_ctx.get().cloned().unwrap());
172 li
173 } else {
174 LateInit::default()
175 },
176 md_ctx: if self.md_ctx.get().is_some() {
177 let li = LateInit::default();
178 li.init(self.md_ctx.get().cloned().unwrap());
179 li
180 } else {
181 LateInit::default()
182 },
183 locks_ctx: if self.locks_ctx.get().is_some() {
184 let li = LateInit::default();
185 li.init(self.locks_ctx.get().cloned().unwrap());
186 li
187 } else {
188 LateInit::default()
189 },
190 restrict_ctx: if self.restrict_ctx.get().is_some() {
191 let li = LateInit::default();
192 li.init(self.restrict_ctx.get().cloned().unwrap());
193 li
194 } else {
195 LateInit::default()
196 },
197 conf_files: self.conf_files.clone(),
198 args: self.args.clone(),
199 db: self.db.clone(),
200 internal_run: self.internal_run,
201 did_init: self.did_init,
202 disable_pb: self.disable_pb,
203 registry: self.registry.clone(),
204 compute_url: self.compute_url.clone(),
205 identity_url: self.identity_url.clone(),
206 metadata_url: self.metadata_url.clone(),
207 locks_url: self.locks_url.clone(),
208 insecure_urls: self.insecure_urls,
209 invalid_certs: self.invalid_certs,
210 }
211 }
212}
213
214impl Default for Ctx {
215 fn default() -> Self {
216 Self {
217 data_dir: fs::data_dir(),
218 flight_ctx: LateInit::default(),
219 formation_ctx: LateInit::default(),
220 md_ctx: LateInit::default(),
221 locks_ctx: LateInit::default(),
222 restrict_ctx: LateInit::default(),
223 conf_files: Vec::new(),
224 args: Args::default(),
225 db: Db::default(),
226 internal_run: false,
227 did_init: false,
228 disable_pb: false,
229 compute_url: None,
230 identity_url: None,
231 metadata_url: None,
232 locks_url: None,
233 insecure_urls: false,
234 invalid_certs: false,
235 registry: DEFAULT_IMAGE_REGISTRY_URL.into(),
236 }
237 }
238}
239
240impl From<RawConfig> for Ctx {
241 fn from(cfg: RawConfig) -> Self {
242 Self {
243 data_dir: fs::data_dir(),
244 conf_files: cfg.loaded_from.clone(),
245 args: Args {
246 color: cfg.seaplane.color.unwrap_or_default(),
249 api_key: cfg.account.api_key,
250 ..Default::default()
251 },
252 registry: cfg
253 .seaplane
254 .default_registry_url
255 .unwrap_or_else(|| DEFAULT_IMAGE_REGISTRY_URL.into())
256 .trim_end_matches('/')
257 .to_string(),
258 compute_url: cfg.api.compute_url,
259 identity_url: cfg.api.identity_url,
260 metadata_url: cfg.api.metadata_url,
261 locks_url: cfg.api.locks_url,
262 did_init: cfg.did_init,
263 #[cfg(feature = "allow_insecure_urls")]
264 insecure_urls: cfg.danger_zone.allow_insecure_urls,
265 #[cfg(feature = "allow_invalid_certs")]
266 invalid_certs: cfg.danger_zone.allow_invalid_certs,
267 ..Self::default()
268 }
269 }
270}
271
272impl Ctx {
273 pub fn update_from_env(&mut self) -> Result<()> {
274 Ok(())
277 }
278
279 #[inline]
280 pub fn data_dir(&self) -> &Path { &self.data_dir }
281
282 pub fn conf_files(&self) -> &[PathBuf] { &self.conf_files }
283
284 pub fn flights_file(&self) -> PathBuf { self.data_dir.join(FLIGHTS_FILE) }
285
286 pub fn formations_file(&self) -> PathBuf { self.data_dir.join(FORMATIONS_FILE) }
287
288 pub fn persist_formations(&self) -> Result<()> {
290 self.db
291 .formations
292 .persist_if(!self.args.stateless)
293 .with_context(|| format!("Path: {:?}\n", self.formations_file()))
294 }
295
296 pub fn persist_flights(&self) -> Result<()> {
298 self.db
299 .flights
300 .persist_if(!self.args.stateless)
301 .with_context(|| format!("Path: {:?}\n", self.flights_file()))
302 }
303}
304
305#[derive(Debug, Default, Clone)]
307pub struct Db {
308 pub flights: Flights,
310
311 pub formations: Formations,
313
314 pub needs_persist: bool,
316}
317
318impl Db {
319 pub fn load<P: AsRef<Path>>(flights: P, formations: P) -> Result<Self> {
320 Self::load_if(flights, formations, true)
321 }
322
323 pub fn load_if<P: AsRef<Path>>(flights: P, formations: P, yes: bool) -> Result<Self> {
324 Ok(Self {
325 flights: FromDisk::load_if(flights, yes).unwrap_or_else(|| Ok(Flights::default()))?,
326 formations: FromDisk::load_if(formations, yes)
327 .unwrap_or_else(|| Ok(Formations::default()))?,
328 needs_persist: false,
329 })
330 }
331}
332
333#[derive(Debug)]
335pub struct LateInit<T> {
336 inner: OnceCell<T>,
337}
338
339impl<T> Default for LateInit<T> {
340 fn default() -> Self { Self { inner: OnceCell::default() } }
341}
342
343impl<T> LateInit<T> {
344 pub fn init(&self, val: T) { assert!(self.inner.set(val).is_ok()) }
345 pub fn get(&self) -> Option<&T> { self.inner.get() }
346 pub fn get_mut(&mut self) -> Option<&mut T> { self.inner.get_mut() }
347}
348
349impl<T: Default> LateInit<T> {
350 pub fn get_or_init(&self) -> &T { self.inner.get_or_init(|| T::default()) }
351 pub fn get_mut_or_init(&mut self) -> &mut T {
352 self.inner.get_or_init(|| T::default());
353 self.inner.get_mut().unwrap()
354 }
355}