From b501ba980218bcf0fd15cd18abf1eb42901b11f1 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 16 Jun 2025 20:45:45 +0800 Subject: [PATCH] fix --- models/packages/container/search.go | 4 +- models/packages/descriptor.go | 6 +- .../packages/container/const.go | 0 modules/packages/nuget/metadata.go | 90 +++++++++++------ modules/packages/nuget/metadata_test.go | 96 +++++++++++------- modules/packages/nuget/symbol_extractor.go | 8 +- .../packages/nuget/symbol_extractor_test.go | 6 +- routers/api/packages/container/blob.go | 4 +- routers/api/packages/container/manifest.go | 6 +- routers/api/packages/nuget/api_v2.go | 46 ++++++--- routers/api/packages/rubygems/rubygems.go | 48 +++++++-- .../api/packages/rubygems/rubygems_test.go | 41 ++++++++ services/packages/container/cleanup.go | 2 +- .../api_packages_container_test.go | 11 +-- tests/integration/api_packages_nuget_test.go | 97 ++++++++++++++----- tests/integration/api_packages_test.go | 6 +- 16 files changed, 335 insertions(+), 136 deletions(-) rename {models => modules}/packages/container/const.go (100%) create mode 100644 routers/api/packages/rubygems/rubygems_test.go diff --git a/models/packages/container/search.go b/models/packages/container/search.go index a513da08b9cff..9321d9eb4106a 100644 --- a/models/packages/container/search.go +++ b/models/packages/container/search.go @@ -44,7 +44,7 @@ func (opts *BlobSearchOptions) toConds() builder.Cond { cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)}) } if opts.IsManifest { - cond = cond.And(builder.Eq{"package_file.lower_name": ManifestFilename}) + cond = cond.And(builder.Eq{"package_file.lower_name": container_module.ManifestFilename}) } if opts.OnlyLead { cond = cond.And(builder.Eq{"package_file.is_lead": true}) @@ -235,7 +235,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) { var cond builder.Cond = builder.Eq{ "package_version.is_internal": true, - "package_version.lower_version": UploadVersion, + "package_version.lower_version": container_module.UploadVersion, "package.type": packages.TypeContainer, } cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()}) diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 1ea181c72320b..2d43dc30466cd 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -103,10 +103,10 @@ func (pd *PackageDescriptor) CalculateBlobSize() int64 { // GetPackageDescriptor gets the package description for a version func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) { - return getPackageDescriptor(ctx, pv, cache.NewEphemeralCache()) + return GetPackageDescriptorWithCache(ctx, pv, cache.NewEphemeralCache()) } -func getPackageDescriptor(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) { +func GetPackageDescriptorWithCache(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) { p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID) if err != nil { return nil, err @@ -270,7 +270,7 @@ func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*Packa func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) { pds := make([]*PackageDescriptor, 0, len(pvs)) for _, pv := range pvs { - pd, err := getPackageDescriptor(ctx, pv, c) + pd, err := GetPackageDescriptorWithCache(ctx, pv, c) if err != nil { return nil, err } diff --git a/models/packages/container/const.go b/modules/packages/container/const.go similarity index 100% rename from models/packages/container/const.go rename to modules/packages/container/const.go diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 1e98ddffde40e..a122590bf17bb 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -57,14 +57,24 @@ type Package struct { // Metadata represents the metadata of a Nuget package type Metadata struct { - Description string `json:"description,omitempty"` - ReleaseNotes string `json:"release_notes,omitempty"` - Readme string `json:"readme,omitempty"` - Authors string `json:"authors,omitempty"` - ProjectURL string `json:"project_url,omitempty"` - RepositoryURL string `json:"repository_url,omitempty"` - RequireLicenseAcceptance bool `json:"require_license_acceptance"` - Dependencies map[string][]Dependency `json:"dependencies,omitempty"` + Authors string `json:"authors,omitempty"` + Copyright string `json:"copyright,omitempty"` + Description string `json:"description,omitempty"` + DevelopmentDependency bool `json:"development_dependency,omitempty"` + IconURL string `json:"icon_url,omitempty"` + Language string `json:"language,omitempty"` + LicenseURL string `json:"license_url,omitempty"` + MinClientVersion string `json:"min_client_version,omitempty"` + Owners string `json:"owners,omitempty"` + ProjectURL string `json:"project_url,omitempty"` + Readme string `json:"readme,omitempty"` + ReleaseNotes string `json:"release_notes,omitempty"` + RepositoryURL string `json:"repository_url,omitempty"` + RequireLicenseAcceptance bool `json:"require_license_acceptance"` + Tags string `json:"tags,omitempty"` + Title string `json:"title,omitempty"` + + Dependencies map[string][]Dependency `json:"dependencies,omitempty"` } // Dependency represents a dependency of a Nuget package @@ -74,24 +84,30 @@ type Dependency struct { } // https://learn.microsoft.com/en-us/nuget/reference/nuspec +// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd type nuspecPackage struct { Metadata struct { - ID string `xml:"id"` - Version string `xml:"version"` - Authors string `xml:"authors"` - RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` + // required fields + Authors string `xml:"authors"` + Description string `xml:"description"` + ID string `xml:"id"` + Version string `xml:"version"` + + // optional fields + Copyright string `xml:"copyright"` + DevelopmentDependency bool `xml:"developmentDependency"` + IconURL string `xml:"iconUrl"` + Language string `xml:"language"` + LicenseURL string `xml:"licenseUrl"` + MinClientVersion string `xml:"minClientVersion,attr"` + Owners string `xml:"owners"` ProjectURL string `xml:"projectUrl"` - Description string `xml:"description"` - ReleaseNotes string `xml:"releaseNotes"` Readme string `xml:"readme"` - PackageTypes struct { - PackageType []struct { - Name string `xml:"name,attr"` - } `xml:"packageType"` - } `xml:"packageTypes"` - Repository struct { - URL string `xml:"url,attr"` - } `xml:"repository"` + ReleaseNotes string `xml:"releaseNotes"` + RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` + Tags string `xml:"tags"` + Title string `xml:"title"` + Dependencies struct { Dependency []struct { ID string `xml:"id,attr"` @@ -107,6 +123,14 @@ type nuspecPackage struct { } `xml:"dependency"` } `xml:"group"` } `xml:"dependencies"` + PackageTypes struct { + PackageType []struct { + Name string `xml:"name,attr"` + } `xml:"packageType"` + } `xml:"packageTypes"` + Repository struct { + URL string `xml:"url,attr"` + } `xml:"repository"` } `xml:"metadata"` } @@ -167,13 +191,23 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { } m := &Metadata{ - Description: p.Metadata.Description, - ReleaseNotes: p.Metadata.ReleaseNotes, Authors: p.Metadata.Authors, + Copyright: p.Metadata.Copyright, + Description: p.Metadata.Description, + DevelopmentDependency: p.Metadata.DevelopmentDependency, + IconURL: p.Metadata.IconURL, + Language: p.Metadata.Language, + LicenseURL: p.Metadata.LicenseURL, + MinClientVersion: p.Metadata.MinClientVersion, + Owners: p.Metadata.Owners, ProjectURL: p.Metadata.ProjectURL, + ReleaseNotes: p.Metadata.ReleaseNotes, RepositoryURL: p.Metadata.Repository.URL, RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance, - Dependencies: make(map[string][]Dependency), + Tags: p.Metadata.Tags, + Title: p.Metadata.Title, + + Dependencies: make(map[string][]Dependency), } if p.Metadata.Readme != "" { @@ -227,13 +261,13 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { func toNormalizedVersion(v *version.Version) string { var buf bytes.Buffer segments := v.Segments64() - fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2]) + _, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2]) if len(segments) > 3 && segments[3] > 0 { - fmt.Fprintf(&buf, ".%d", segments[3]) + _, _ = fmt.Fprintf(&buf, ".%d", segments[3]) } pre := v.Prerelease() if pre != "" { - fmt.Fprint(&buf, "-", pre) + _, _ = fmt.Fprint(&buf, "-", pre) } return buf.String() } diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go index f466492f8a85e..90c3e8dfeb64e 100644 --- a/modules/packages/nuget/metadata_test.go +++ b/modules/packages/nuget/metadata_test.go @@ -12,44 +12,62 @@ import ( ) const ( - id = "System.Gitea" - semver = "1.0.1" - authors = "Gitea Authors" - projectURL = "https://gitea.io" - description = "Package Description" - releaseNotes = "Package Release Notes" - readme = "Readme" - repositoryURL = "https://gitea.io/gitea/gitea" - targetFramework = ".NETStandard2.1" - dependencyID = "System.Text.Json" - dependencyVersion = "5.0.0" + authors = "Gitea Authors" + copyright = "Package Copyright" + dependencyID = "System.Text.Json" + dependencyVersion = "5.0.0" + developmentDependency = true + description = "Package Description" + iconURL = "https://gitea.io/favicon.png" + id = "System.Gitea" + language = "Package Language" + licenseURL = "https://gitea.io/license" + minClientVersion = "1.0.0.0" + owners = "Package Owners" + projectURL = "https://gitea.io" + readme = "Readme" + releaseNotes = "Package Release Notes" + repositoryURL = "https://gitea.io/gitea/gitea" + requireLicenseAcceptance = true + tags = "tag_1 tag_2 tag_3" + targetFramework = ".NETStandard2.1" + title = "Package Title" + versionStr = "1.0.1" ) const nuspecContent = ` - - ` + id + ` - ` + semver + ` - ` + authors + ` - true - ` + projectURL + ` - ` + description + ` - ` + releaseNotes + ` - - README.md - - - - - - + + ` + authors + ` + ` + copyright + ` + ` + description + ` + true + ` + iconURL + ` + ` + id + ` + ` + language + ` + ` + licenseURL + ` + ` + owners + ` + ` + projectURL + ` + README.md + ` + releaseNotes + ` + + true + ` + tags + ` + ` + title + ` + ` + versionStr + ` + + + + + + ` const symbolsNuspecContent = ` ` + id + ` - ` + semver + ` + ` + versionStr + ` ` + description + ` @@ -140,14 +158,26 @@ func TestParsePackageMetaData(t *testing.T) { assert.NotNil(t, np) assert.Equal(t, DependencyPackage, np.PackageType) - assert.Equal(t, id, np.ID) - assert.Equal(t, semver, np.Version) assert.Equal(t, authors, np.Metadata.Authors) - assert.Equal(t, projectURL, np.Metadata.ProjectURL) assert.Equal(t, description, np.Metadata.Description) - assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) + assert.Equal(t, id, np.ID) + assert.Equal(t, versionStr, np.Version) + + assert.Equal(t, copyright, np.Metadata.Copyright) + assert.Equal(t, developmentDependency, np.Metadata.DevelopmentDependency) + assert.Equal(t, iconURL, np.Metadata.IconURL) + assert.Equal(t, language, np.Metadata.Language) + assert.Equal(t, licenseURL, np.Metadata.LicenseURL) + assert.Equal(t, minClientVersion, np.Metadata.MinClientVersion) + assert.Equal(t, owners, np.Metadata.Owners) + assert.Equal(t, projectURL, np.Metadata.ProjectURL) assert.Equal(t, readme, np.Metadata.Readme) + assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) + assert.Equal(t, requireLicenseAcceptance, np.Metadata.RequireLicenseAcceptance) + assert.Equal(t, tags, np.Metadata.Tags) + assert.Equal(t, title, np.Metadata.Title) + assert.Len(t, np.Metadata.Dependencies, 1) assert.Contains(t, np.Metadata.Dependencies, targetFramework) deps := np.Metadata.Dependencies[targetFramework] @@ -180,7 +210,7 @@ func TestParsePackageMetaData(t *testing.T) { assert.Equal(t, SymbolsPackage, np.PackageType) assert.Equal(t, id, np.ID) - assert.Equal(t, semver, np.Version) + assert.Equal(t, versionStr, np.Version) assert.Equal(t, description, np.Metadata.Description) assert.Empty(t, np.Metadata.Dependencies) }) diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go index 81bf0371a0b1a..9c952e1f1095d 100644 --- a/modules/packages/nuget/symbol_extractor.go +++ b/modules/packages/nuget/symbol_extractor.go @@ -34,7 +34,7 @@ type PortablePdbList []*PortablePdb func (l PortablePdbList) Close() { for _, pdb := range l { - pdb.Content.Close() + _ = pdb.Content.Close() } } @@ -65,7 +65,7 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { buf, err := packages.CreateHashedBufferFromReader(f) - f.Close() + _ = f.Close() if err != nil { return err @@ -73,12 +73,12 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { id, err := ParseDebugHeaderID(buf) if err != nil { - buf.Close() + _ = buf.Close() return fmt.Errorf("Invalid PDB file: %w", err) } if _, err := buf.Seek(0, io.SeekStart); err != nil { - buf.Close() + _ = buf.Close() return err } diff --git a/modules/packages/nuget/symbol_extractor_test.go b/modules/packages/nuget/symbol_extractor_test.go index 711ad6d096a18..e841e377d983c 100644 --- a/modules/packages/nuget/symbol_extractor_test.go +++ b/modules/packages/nuget/symbol_extractor_test.go @@ -24,14 +24,14 @@ func TestExtractPortablePdb(t *testing.T) { var buf bytes.Buffer archive := zip.NewWriter(&buf) w, _ := archive.Create(name) - w.Write(content) - archive.Close() + _, _ = w.Write(content) + _ = archive.Close() return buf.Bytes() } t.Run("MissingPdbFiles", func(t *testing.T) { var buf bytes.Buffer - zip.NewWriter(&buf).Close() + _ = zip.NewWriter(&buf).Close() pdbs, err := ExtractPortablePdb(bytes.NewReader(buf.Bytes()), int64(buf.Len())) assert.ErrorIs(t, err, ErrMissingPdbFiles) diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go index c1ccbeb10d637..2ea9b3839cedf 100644 --- a/routers/api/packages/container/blob.go +++ b/routers/api/packages/container/blob.go @@ -130,8 +130,8 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: pi.Owner.ID, - Version: container_model.UploadVersion, - LowerVersion: container_model.UploadVersion, + Version: container_module.UploadVersion, + LowerVersion: container_module.UploadVersion, IsInternal: true, MetadataJSON: "null", } diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index ea4de7e42c499..0cbd46e9431d3 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -151,7 +151,7 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf return err } - uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_model.UploadVersion) + uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_module.UploadVersion) if err != nil && err != packages_model.ErrPackageNotExist { return err } @@ -492,7 +492,7 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{ Digest: digest.Digest(manifestDigest), MediaType: mci.MediaType, - Name: container_model.ManifestFilename, + Name: container_module.ManifestFilename, File: &packages_model.PackageFileDescriptor{Blob: pb}, ExpectedSize: pb.Size, IsLead: true, @@ -505,7 +505,7 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack OwnerID: mci.Owner.ID, PackageType: packages_model.TypeContainer, VersionID: pv.ID, - Query: container_model.ManifestFilename, + Query: container_module.ManifestFilename, }) if err != nil { return nil, false, "", err diff --git a/routers/api/packages/nuget/api_v2.go b/routers/api/packages/nuget/api_v2.go index a726065ad0877..801c60af130c6 100644 --- a/routers/api/packages/nuget/api_v2.go +++ b/routers/api/packages/nuget/api_v2.go @@ -246,21 +246,30 @@ type TypedValue[T any] struct { } type FeedEntryProperties struct { - Version string `xml:"d:Version"` - NormalizedVersion string `xml:"d:NormalizedVersion"` Authors string `xml:"d:Authors"` + Copyright string `xml:"d:Copyright,omitempty"` + Created TypedValue[time.Time] `xml:"d:Created"` Dependencies string `xml:"d:Dependencies"` Description string `xml:"d:Description"` - VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"` + DevelopmentDependency TypedValue[bool] `xml:"d:DevelopmentDependency"` DownloadCount TypedValue[int64] `xml:"d:DownloadCount"` - PackageSize TypedValue[int64] `xml:"d:PackageSize"` - Created TypedValue[time.Time] `xml:"d:Created"` + ID string `xml:"d:Id"` + IconURL string `xml:"d:IconUrl,omitempty"` + Language string `xml:"d:Language,omitempty"` LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"` - Published TypedValue[time.Time] `xml:"d:Published"` + LicenseURL string `xml:"d:LicenseUrl,omitempty"` + MinClientVersion string `xml:"d:MinClientVersion,omitempty"` + NormalizedVersion string `xml:"d:NormalizedVersion"` + Owners string `xml:"d:Owners,omitempty"` + PackageSize TypedValue[int64] `xml:"d:PackageSize"` ProjectURL string `xml:"d:ProjectUrl,omitempty"` + Published TypedValue[time.Time] `xml:"d:Published"` ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"` RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"` - Title string `xml:"d:Title"` + Tags string `xml:"d:Tags,omitempty"` + Title string `xml:"d:Title,omitempty"` + Version string `xml:"d:Version"` + VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"` } type FeedEntry struct { @@ -353,21 +362,30 @@ func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNames Author: metadata.Authors, Content: content, Properties: &FeedEntryProperties{ - Version: pd.Version.Version, - NormalizedVersion: pd.Version.Version, Authors: metadata.Authors, + Copyright: metadata.Copyright, + Created: createdValue, Dependencies: buildDependencyString(metadata), Description: metadata.Description, - VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, + DevelopmentDependency: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.DevelopmentDependency}, DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, - PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()}, - Created: createdValue, + ID: pd.Package.Name, + IconURL: metadata.IconURL, + Language: metadata.Language, LastUpdated: createdValue, - Published: createdValue, + LicenseURL: metadata.LicenseURL, + MinClientVersion: metadata.MinClientVersion, + NormalizedVersion: pd.Version.Version, + Owners: metadata.Owners, + PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()}, ProjectURL: metadata.ProjectURL, + Published: createdValue, ReleaseNotes: metadata.ReleaseNotes, RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance}, - Title: pd.Package.Name, + Tags: metadata.Tags, + Title: metadata.Title, + Version: pd.Version.Version, + VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, }, } diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index de8c7ef3ed009..cb880c8bdb6d8 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -14,6 +14,7 @@ import ( "strings" packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/optional" packages_module "code.gitea.io/gitea/modules/packages" rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" @@ -309,7 +310,7 @@ func GetPackageInfo(ctx *context.Context) { apiError(ctx, http.StatusNotFound, nil) return } - infoContent, err := makePackageInfo(ctx, versions) + infoContent, err := makePackageInfo(ctx, versions, cache.NewEphemeralCache()) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -317,7 +318,7 @@ func GetPackageInfo(ctx *context.Context) { ctx.PlainText(http.StatusOK, infoContent) } -// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems. +// GetAllPackagesVersions returns a custom text-based format containing information about all versions of all rubygems. // ref: https://guides.rubygems.org/rubygems-org-compact-index-api/ func GetAllPackagesVersions(ctx *context.Context) { packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems) @@ -326,6 +327,7 @@ func GetAllPackagesVersions(ctx *context.Context) { return } + ephemeralCache := cache.NewEphemeralCache() out := &strings.Builder{} out.WriteString("---\n") for _, pkg := range packages { @@ -338,7 +340,7 @@ func GetAllPackagesVersions(ctx *context.Context) { continue } - info, err := makePackageInfo(ctx, versions) + info, err := makePackageInfo(ctx, versions, ephemeralCache) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -348,7 +350,14 @@ func GetAllPackagesVersions(ctx *context.Context) { _, _ = fmt.Fprintf(out, "%s ", pkg.Name) for i, v := range versions { sep := util.Iif(i == len(versions)-1, "", ",") - _, _ = fmt.Fprintf(out, "%s%s", v.Version, sep) + pd, err := packages_model.GetPackageDescriptorWithCache(ctx, v, ephemeralCache) + if errors.Is(err, util.ErrNotExist) { + continue + } else if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + writePackageVersionForList(pd.Metadata, v.Version, sep, out) } _, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info))) } @@ -356,6 +365,16 @@ func GetAllPackagesVersions(ctx *context.Context) { ctx.PlainText(http.StatusOK, out.String()) } +func writePackageVersionForList(metadata any, version, sep string, out *strings.Builder) { + if metadata, _ := metadata.(*rubygems_module.Metadata); metadata != nil && metadata.Platform != "" && metadata.Platform != "ruby" { + // VERSION_PLATFORM (see comment above in GetAllPackagesVersions) + _, _ = fmt.Fprintf(out, "%s_%s%s", version, metadata.Platform, sep) + } else { + // VERSION only + _, _ = fmt.Fprintf(out, "%s%s", version, sep) + } +} + func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) { out.WriteString(prefix) if len(reqs) == 0 { @@ -367,11 +386,21 @@ func writePackageVersionRequirements(prefix string, reqs []rubygems_module.Versi } } -func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) { +func writePackageVersionForDependency(version, platform string, out *strings.Builder) { + if platform != "" && platform != "ruby" { + // VERSION-PLATFORM (see comment below in makePackageVersionDependency) + _, _ = fmt.Fprintf(out, "%s-%s ", version, platform) + } else { + // VERSION only + _, _ = fmt.Fprintf(out, "%s ", version) + } +} + +func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) { // format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...] // DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT] // REQUIREMENT: KEY:VALUE (always contains "checksum") - pd, err := packages_model.GetPackageDescriptor(ctx, version) + pd, err := packages_model.GetPackageDescriptorWithCache(ctx, version, c) if err != nil { return "", err } @@ -388,8 +417,7 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model. } buf := &strings.Builder{} - buf.WriteString(version.Version) - buf.WriteByte(' ') + writePackageVersionForDependency(version.Version, metadata.Platform, buf) for i, dep := range metadata.RuntimeDependencies { sep := util.Iif(i == 0, "", ",") writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf) @@ -404,10 +432,10 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model. return buf.String(), nil } -func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) { +func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) { ret := "---\n" for _, v := range versions { - dep, err := makePackageVersionDependency(ctx, v) + dep, err := makePackageVersionDependency(ctx, v, c) if err != nil { return "", err } diff --git a/routers/api/packages/rubygems/rubygems_test.go b/routers/api/packages/rubygems/rubygems_test.go new file mode 100644 index 0000000000000..a07e12a7d3c8f --- /dev/null +++ b/routers/api/packages/rubygems/rubygems_test.go @@ -0,0 +1,41 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rubygems + +import ( + "strings" + "testing" + + rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" + + "github.com/stretchr/testify/assert" +) + +func TestWritePackageVersion(t *testing.T) { + buf := &strings.Builder{} + + writePackageVersionForList(nil, "1.0", " ", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForList(&rubygems_module.Metadata{Platform: "ruby"}, "1.0", " ", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForList(&rubygems_module.Metadata{Platform: "linux"}, "1.0", " ", buf) + assert.Equal(t, "1.0_linux ", buf.String()) + buf.Reset() + + writePackageVersionForDependency("1.0", "", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForDependency("1.0", "ruby", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForDependency("1.0", "os", buf) + assert.Equal(t, "1.0-os ", buf.String()) + buf.Reset() +} diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go index 3f5f43bbc0bb1..d15d6b6c84673 100644 --- a/services/packages/container/cleanup.go +++ b/services/packages/container/cleanup.go @@ -57,7 +57,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e Type: packages_model.TypeContainer, Version: packages_model.SearchValue{ ExactMatch: true, - Value: container_model.UploadVersion, + Value: container_module.UploadVersion, }, IsInternal: optional.Some(true), HasFiles: optional.Some(false), diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 351807c0da7ce..4bcb13c448ea1 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -18,7 +18,6 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" - container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" container_module "code.gitea.io/gitea/modules/packages/container" @@ -71,8 +70,8 @@ func TestPackageContainer(t *testing.T) { configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}` manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" - manifestContent := `{"schemaVersion":2,"mediaType":"` + container_model.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` - manifestContentType := container_model.ContentTypeDockerDistributionManifestV2 + manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` + manifestContentType := container_module.ContentTypeDockerDistributionManifestV2 untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` @@ -252,7 +251,7 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) - pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_model.UploadVersion) + pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_module.UploadVersion) assert.NoError(t, err) pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID) @@ -432,7 +431,7 @@ func TestPackageContainer(t *testing.T) { assert.Len(t, pd.Files, 3) for _, pfd := range pd.Files { switch pfd.File.Name { - case container_model.ManifestFilename: + case container_module.ManifestFilename: assert.True(t, pfd.File.IsLead) assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) @@ -534,7 +533,7 @@ func TestPackageContainer(t *testing.T) { assert.Len(t, pd.Files, 3) for _, pfd := range pd.Files { - if pfd.File.Name == container_model.ManifestFilename { + if pfd.File.Name == container_module.ManifestFilename { assert.True(t, pfd.File.IsLead) assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index c0e69a82cd565..65b1b9845a2cd 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -46,21 +46,30 @@ func TestPackageNuGet(t *testing.T) { defer tests.PrepareTestEnv(t)() type FeedEntryProperties struct { - Version string `xml:"Version"` - NormalizedVersion string `xml:"NormalizedVersion"` Authors string `xml:"Authors"` + Copyright string `xml:"Copyright,omitempty"` + Created nuget.TypedValue[time.Time] `xml:"Created"` Dependencies string `xml:"Dependencies"` Description string `xml:"Description"` - VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` + DevelopmentDependency nuget.TypedValue[bool] `xml:"DevelopmentDependency"` DownloadCount nuget.TypedValue[int64] `xml:"DownloadCount"` - PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` - Created nuget.TypedValue[time.Time] `xml:"Created"` + ID string `xml:"Id"` + IconURL string `xml:"IconUrl,omitempty"` + Language string `xml:"Language,omitempty"` LastUpdated nuget.TypedValue[time.Time] `xml:"LastUpdated"` - Published nuget.TypedValue[time.Time] `xml:"Published"` + LicenseURL string `xml:"LicenseUrl,omitempty"` + MinClientVersion string `xml:"MinClientVersion,omitempty"` + NormalizedVersion string `xml:"NormalizedVersion"` + Owners string `xml:"Owners,omitempty"` + PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` ProjectURL string `xml:"ProjectUrl,omitempty"` + Published nuget.TypedValue[time.Time] `xml:"Published"` ReleaseNotes string `xml:"ReleaseNotes,omitempty"` RequireLicenseAcceptance nuget.TypedValue[bool] `xml:"RequireLicenseAcceptance"` + Tags string `xml:"Tags,omitempty"` Title string `xml:"Title"` + Version string `xml:"Version"` + VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` } type FeedEntry struct { @@ -86,28 +95,54 @@ func TestPackageNuGet(t *testing.T) { readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage) badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification) - packageName := "test.package" + packageName := "test.package" // id + packageID := packageName packageVersion := "1.0.3" packageAuthors := "KN4CK3R" packageDescription := "Gitea Test Package" + symbolFilename := "test.pdb" symbolID := "d910bb6948bd4c6cb40155bcf52c3c94" + packageCopyright := "Package Copyright" + packageIconURL := "https://gitea.io/favicon.png" + packageLanguage := "Package Language" + packageLicenseURL := "https://gitea.io/license" + packageMinClientVersion := "1.0.0.0" + packageOwners := "Package Owners" + packageProjectURL := "https://gitea.io" + packageReleaseNotes := "Package Release Notes" + packageTags := "tag_1 tag_2 tag_3" + packageTitle := "Package Title" + packageDevelopmentDependency := true + packageRequireLicenseAcceptance := true + createNuspec := func(id, version string) string { return ` - - - ` + id + ` - ` + version + ` - ` + packageAuthors + ` - ` + packageDescription + ` - - - - - - -` + + + ` + packageAuthors + ` + ` + packageCopyright + ` + ` + packageDescription + ` + true + ` + packageIconURL + ` + ` + id + ` + ` + packageLanguage + ` + ` + packageLicenseURL + ` + ` + packageOwners + ` + ` + packageProjectURL + ` + ` + packageReleaseNotes + ` + true + ` + packageTags + ` + ` + packageTitle + ` + ` + version + ` + + + + + + + ` } createPackage := func(id, version string) *bytes.Buffer { @@ -393,7 +428,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(412), pb.Size) + assert.Equal(t, int64(610), pb.Size) case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion): assert.False(t, pf.IsLead) @@ -405,7 +440,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(427), pb.Size) + assert.Equal(t, int64(996), pb.Size) case symbolFilename: assert.False(t, pf.IsLead) @@ -736,10 +771,24 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) var result FeedEntry decodeXML(t, resp, &result) - assert.Equal(t, packageName, result.Properties.Title) - assert.Equal(t, packageVersion, result.Properties.Version) assert.Equal(t, packageAuthors, result.Properties.Authors) assert.Equal(t, packageDescription, result.Properties.Description) + assert.Equal(t, packageID, result.Properties.ID) + assert.Equal(t, packageVersion, result.Properties.Version) + + assert.Equal(t, packageCopyright, result.Properties.Copyright) + assert.Equal(t, packageDevelopmentDependency, result.Properties.DevelopmentDependency.Value) + assert.Equal(t, packageIconURL, result.Properties.IconURL) + assert.Equal(t, packageLanguage, result.Properties.Language) + assert.Equal(t, packageLicenseURL, result.Properties.LicenseURL) + assert.Equal(t, packageMinClientVersion, result.Properties.MinClientVersion) + assert.Equal(t, packageOwners, result.Properties.Owners) + assert.Equal(t, packageProjectURL, result.Properties.ProjectURL) + assert.Equal(t, packageReleaseNotes, result.Properties.ReleaseNotes) + assert.Equal(t, packageRequireLicenseAcceptance, result.Properties.RequireLicenseAcceptance.Value) + assert.Equal(t, packageTags, result.Properties.Tags) + assert.Equal(t, packageTitle, result.Properties.Title) + assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies) }) diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 786addbd76c2c..b6a79940cb28b 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -15,9 +15,9 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" - container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + container_module "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -538,7 +538,7 @@ func TestPackageCleanup(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, pbs) - _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion) assert.NoError(t, err) err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration) @@ -548,7 +548,7 @@ func TestPackageCleanup(t *testing.T) { assert.NoError(t, err) assert.Empty(t, pbs) - _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion) assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) })