feat(nuget): basic manifest download

This commit is contained in:
Michael Kriese 2024-01-23 22:42:46 +01:00
parent 9fe826605f
commit 5f837efc15
No known key found for this signature in database
GPG key ID: B83F553A0724D44E
3 changed files with 127 additions and 46 deletions

View file

@ -5,6 +5,7 @@ package context
import ( import (
"context" "context"
"encoding/xml"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -136,6 +137,15 @@ func (b *Base) JSON(status int, content any) {
} }
} }
// XML render content as XML
func (b *Base) XML(status int, content any) {
b.Resp.Header().Set("Content-Type", "application/xml;charset=utf-8")
b.Resp.WriteHeader(status)
if err := xml.NewEncoder(b.Resp).Encode(content); err != nil {
log.Error("Render XML failed: %v", err)
}
}
// RemoteAddr returns the client machine ip address // RemoteAddr returns the client machine ip address
func (b *Base) RemoteAddr() string { func (b *Base) RemoteAddr() string {
return b.Req.RemoteAddr return b.Req.RemoteAddr

View file

@ -71,34 +71,47 @@ 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"`
}
type nuspecDependency struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
}
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"`
Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"`
PackageTypes nuspecPackageTypes `xml:"packageTypes"`
Repository nuspecRepository `xml:"repository"`
Dependencies nuspecDependencies `xml:"dependencies"`
}
type nuspecPackage struct { type nuspecPackage struct {
Metadata struct { Metadata nuspeceMetadata `xml:"metadata"`
ID string `xml:"id"`
Version string `xml:"version"`
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
@ -204,3 +217,36 @@ 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,
ProjectURL: pd.Metadata.ProjectURL,
Repository: nuspecRepository{URL: pd.Metadata.RepositoryURL},
RequireLicenseAcceptance: pd.Metadata.RequireLicenseAcceptance,
Dependencies: nuspecDependencies{
Group: make([]nuspecGroup, len(pd.Metadata.Dependencies)),
},
}
for tgf, deps := range pd.Metadata.Dependencies {
gDeps := make([]nuspecDependency, len(deps))
for i, dep := range deps {
gDeps[i] = nuspecDependency{
ID: dep.ID,
Version: dep.Version,
}
}
m.Dependencies.Group = append(m.Dependencies.Group, nuspecGroup{
TargetFramework: tgf,
Dependency: gDeps,
})
}
return &nuspecPackage{
Metadata: m,
}
}

View file

@ -387,34 +387,59 @@ 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),
}
ctx.XML(http.StatusOK, nuget_module.GenerateNuspec(pkg))
} else if filename == fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion) {
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)
} else {
apiError(ctx, http.StatusInternalServerError, "Invalid filename")
}
} }
// 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