Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Helm Chart registry #19406

Merged
merged 3 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs/content/doc/packages/helm.en-us.md
Original file line number Diff line number Diff line change
@@ -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. |
2 changes: 1 addition & 1 deletion docs/content/doc/packages/maven.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "Maven"
weight: 50
weight: 60
identifier: "maven"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/doc/packages/npm.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "npm"
weight: 60
weight: 70
identifier: "npm"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/doc/packages/nuget.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "NuGet"
weight: 70
weight: 80
identifier: "nuget"
---

Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/packages/overview.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The following package managers are currently supported:
| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` |
| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client |
| [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP client |
| [Helm]({{< relref "doc/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` |
| [Maven]({{< relref "doc/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` |
| [npm]({{< relref "doc/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn` |
| [NuGet]({{< relref "doc/packages/nuget.en-us.md" >}}) | .NET | `nuget` |
Expand Down
2 changes: 1 addition & 1 deletion docs/content/doc/packages/pypi.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "PyPI"
weight: 80
weight: 90
identifier: "pypi"
---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/doc/packages/rubygems.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ menu:
sidebar:
parent: "packages"
name: "RubyGems"
weight: 90
weight: 100
identifier: "rubygems"
---

Expand Down
166 changes: 166 additions & 0 deletions integrations/api_packages_helm_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
3 changes: 3 additions & 0 deletions models/packages/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/packages/helm"
"code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/modules/packages/npm"
"code.gitea.io/gitea/modules/packages/nuget"
Expand Down Expand Up @@ -129,6 +130,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
metadata = &container.Metadata{}
case TypeGeneric:
// generic packages have no metadata
case TypeHelm:
metadata = &helm.Metadata{}
case TypeNuGet:
metadata = &nuget.Metadata{}
case TypeNpm:
Expand Down
25 changes: 15 additions & 10 deletions models/packages/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ const (
TypeConan Type = "conan"
TypeContainer Type = "container"
TypeGeneric Type = "generic"
TypeNuGet Type = "nuget"
TypeNpm Type = "npm"
TypeHelm Type = "helm"
TypeMaven Type = "maven"
TypeNpm Type = "npm"
TypeNuGet Type = "nuget"
TypePyPI Type = "pypi"
TypeRubyGems Type = "rubygems"
)
Expand All @@ -53,12 +54,14 @@ func (pt Type) Name() string {
return "Container"
case TypeGeneric:
return "Generic"
case TypeNuGet:
return "NuGet"
case TypeNpm:
return "npm"
case TypeHelm:
return "Helm"
case TypeMaven:
return "Maven"
case TypeNpm:
return "npm"
case TypeNuGet:
return "NuGet"
case TypePyPI:
return "PyPI"
case TypeRubyGems:
Expand All @@ -78,12 +81,14 @@ func (pt Type) SVGName() string {
return "octicon-container"
case TypeGeneric:
return "octicon-package"
case TypeNuGet:
return "gitea-nuget"
case TypeNpm:
return "gitea-npm"
case TypeHelm:
return "gitea-helm"
case TypeMaven:
return "gitea-maven"
case TypeNpm:
return "gitea-npm"
case TypeNuGet:
return "gitea-nuget"
case TypePyPI:
return "gitea-python"
case TypeRubyGems:
Expand Down
Loading