Skip to content

Commit 1856ba8

Browse files
authored
Merge pull request #1980 from fermyon/rejigger-spin-up
Fix `spin up --help` for unbuilt apps
2 parents 8d9a771 + e3cccea commit 1856ba8

File tree

2 files changed

+173
-112
lines changed

2 files changed

+173
-112
lines changed

src/commands/up.rs

Lines changed: 72 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
mod app_source;
2+
13
use std::{
2-
collections::HashSet,
34
ffi::OsString,
4-
fmt::{Debug, Display},
5+
fmt::Debug,
56
path::{Path, PathBuf},
67
};
78

8-
use anyhow::{anyhow, bail, ensure, Context, Result};
9+
use anyhow::{anyhow, bail, Context, Result};
910
use clap::{CommandFactory, Parser};
1011
use reqwest::Url;
1112
use spin_app::locked::LockedApp;
@@ -16,6 +17,8 @@ use tempfile::TempDir;
1617

1718
use crate::opts::*;
1819

20+
use self::app_source::{AppSource, ResolvedAppSource};
21+
1922
const APPLICATION_OPT: &str = "APPLICATION";
2023

2124
/// Start the Fermyon runtime.
@@ -120,7 +123,7 @@ impl UpCommand {
120123
}
121124

122125
async fn run_inner(self) -> Result<()> {
123-
let app_source = self.resolve_app_source();
126+
let app_source = self.app_source();
124127

125128
if app_source == AppSource::None {
126129
if self.help {
@@ -144,20 +147,19 @@ impl UpCommand {
144147
.canonicalize()
145148
.context("Could not canonicalize working directory")?;
146149

147-
let mut locked_app = match &app_source {
148-
AppSource::None => bail!("Internal error - should have shown help"),
149-
AppSource::File(path) => self.prepare_app_from_file(path, &working_dir).await?,
150-
AppSource::OciRegistry(oci) => self.prepare_app_from_oci(oci, &working_dir).await?,
151-
AppSource::Unresolvable(err) => bail!("{err}"),
152-
};
150+
let resolved_app_source = self.resolve_app_source(&app_source, &working_dir).await?;
153151

154-
let trigger_cmd = trigger_command_from_locked_app(&locked_app)
152+
let trigger_cmd = trigger_command_for_resolved_app_source(&resolved_app_source)
155153
.with_context(|| format!("Couldn't find trigger executor for {app_source}"))?;
156154

157155
if self.help {
158156
return self.run_trigger(trigger_cmd, None).await;
159157
}
160158

159+
let mut locked_app = self
160+
.load_resolved_app_source(resolved_app_source, &working_dir)
161+
.await?;
162+
161163
self.update_locked_app(&mut locked_app);
162164

163165
let local_app_dir = app_source.local_app_dir().map(Into::into);
@@ -240,11 +242,11 @@ impl UpCommand {
240242
}
241243
}
242244

243-
fn resolve_app_source(&self) -> AppSource {
245+
fn app_source(&self) -> AppSource {
244246
match (&self.app_source, &self.file_source, &self.registry_source) {
245247
(None, None, None) => self.default_manifest_or_none(),
246-
(Some(source), None, None) => Self::infer_source(source),
247-
(None, Some(file), None) => Self::infer_file_source(file.to_owned()),
248+
(Some(source), None, None) => AppSource::infer_source(source),
249+
(None, Some(file), None) => AppSource::infer_file_source(file.to_owned()),
248250
(None, None, Some(reference)) => AppSource::OciRegistry(reference.to_owned()),
249251
_ => AppSource::unresolvable("More than one application source was specified"),
250252
}
@@ -265,24 +267,6 @@ impl UpCommand {
265267
}
266268
}
267269

268-
fn infer_source(source: &str) -> AppSource {
269-
let path = PathBuf::from(source);
270-
if path.exists() {
271-
Self::infer_file_source(path)
272-
} else if spin_oci::is_probably_oci_reference(source) {
273-
AppSource::OciRegistry(source.to_owned())
274-
} else {
275-
AppSource::Unresolvable(format!("File or directory '{source}' not found. If you meant to load from a registry, use the `--from-registry` option."))
276-
}
277-
}
278-
279-
fn infer_file_source(path: impl Into<PathBuf>) -> AppSource {
280-
match spin_common::paths::resolve_manifest_file_path(path.into()) {
281-
Ok(file) => AppSource::File(file),
282-
Err(e) => AppSource::Unresolvable(e.to_string()),
283-
}
284-
}
285-
286270
fn trigger_args_look_file_like(&self) -> bool {
287271
// Heuristic for the user typing `spin up foo` instead of `spin up -f foo` - in the
288272
// first case `foo` gets interpreted as a trigger arg which is probably not what the
@@ -308,29 +292,54 @@ impl UpCommand {
308292
Ok(locked_url)
309293
}
310294

311-
async fn prepare_app_from_file(
295+
// Take the AppSource and do the minimum amount of work necessary to
296+
// be able to resolve the trigger executor.
297+
async fn resolve_app_source(
312298
&self,
313-
manifest_path: &Path,
299+
app_source: &AppSource,
314300
working_dir: &Path,
315-
) -> Result<LockedApp> {
316-
let files_mount_strategy = if self.direct_mounts {
317-
FilesMountStrategy::Direct
318-
} else {
319-
FilesMountStrategy::Copy(working_dir.join("assets"))
320-
};
321-
spin_loader::from_file(manifest_path, files_mount_strategy)
322-
.await
323-
.with_context(|| format!("Failed to load manifest from {manifest_path:?}"))
301+
) -> anyhow::Result<ResolvedAppSource> {
302+
Ok(match &app_source {
303+
AppSource::File(path) => ResolvedAppSource::File {
304+
manifest_path: path.clone(),
305+
manifest: spin_manifest::manifest_from_file(path)?,
306+
},
307+
// TODO: We could make the `--help` experience a little faster if
308+
// we could fetch just the locked app JSON at this stage.
309+
AppSource::OciRegistry(reference) => {
310+
let mut client = spin_oci::Client::new(self.insecure, None)
311+
.await
312+
.context("cannot create registry client")?;
313+
314+
let locked_app = OciLoader::new(working_dir)
315+
.load_app(&mut client, reference)
316+
.await?;
317+
ResolvedAppSource::OciRegistry { locked_app }
318+
}
319+
AppSource::Unresolvable(err) => bail!("{err}"),
320+
AppSource::None => bail!("Internal error - should have shown help"),
321+
})
324322
}
325323

326-
async fn prepare_app_from_oci(&self, reference: &str, working_dir: &Path) -> Result<LockedApp> {
327-
let mut client = spin_oci::Client::new(self.insecure, None)
328-
.await
329-
.context("cannot create registry client")?;
330-
331-
OciLoader::new(working_dir)
332-
.load_app(&mut client, reference)
333-
.await
324+
// Finish preparing a ResolvedAppSource for execution.
325+
async fn load_resolved_app_source(
326+
&self,
327+
resolved: ResolvedAppSource,
328+
working_dir: &Path,
329+
) -> anyhow::Result<LockedApp> {
330+
match resolved {
331+
ResolvedAppSource::File { manifest_path, .. } => {
332+
let files_mount_strategy = if self.direct_mounts {
333+
FilesMountStrategy::Direct
334+
} else {
335+
FilesMountStrategy::Copy(working_dir.join("assets"))
336+
};
337+
spin_loader::from_file(&manifest_path, files_mount_strategy)
338+
.await
339+
.with_context(|| format!("Failed to load manifest from {manifest_path:?}"))
340+
}
341+
ResolvedAppSource::OciRegistry { locked_app } => Ok(locked_app),
342+
}
334343
}
335344

336345
fn update_locked_app(&self, locked_app: &mut LockedApp) {
@@ -409,17 +418,8 @@ fn trigger_command(trigger_type: &str) -> Vec<String> {
409418
vec!["trigger".to_owned(), trigger_type.to_owned()]
410419
}
411420

412-
fn trigger_command_from_locked_app(locked_app: &LockedApp) -> Result<Vec<String>> {
413-
let trigger_type = {
414-
let types = locked_app
415-
.triggers
416-
.iter()
417-
.map(|t| t.trigger_type.as_str())
418-
.collect::<HashSet<_>>();
419-
ensure!(!types.is_empty(), "no triggers in app");
420-
ensure!(types.len() == 1, "multiple trigger types not yet supported");
421-
types.into_iter().next().unwrap()
422-
};
421+
fn trigger_command_for_resolved_app_source(resolved: &ResolvedAppSource) -> Result<Vec<String>> {
422+
let trigger_type = resolved.trigger_type()?;
423423

424424
match trigger_type {
425425
"http" | "redis" => Ok(trigger_command(trigger_type)),
@@ -430,50 +430,10 @@ fn trigger_command_from_locked_app(locked_app: &LockedApp) -> Result<Vec<String>
430430
}
431431
}
432432

433-
#[derive(Debug, PartialEq, Eq)]
434-
enum AppSource {
435-
None,
436-
File(PathBuf),
437-
OciRegistry(String),
438-
Unresolvable(String),
439-
}
440-
441-
impl AppSource {
442-
fn unresolvable(message: impl Into<String>) -> Self {
443-
Self::Unresolvable(message.into())
444-
}
445-
446-
fn local_app_dir(&self) -> Option<&Path> {
447-
match self {
448-
Self::File(path) => path.parent().or_else(|| {
449-
tracing::warn!("Error finding local app dir from source {path:?}");
450-
None
451-
}),
452-
_ => None,
453-
}
454-
}
455-
456-
async fn build(&self) -> anyhow::Result<()> {
457-
match self {
458-
Self::File(path) => spin_build::build(path, &[]).await,
459-
_ => Ok(()),
460-
}
461-
}
462-
}
463-
464-
impl Display for AppSource {
465-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466-
match self {
467-
AppSource::None => write!(f, "<no source>"),
468-
AppSource::File(path) => write!(f, "local app {path:?}"),
469-
AppSource::OciRegistry(reference) => write!(f, "remote app {reference:?}"),
470-
AppSource::Unresolvable(s) => write!(f, "unknown app source: {s:?}"),
471-
}
472-
}
473-
}
474-
475433
#[cfg(test)]
476434
mod test {
435+
use crate::commands::up::app_source::AppSource;
436+
477437
use super::*;
478438

479439
fn repo_path(path: &str) -> String {
@@ -490,7 +450,7 @@ mod test {
490450
app_source: Some(file.clone()),
491451
..Default::default()
492452
}
493-
.resolve_app_source();
453+
.app_source();
494454

495455
assert_eq!(AppSource::File(PathBuf::from(file)), source);
496456
}
@@ -503,7 +463,7 @@ mod test {
503463
app_source: Some(dir.clone()),
504464
..Default::default()
505465
}
506-
.resolve_app_source();
466+
.app_source();
507467

508468
assert_eq!(
509469
AppSource::File(PathBuf::from(dir).join("spin.toml")),
@@ -519,7 +479,7 @@ mod test {
519479
app_source: Some(file),
520480
..Default::default()
521481
}
522-
.resolve_app_source();
482+
.app_source();
523483

524484
assert!(matches!(source, AppSource::Unresolvable(_)));
525485
}
@@ -532,7 +492,7 @@ mod test {
532492
app_source: Some(file),
533493
..Default::default()
534494
}
535-
.resolve_app_source();
495+
.app_source();
536496

537497
assert!(matches!(source, AppSource::Unresolvable(_)));
538498
}
@@ -545,7 +505,7 @@ mod test {
545505
app_source: Some(dir),
546506
..Default::default()
547507
}
548-
.resolve_app_source();
508+
.app_source();
549509

550510
assert!(matches!(source, AppSource::Unresolvable(_)));
551511
}
@@ -558,7 +518,7 @@ mod test {
558518
app_source: Some(reference.clone()),
559519
..Default::default()
560520
}
561-
.resolve_app_source();
521+
.app_source();
562522

563523
assert_eq!(AppSource::OciRegistry(reference), source);
564524
}
@@ -572,7 +532,7 @@ mod test {
572532
app_source: Some(reference.clone()),
573533
..Default::default()
574534
}
575-
.resolve_app_source();
535+
.app_source();
576536

577537
assert_eq!(AppSource::OciRegistry(reference), source);
578538
}
@@ -585,7 +545,7 @@ mod test {
585545
app_source: Some(garbage),
586546
..Default::default()
587547
}
588-
.resolve_app_source();
548+
.app_source();
589549

590550
// Honestly I feel Unresolvable might be a bit weak sauce for this case
591551
assert!(matches!(source, AppSource::Unresolvable(_)));

0 commit comments

Comments
 (0)