feat(packages): add support for uploading helm provenance files (#36695)

Adds `POST helm/api/prov` endpoint for helm repository allowing for upload of provenance files.

Tested manually to a degree but I really didn't want to mess with gpg
again so I'm not sure if helm will correctly verify the chart.
Initial draft made by gemini 3 flash but was finetuned somewhat.

Additionally there's an route that allows for upload of both files via
/api/charts - as separate files in form. If there's any interest in that
I guess it can be added but I think helm is moving to OCI anyway which
we support.

Fixes: https://github.com/go-gitea/gitea/issues/36678
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
TheFox0x7
2026-08-09 14:55:43 +02:00
committed by GitHub
parent 7e34eae370
commit 85558c28fc
4 changed files with 163 additions and 0 deletions
+1
View File
@@ -359,6 +359,7 @@ func CommonRoutes() *web.Router {
r.Get("/index.yaml", helm.Index)
r.Get("/{filename}", helm.DownloadPackageFile)
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
r.Post("/api/prov", reqPackageAccess(perm.AccessModeWrite), helm.UploadProvenanceFile)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/maven", func() {
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
+70
View File
@@ -209,6 +209,76 @@ func UploadPackage(ctx *context.Context) {
ctx.Status(http.StatusCreated)
}
// UploadProvenanceFile uploads and attaches the provenance file to existing helm chart
func UploadProvenanceFile(ctx *context.Context) {
upload, needToClose, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if needToClose {
defer upload.Close()
}
buf, err := packages_module.CreateHashedBufferFromReader(upload)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer buf.Close()
metadata, err := helm_module.ParseProvenanceFile(buf)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) || errors.Is(err, io.EOF) {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
_, err = packages_service.AddFileToExistingPackage(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeHelm,
Name: metadata.Name,
Version: metadata.Version,
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: createProvenanceFilename(metadata),
},
Creator: ctx.Doer,
Data: buf,
IsLead: false,
OverwriteExisting: true,
},
)
if err != nil {
switch err {
case packages_model.ErrPackageNotExist:
apiError(ctx, http.StatusNotFound, err)
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
ctx.Status(http.StatusCreated)
}
func createFilename(metadata *helm_module.Metadata) string {
return strings.ToLower(fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version))
}
func createProvenanceFilename(metadata *helm_module.Metadata) string {
return strings.ToLower(fmt.Sprintf("%s-%s.tgz.prov", metadata.Name, metadata.Version))
}