2022-04-19 19:55:35 +03:00
// Copyright 2022 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-04-19 19:55:35 +03:00
package helm
import (
2022-12-31 14:49:37 +03:00
"errors"
2022-04-19 19:55:35 +03:00
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
2024-03-02 18:42:31 +03:00
"code.gitea.io/gitea/modules/optional"
2022-04-19 19:55:35 +03:00
packages_module "code.gitea.io/gitea/modules/packages"
helm_module "code.gitea.io/gitea/modules/packages/helm"
"code.gitea.io/gitea/modules/setting"
2022-07-27 04:59:10 +03:00
"code.gitea.io/gitea/modules/util"
2022-04-19 19:55:35 +03:00
"code.gitea.io/gitea/routers/api/packages/helper"
2024-02-27 10:12:22 +03:00
"code.gitea.io/gitea/services/context"
2022-04-19 19:55:35 +03:00
packages_service "code.gitea.io/gitea/services/packages"
2022-11-21 11:36:59 +03:00
"gopkg.in/yaml.v3"
2022-04-19 19:55:35 +03:00
)
2023-07-04 21:36:08 +03:00
func apiError ( ctx * context . Context , status int , obj any ) {
2022-04-19 19:55:35 +03:00
helper . LogAndProcessError ( ctx , status , obj , func ( message string ) {
type Error struct {
Error string ` json:"error" `
}
ctx . JSON ( status , Error {
Error : message ,
} )
} )
}
// Index generates the Helm charts index
func Index ( ctx * context . Context ) {
pvs , _ , err := packages_model . SearchVersions ( ctx , & packages_model . PackageSearchOptions {
2022-07-27 04:59:10 +03:00
OwnerID : ctx . Package . Owner . ID ,
Type : packages_model . TypeHelm ,
2024-03-02 18:42:31 +03:00
IsInternal : optional . Some ( false ) ,
2022-04-19 19:55:35 +03:00
} )
if err != nil {
apiError ( ctx , http . StatusInternalServerError , err )
return
}
baseURL := setting . AppURL + "api/packages/" + url . PathEscape ( ctx . Package . Owner . Name ) + "/helm"
type ChartVersion struct {
helm_module . Metadata ` yaml:",inline" `
URLs [ ] string ` yaml:"urls" `
Created time . Time ` yaml:"created,omitempty" `
Removed bool ` yaml:"removed,omitempty" `
Digest string ` yaml:"digest,omitempty" `
}
type ServerInfo struct {
ContextPath string ` yaml:"contextPath,omitempty" `
}
type Index struct {
APIVersion string ` yaml:"apiVersion" `
Entries map [ string ] [ ] * ChartVersion ` yaml:"entries" `
Generated time . Time ` yaml:"generated,omitempty" `
ServerInfo * ServerInfo ` yaml:"serverInfo,omitempty" `
}
entries := make ( map [ string ] [ ] * ChartVersion )
for _ , pv := range pvs {
metadata := & helm_module . Metadata { }
if err := json . Unmarshal ( [ ] byte ( pv . MetadataJSON ) , & metadata ) ; err != nil {
apiError ( ctx , http . StatusInternalServerError , err )
return
}
entries [ metadata . Name ] = append ( entries [ metadata . Name ] , & ChartVersion {
Metadata : * metadata ,
Created : pv . CreatedUnix . AsTime ( ) ,
URLs : [ ] string { fmt . Sprintf ( "%s/%s" , baseURL , url . PathEscape ( createFilename ( metadata ) ) ) } ,
} )
}
ctx . Resp . WriteHeader ( http . StatusOK )
if err := yaml . NewEncoder ( ctx . Resp ) . Encode ( & Index {
APIVersion : "v1" ,
Entries : entries ,
Generated : time . Now ( ) ,
ServerInfo : & ServerInfo {
ContextPath : setting . AppSubURL + "/api/packages/" + url . PathEscape ( ctx . Package . Owner . Name ) + "/helm" ,
} ,
} ) ; err != nil {
log . Error ( "YAML encode failed: %v" , err )
}
}
// DownloadPackageFile serves the content of a package
func DownloadPackageFile ( ctx * context . Context ) {
filename := ctx . Params ( "filename" )
pvs , _ , err := packages_model . SearchVersions ( ctx , & packages_model . PackageSearchOptions {
OwnerID : ctx . Package . Owner . ID ,
Type : packages_model . TypeHelm ,
Name : packages_model . SearchValue {
ExactMatch : true ,
Value : ctx . Params ( "package" ) ,
} ,
HasFileWithName : filename ,
2024-03-02 18:42:31 +03:00
IsInternal : optional . Some ( false ) ,
2022-04-19 19:55:35 +03:00
} )
if err != nil {
apiError ( ctx , http . StatusInternalServerError , err )
return
}
if len ( pvs ) != 1 {
apiError ( ctx , http . StatusNotFound , nil )
return
}
2023-07-03 16:33:28 +03:00
s , u , pf , err := packages_service . GetFileStreamByPackageVersion (
2022-04-19 19:55:35 +03:00
ctx ,
pvs [ 0 ] ,
& packages_service . PackageFileInfo {
Filename : filename ,
} ,
)
if err != nil {
if err == packages_model . ErrPackageFileNotExist {
apiError ( ctx , http . StatusNotFound , err )
return
}
apiError ( ctx , http . StatusInternalServerError , err )
return
}
2023-07-03 16:33:28 +03:00
helper . ServePackageFile ( ctx , s , u , pf )
2022-04-19 19:55:35 +03:00
}
// UploadPackage creates a new package
func UploadPackage ( ctx * context . Context ) {
upload , needToClose , err := ctx . UploadStream ( )
if err != nil {
apiError ( ctx , http . StatusInternalServerError , err )
return
}
if needToClose {
defer upload . Close ( )
}
2023-05-02 19:31:35 +03:00
buf , err := packages_module . CreateHashedBufferFromReader ( upload )
2022-04-19 19:55:35 +03:00
if err != nil {
apiError ( ctx , http . StatusInternalServerError , err )
return
}
defer buf . Close ( )
metadata , err := helm_module . ParseChartArchive ( buf )
if err != nil {
2022-12-31 14:49:37 +03:00
if errors . Is ( err , util . ErrInvalidArgument ) {
apiError ( ctx , http . StatusBadRequest , err )
} else {
apiError ( ctx , http . StatusInternalServerError , err )
}
2022-04-19 19:55:35 +03:00
return
}
if _ , err := buf . Seek ( 0 , io . SeekStart ) ; err != nil {
apiError ( ctx , http . StatusInternalServerError , err )
return
}
_ , _ , err = packages_service . CreatePackageOrAddFileToExisting (
2023-09-25 16:17:37 +03:00
ctx ,
2022-04-19 19:55:35 +03:00
& packages_service . PackageCreationInfo {
PackageInfo : packages_service . PackageInfo {
Owner : ctx . Package . Owner ,
PackageType : packages_model . TypeHelm ,
Name : metadata . Name ,
Version : metadata . Version ,
} ,
SemverCompatible : true ,
Creator : ctx . Doer ,
Metadata : metadata ,
} ,
& packages_service . PackageFileCreationInfo {
PackageFileInfo : packages_service . PackageFileInfo {
Filename : createFilename ( metadata ) ,
} ,
2022-11-09 09:34:27 +03:00
Creator : ctx . Doer ,
2022-04-19 19:55:35 +03:00
Data : buf ,
IsLead : true ,
OverwriteExisting : true ,
} ,
)
if err != nil {
2022-11-09 09:34:27 +03:00
switch err {
case packages_model . ErrDuplicatePackageVersion :
2022-04-19 19:55:35 +03:00
apiError ( ctx , http . StatusConflict , err )
2022-11-09 09:34:27 +03:00
case packages_service . ErrQuotaTotalCount , packages_service . ErrQuotaTypeSize , packages_service . ErrQuotaTotalSize :
apiError ( ctx , http . StatusForbidden , err )
default :
apiError ( ctx , http . StatusInternalServerError , err )
2022-04-19 19:55:35 +03:00
}
return
}
ctx . Status ( http . StatusCreated )
}
func createFilename ( metadata * helm_module . Metadata ) string {
return strings . ToLower ( fmt . Sprintf ( "%s-%s.tgz" , metadata . Name , metadata . Version ) )
}