Merge pull request 'feat(nuget): basic manifest download' (#2222) from viceice/forgejo:nuget-package-nuspec into forgejo-development

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2222
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Gusted 2024-01-24 15:14:29 +00:00
commit 089b86a918
3 changed files with 170 additions and 64 deletions

View file

@ -71,34 +71,50 @@ type Dependency struct {
Version string `json:"version"`
}
type nuspecPackage struct {
Metadata struct {
type nuspecPackageType struct {
Name string `xml:"name,attr"`
}
type nuspecPackageTypes struct {
PackageType []nuspecPackageType `xml:"packageType"`
}
type nuspecRepository struct {
URL string `xml:"url,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
}
type nuspecDependency struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr,omitempty"`
}
type nuspecGroup struct {
TargetFramework string `xml:"targetFramework,attr"`
Dependency []nuspecDependency `xml:"dependency"`
}
type nuspecDependencies struct {
Group []nuspecGroup `xml:"group"`
}
type nuspeceMetadata struct {
ID string `xml:"id"`
Version string `xml:"version"`
Authors string `xml:"authors"`
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
ProjectURL string `xml:"projectUrl"`
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance,omitempty"`
ProjectURL string `xml:"projectUrl,omitempty"`
Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"`
PackageTypes struct {
PackageType []struct {
Name string `xml:"name,attr"`
} `xml:"packageType"`
} `xml:"packageTypes"`
Repository struct {
URL string `xml:"url,attr"`
} `xml:"repository"`
Dependencies struct {
Group []struct {
TargetFramework string `xml:"targetFramework,attr"`
Dependency []struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
} `xml:"dependency"`
} `xml:"group"`
} `xml:"dependencies"`
} `xml:"metadata"`
ReleaseNotes string `xml:"releaseNotes,omitempty"`
PackageTypes *nuspecPackageTypes `xml:"packageTypes,omitempty"`
Repository *nuspecRepository `xml:"repository,omitempty"`
Dependencies *nuspecDependencies `xml:"dependencies,omitempty"`
}
type nuspecPackage struct {
XMLName xml.Name `xml:"package"`
Xmlns string `xml:"xmlns,attr"`
Metadata nuspeceMetadata `xml:"metadata"`
}
// ParsePackageMetaData parses the metadata of a Nuget package file
@ -149,23 +165,27 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
}
packageType := DependencyPackage
if p.Metadata.PackageTypes != nil {
for _, pt := range p.Metadata.PackageTypes.PackageType {
if pt.Name == "SymbolsPackage" {
packageType = SymbolsPackage
break
}
}
}
m := &Metadata{
Description: p.Metadata.Description,
ReleaseNotes: p.Metadata.ReleaseNotes,
Authors: p.Metadata.Authors,
ProjectURL: p.Metadata.ProjectURL,
RepositoryURL: p.Metadata.Repository.URL,
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
Dependencies: make(map[string][]Dependency),
}
if p.Metadata.Repository != nil {
m.RepositoryURL = p.Metadata.Repository.URL
}
if p.Metadata.Dependencies != nil {
for _, group := range p.Metadata.Dependencies.Group {
deps := make([]Dependency, 0, len(group.Dependency))
for _, dep := range group.Dependency {
@ -181,6 +201,7 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
m.Dependencies[group.TargetFramework] = deps
}
}
}
return &Package{
PackageType: packageType,
ID: p.Metadata.ID,
@ -204,3 +225,51 @@ func toNormalizedVersion(v *version.Version) string {
}
return buf.String()
}
// returning any here because we use a private type and we don't need the type for xml marshalling
func GenerateNuspec(pd *Package) any {
m := nuspeceMetadata{
ID: pd.ID,
Version: pd.Version,
Authors: pd.Metadata.Authors,
Description: pd.Metadata.Description,
ProjectURL: pd.Metadata.ProjectURL,
RequireLicenseAcceptance: pd.Metadata.RequireLicenseAcceptance,
}
if pd.Metadata.RepositoryURL != "" {
m.Repository = &nuspecRepository{
URL: pd.Metadata.RepositoryURL,
}
}
groups := len(pd.Metadata.Dependencies)
if groups > 0 {
m.Dependencies = &nuspecDependencies{
Group: make([]nuspecGroup, 0, groups),
}
for tgf, deps := range pd.Metadata.Dependencies {
if len(deps) == 0 {
continue
}
gDeps := make([]nuspecDependency, 0, len(deps))
for _, dep := range deps {
gDeps = append(gDeps, nuspecDependency{
ID: dep.ID,
Version: dep.Version,
})
}
m.Dependencies.Group = append(m.Dependencies.Group, nuspecGroup{
TargetFramework: tgf,
Dependency: gDeps,
})
}
}
return &nuspecPackage{
Xmlns: "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd",
Metadata: m,
}
}

View file

@ -387,12 +387,33 @@ func EnumeratePackageVersionsV3(ctx *context.Context) {
ctx.JSON(http.StatusOK, resp)
}
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
// https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-manifest-nuspec
// https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
func DownloadPackageFile(ctx *context.Context) {
packageName := ctx.Params("id")
packageVersion := ctx.Params("version")
filename := ctx.Params("filename")
if filename == fmt.Sprintf("%s.nuspec", packageName) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
if err != nil {
apiError(ctx, http.StatusNotFound, err)
return
}
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pkg := &nuget_module.Package{
ID: pd.Package.Name,
Version: packageVersion,
Metadata: pd.Metadata.(*nuget_module.Metadata),
}
xmlResponse(ctx, http.StatusOK, nuget_module.GenerateNuspec(pkg))
} else {
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
@ -416,6 +437,7 @@ func DownloadPackageFile(ctx *context.Context) {
helper.ServePackageFile(ctx, s, u, pf)
}
}
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#push-a-package

View file

@ -353,6 +353,21 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
assert.Equal(t, content, resp.Body.Bytes())
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.nuspec", url, packageName, packageVersion, packageName)).
AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusOK)
nuspec := `<?xml version="1.0" encoding="UTF-8"?>` + "\n" +
`<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"><metadata>` +
`<id>` + packageName + `</id><version>` + packageVersion + `</version><authors>` + packageAuthors + `</authors><description>` + packageDescription + `</description>` +
`<dependencies><group targetFramework=".NETStandard2.0">` +
// https://github.com/golang/go/issues/21399 go can't generate self-closing tags
`<dependency id="Microsoft.CSharp" version="4.5.0"></dependency>` +
`</group></dependencies>` +
`</metadata></package>`
assert.Equal(t, nuspec, resp.Body.String())
checkDownloadCount(1)
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)).