Add Helm Chart registry (#19406)
parent
b74322dfce
commit
18727df73a
@ -0,0 +1,67 @@ |
|||||||
|
--- |
||||||
|
date: "2022-04-14T00:00:00+00:00" |
||||||
|
title: "Helm Chart Registry" |
||||||
|
slug: "packages/helm" |
||||||
|
draft: false |
||||||
|
toc: false |
||||||
|
menu: |
||||||
|
sidebar: |
||||||
|
parent: "packages" |
||||||
|
name: "Helm" |
||||||
|
weight: 50 |
||||||
|
identifier: "helm" |
||||||
|
--- |
||||||
|
|
||||||
|
# Helm Chart Registry |
||||||
|
|
||||||
|
Publish [Helm](https://helm.sh/) charts for your user or organization. |
||||||
|
|
||||||
|
**Table of Contents** |
||||||
|
|
||||||
|
{{< toc >}} |
||||||
|
|
||||||
|
## Requirements |
||||||
|
|
||||||
|
To work with the Helm Chart registry use a simple HTTP client like `curl` or the [`helm cm-push`](https://github.com/chartmuseum/helm-push/) plugin. |
||||||
|
|
||||||
|
## Publish a package |
||||||
|
|
||||||
|
Publish a package by running the following command: |
||||||
|
|
||||||
|
```shell |
||||||
|
curl --user {username}:{password} -X POST --upload-file ./{chart_file}.tgz https://gitea.example.com/api/packages/{owner}/helm/api/charts |
||||||
|
``` |
||||||
|
|
||||||
|
or with the `helm cm-push` plugin: |
||||||
|
|
||||||
|
```shell |
||||||
|
helm repo add --username {username} --password {password} {repo} https://gitea.example.com/api/packages/{owner}/helm |
||||||
|
helm cm-push ./{chart_file}.tgz {repo} |
||||||
|
``` |
||||||
|
|
||||||
|
| Parameter | Description | |
||||||
|
| ------------ | ----------- | |
||||||
|
| `username` | Your Gitea username. | |
||||||
|
| `password` | Your Gitea password or a personal access token. | |
||||||
|
| `repo` | The name for the repository. | |
||||||
|
| `chart_file` | The Helm Chart archive. | |
||||||
|
| `owner` | The owner of the package. | |
||||||
|
|
||||||
|
## Install a package |
||||||
|
|
||||||
|
To install a Helm char from the registry, execute the following command: |
||||||
|
|
||||||
|
```shell |
||||||
|
helm repo add --username {username} --password {password} {repo} https://gitea.example.com/api/packages/{owner}/helm |
||||||
|
helm repo update |
||||||
|
helm install {name} {repo}/{chart} |
||||||
|
``` |
||||||
|
|
||||||
|
| Parameter | Description | |
||||||
|
| ---------- | ----------- | |
||||||
|
| `username` | Your Gitea username. | |
||||||
|
| `password` | Your Gitea password or a personal access token. | |
||||||
|
| `repo` | The name for the repository. | |
||||||
|
| `owner` | The owner of the package. | |
||||||
|
| `name` | The local name. | |
||||||
|
| `chart` | The name Helm Chart. | |
@ -0,0 +1,166 @@ |
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package integrations |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/tar" |
||||||
|
"bytes" |
||||||
|
"compress/gzip" |
||||||
|
"fmt" |
||||||
|
"net/http" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db" |
||||||
|
"code.gitea.io/gitea/models/packages" |
||||||
|
"code.gitea.io/gitea/models/unittest" |
||||||
|
user_model "code.gitea.io/gitea/models/user" |
||||||
|
helm_module "code.gitea.io/gitea/modules/packages/helm" |
||||||
|
"code.gitea.io/gitea/modules/setting" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
"gopkg.in/yaml.v2" |
||||||
|
) |
||||||
|
|
||||||
|
func TestPackageHelm(t *testing.T) { |
||||||
|
defer prepareTestEnv(t)() |
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) |
||||||
|
|
||||||
|
packageName := "test-chart" |
||||||
|
packageVersion := "1.0.3" |
||||||
|
packageAuthor := "KN4CK3R" |
||||||
|
packageDescription := "Gitea Test Package" |
||||||
|
|
||||||
|
filename := fmt.Sprintf("%s-%s.tgz", packageName, packageVersion) |
||||||
|
|
||||||
|
chartContent := `apiVersion: v2 |
||||||
|
description: ` + packageDescription + ` |
||||||
|
name: ` + packageName + ` |
||||||
|
type: application |
||||||
|
version: ` + packageVersion + ` |
||||||
|
maintainers: |
||||||
|
- name: ` + packageAuthor + ` |
||||||
|
dependencies: |
||||||
|
- name: dep1 |
||||||
|
repository: https://example.com/
|
||||||
|
version: 1.0.0` |
||||||
|
|
||||||
|
var buf bytes.Buffer |
||||||
|
zw := gzip.NewWriter(&buf) |
||||||
|
archive := tar.NewWriter(zw) |
||||||
|
archive.WriteHeader(&tar.Header{ |
||||||
|
Name: fmt.Sprintf("%s/Chart.yaml", packageName), |
||||||
|
Mode: 0o600, |
||||||
|
Size: int64(len(chartContent)), |
||||||
|
}) |
||||||
|
archive.Write([]byte(chartContent)) |
||||||
|
archive.Close() |
||||||
|
zw.Close() |
||||||
|
content := buf.Bytes() |
||||||
|
|
||||||
|
url := fmt.Sprintf("/api/packages/%s/helm", user.Name) |
||||||
|
|
||||||
|
t.Run("Upload", func(t *testing.T) { |
||||||
|
defer PrintCurrentTest(t)() |
||||||
|
|
||||||
|
uploadURL := url + "/api/charts" |
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", uploadURL, bytes.NewReader(content)) |
||||||
|
req = AddBasicAuthHeader(req, user.Name) |
||||||
|
MakeRequest(t, req, http.StatusCreated) |
||||||
|
|
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeHelm) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.Len(t, pvs, 1) |
||||||
|
|
||||||
|
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.NotNil(t, pd.SemVer) |
||||||
|
assert.IsType(t, &helm_module.Metadata{}, pd.Metadata) |
||||||
|
assert.Equal(t, packageName, pd.Package.Name) |
||||||
|
assert.Equal(t, packageVersion, pd.Version.Version) |
||||||
|
|
||||||
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.Len(t, pfs, 1) |
||||||
|
assert.Equal(t, filename, pfs[0].Name) |
||||||
|
assert.True(t, pfs[0].IsLead) |
||||||
|
|
||||||
|
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.Equal(t, int64(len(content)), pb.Size) |
||||||
|
|
||||||
|
req = NewRequestWithBody(t, "POST", uploadURL, bytes.NewReader(content)) |
||||||
|
req = AddBasicAuthHeader(req, user.Name) |
||||||
|
MakeRequest(t, req, http.StatusCreated) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("Download", func(t *testing.T) { |
||||||
|
defer PrintCurrentTest(t)() |
||||||
|
|
||||||
|
checkDownloadCount := func(count int64) { |
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeHelm) |
||||||
|
assert.NoError(t, err) |
||||||
|
assert.Len(t, pvs, 1) |
||||||
|
assert.Equal(t, count, pvs[0].DownloadCount) |
||||||
|
} |
||||||
|
|
||||||
|
checkDownloadCount(0) |
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s", url, filename)) |
||||||
|
req = AddBasicAuthHeader(req, user.Name) |
||||||
|
resp := MakeRequest(t, req, http.StatusOK) |
||||||
|
|
||||||
|
assert.Equal(t, content, resp.Body.Bytes()) |
||||||
|
|
||||||
|
checkDownloadCount(1) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("Index", func(t *testing.T) { |
||||||
|
defer PrintCurrentTest(t)() |
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.yaml", url)) |
||||||
|
req = AddBasicAuthHeader(req, user.Name) |
||||||
|
resp := MakeRequest(t, req, http.StatusOK) |
||||||
|
|
||||||
|
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"` |
||||||
|
} |
||||||
|
|
||||||
|
var result Index |
||||||
|
assert.NoError(t, yaml.NewDecoder(resp.Body).Decode(&result)) |
||||||
|
assert.NotEmpty(t, result.Entries) |
||||||
|
assert.Contains(t, result.Entries, packageName) |
||||||
|
|
||||||
|
cvs := result.Entries[packageName] |
||||||
|
assert.Len(t, cvs, 1) |
||||||
|
|
||||||
|
cv := cvs[0] |
||||||
|
assert.Equal(t, packageName, cv.Name) |
||||||
|
assert.Equal(t, packageVersion, cv.Version) |
||||||
|
assert.Equal(t, packageDescription, cv.Description) |
||||||
|
assert.Len(t, cv.Maintainers, 1) |
||||||
|
assert.Equal(t, packageAuthor, cv.Maintainers[0].Name) |
||||||
|
assert.Len(t, cv.Dependencies, 1) |
||||||
|
assert.ElementsMatch(t, []string{fmt.Sprintf("%s%s/%s", setting.AppURL, url[1:], filename)}, cv.URLs) |
||||||
|
|
||||||
|
assert.Equal(t, url, result.ServerInfo.ContextPath) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package helm |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/tar" |
||||||
|
"compress/gzip" |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/validation" |
||||||
|
|
||||||
|
"github.com/hashicorp/go-version" |
||||||
|
"gopkg.in/yaml.v2" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// ErrMissingChartFile indicates a missing Chart.yaml file
|
||||||
|
ErrMissingChartFile = errors.New("Chart.yaml file is missing") |
||||||
|
// ErrInvalidName indicates an invalid package name
|
||||||
|
ErrInvalidName = errors.New("package name is invalid") |
||||||
|
// ErrInvalidVersion indicates an invalid package version
|
||||||
|
ErrInvalidVersion = errors.New("package version is invalid") |
||||||
|
// ErrInvalidChart indicates an invalid chart
|
||||||
|
ErrInvalidChart = errors.New("chart is invalid") |
||||||
|
) |
||||||
|
|
||||||
|
// Metadata for a Chart file. This models the structure of a Chart.yaml file.
|
||||||
|
type Metadata struct { |
||||||
|
APIVersion string `json:"api_version" yaml:"apiVersion"` |
||||||
|
Type string `json:"type,omitempty" yaml:"type,omitempty"` |
||||||
|
Name string `json:"name" yaml:"name"` |
||||||
|
Version string `json:"version" yaml:"version"` |
||||||
|
AppVersion string `json:"app_version,omitempty" yaml:"appVersion,omitempty"` |
||||||
|
Home string `json:"home,omitempty" yaml:"home,omitempty"` |
||||||
|
Sources []string `json:"sources,omitempty" yaml:"sources,omitempty"` |
||||||
|
Description string `json:"description,omitempty" yaml:"description,omitempty"` |
||||||
|
Keywords []string `json:"keywords,omitempty" yaml:"keywords,omitempty"` |
||||||
|
Maintainers []*Maintainer `json:"maintainers,omitempty" yaml:"maintainers,omitempty"` |
||||||
|
Icon string `json:"icon,omitempty" yaml:"icon,omitempty"` |
||||||
|
Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` |
||||||
|
Tags string `json:"tags,omitempty" yaml:"tags,omitempty"` |
||||||
|
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` |
||||||
|
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` |
||||||
|
KubeVersion string `json:"kube_version,omitempty" yaml:"kubeVersion,omitempty"` |
||||||
|
Dependencies []*Dependency `json:"dependencies,omitempty" yaml:"dependencies,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type Maintainer struct { |
||||||
|
Name string `json:"name,omitempty" yaml:"name,omitempty"` |
||||||
|
Email string `json:"email,omitempty" yaml:"email,omitempty"` |
||||||
|
URL string `json:"url,omitempty" yaml:"url,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type Dependency struct { |
||||||
|
Name string `json:"name" yaml:"name"` |
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"` |
||||||
|
Repository string `json:"repository" yaml:"repository"` |
||||||
|
Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` |
||||||
|
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` |
||||||
|
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` |
||||||
|
ImportValues []interface{} `json:"import_values,omitempty" yaml:"import-values,omitempty"` |
||||||
|
Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// ParseChartArchive parses the metadata of a Helm archive
|
||||||
|
func ParseChartArchive(r io.Reader) (*Metadata, error) { |
||||||
|
gzr, err := gzip.NewReader(r) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer gzr.Close() |
||||||
|
|
||||||
|
tr := tar.NewReader(gzr) |
||||||
|
for { |
||||||
|
hd, err := tr.Next() |
||||||
|
if err == io.EOF { |
||||||
|
break |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if hd.Typeflag != tar.TypeReg { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if hd.FileInfo().Name() == "Chart.yaml" { |
||||||
|
if strings.Count(hd.Name, "/") != 1 { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
return ParseChartFile(tr) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil, ErrMissingChartFile |
||||||
|
} |
||||||
|
|
||||||
|
// ParseChartFile parses a Chart.yaml file to retrieve the metadata of a Helm chart
|
||||||
|
func ParseChartFile(r io.Reader) (*Metadata, error) { |
||||||
|
var metadata *Metadata |
||||||
|
if err := yaml.NewDecoder(r).Decode(&metadata); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if metadata.APIVersion == "" { |
||||||
|
return nil, ErrInvalidChart |
||||||
|
} |
||||||
|
|
||||||
|
if metadata.Type != "" && metadata.Type != "application" && metadata.Type != "library" { |
||||||
|
return nil, ErrInvalidChart |
||||||
|
} |
||||||
|
|
||||||
|
if metadata.Name == "" { |
||||||
|
return nil, ErrInvalidName |
||||||
|
} |
||||||
|
|
||||||
|
if _, err := version.NewSemver(metadata.Version); err != nil { |
||||||
|
return nil, ErrInvalidVersion |
||||||
|
} |
||||||
|
|
||||||
|
if !validation.IsValidURL(metadata.Home) { |
||||||
|
metadata.Home = "" |
||||||
|
} |
||||||
|
|
||||||
|
return metadata, nil |
||||||
|
} |
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,205 @@ |
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package helm |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"net/url" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
packages_model "code.gitea.io/gitea/models/packages" |
||||||
|
"code.gitea.io/gitea/modules/context" |
||||||
|
"code.gitea.io/gitea/modules/json" |
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
packages_module "code.gitea.io/gitea/modules/packages" |
||||||
|
helm_module "code.gitea.io/gitea/modules/packages/helm" |
||||||
|
"code.gitea.io/gitea/modules/setting" |
||||||
|
"code.gitea.io/gitea/routers/api/packages/helper" |
||||||
|
packages_service "code.gitea.io/gitea/services/packages" |
||||||
|
|
||||||
|
"gopkg.in/yaml.v2" |
||||||
|
) |
||||||
|
|
||||||
|
func apiError(ctx *context.Context, status int, obj interface{}) { |
||||||
|
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{ |
||||||
|
OwnerID: ctx.Package.Owner.ID, |
||||||
|
Type: packages_model.TypeHelm, |
||||||
|
}) |
||||||
|
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, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
apiError(ctx, http.StatusInternalServerError, err) |
||||||
|
return |
||||||
|
} |
||||||
|
if len(pvs) != 1 { |
||||||
|
apiError(ctx, http.StatusNotFound, nil) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
s, pf, err := packages_service.GetFileStreamByPackageVersion( |
||||||
|
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 |
||||||
|
} |
||||||
|
defer s.Close() |
||||||
|
|
||||||
|
ctx.ServeStream(s, pf.Name) |
||||||
|
} |
||||||
|
|
||||||
|
// 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() |
||||||
|
} |
||||||
|
|
||||||
|
buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) |
||||||
|
if err != nil { |
||||||
|
apiError(ctx, http.StatusInternalServerError, err) |
||||||
|
return |
||||||
|
} |
||||||
|
defer buf.Close() |
||||||
|
|
||||||
|
metadata, err := helm_module.ParseChartArchive(buf) |
||||||
|
if err != nil { |
||||||
|
apiError(ctx, http.StatusBadRequest, err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if _, err := buf.Seek(0, io.SeekStart); err != nil { |
||||||
|
apiError(ctx, http.StatusInternalServerError, err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
_, _, err = packages_service.CreatePackageOrAddFileToExisting( |
||||||
|
&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), |
||||||
|
}, |
||||||
|
Data: buf, |
||||||
|
IsLead: true, |
||||||
|
OverwriteExisting: true, |
||||||
|
}, |
||||||
|
) |
||||||
|
if err != nil { |
||||||
|
if err == packages_model.ErrDuplicatePackageVersion { |
||||||
|
apiError(ctx, http.StatusConflict, err) |
||||||
|
return |
||||||
|
} |
||||||
|
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)) |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
{{if eq .PackageDescriptor.Package.Type "helm"}} |
||||||
|
<h4 class="ui top attached header">{{.i18n.Tr "packages.installation"}}</h4> |
||||||
|
<div class="ui attached segment"> |
||||||
|
<div class="ui form"> |
||||||
|
<div class="field"> |
||||||
|
<label>{{svg "octicon-terminal"}} {{.i18n.Tr "packages.helm.registry"}}</label> |
||||||
|
<div class="markup"><pre class="code-block"><code>helm repo add gitea {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/helm |
||||||
|
helm repo update</code></pre></div> |
||||||
|
</div> |
||||||
|
<div class="field"> |
||||||
|
<label>{{svg "octicon-terminal"}} {{.i18n.Tr "packages.helm.install"}}</label> |
||||||
|
<div class="markup"><pre class="code-block"><code>helm install {{.PackageDescriptor.Package.Name}} gitea/{{.PackageDescriptor.Package.Name}}</code></pre></div> |
||||||
|
</div> |
||||||
|
<div class="field"> |
||||||
|
<label>{{.i18n.Tr "packages.helm.documentation" | Safe}}</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{{if .PackageDescriptor.Metadata.Description}} |
||||||
|
<h4 class="ui top attached header">{{.i18n.Tr "packages.about"}}</h4> |
||||||
|
<div class="ui attached segment"> |
||||||
|
{{.PackageDescriptor.Metadata.Description}} |
||||||
|
</div> |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{if .PackageDescriptor.Metadata.Dependencies}} |
||||||
|
<h4 class="ui top attached header">{{.i18n.Tr "packages.dependencies"}}</h4> |
||||||
|
<div class="ui attached segment"> |
||||||
|
<table class="ui single line very basic table"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th class="ten wide">{{.i18n.Tr "packages.dependency.id"}}</th> |
||||||
|
<th class="six wide">{{.i18n.Tr "packages.dependency.version"}}</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{{range .PackageDescriptor.Metadata.Dependencies}} |
||||||
|
<tr> |
||||||
|
<td>{{.Name}}</td> |
||||||
|
<td>{{.Version}}</td> |
||||||
|
</tr> |
||||||
|
{{end}} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{if .PackageDescriptor.Metadata.Keywords}} |
||||||
|
<h4 class="ui top attached header">{{.i18n.Tr "packages.keywords"}}</h4> |
||||||
|
<div class="ui attached segment"> |
||||||
|
{{range .PackageDescriptor.Metadata.Keywords}} |
||||||
|
{{.}} |
||||||
|
{{end}} |
||||||
|
</div> |
||||||
|
{{end}} |
||||||
|
{{end}} |
@ -0,0 +1,4 @@ |
|||||||
|
{{if eq .PackageDescriptor.Package.Type "helm"}} |
||||||
|
{{range .PackageDescriptor.Metadata.Maintainers}}<div class="item" title="{{$.i18n.Tr "packages.details.author"}}">{{svg "octicon-person" 16 "mr-3"}} {{.Name}}</div>{{end}} |
||||||
|
{{if .PackageDescriptor.Metadata.Home}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.Home}}" target="_blank" rel="noopener noreferrer me">{{.i18n.Tr "packages.details.project_site"}}</a></div>{{end}} |
||||||
|
{{end}} |
After Width: | Height: | Size: 1.8 KiB |
Loading…
Reference in new issue