[GITEA] feat(nuget): basic manifest download
Refs: https://codeberg.org/forgejo/forgejo/pulls/2222 (cherry picked from commit5f837efc15
) fix: write xml header (cherry picked from commita715984a42
) fix: optional elements and xml schema (cherry picked from commit6ea6895a36
) fix: pass all other requests to file search (cherry picked from commit9bfc74833a
) test: add integration test (cherry picked from commitb798f4ce86
) fix: use xmlResponse (cherry picked from commit7f76df0b24
)
This commit is contained in:
parent
c296aeaca6
commit
e18d574ca4
3 changed files with 170 additions and 64 deletions
|
@ -71,34 +71,50 @@ type Dependency struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,omitempty"`
|
||||||
|
ProjectURL string `xml:"projectUrl,omitempty"`
|
||||||
|
Description string `xml:"description"`
|
||||||
|
ReleaseNotes string `xml:"releaseNotes,omitempty"`
|
||||||
|
PackageTypes *nuspecPackageTypes `xml:"packageTypes,omitempty"`
|
||||||
|
Repository *nuspecRepository `xml:"repository,omitempty"`
|
||||||
|
Dependencies *nuspecDependencies `xml:"dependencies,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type nuspecPackage struct {
|
type nuspecPackage struct {
|
||||||
Metadata struct {
|
XMLName xml.Name `xml:"package"`
|
||||||
ID string `xml:"id"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Version string `xml:"version"`
|
Metadata nuspeceMetadata `xml:"metadata"`
|
||||||
Authors string `xml:"authors"`
|
|
||||||
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
|
|
||||||
ProjectURL string `xml:"projectUrl"`
|
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePackageMetaData parses the metadata of a Nuget package file
|
// ParsePackageMetaData parses the metadata of a Nuget package file
|
||||||
|
@ -149,10 +165,12 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
packageType := DependencyPackage
|
packageType := DependencyPackage
|
||||||
for _, pt := range p.Metadata.PackageTypes.PackageType {
|
if p.Metadata.PackageTypes != nil {
|
||||||
if pt.Name == "SymbolsPackage" {
|
for _, pt := range p.Metadata.PackageTypes.PackageType {
|
||||||
packageType = SymbolsPackage
|
if pt.Name == "SymbolsPackage" {
|
||||||
break
|
packageType = SymbolsPackage
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,24 +179,27 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
|
||||||
ReleaseNotes: p.Metadata.ReleaseNotes,
|
ReleaseNotes: p.Metadata.ReleaseNotes,
|
||||||
Authors: p.Metadata.Authors,
|
Authors: p.Metadata.Authors,
|
||||||
ProjectURL: p.Metadata.ProjectURL,
|
ProjectURL: p.Metadata.ProjectURL,
|
||||||
RepositoryURL: p.Metadata.Repository.URL,
|
|
||||||
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
|
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
|
||||||
Dependencies: make(map[string][]Dependency),
|
Dependencies: make(map[string][]Dependency),
|
||||||
}
|
}
|
||||||
|
if p.Metadata.Repository != nil {
|
||||||
for _, group := range p.Metadata.Dependencies.Group {
|
m.RepositoryURL = p.Metadata.Repository.URL
|
||||||
deps := make([]Dependency, 0, len(group.Dependency))
|
}
|
||||||
for _, dep := range group.Dependency {
|
if p.Metadata.Dependencies != nil {
|
||||||
if dep.ID == "" || dep.Version == "" {
|
for _, group := range p.Metadata.Dependencies.Group {
|
||||||
continue
|
deps := make([]Dependency, 0, len(group.Dependency))
|
||||||
|
for _, dep := range group.Dependency {
|
||||||
|
if dep.ID == "" || dep.Version == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deps = append(deps, Dependency{
|
||||||
|
ID: dep.ID,
|
||||||
|
Version: dep.Version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(deps) > 0 {
|
||||||
|
m.Dependencies[group.TargetFramework] = deps
|
||||||
}
|
}
|
||||||
deps = append(deps, Dependency{
|
|
||||||
ID: dep.ID,
|
|
||||||
Version: dep.Version,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(deps) > 0 {
|
|
||||||
m.Dependencies[group.TargetFramework] = deps
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Package{
|
return &Package{
|
||||||
|
@ -204,3 +225,51 @@ func toNormalizedVersion(v *version.Version) string {
|
||||||
}
|
}
|
||||||
return buf.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -387,34 +387,56 @@ func EnumeratePackageVersionsV3(ctx *context.Context) {
|
||||||
ctx.JSON(http.StatusOK, resp)
|
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) {
|
func DownloadPackageFile(ctx *context.Context) {
|
||||||
packageName := ctx.Params("id")
|
packageName := ctx.Params("id")
|
||||||
packageVersion := ctx.Params("version")
|
packageVersion := ctx.Params("version")
|
||||||
filename := ctx.Params("filename")
|
filename := ctx.Params("filename")
|
||||||
|
|
||||||
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
if filename == fmt.Sprintf("%s.nuspec", packageName) {
|
||||||
ctx,
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
|
||||||
&packages_service.PackageInfo{
|
if err != nil {
|
||||||
Owner: ctx.Package.Owner,
|
|
||||||
PackageType: packages_model.TypeNuGet,
|
|
||||||
Name: packageName,
|
|
||||||
Version: packageVersion,
|
|
||||||
},
|
|
||||||
&packages_service.PackageFileInfo{
|
|
||||||
Filename: filename,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
|
|
||||||
apiError(ctx, http.StatusNotFound, err)
|
apiError(ctx, http.StatusNotFound, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.ServePackageFile(ctx, s, u, pf)
|
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{
|
||||||
|
Owner: ctx.Package.Owner,
|
||||||
|
PackageType: packages_model.TypeNuGet,
|
||||||
|
Name: packageName,
|
||||||
|
Version: packageVersion,
|
||||||
|
},
|
||||||
|
&packages_service.PackageFileInfo{
|
||||||
|
Filename: filename,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
|
||||||
|
apiError(ctx, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.ServePackageFile(ctx, s, u, pf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
|
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
|
||||||
|
|
|
@ -353,6 +353,21 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||||
|
|
||||||
assert.Equal(t, content, resp.Body.Bytes())
|
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)
|
checkDownloadCount(1)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)).
|
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)).
|
||||||
|
|
Loading…
Reference in a new issue