@@ -21,7 +21,7 @@ use spk_schema::foundation::name::{PkgName, PkgNameBuf, RepositoryName, Reposito
2121use spk_schema:: foundation:: version:: { parse_version, Version } ;
2222use spk_schema:: ident:: VersionIdent ;
2323use spk_schema:: ident_build:: parsing:: embedded_source_package;
24- use spk_schema:: ident_build:: EmbeddedSource ;
24+ use spk_schema:: ident_build:: { EmbeddedSource , EmbeddedSourcePackage } ;
2525use spk_schema:: ident_ops:: TagPath ;
2626use spk_schema:: { AnyIdent , BuildIdent , FromYaml , Package , Recipe , Spec , SpecRecipe } ;
2727use tokio:: io:: AsyncReadExt ;
@@ -220,13 +220,33 @@ impl Storage for SPFSRepository {
220220 type Package = Spec ;
221221
222222 async fn get_concrete_package_builds ( & self , pkg : & VersionIdent ) -> Result < HashSet < BuildIdent > > {
223- let base = self . build_package_tag ( pkg) ?;
224- let builds: HashSet < _ > = self
225- . ls_tags ( & base)
226- . await
223+ // It is possible for a `spk/spec/pkgname/1.0.0/BUILDKEY` tag to
224+ // exist without a corresponding `spk/spk/pkgname/1.0.0/BUILDKEY`
225+ // tag. In this scenario, "pkgname" will appear in the results of
226+ // `list_packages` and `list_package_versions`, because those look at
227+ // the `spk/spec/...` spfs tag tree, i.e., this package will appear
228+ // in the output of `spk ls`. In order to make it possible to locate
229+ // the build spec, e.g., for `spk rm pkgname/1.0.0` to work, this
230+ // method needs to return a union of all the build tags of both the
231+ // `spk/spec/` and `spk/pkg/` tag trees.
232+ let spec_base = self . build_spec_tag ( pkg) ;
233+ let package_base = self . build_package_tag ( pkg) ;
234+
235+ let spec_tags = self . ls_tags ( & spec_base) ;
236+ let package_tags = self . ls_tags ( & package_base) ;
237+
238+ let ( spec_tags, package_tags) = tokio:: join!( spec_tags, package_tags) ;
239+
240+ let builds: HashSet < _ > = spec_tags
227241 . into_iter ( )
242+ . chain ( package_tags. into_iter ( ) )
228243 . filter_map ( |entry| match entry {
229- Ok ( EntryType :: Tag ( name) ) => Some ( name) ,
244+ Ok ( EntryType :: Tag ( name) )
245+ if !name. starts_with ( EmbeddedSourcePackage :: EMBEDDED_BY_PREFIX ) =>
246+ {
247+ Some ( name)
248+ }
249+ Ok ( EntryType :: Tag ( _) ) => None ,
230250 Ok ( EntryType :: Folder ( name) ) => Some ( name) ,
231251 Err ( _) => None ,
232252 } )
@@ -261,7 +281,7 @@ impl Storage for SPFSRepository {
261281 Err ( _) => None ,
262282 } )
263283 . filter_map ( |b| {
264- b. strip_prefix ( "embedded-by-" )
284+ b. strip_prefix ( EmbeddedSourcePackage :: EMBEDDED_BY_PREFIX )
265285 . and_then ( |encoded_ident| {
266286 data_encoding:: BASE32_NOPAD
267287 . decode ( encoded_ident. as_bytes ( ) )
@@ -317,7 +337,7 @@ impl Storage for SPFSRepository {
317337 package : & <Self :: Recipe as spk_schema:: Recipe >:: Output ,
318338 components : & HashMap < Component , spfs:: encoding:: Digest > ,
319339 ) -> Result < ( ) > {
320- let tag_path = self . build_package_tag ( package. ident ( ) ) ? ;
340+ let tag_path = self . build_package_tag ( package. ident ( ) ) ;
321341
322342 // We will also publish the 'run' component in the old style
323343 // for compatibility with older versions of the spk command.
@@ -456,28 +476,97 @@ impl Storage for SPFSRepository {
456476 }
457477
458478 async fn remove_package_from_storage ( & self , pkg : & BuildIdent ) -> Result < ( ) > {
459- for tag_spec in
460- with_cache_policy ! ( self , CachePolicy :: BypassCache , { self . lookup_package( pkg) } )
461- . await ?
462- . tags ( )
463- {
464- match self . inner . remove_tag_stream ( tag_spec) . await {
465- Err ( spfs:: Error :: UnknownReference ( _) ) => ( ) ,
466- res => res?,
479+ // The three things this method is responsible for deleting are:
480+ //
481+ // 1. Component build tags like: `spk/pkg/example/4.2.1/GMTG3CXY/build`.
482+ // 2. Legacy build tags like : `spk/pkg/example/4.2.1/GMTG3CXY`.
483+ // 3. Build recipe tags like : `spk/spec/example/4.2.1/GMTG3CXY`.
484+ //
485+ // It should make an effort to delete all three types before returning
486+ // any failures.
487+
488+ let component_tags = async {
489+ let mut deleted_something = false ;
490+
491+ for tag_spec in
492+ with_cache_policy ! ( self , CachePolicy :: BypassCache , { self . lookup_package( pkg) } )
493+ . await ?
494+ . tags ( )
495+ {
496+ match self . inner . remove_tag_stream ( tag_spec) . await {
497+ Err ( spfs:: Error :: UnknownReference ( _) ) => ( ) ,
498+ Ok ( _) => deleted_something = true ,
499+ res => res?,
500+ } ;
467501 }
468- }
469- // because we double-publish packages to be visible/compatible
470- // with the old repo tag structure, we must also try to remove
471- // the legacy version of the tag after removing the discovered
472- // as it may still be there and cause the removal to be ineffective
473- if let Ok ( legacy_tag) = spfs:: tracking:: TagSpec :: parse ( self . build_package_tag ( pkg) ?) {
474- match self . inner . remove_tag_stream ( & legacy_tag) . await {
475- Err ( spfs:: Error :: UnknownReference ( _) ) => ( ) ,
476- res => res?,
502+ Ok :: < _ , Error > ( deleted_something)
503+ } ;
504+
505+ let legacy_tags = async {
506+ // because we double-publish packages to be visible/compatible
507+ // with the old repo tag structure, we must also try to remove
508+ // the legacy version of the tag after removing the discovered
509+ // as it may still be there and cause the removal to be ineffective
510+ let mut deleted_something = false ;
511+
512+ if let Ok ( legacy_tag) = spfs:: tracking:: TagSpec :: parse ( self . build_package_tag ( pkg) ) {
513+ match self . inner . remove_tag_stream ( & legacy_tag) . await {
514+ Err ( spfs:: Error :: UnknownReference ( _) ) => ( ) ,
515+ Ok ( _) => deleted_something = true ,
516+ res => res?,
517+ }
518+ } ;
519+ Ok :: < _ , Error > ( deleted_something)
520+ } ;
521+
522+ let build_recipe_tags = async {
523+ let tag_path = self . build_spec_tag ( pkg) ;
524+ let tag_spec = spfs:: tracking:: TagSpec :: parse ( & tag_path) ?;
525+ match self . inner . remove_tag_stream ( & tag_spec) . await {
526+ Err ( spfs:: Error :: UnknownReference ( _) ) => Err ( Error :: SpkValidatorsError (
527+ spk_schema:: validators:: Error :: PackageNotFoundError ( pkg. to_any ( ) ) ,
528+ ) ) ,
529+ Err ( err) => Err ( err. into ( ) ) ,
530+ Ok ( _) => Ok ( true ) ,
477531 }
478- }
532+ } ;
533+
534+ let ( component_tags_result, legacy_tags_result, build_recipe_tags_result) =
535+ tokio:: join!( component_tags, legacy_tags, build_recipe_tags) ;
536+
537+ // Still invalidate caches in case some of individual deletions were
538+ // successful.
479539 self . invalidate_caches ( ) ;
480- Ok ( ( ) )
540+
541+ // If any of the three sub-tasks successfully deleted something *and*
542+ // the only failures otherwise was `PackageNotFoundError`, then return
543+ // success. Since something was deleted then the package was
544+ // technically "found."
545+ [
546+ component_tags_result,
547+ build_recipe_tags_result,
548+ // Check legacy tags last because errors deleting legacy tags are
549+ // less important.
550+ legacy_tags_result,
551+ ]
552+ . into_iter ( )
553+ . fold ( Ok :: < _ , Error > ( false ) , |acc, x| match ( acc, x) {
554+ // Preserve the first non-PackageNotFoundError encountered.
555+ ( Err ( err) , _) if !err. is_package_not_found ( ) => Err ( err) ,
556+ // Incoming error is not PackageNotFoundError.
557+ ( _, Err ( err) ) if !err. is_package_not_found ( ) => Err ( err) ,
558+ // Successes merge with successes and retain "deleted
559+ // something" if either did.
560+ ( Ok ( x) , Ok ( y) ) => Ok ( x || y) ,
561+ // Having successfully deleted something trumps
562+ // `PackageNotFound`.
563+ ( Ok ( true ) , Err ( err) ) if err. is_package_not_found ( ) => Ok ( true ) ,
564+ ( Err ( err) , Ok ( true ) ) if err. is_package_not_found ( ) => Ok ( true ) ,
565+ // Otherwise, keep the prevailing error.
566+ ( Err ( err) , _) => Err ( err) ,
567+ ( _, Err ( err) ) => Err ( err) ,
568+ } )
569+ . map ( |_| ( ) )
481570 }
482571}
483572
@@ -699,7 +788,7 @@ impl Repository for SPFSRepository {
699788 let components = stored. into_components ( ) ;
700789 for ( name, tag_spec) in components. into_iter ( ) {
701790 let tag = self . inner . resolve_tag ( & tag_spec) . await ?;
702- let new_tag_path = self . build_package_tag ( & build) ? . join ( name. to_string ( ) ) ;
791+ let new_tag_path = self . build_package_tag ( & build) . join ( name. to_string ( ) ) ;
703792 let new_tag_spec = spfs:: tracking:: TagSpec :: parse ( & new_tag_path) ?;
704793
705794 // NOTE(rbottriell): this copying process feels annoying
@@ -855,7 +944,7 @@ impl SPFSRepository {
855944 /// (with or without package components)
856945 async fn lookup_package ( & self , pkg : & BuildIdent ) -> Result < StoredPackage > {
857946 use spfs:: tracking:: TagSpec ;
858- let tag_path = self . build_package_tag ( pkg) ? ;
947+ let tag_path = self . build_package_tag ( pkg) ;
859948 let tag_specs: HashMap < Component , TagSpec > = self
860949 . ls_tags ( & tag_path)
861950 . await
@@ -881,15 +970,15 @@ impl SPFSRepository {
881970 }
882971
883972 /// Construct an spfs tag string to represent a binary package layer.
884- fn build_package_tag < T > ( & self , pkg : & T ) -> Result < RelativePathBuf >
973+ fn build_package_tag < T > ( & self , pkg : & T ) -> RelativePathBuf
885974 where
886975 T : TagPath ,
887976 {
888977 let mut tag = RelativePathBuf :: from ( "spk" ) ;
889978 tag. push ( "pkg" ) ;
890979 tag. push ( pkg. tag_path ( ) ) ;
891980
892- Ok ( tag)
981+ tag
893982 }
894983
895984 /// Construct an spfs tag string to represent a spec file blob.
0 commit comments