Skip to content

Commit 18727df

Browse files
authored
Add Helm Chart registry (#19406)
1 parent b74322d commit 18727df

File tree

24 files changed

+679
-21
lines changed

24 files changed

+679
-21
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
date: "2022-04-14T00:00:00+00:00"
3+
title: "Helm Chart Registry"
4+
slug: "packages/helm"
5+
draft: false
6+
toc: false
7+
menu:
8+
sidebar:
9+
parent: "packages"
10+
name: "Helm"
11+
weight: 50
12+
identifier: "helm"
13+
---
14+
15+
# Helm Chart Registry
16+
17+
Publish [Helm](https://helm.sh/) charts for your user or organization.
18+
19+
**Table of Contents**
20+
21+
{{< toc >}}
22+
23+
## Requirements
24+
25+
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.
26+
27+
## Publish a package
28+
29+
Publish a package by running the following command:
30+
31+
```shell
32+
curl --user {username}:{password} -X POST --upload-file ./{chart_file}.tgz https://gitea.example.com/api/packages/{owner}/helm/api/charts
33+
```
34+
35+
or with the `helm cm-push` plugin:
36+
37+
```shell
38+
helm repo add --username {username} --password {password} {repo} https://gitea.example.com/api/packages/{owner}/helm
39+
helm cm-push ./{chart_file}.tgz {repo}
40+
```
41+
42+
| Parameter | Description |
43+
| ------------ | ----------- |
44+
| `username` | Your Gitea username. |
45+
| `password` | Your Gitea password or a personal access token. |
46+
| `repo` | The name for the repository. |
47+
| `chart_file` | The Helm Chart archive. |
48+
| `owner` | The owner of the package. |
49+
50+
## Install a package
51+
52+
To install a Helm char from the registry, execute the following command:
53+
54+
```shell
55+
helm repo add --username {username} --password {password} {repo} https://gitea.example.com/api/packages/{owner}/helm
56+
helm repo update
57+
helm install {name} {repo}/{chart}
58+
```
59+
60+
| Parameter | Description |
61+
| ---------- | ----------- |
62+
| `username` | Your Gitea username. |
63+
| `password` | Your Gitea password or a personal access token. |
64+
| `repo` | The name for the repository. |
65+
| `owner` | The owner of the package. |
66+
| `name` | The local name. |
67+
| `chart` | The name Helm Chart. |

docs/content/doc/packages/maven.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ menu:
88
sidebar:
99
parent: "packages"
1010
name: "Maven"
11-
weight: 50
11+
weight: 60
1212
identifier: "maven"
1313
---
1414

docs/content/doc/packages/npm.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ menu:
88
sidebar:
99
parent: "packages"
1010
name: "npm"
11-
weight: 60
11+
weight: 70
1212
identifier: "npm"
1313
---
1414

docs/content/doc/packages/nuget.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ menu:
88
sidebar:
99
parent: "packages"
1010
name: "NuGet"
11-
weight: 70
11+
weight: 80
1212
identifier: "nuget"
1313
---
1414

docs/content/doc/packages/overview.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The following package managers are currently supported:
3030
| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` |
3131
| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client |
3232
| [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP client |
33+
| [Helm]({{< relref "doc/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` |
3334
| [Maven]({{< relref "doc/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` |
3435
| [npm]({{< relref "doc/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn` |
3536
| [NuGet]({{< relref "doc/packages/nuget.en-us.md" >}}) | .NET | `nuget` |

docs/content/doc/packages/pypi.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ menu:
88
sidebar:
99
parent: "packages"
1010
name: "PyPI"
11-
weight: 80
11+
weight: 90
1212
identifier: "pypi"
1313
---
1414

docs/content/doc/packages/rubygems.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ menu:
88
sidebar:
99
parent: "packages"
1010
name: "RubyGems"
11-
weight: 90
11+
weight: 100
1212
identifier: "rubygems"
1313
---
1414

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"archive/tar"
9+
"bytes"
10+
"compress/gzip"
11+
"fmt"
12+
"net/http"
13+
"testing"
14+
"time"
15+
16+
"code.gitea.io/gitea/models/db"
17+
"code.gitea.io/gitea/models/packages"
18+
"code.gitea.io/gitea/models/unittest"
19+
user_model "code.gitea.io/gitea/models/user"
20+
helm_module "code.gitea.io/gitea/modules/packages/helm"
21+
"code.gitea.io/gitea/modules/setting"
22+
23+
"github.com/stretchr/testify/assert"
24+
"gopkg.in/yaml.v2"
25+
)
26+
27+
func TestPackageHelm(t *testing.T) {
28+
defer prepareTestEnv(t)()
29+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
30+
31+
packageName := "test-chart"
32+
packageVersion := "1.0.3"
33+
packageAuthor := "KN4CK3R"
34+
packageDescription := "Gitea Test Package"
35+
36+
filename := fmt.Sprintf("%s-%s.tgz", packageName, packageVersion)
37+
38+
chartContent := `apiVersion: v2
39+
description: ` + packageDescription + `
40+
name: ` + packageName + `
41+
type: application
42+
version: ` + packageVersion + `
43+
maintainers:
44+
- name: ` + packageAuthor + `
45+
dependencies:
46+
- name: dep1
47+
repository: https://example.com/
48+
version: 1.0.0`
49+
50+
var buf bytes.Buffer
51+
zw := gzip.NewWriter(&buf)
52+
archive := tar.NewWriter(zw)
53+
archive.WriteHeader(&tar.Header{
54+
Name: fmt.Sprintf("%s/Chart.yaml", packageName),
55+
Mode: 0o600,
56+
Size: int64(len(chartContent)),
57+
})
58+
archive.Write([]byte(chartContent))
59+
archive.Close()
60+
zw.Close()
61+
content := buf.Bytes()
62+
63+
url := fmt.Sprintf("/api/packages/%s/helm", user.Name)
64+
65+
t.Run("Upload", func(t *testing.T) {
66+
defer PrintCurrentTest(t)()
67+
68+
uploadURL := url + "/api/charts"
69+
70+
req := NewRequestWithBody(t, "POST", uploadURL, bytes.NewReader(content))
71+
req = AddBasicAuthHeader(req, user.Name)
72+
MakeRequest(t, req, http.StatusCreated)
73+
74+
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeHelm)
75+
assert.NoError(t, err)
76+
assert.Len(t, pvs, 1)
77+
78+
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
79+
assert.NoError(t, err)
80+
assert.NotNil(t, pd.SemVer)
81+
assert.IsType(t, &helm_module.Metadata{}, pd.Metadata)
82+
assert.Equal(t, packageName, pd.Package.Name)
83+
assert.Equal(t, packageVersion, pd.Version.Version)
84+
85+
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
86+
assert.NoError(t, err)
87+
assert.Len(t, pfs, 1)
88+
assert.Equal(t, filename, pfs[0].Name)
89+
assert.True(t, pfs[0].IsLead)
90+
91+
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
92+
assert.NoError(t, err)
93+
assert.Equal(t, int64(len(content)), pb.Size)
94+
95+
req = NewRequestWithBody(t, "POST", uploadURL, bytes.NewReader(content))
96+
req = AddBasicAuthHeader(req, user.Name)
97+
MakeRequest(t, req, http.StatusCreated)
98+
})
99+
100+
t.Run("Download", func(t *testing.T) {
101+
defer PrintCurrentTest(t)()
102+
103+
checkDownloadCount := func(count int64) {
104+
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeHelm)
105+
assert.NoError(t, err)
106+
assert.Len(t, pvs, 1)
107+
assert.Equal(t, count, pvs[0].DownloadCount)
108+
}
109+
110+
checkDownloadCount(0)
111+
112+
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s", url, filename))
113+
req = AddBasicAuthHeader(req, user.Name)
114+
resp := MakeRequest(t, req, http.StatusOK)
115+
116+
assert.Equal(t, content, resp.Body.Bytes())
117+
118+
checkDownloadCount(1)
119+
})
120+
121+
t.Run("Index", func(t *testing.T) {
122+
defer PrintCurrentTest(t)()
123+
124+
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.yaml", url))
125+
req = AddBasicAuthHeader(req, user.Name)
126+
resp := MakeRequest(t, req, http.StatusOK)
127+
128+
type ChartVersion struct {
129+
helm_module.Metadata `yaml:",inline"`
130+
URLs []string `yaml:"urls"`
131+
Created time.Time `yaml:"created,omitempty"`
132+
Removed bool `yaml:"removed,omitempty"`
133+
Digest string `yaml:"digest,omitempty"`
134+
}
135+
136+
type ServerInfo struct {
137+
ContextPath string `yaml:"contextPath,omitempty"`
138+
}
139+
140+
type Index struct {
141+
APIVersion string `yaml:"apiVersion"`
142+
Entries map[string][]*ChartVersion `yaml:"entries"`
143+
Generated time.Time `yaml:"generated,omitempty"`
144+
ServerInfo *ServerInfo `yaml:"serverInfo,omitempty"`
145+
}
146+
147+
var result Index
148+
assert.NoError(t, yaml.NewDecoder(resp.Body).Decode(&result))
149+
assert.NotEmpty(t, result.Entries)
150+
assert.Contains(t, result.Entries, packageName)
151+
152+
cvs := result.Entries[packageName]
153+
assert.Len(t, cvs, 1)
154+
155+
cv := cvs[0]
156+
assert.Equal(t, packageName, cv.Name)
157+
assert.Equal(t, packageVersion, cv.Version)
158+
assert.Equal(t, packageDescription, cv.Description)
159+
assert.Len(t, cv.Maintainers, 1)
160+
assert.Equal(t, packageAuthor, cv.Maintainers[0].Name)
161+
assert.Len(t, cv.Dependencies, 1)
162+
assert.ElementsMatch(t, []string{fmt.Sprintf("%s%s/%s", setting.AppURL, url[1:], filename)}, cv.URLs)
163+
164+
assert.Equal(t, url, result.ServerInfo.ContextPath)
165+
})
166+
}

models/packages/descriptor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"code.gitea.io/gitea/modules/packages/composer"
1616
"code.gitea.io/gitea/modules/packages/conan"
1717
"code.gitea.io/gitea/modules/packages/container"
18+
"code.gitea.io/gitea/modules/packages/helm"
1819
"code.gitea.io/gitea/modules/packages/maven"
1920
"code.gitea.io/gitea/modules/packages/npm"
2021
"code.gitea.io/gitea/modules/packages/nuget"
@@ -129,6 +130,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
129130
metadata = &container.Metadata{}
130131
case TypeGeneric:
131132
// generic packages have no metadata
133+
case TypeHelm:
134+
metadata = &helm.Metadata{}
132135
case TypeNuGet:
133136
metadata = &nuget.Metadata{}
134137
case TypeNpm:

models/packages/package.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ const (
3535
TypeConan Type = "conan"
3636
TypeContainer Type = "container"
3737
TypeGeneric Type = "generic"
38-
TypeNuGet Type = "nuget"
39-
TypeNpm Type = "npm"
38+
TypeHelm Type = "helm"
4039
TypeMaven Type = "maven"
40+
TypeNpm Type = "npm"
41+
TypeNuGet Type = "nuget"
4142
TypePyPI Type = "pypi"
4243
TypeRubyGems Type = "rubygems"
4344
)
@@ -53,12 +54,14 @@ func (pt Type) Name() string {
5354
return "Container"
5455
case TypeGeneric:
5556
return "Generic"
56-
case TypeNuGet:
57-
return "NuGet"
58-
case TypeNpm:
59-
return "npm"
57+
case TypeHelm:
58+
return "Helm"
6059
case TypeMaven:
6160
return "Maven"
61+
case TypeNpm:
62+
return "npm"
63+
case TypeNuGet:
64+
return "NuGet"
6265
case TypePyPI:
6366
return "PyPI"
6467
case TypeRubyGems:
@@ -78,12 +81,14 @@ func (pt Type) SVGName() string {
7881
return "octicon-container"
7982
case TypeGeneric:
8083
return "octicon-package"
81-
case TypeNuGet:
82-
return "gitea-nuget"
83-
case TypeNpm:
84-
return "gitea-npm"
84+
case TypeHelm:
85+
return "gitea-helm"
8586
case TypeMaven:
8687
return "gitea-maven"
88+
case TypeNpm:
89+
return "gitea-npm"
90+
case TypeNuGet:
91+
return "gitea-nuget"
8792
case TypePyPI:
8893
return "gitea-python"
8994
case TypeRubyGems:

0 commit comments

Comments
 (0)