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  }