1+ mod app_source;
2+
13use 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 } ;
910use clap:: { CommandFactory , Parser } ;
1011use reqwest:: Url ;
1112use spin_app:: locked:: LockedApp ;
@@ -16,6 +17,8 @@ use tempfile::TempDir;
1617
1718use crate :: opts:: * ;
1819
20+ use self :: app_source:: { AppSource , ResolvedAppSource } ;
21+
1922const 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) ]
476434mod 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