diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go index 0920beac5c..f3a1509406 100644 --- a/routers/web/shared/packages/packages.go +++ b/routers/web/shared/packages/packages.go @@ -7,20 +7,18 @@ import ( "errors" "fmt" "net/http" - "time" packages_model "forgejo.org/models/packages" repo_model "forgejo.org/models/repo" user_model "forgejo.org/models/user" "forgejo.org/modules/base" "forgejo.org/modules/log" - "forgejo.org/modules/optional" "forgejo.org/modules/util" "forgejo.org/modules/web" "forgejo.org/services/context" "forgejo.org/services/forms" cargo_service "forgejo.org/services/packages/cargo" - container_service "forgejo.org/services/packages/container" + cleanup_service "forgejo.org/services/packages/cleanup" ) func SetPackagesContext(ctx *context.Context, owner *user_model.User) { @@ -147,66 +145,18 @@ func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) { return } - if err := pcr.CompiledPattern(); err != nil { - ctx.ServerError("CompiledPattern", err) - return - } - - olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays) - - packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type) + versionsToRemove, err := cleanup_service.GetCleanupTargets(ctx, pcr, false) if err != nil { - ctx.ServerError("GetPackagesByType", err) + ctx.ServerError("GetCleanupTargets", err) return } - - versionsToRemove := make([]*packages_model.PackageDescriptor, 0, 10) - - for _, p := range packages { - pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ - PackageID: p.ID, - IsInternal: optional.Some(false), - Sort: packages_model.SortCreatedDesc, - }) - if err != nil { - ctx.ServerError("SearchVersions", err) - return - } - keep := min(len(pvs), pcr.KeepCount) - for _, pv := range pvs[keep:] { - if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil { - ctx.ServerError("ShouldBeSkipped", err) - return - } else if skip { - continue - } - - toMatch := pv.LowerVersion - if pcr.MatchFullName { - toMatch = p.LowerName + "/" + pv.LowerVersion - } - - if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) { - continue - } - if pv.CreatedUnix.AsLocalTime().After(olderThan) { - continue - } - if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) { - continue - } - - pd, err := packages_model.GetPackageDescriptor(ctx, pv) - if err != nil { - ctx.ServerError("GetPackageDescriptor", err) - return - } - versionsToRemove = append(versionsToRemove, pd) - } + packageDescriptors := make([]*packages_model.PackageDescriptor, len(versionsToRemove)) + for i := range len(versionsToRemove) { + packageDescriptors[i] = versionsToRemove[i].PackageDescriptor } ctx.Data["CleanupRule"] = pcr - ctx.Data["VersionsToRemove"] = versionsToRemove + ctx.Data["VersionsToRemove"] = packageDescriptors } func getCleanupRuleByContext(ctx *context.Context, owner *user_model.User) *packages_model.PackageCleanupRule { diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go index d938ac6970..fac2e62540 100644 --- a/services/packages/cleanup/cleanup.go +++ b/services/packages/cleanup/cleanup.go @@ -47,76 +47,29 @@ func ExecuteCleanupRules(outerCtx context.Context) error { default: } - if err := pcr.CompiledPattern(); err != nil { - return fmt.Errorf("CleanupRule [%d]: CompilePattern failed: %w", pcr.ID, err) - } - - olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays) - - packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type) + versionsToRemove, err := GetCleanupTargets(ctx, pcr, true) if err != nil { - return fmt.Errorf("CleanupRule [%d]: GetPackagesByType failed: %w", pcr.ID, err) + return fmt.Errorf("CleanupRule [%d]: GetCleanupTargets failed: %w", pcr.ID, err) } anyVersionDeleted := false - for _, p := range packages { - pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ - PackageID: p.ID, - IsInternal: optional.Some(false), - Sort: packages_model.SortCreatedDesc, - }) - if err != nil { - return fmt.Errorf("CleanupRule [%d]: SearchVersions failed: %w", pcr.ID, err) + packageWithVersionDeleted := make(map[int64]bool) // set of Package.ID's where at least one package version was removed + for _, ct := range versionsToRemove { + if err := packages_service.DeletePackageVersionAndReferences(ctx, ct.PackageVersion); err != nil { + return fmt.Errorf("CleanupRule [%d]: DeletePackageVersionAndReferences failed: %w", pcr.ID, err) } - versionDeleted := false - keep := min(len(pvs), pcr.KeepCount) - for _, pv := range pvs[keep:] { - if pcr.Type == packages_model.TypeContainer { - if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil { - return fmt.Errorf("CleanupRule [%d]: container.ShouldBeSkipped failed: %w", pcr.ID, err) - } else if skip { - log.Debug("Rule[%d]: keep '%s/%s' (container)", pcr.ID, p.Name, pv.Version) - continue - } - } + packageWithVersionDeleted[ct.Package.ID] = true + anyVersionDeleted = true + } - toMatch := pv.LowerVersion - if pcr.MatchFullName { - toMatch = p.LowerName + "/" + pv.LowerVersion + if pcr.Type == packages_model.TypeCargo { + for packageID := range packageWithVersionDeleted { + owner, err := user_model.GetUserByID(ctx, pcr.OwnerID) + if err != nil { + return fmt.Errorf("GetUserByID failed: %w", err) } - - if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) { - log.Debug("Rule[%d]: keep '%s/%s' (keep pattern)", pcr.ID, p.Name, pv.Version) - continue - } - if pv.CreatedUnix.AsLocalTime().After(olderThan) { - log.Debug("Rule[%d]: keep '%s/%s' (remove days)", pcr.ID, p.Name, pv.Version) - continue - } - if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) { - log.Debug("Rule[%d]: keep '%s/%s' (remove pattern)", pcr.ID, p.Name, pv.Version) - continue - } - - log.Debug("Rule[%d]: remove '%s/%s'", pcr.ID, p.Name, pv.Version) - - if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { - return fmt.Errorf("CleanupRule [%d]: DeletePackageVersionAndReferences failed: %w", pcr.ID, err) - } - - versionDeleted = true - anyVersionDeleted = true - } - - if versionDeleted { - if pcr.Type == packages_model.TypeCargo { - owner, err := user_model.GetUserByID(ctx, pcr.OwnerID) - if err != nil { - return fmt.Errorf("GetUserByID failed: %w", err) - } - if err := cargo_service.UpdatePackageIndexIfExists(ctx, owner, owner, p.ID); err != nil { - return fmt.Errorf("CleanupRule [%d]: cargo.UpdatePackageIndexIfExists failed: %w", pcr.ID, err) - } + if err := cargo_service.UpdatePackageIndexIfExists(ctx, owner, owner, packageID); err != nil { + return fmt.Errorf("CleanupRule [%d]: cargo.UpdatePackageIndexIfExists failed: %w", pcr.ID, err) } } } @@ -154,6 +107,83 @@ func ExecuteCleanupRules(outerCtx context.Context) error { return committer.Commit() } +type CleanupTarget struct { + Package *packages_model.Package + PackageVersion *packages_model.PackageVersion + PackageDescriptor *packages_model.PackageDescriptor +} + +func GetCleanupTargets(ctx context.Context, pcr *packages_model.PackageCleanupRule, skipPackageDescriptor bool) ([]*CleanupTarget, error) { + if err := pcr.CompiledPattern(); err != nil { + return nil, err + } + + olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays) + + packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type) + if err != nil { + return nil, fmt.Errorf("failure to GetPackagesByType for package cleanup rule: %w", err) + } + + versionsToRemove := make([]*CleanupTarget, 0, 10) + + for _, p := range packages { + pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + PackageID: p.ID, + IsInternal: optional.Some(false), + Sort: packages_model.SortCreatedDesc, + }) + if err != nil { + return nil, fmt.Errorf("failure to SearchVersions for package cleanup rule: %w", err) + } + keep := min(len(pvs), pcr.KeepCount) + for _, pv := range pvs[keep:] { + if pcr.Type == packages_model.TypeContainer { + if skip := container_service.ShouldBeSkipped(pv); skip { + log.Debug("Rule[%d]: keep '%s/%s' (container)", pcr.ID, p.Name, pv.Version) + continue + } + } + + toMatch := pv.LowerVersion + if pcr.MatchFullName { + toMatch = p.LowerName + "/" + pv.LowerVersion + } + + if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) { + log.Debug("Rule[%d]: keep '%s/%s' (keep pattern)", pcr.ID, p.Name, pv.Version) + continue + } + if pv.CreatedUnix.AsLocalTime().After(olderThan) { + log.Debug("Rule[%d]: keep '%s/%s' (remove days)", pcr.ID, p.Name, pv.Version) + continue + } + if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) { + log.Debug("Rule[%d]: keep '%s/%s' (remove pattern)", pcr.ID, p.Name, pv.Version) + continue + } + + log.Debug("Rule[%d]: remove '%s/%s'", pcr.ID, p.Name, pv.Version) + + var pd *packages_model.PackageDescriptor + // GetPackageDescriptor is a bit expensive and can be skipped; only used for cleanup preview to display the package to the UI + if !skipPackageDescriptor { + pd, err = packages_model.GetPackageDescriptor(ctx, pv) + if err != nil { + return nil, fmt.Errorf("failure to GetPackageDescriptor for package cleanup rule: %w", err) + } + } + versionsToRemove = append(versionsToRemove, &CleanupTarget{ + Package: p, + PackageVersion: pv, + PackageDescriptor: pd, + }) + } + } + + return versionsToRemove, nil +} + func CleanupExpiredData(outerCtx context.Context, olderThan time.Duration) error { ctx, committer, err := db.TxContext(outerCtx) if err != nil { diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go index a4ae7118f5..81fb6ab2f2 100644 --- a/services/packages/container/cleanup.go +++ b/services/packages/container/cleanup.go @@ -10,7 +10,6 @@ import ( packages_model "forgejo.org/models/packages" container_model "forgejo.org/models/packages/container" "forgejo.org/modules/optional" - container_module "forgejo.org/modules/packages/container" packages_service "forgejo.org/services/packages" digest "github.com/opencontainers/go-digest" @@ -82,30 +81,18 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e return nil } -func ShouldBeSkipped(ctx context.Context, pcr *packages_model.PackageCleanupRule, p *packages_model.Package, pv *packages_model.PackageVersion) (bool, error) { +func ShouldBeSkipped(pv *packages_model.PackageVersion) bool { // Always skip the "latest" tag if pv.LowerVersion == "latest" { - return true, nil + return true } // Check if the version is a digest (or untagged) if digest.Digest(pv.LowerVersion).Validate() == nil { - // Check if there is another manifest referencing this version - has, err := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{ - PackageID: p.ID, - Properties: map[string]string{ - container_module.PropertyManifestReference: pv.LowerVersion, - }, - }) - if err != nil { - return false, err - } - - // Skip it if the version is referenced - if has { - return true, nil - } + // Don't apply PackageCleanupRules to image versions that aren't tags; rely on `CleanupSHA256` to do mass + // cleanup of them once they're dangling due to not being referenced. + return true } - return false, nil + return false } diff --git a/services/packages/container/cleanup_test.go b/services/packages/container/cleanup_test.go new file mode 100644 index 0000000000..7a3af819d9 --- /dev/null +++ b/services/packages/container/cleanup_test.go @@ -0,0 +1,42 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package container + +import ( + "testing" + + packages_model "forgejo.org/models/packages" + + "github.com/stretchr/testify/assert" +) + +func TestShouldBeSkipped(t *testing.T) { + tests := []struct { + name string + pv *packages_model.PackageVersion + skipped bool + }{ + { + name: "latest: never cleaned up", + pv: &packages_model.PackageVersion{LowerVersion: "latest"}, + skipped: true, + }, + { + name: "sha256:* never cleaned up", + pv: &packages_model.PackageVersion{LowerVersion: "sha256:8a4d01effd20bcc5c65857885013efdbbdd466ac0a5a26b3fac573095533e3fc"}, + skipped: true, + }, + { + name: "sha256:* never cleaned up", + pv: &packages_model.PackageVersion{LowerVersion: "v1.0.0"}, + skipped: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + skipped := ShouldBeSkipped(tt.pv) + assert.Equal(t, tt.skipped, skipped) + }) + } +} diff --git a/tests/integration/api_packages_container_cleanup_sha256_test.go b/tests/integration/api_packages_container_cleanup_sha256_test.go index 5c661a6858..19b73f7698 100644 --- a/tests/integration/api_packages_container_cleanup_sha256_test.go +++ b/tests/integration/api_packages_container_cleanup_sha256_test.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestPackagesContainerCleanupSHA256(t *testing.T) { +func TestPackageContainerCleanupSHA256(t *testing.T) { defer tests.PrepareTestEnv(t, 1)() defer test.MockVariableValue(&setting.Packages.Storage.Type, setting.LocalStorageType)() defer test.MockVariableValue(&packages_container.SHA256BatchSize, 1)() diff --git a/tests/integration/api_packages_container_cleanup_test.go b/tests/integration/api_packages_container_cleanup_test.go new file mode 100644 index 0000000000..f75bcca3c4 --- /dev/null +++ b/tests/integration/api_packages_container_cleanup_test.go @@ -0,0 +1,101 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package integration + +import ( + "testing" + "time" + + packages_model "forgejo.org/models/packages" + "forgejo.org/models/unittest" + packages_service "forgejo.org/services/packages" + packages_cleanup "forgejo.org/services/packages/cleanup" + "forgejo.org/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPackageContainerCleanup(t *testing.T) { + // Test fixture data contains three images; one that is a standard single-platform image (v2.0), and two that are a + // manifest multi-platform image (v1.0). + // + // The goal of testing here is to ensure that package cleanup can only remove the tags, and leave the `sha256:*` + // versions for cleanup once they're dangling by having no tags referencing them. + + testCases := []struct { + name string + cleanupRule *packages_model.PackageCleanupRule + expectedCleanupVersion []string + expectedRemainingVersion []string + }{ + { + name: "keep single-platform", + cleanupRule: &packages_model.PackageCleanupRule{ + KeepCount: 1, + }, + expectedCleanupVersion: []string{"v1.0"}, + expectedRemainingVersion: []string{"v2.0"}, + }, + { + name: "keep multi-platform", + cleanupRule: &packages_model.PackageCleanupRule{ + RemovePattern: "v2\\.0", + }, + expectedCleanupVersion: []string{"v2.0"}, + expectedRemainingVersion: []string{ + "v1.0", + "sha256:4759bcf56210784f3b2c2b7438a43a5771c5eda61a11825c80f7b32143ac9c12", // linux/arm64/v8 version of v1.0 + "sha256:a972a01ff13b1cb6a9c49b3c34a283b67b75f441b8ff17ad4833a472f7b0fe08", // linux/amd64 version of v1.0 + }, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + defer unittest.OverrideFixtures("tests/integration/fixtures/TestPackageContainerCleanup")() + defer tests.PrepareTestEnvWithPackageData(t)() + + // Reduce repetition in the test case array + tt.cleanupRule.Type = packages_model.TypeContainer + tt.cleanupRule.OwnerID = 1 + + targets, err := packages_cleanup.GetCleanupTargets(t.Context(), tt.cleanupRule, true) + require.NoError(t, err) + + // Verify we have the expected targets + targetVersions := make([]string, len(targets)) + for i := range targets { + targetVersions[i] = targets[i].PackageVersion.LowerVersion + } + assert.Len(t, targetVersions, len(tt.expectedCleanupVersion)) + for _, expected := range tt.expectedCleanupVersion { + assert.Containsf(t, targetVersions, expected, "expected to cleanup %s, but didn't", expected) + } + + // Delete the target packages + for _, ct := range targets { + err := packages_service.DeletePackageVersionAndReferences(t.Context(), ct.PackageVersion) + require.NoError(t, err) + } + + // Perform the sha256 cleanup routine + require.NoError(t, packages_cleanup.CleanupExpiredData(t.Context(), -1*time.Hour)) + + // Verify the remaining versions are correct + remainingVersions, _, err := packages_model.SearchVersions(t.Context(), &packages_model.PackageSearchOptions{ + OwnerID: tt.cleanupRule.OwnerID, + Type: tt.cleanupRule.Type, + }) + require.NoError(t, err) + remainingVersionsStr := make([]string, len(remainingVersions)) + for i := range remainingVersions { + remainingVersionsStr[i] = remainingVersions[i].LowerVersion + } + assert.Len(t, remainingVersionsStr, len(tt.expectedRemainingVersion)) + for _, expected := range tt.expectedRemainingVersion { + assert.Contains(t, remainingVersionsStr, expected, "expected to find remaining version %s, but didn't", expected) + } + }) + } +} diff --git a/tests/integration/fixtures/TestPackageContainerCleanup/package.yml b/tests/integration/fixtures/TestPackageContainerCleanup/package.yml new file mode 100644 index 0000000000..e315448e10 --- /dev/null +++ b/tests/integration/fixtures/TestPackageContainerCleanup/package.yml @@ -0,0 +1,9 @@ +- id: 7 + owner_id: 1 + repo_id: 0 + type: container + name: forgejo-runner-test + lower_name: forgejo-runner-test + semver_compatible: false + is_internal: false + diff --git a/tests/integration/fixtures/TestPackageContainerCleanup/package_file.yml b/tests/integration/fixtures/TestPackageContainerCleanup/package_file.yml new file mode 100644 index 0000000000..bfd2ced85c --- /dev/null +++ b/tests/integration/fixtures/TestPackageContainerCleanup/package_file.yml @@ -0,0 +1,128 @@ +- id: 4090 + version_id: 736 + blob_id: 1214 + name: sha256_efc44d65797de15fdc9787e194f9e24ea42adc2fa2821203bb65ead939908375 + lower_name: sha256_efc44d65797de15fdc9787e194f9e24ea42adc2fa2821203bb65ead939908375 + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4091 + version_id: 736 + blob_id: 222 + name: sha256_63d36825ec9a6be406f73910fa6f0d6542a7b993a750aac151f30a8e1fd58cf7 + lower_name: sha256_63d36825ec9a6be406f73910fa6f0d6542a7b993a750aac151f30a8e1fd58cf7 + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4092 + version_id: 736 + blob_id: 1212 + name: sha256_9a0d1f70c6da347f1fa0c3856bbe2642a317361a54ca8b0e44b64c362b45e01d + lower_name: sha256_9a0d1f70c6da347f1fa0c3856bbe2642a317361a54ca8b0e44b64c362b45e01d + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4093 + version_id: 736 + blob_id: 1213 + name: sha256_5a0f445468c4d9b40c4efb4bb292967672f5e9a2dd23e605a34fe10898e4cc4e + lower_name: sha256_5a0f445468c4d9b40c4efb4bb292967672f5e9a2dd23e605a34fe10898e4cc4e + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4094 + version_id: 736 + blob_id: 1215 + name: manifest.json + lower_name: manifest.json + composite_key: '' + is_lead: true + created_unix: 1764694509 +- id: 4099 + version_id: 737 + blob_id: 1219 + name: sha256_495a889816ababcd9e1af0ed66ccc55c7225c6fcf171a7eac3f24ee62fa30f74 + lower_name: sha256_495a889816ababcd9e1af0ed66ccc55c7225c6fcf171a7eac3f24ee62fa30f74 + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4100 + version_id: 737 + blob_id: 1216 + name: sha256_5096682701dd58e54ccbeb5d1fa285e9980a86568cfb0ca522fd4871892ab318 + lower_name: sha256_5096682701dd58e54ccbeb5d1fa285e9980a86568cfb0ca522fd4871892ab318 + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4101 + version_id: 737 + blob_id: 1218 + name: sha256_714573ef664bd4969f0cb9b445efbb337f0d2f52d906f3f1323063ca9bdcb6a6 + lower_name: sha256_714573ef664bd4969f0cb9b445efbb337f0d2f52d906f3f1323063ca9bdcb6a6 + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4102 + version_id: 737 + blob_id: 1217 + name: sha256_3dbb52544aa310f7819092b6c556aa1fb446e2c77711eda8e4680851445f8f18 + lower_name: sha256_3dbb52544aa310f7819092b6c556aa1fb446e2c77711eda8e4680851445f8f18 + composite_key: '' + is_lead: false + created_unix: 1764694509 +- id: 4103 + version_id: 737 + blob_id: 1220 + name: manifest.json + lower_name: manifest.json + composite_key: '' + is_lead: true + created_unix: 1764694509 +- id: 4104 + version_id: 738 + blob_id: 1221 + name: manifest.json + lower_name: manifest.json + composite_key: '' + is_lead: true + created_unix: 1764694509 +- id: 4115 + version_id: 740 + blob_id: 2114 + name: sha256_dd4d2c29faafea81b0b44cf8df13b8d0e01bc42059cdb69bf8f5d147b29904bf + lower_name: sha256_dd4d2c29faafea81b0b44cf8df13b8d0e01bc42059cdb69bf8f5d147b29904bf + composite_key: '' + is_lead: false + created_unix: 1764695172 +- id: 4116 + version_id: 740 + blob_id: 222 + name: sha256_63d36825ec9a6be406f73910fa6f0d6542a7b993a750aac151f30a8e1fd58cf7 + lower_name: sha256_63d36825ec9a6be406f73910fa6f0d6542a7b993a750aac151f30a8e1fd58cf7 + composite_key: '' + is_lead: false + created_unix: 1764695172 +- id: 4117 + version_id: 740 + blob_id: 1222 + name: sha256_9c3b61a2981c0986b91339acca08bff16acc239d1909037652d6f439d3e84578 + lower_name: sha256_9c3b61a2981c0986b91339acca08bff16acc239d1909037652d6f439d3e84578 + composite_key: '' + is_lead: false + created_unix: 1764695172 +- id: 4118 + version_id: 740 + blob_id: 2113 + name: sha256_bbd073df829f756775f9e7143f99bd5a0637a4be4ba95280a496c732e9ed45ea + lower_name: sha256_bbd073df829f756775f9e7143f99bd5a0637a4be4ba95280a496c732e9ed45ea + composite_key: '' + is_lead: false + created_unix: 1764695172 +- id: 4119 + version_id: 740 + blob_id: 2115 + name: manifest.json + lower_name: manifest.json + composite_key: '' + is_lead: true + created_unix: 1764695172 diff --git a/tests/integration/fixtures/TestPackageContainerCleanup/package_version.yml b/tests/integration/fixtures/TestPackageContainerCleanup/package_version.yml new file mode 100644 index 0000000000..37a3caf5a6 --- /dev/null +++ b/tests/integration/fixtures/TestPackageContainerCleanup/package_version.yml @@ -0,0 +1,112 @@ +- id: 735 + package_id: 7 + creator_id: 1 + version: _upload + lower_version: _upload + created_unix: 1764694509 + is_internal: true + metadata_json: 'null' + download_count: 0 +- id: 736 + package_id: 7 + creator_id: 1 + version: 'sha256:a972a01ff13b1cb6a9c49b3c34a283b67b75f441b8ff17ad4833a472f7b0fe08' + lower_version: 'sha256:a972a01ff13b1cb6a9c49b3c34a283b67b75f441b8ff17ad4833a472f7b0fe08' + created_unix: 1764694509 + is_internal: false + metadata_json: >- + {"type":"oci","is_tagged":false,"platform":"linux/amd64","description":"A + runner for Forgejo + Actions.","authors":["Forgejo"],"license":"GPL-3.0-or-later","project_url":"https://forgejo.org","repository_url":"https://code.forgejo.org/forgejo/runner","documentation_url":"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner","labels":{"io.buildah.version":"1.42.0","maintainer":"contact@forgejo.org","org.opencontainers.image.authors":"Forgejo","org.opencontainers.image.description":"A + runner for Forgejo + Actions.","org.opencontainers.image.documentation":"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner","org.opencontainers.image.licenses":"GPL-3.0-or-later","org.opencontainers.image.source":"https://code.forgejo.org/forgejo/runner","org.opencontainers.image.title":"Forgejo + Runner","org.opencontainers.image.url":"https://forgejo.org","org.opencontainers.image.vendor":"Forgejo","org.opencontainers.image.version":"1"},"layer_creation":["ADD + alpine-minirootfs-3.22.2-x86_64.tar.gz / # buildkit","CMD + [\"/bin/sh\"]","ARG RELEASE_VERSION","|1 RELEASE_VERSION=1 /bin/sh -c apk + add --no-cache git bash","COPY + file:abe2aa9c7d7eee672ac5953937e3ae56536980f485e40b8a70d94476ce576d39 in + /bin/forgejo-runner","LABEL maintainer=\"contact@forgejo.org\" + org.opencontainers.image.authors=\"Forgejo\" + org.opencontainers.image.url=\"https://forgejo.org\" + org.opencontainers.image.documentation=\"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner\" + org.opencontainers.image.source=\"https://code.forgejo.org/forgejo/runner\" + org.opencontainers.image.version=\"${RELEASE_VERSION}\" + org.opencontainers.image.vendor=\"Forgejo\" + org.opencontainers.image.licenses=\"GPL-3.0-or-later\" + org.opencontainers.image.title=\"Forgejo Runner\" + org.opencontainers.image.description=\"A runner for Forgejo Actions.\"","ENV + HOME=/data","USER 1000:1000","WORKDIR /data","VOLUME [\"/data\"]","CMD + [\"/bin/forgejo-runner\"]"]} + download_count: 0 +- id: 737 + package_id: 7 + creator_id: 1 + version: 'sha256:4759bcf56210784f3b2c2b7438a43a5771c5eda61a11825c80f7b32143ac9c12' + lower_version: 'sha256:4759bcf56210784f3b2c2b7438a43a5771c5eda61a11825c80f7b32143ac9c12' + created_unix: 1764694509 + is_internal: false + metadata_json: >- + {"type":"oci","is_tagged":false,"platform":"linux/arm64/v8","description":"A + runner for Forgejo + Actions.","authors":["Forgejo"],"license":"GPL-3.0-or-later","project_url":"https://forgejo.org","repository_url":"https://code.forgejo.org/forgejo/runner","documentation_url":"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner","labels":{"io.buildah.version":"1.42.0","maintainer":"contact@forgejo.org","org.opencontainers.image.authors":"Forgejo","org.opencontainers.image.description":"A + runner for Forgejo + Actions.","org.opencontainers.image.documentation":"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner","org.opencontainers.image.licenses":"GPL-3.0-or-later","org.opencontainers.image.source":"https://code.forgejo.org/forgejo/runner","org.opencontainers.image.title":"Forgejo + Runner","org.opencontainers.image.url":"https://forgejo.org","org.opencontainers.image.vendor":"Forgejo","org.opencontainers.image.version":"1"},"layer_creation":["ADD + alpine-minirootfs-3.22.2-aarch64.tar.gz / # buildkit","CMD + [\"/bin/sh\"]","ARG RELEASE_VERSION","|1 RELEASE_VERSION=1 /bin/sh -c apk + add --no-cache git bash","COPY + file:26caa6ee49a60c7da3a00fe966069655c32d484f1659bb0389dc18fd9616b521 in + /bin/forgejo-runner","LABEL maintainer=\"contact@forgejo.org\" + org.opencontainers.image.authors=\"Forgejo\" + org.opencontainers.image.url=\"https://forgejo.org\" + org.opencontainers.image.documentation=\"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner\" + org.opencontainers.image.source=\"https://code.forgejo.org/forgejo/runner\" + org.opencontainers.image.version=\"${RELEASE_VERSION}\" + org.opencontainers.image.vendor=\"Forgejo\" + org.opencontainers.image.licenses=\"GPL-3.0-or-later\" + org.opencontainers.image.title=\"Forgejo Runner\" + org.opencontainers.image.description=\"A runner for Forgejo Actions.\"","ENV + HOME=/data","USER 1000:1000","WORKDIR /data","VOLUME [\"/data\"]","CMD + [\"/bin/forgejo-runner\"]"]} + download_count: 0 +- id: 738 + package_id: 7 + creator_id: 1 + version: v1.0 + lower_version: v1.0 + created_unix: 1764694509 + is_internal: false + metadata_json: >- + {"type":"oci","is_tagged":true,"manifests":[{"platform":"linux/amd64","digest":"sha256:a972a01ff13b1cb6a9c49b3c34a283b67b75f441b8ff17ad4833a472f7b0fe08","size":19758113},{"platform":"linux/arm64","digest":"sha256:4759bcf56210784f3b2c2b7438a43a5771c5eda61a11825c80f7b32143ac9c12","size":19297077}]} + download_count: 0 +- id: 740 + package_id: 7 + creator_id: 1 + version: v2.0 + lower_version: v2.0 + created_unix: 1764695172 + is_internal: false + metadata_json: >- + {"type":"oci","is_tagged":true,"platform":"linux/amd64","description":"A + runner for Forgejo + Actions.","authors":["Forgejo"],"license":"GPL-3.0-or-later","project_url":"https://forgejo.org","repository_url":"https://code.forgejo.org/forgejo/runner","documentation_url":"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner","labels":{"io.buildah.version":"1.42.0","maintainer":"contact@forgejo.org","org.opencontainers.image.authors":"Forgejo","org.opencontainers.image.description":"A + runner for Forgejo + Actions.","org.opencontainers.image.documentation":"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner","org.opencontainers.image.licenses":"GPL-3.0-or-later","org.opencontainers.image.source":"https://code.forgejo.org/forgejo/runner","org.opencontainers.image.title":"Forgejo + Runner","org.opencontainers.image.url":"https://forgejo.org","org.opencontainers.image.vendor":"Forgejo","org.opencontainers.image.version":"2"},"layer_creation":["ADD + alpine-minirootfs-3.22.2-x86_64.tar.gz / # buildkit","CMD + [\"/bin/sh\"]","ARG RELEASE_VERSION","|1 RELEASE_VERSION=2 /bin/sh -c apk + add --no-cache git bash","COPY + file:3c24d4a08c7006979c8b4397315c24dbb40df6b8fa474d8744daaf6fe17dce6f in + /bin/forgejo-runner","LABEL maintainer=\"contact@forgejo.org\" + org.opencontainers.image.authors=\"Forgejo\" + org.opencontainers.image.url=\"https://forgejo.org\" + org.opencontainers.image.documentation=\"https://forgejo.org/docs/latest/admin/actions/#forgejo-runner\" + org.opencontainers.image.source=\"https://code.forgejo.org/forgejo/runner\" + org.opencontainers.image.version=\"${RELEASE_VERSION}\" + org.opencontainers.image.vendor=\"Forgejo\" + org.opencontainers.image.licenses=\"GPL-3.0-or-later\" + org.opencontainers.image.title=\"Forgejo Runner\" + org.opencontainers.image.description=\"A runner for Forgejo Actions.\"","ENV + HOME=/data","USER 1000:1000","WORKDIR /data","VOLUME [\"/data\"]","CMD + [\"/bin/forgejo-runner\"]"]} + download_count: 0 diff --git a/tests/test_utils.go b/tests/test_utils.go index 293856e1b3..6432dfc271 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -324,6 +324,13 @@ func PrepareCleanPackageData(t testing.TB) { var inTestEnv atomic.Bool func PrepareTestEnv(t testing.TB, skip ...int) func() { + deferFn := PrepareTestEnvWithPackageData(t, skip...) + PrepareCleanPackageData(t) + return deferFn +} + +// Doesn't perform the `PrepareCleanPackageData` that `PrepareTestEnv` does... +func PrepareTestEnvWithPackageData(t testing.TB, skip ...int) func() { t.Helper() if !inTestEnv.CompareAndSwap(false, true) { @@ -348,7 +355,6 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() { // do not add more Prepare* functions here, only call necessary ones in the related test functions PrepareGitRepoDirectory(t) PrepareLFSStorage(t) - PrepareCleanPackageData(t) return deferFn }