code.gitea.io/gitea@v1.21.7/services/packages/cleanup/cleanup.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package container 5 6 import ( 7 "context" 8 "fmt" 9 "time" 10 11 "code.gitea.io/gitea/models/db" 12 packages_model "code.gitea.io/gitea/models/packages" 13 user_model "code.gitea.io/gitea/models/user" 14 "code.gitea.io/gitea/modules/log" 15 packages_module "code.gitea.io/gitea/modules/packages" 16 "code.gitea.io/gitea/modules/util" 17 packages_service "code.gitea.io/gitea/services/packages" 18 alpine_service "code.gitea.io/gitea/services/packages/alpine" 19 cargo_service "code.gitea.io/gitea/services/packages/cargo" 20 container_service "code.gitea.io/gitea/services/packages/container" 21 debian_service "code.gitea.io/gitea/services/packages/debian" 22 ) 23 24 // Task method to execute cleanup rules and cleanup expired package data 25 func CleanupTask(ctx context.Context, olderThan time.Duration) error { 26 if err := ExecuteCleanupRules(ctx); err != nil { 27 return err 28 } 29 30 return CleanupExpiredData(ctx, olderThan) 31 } 32 33 func ExecuteCleanupRules(outerCtx context.Context) error { 34 ctx, committer, err := db.TxContext(outerCtx) 35 if err != nil { 36 return err 37 } 38 defer committer.Close() 39 40 err = packages_model.IterateEnabledCleanupRules(ctx, func(ctx context.Context, pcr *packages_model.PackageCleanupRule) error { 41 select { 42 case <-outerCtx.Done(): 43 return db.ErrCancelledf("While processing package cleanup rules") 44 default: 45 } 46 47 if err := pcr.CompiledPattern(); err != nil { 48 return fmt.Errorf("CleanupRule [%d]: CompilePattern failed: %w", pcr.ID, err) 49 } 50 51 olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays) 52 53 packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type) 54 if err != nil { 55 return fmt.Errorf("CleanupRule [%d]: GetPackagesByType failed: %w", pcr.ID, err) 56 } 57 58 anyVersionDeleted := false 59 for _, p := range packages { 60 pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 61 PackageID: p.ID, 62 IsInternal: util.OptionalBoolFalse, 63 Sort: packages_model.SortCreatedDesc, 64 Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200), 65 }) 66 if err != nil { 67 return fmt.Errorf("CleanupRule [%d]: SearchVersions failed: %w", pcr.ID, err) 68 } 69 versionDeleted := false 70 for _, pv := range pvs { 71 if pcr.Type == packages_model.TypeContainer { 72 if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil { 73 return fmt.Errorf("CleanupRule [%d]: container.ShouldBeSkipped failed: %w", pcr.ID, err) 74 } else if skip { 75 log.Debug("Rule[%d]: keep '%s/%s' (container)", pcr.ID, p.Name, pv.Version) 76 continue 77 } 78 } 79 80 toMatch := pv.LowerVersion 81 if pcr.MatchFullName { 82 toMatch = p.LowerName + "/" + pv.LowerVersion 83 } 84 85 if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) { 86 log.Debug("Rule[%d]: keep '%s/%s' (keep pattern)", pcr.ID, p.Name, pv.Version) 87 continue 88 } 89 if pv.CreatedUnix.AsLocalTime().After(olderThan) { 90 log.Debug("Rule[%d]: keep '%s/%s' (remove days)", pcr.ID, p.Name, pv.Version) 91 continue 92 } 93 if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) { 94 log.Debug("Rule[%d]: keep '%s/%s' (remove pattern)", pcr.ID, p.Name, pv.Version) 95 continue 96 } 97 98 log.Debug("Rule[%d]: remove '%s/%s'", pcr.ID, p.Name, pv.Version) 99 100 if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { 101 return fmt.Errorf("CleanupRule [%d]: DeletePackageVersionAndReferences failed: %w", pcr.ID, err) 102 } 103 104 versionDeleted = true 105 anyVersionDeleted = true 106 } 107 108 if versionDeleted { 109 if pcr.Type == packages_model.TypeCargo { 110 owner, err := user_model.GetUserByID(ctx, pcr.OwnerID) 111 if err != nil { 112 return fmt.Errorf("GetUserByID failed: %w", err) 113 } 114 if err := cargo_service.UpdatePackageIndexIfExists(ctx, owner, owner, p.ID); err != nil { 115 return fmt.Errorf("CleanupRule [%d]: cargo.UpdatePackageIndexIfExists failed: %w", pcr.ID, err) 116 } 117 } 118 } 119 } 120 121 if anyVersionDeleted { 122 if pcr.Type == packages_model.TypeDebian { 123 if err := debian_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil { 124 return fmt.Errorf("CleanupRule [%d]: debian.BuildAllRepositoryFiles failed: %w", pcr.ID, err) 125 } 126 } else if pcr.Type == packages_model.TypeAlpine { 127 if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil { 128 return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err) 129 } 130 } 131 } 132 return nil 133 }) 134 if err != nil { 135 return err 136 } 137 138 return committer.Commit() 139 } 140 141 func CleanupExpiredData(outerCtx context.Context, olderThan time.Duration) error { 142 ctx, committer, err := db.TxContext(outerCtx) 143 if err != nil { 144 return err 145 } 146 defer committer.Close() 147 148 if err := container_service.Cleanup(ctx, olderThan); err != nil { 149 return err 150 } 151 152 ps, err := packages_model.FindUnreferencedPackages(ctx) 153 if err != nil { 154 return err 155 } 156 for _, p := range ps { 157 if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil { 158 return err 159 } 160 if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil { 161 return err 162 } 163 } 164 165 pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan) 166 if err != nil { 167 return err 168 } 169 170 for _, pb := range pbs { 171 if err := packages_model.DeleteBlobByID(ctx, pb.ID); err != nil { 172 return err 173 } 174 } 175 176 if err := committer.Commit(); err != nil { 177 return err 178 } 179 180 contentStore := packages_module.NewContentStore() 181 for _, pb := range pbs { 182 if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil { 183 log.Error("Error deleting package blob [%v]: %v", pb.ID, err) 184 } 185 } 186 187 return nil 188 }