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