11package bundle
22
33import (
4+ "fmt"
45 "os"
56 "path/filepath"
7+ "regexp"
68 "sort"
9+ "strconv"
710 "strings"
811
912 "github.com/crc-org/crc/v2/pkg/crc/constants"
@@ -23,6 +26,45 @@ func getPruneCmd() *cobra.Command {
2326 }
2427}
2528
29+ type bundleVersionInfo struct {
30+ name string
31+ major int
32+ minor int
33+ patch int
34+ arch string
35+ }
36+
37+ // parseBundleVersion parses a bundle filename like "crc_vfkit_4.19.13_arm64.crcbundle"
38+ // and extracts the version parts and architecture.
39+ func parseBundleVersion (filename string ) (bundleVersionInfo , error ) {
40+ re := regexp .MustCompile (`^crc(?:_okd|_microshift)?_(?:vfkit|libvirt|hyperv)_(\d+)\.(\d+)\.(\d+)_([a-z0-9]+)\.crcbundle$` )
41+ matches := re .FindStringSubmatch (filename )
42+ if matches == nil {
43+ return bundleVersionInfo {}, fmt .Errorf ("filename %q does not match expected bundle pattern" , filename )
44+ }
45+
46+ major , err := strconv .Atoi (matches [1 ])
47+ if err != nil {
48+ return bundleVersionInfo {}, fmt .Errorf ("invalid major version in %q: %w" , filename , err )
49+ }
50+ minor , err := strconv .Atoi (matches [2 ])
51+ if err != nil {
52+ return bundleVersionInfo {}, fmt .Errorf ("invalid minor version in %q: %w" , filename , err )
53+ }
54+ patch , err := strconv .Atoi (matches [3 ])
55+ if err != nil {
56+ return bundleVersionInfo {}, fmt .Errorf ("invalid patch version in %q: %w" , filename , err )
57+ }
58+
59+ return bundleVersionInfo {
60+ name : filename ,
61+ major : major ,
62+ minor : minor ,
63+ patch : patch ,
64+ arch : matches [4 ],
65+ }, nil
66+ }
67+
2668func runPrune (keep int ) error {
2769 cacheDir := constants .MachineCacheDir
2870 if _ , err := os .Stat (cacheDir ); os .IsNotExist (err ) {
@@ -35,37 +77,51 @@ func runPrune(keep int) error {
3577 return err
3678 }
3779
38- var bundleFiles []os. DirEntry
39- for _ , file := range files {
40- if strings . HasSuffix ( file . Name (), ".crcbundle" ) {
41- bundleFiles = append ( bundleFiles , file )
42- }
80+ // Group bundles by major.minor + arch
81+ type groupKey struct {
82+ major int
83+ minor int
84+ arch string
4385 }
86+ groups := make (map [groupKey ][]bundleVersionInfo )
4487
45- if len (bundleFiles ) <= keep {
46- logging .Infof ("Nothing to prune (found %d bundles, keeping %d)" , len (bundleFiles ), keep )
47- return nil
88+ for _ , file := range files {
89+ if ! strings .HasSuffix (file .Name (), ".crcbundle" ) {
90+ continue
91+ }
92+ info , err := parseBundleVersion (file .Name ())
93+ if err != nil {
94+ logging .Debugf ("Skipping unrecognized bundle file: %s" , file .Name ())
95+ continue
96+ }
97+ key := groupKey {major : info .major , minor : info .minor , arch : info .arch }
98+ groups [key ] = append (groups [key ], info )
4899 }
49100
50- // Sort by modification time, newest first
51- sort .Slice (bundleFiles , func (i , j int ) bool {
52- infoI , errI := bundleFiles [i ].Info ()
53- infoJ , errJ := bundleFiles [j ].Info ()
54- if errI != nil || errJ != nil {
55- // If we can't get info, treat as oldest (sort to end for pruning)
56- return errJ != nil && errI == nil
101+ pruned := false
102+ for _ , bundles := range groups {
103+ if len (bundles ) <= keep {
104+ continue
57105 }
58- return infoI .ModTime ().After (infoJ .ModTime ())
59- })
60-
61- for i := keep ; i < len (bundleFiles ); i ++ {
62- file := bundleFiles [i ]
63- filePath := filepath .Join (cacheDir , file .Name ())
64- logging .Infof ("Pruning old bundle: %s" , file .Name ())
65- if err := os .RemoveAll (filePath ); err != nil {
66- logging .Errorf ("Failed to remove %s: %v" , filePath , err )
106+
107+ // Sort by patch version descending (newest first)
108+ sort .Slice (bundles , func (i , j int ) bool {
109+ return bundles [i ].patch > bundles [j ].patch
110+ })
111+
112+ for i := keep ; i < len (bundles ); i ++ {
113+ filePath := filepath .Join (cacheDir , bundles [i ].name )
114+ logging .Infof ("Pruning old bundle: %s" , bundles [i ].name )
115+ if err := os .RemoveAll (filePath ); err != nil {
116+ logging .Errorf ("Failed to remove %s: %v" , filePath , err )
117+ }
118+ pruned = true
67119 }
68120 }
69121
122+ if ! pruned {
123+ logging .Infof ("Nothing to prune" )
124+ }
125+
70126 return nil
71127}
0 commit comments