Add Helm Chart registry (#19406)
This commit is contained in:
		
							parent
							
								
									b74322dfce
								
							
						
					
					
						commit
						18727df73a
					
				
					 24 changed files with 679 additions and 21 deletions
				
			
		
							
								
								
									
										67
									
								
								docs/content/doc/packages/helm.en-us.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								docs/content/doc/packages/helm.en-us.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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. |
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ menu:
 | 
				
			||||||
  sidebar:
 | 
					  sidebar:
 | 
				
			||||||
    parent: "packages"
 | 
					    parent: "packages"
 | 
				
			||||||
    name: "Maven"
 | 
					    name: "Maven"
 | 
				
			||||||
    weight: 50
 | 
					    weight: 60
 | 
				
			||||||
    identifier: "maven"
 | 
					    identifier: "maven"
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ menu:
 | 
				
			||||||
  sidebar:
 | 
					  sidebar:
 | 
				
			||||||
    parent: "packages"
 | 
					    parent: "packages"
 | 
				
			||||||
    name: "npm"
 | 
					    name: "npm"
 | 
				
			||||||
    weight: 60
 | 
					    weight: 70
 | 
				
			||||||
    identifier: "npm"
 | 
					    identifier: "npm"
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ menu:
 | 
				
			||||||
  sidebar:
 | 
					  sidebar:
 | 
				
			||||||
    parent: "packages"
 | 
					    parent: "packages"
 | 
				
			||||||
    name: "NuGet"
 | 
					    name: "NuGet"
 | 
				
			||||||
    weight: 70
 | 
					    weight: 80
 | 
				
			||||||
    identifier: "nuget"
 | 
					    identifier: "nuget"
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,7 @@ The following package managers are currently supported:
 | 
				
			||||||
| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` |
 | 
					| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` |
 | 
				
			||||||
| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client |
 | 
					| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client |
 | 
				
			||||||
| [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP 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` |
 | 
					| [Maven]({{< relref "doc/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` |
 | 
				
			||||||
| [npm]({{< relref "doc/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn` |
 | 
					| [npm]({{< relref "doc/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn` |
 | 
				
			||||||
| [NuGet]({{< relref "doc/packages/nuget.en-us.md" >}}) | .NET | `nuget` |
 | 
					| [NuGet]({{< relref "doc/packages/nuget.en-us.md" >}}) | .NET | `nuget` |
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ menu:
 | 
				
			||||||
  sidebar:
 | 
					  sidebar:
 | 
				
			||||||
    parent: "packages"
 | 
					    parent: "packages"
 | 
				
			||||||
    name: "PyPI"
 | 
					    name: "PyPI"
 | 
				
			||||||
    weight: 80
 | 
					    weight: 90
 | 
				
			||||||
    identifier: "pypi"
 | 
					    identifier: "pypi"
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ menu:
 | 
				
			||||||
  sidebar:
 | 
					  sidebar:
 | 
				
			||||||
    parent: "packages"
 | 
					    parent: "packages"
 | 
				
			||||||
    name: "RubyGems"
 | 
					    name: "RubyGems"
 | 
				
			||||||
    weight: 90
 | 
					    weight: 100
 | 
				
			||||||
    identifier: "rubygems"
 | 
					    identifier: "rubygems"
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										166
									
								
								integrations/api_packages_helm_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								integrations/api_packages_helm_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ import (
 | 
				
			||||||
	"code.gitea.io/gitea/modules/packages/composer"
 | 
						"code.gitea.io/gitea/modules/packages/composer"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/packages/conan"
 | 
						"code.gitea.io/gitea/modules/packages/conan"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/packages/container"
 | 
						"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/maven"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/packages/npm"
 | 
						"code.gitea.io/gitea/modules/packages/npm"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/packages/nuget"
 | 
						"code.gitea.io/gitea/modules/packages/nuget"
 | 
				
			||||||
| 
						 | 
					@ -129,6 +130,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
 | 
				
			||||||
		metadata = &container.Metadata{}
 | 
							metadata = &container.Metadata{}
 | 
				
			||||||
	case TypeGeneric:
 | 
						case TypeGeneric:
 | 
				
			||||||
		// generic packages have no metadata
 | 
							// generic packages have no metadata
 | 
				
			||||||
 | 
						case TypeHelm:
 | 
				
			||||||
 | 
							metadata = &helm.Metadata{}
 | 
				
			||||||
	case TypeNuGet:
 | 
						case TypeNuGet:
 | 
				
			||||||
		metadata = &nuget.Metadata{}
 | 
							metadata = &nuget.Metadata{}
 | 
				
			||||||
	case TypeNpm:
 | 
						case TypeNpm:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,9 +35,10 @@ const (
 | 
				
			||||||
	TypeConan     Type = "conan"
 | 
						TypeConan     Type = "conan"
 | 
				
			||||||
	TypeContainer Type = "container"
 | 
						TypeContainer Type = "container"
 | 
				
			||||||
	TypeGeneric   Type = "generic"
 | 
						TypeGeneric   Type = "generic"
 | 
				
			||||||
	TypeNuGet     Type = "nuget"
 | 
						TypeHelm      Type = "helm"
 | 
				
			||||||
	TypeNpm       Type = "npm"
 | 
					 | 
				
			||||||
	TypeMaven     Type = "maven"
 | 
						TypeMaven     Type = "maven"
 | 
				
			||||||
 | 
						TypeNpm       Type = "npm"
 | 
				
			||||||
 | 
						TypeNuGet     Type = "nuget"
 | 
				
			||||||
	TypePyPI      Type = "pypi"
 | 
						TypePyPI      Type = "pypi"
 | 
				
			||||||
	TypeRubyGems  Type = "rubygems"
 | 
						TypeRubyGems  Type = "rubygems"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -53,12 +54,14 @@ func (pt Type) Name() string {
 | 
				
			||||||
		return "Container"
 | 
							return "Container"
 | 
				
			||||||
	case TypeGeneric:
 | 
						case TypeGeneric:
 | 
				
			||||||
		return "Generic"
 | 
							return "Generic"
 | 
				
			||||||
	case TypeNuGet:
 | 
						case TypeHelm:
 | 
				
			||||||
		return "NuGet"
 | 
							return "Helm"
 | 
				
			||||||
	case TypeNpm:
 | 
					 | 
				
			||||||
		return "npm"
 | 
					 | 
				
			||||||
	case TypeMaven:
 | 
						case TypeMaven:
 | 
				
			||||||
		return "Maven"
 | 
							return "Maven"
 | 
				
			||||||
 | 
						case TypeNpm:
 | 
				
			||||||
 | 
							return "npm"
 | 
				
			||||||
 | 
						case TypeNuGet:
 | 
				
			||||||
 | 
							return "NuGet"
 | 
				
			||||||
	case TypePyPI:
 | 
						case TypePyPI:
 | 
				
			||||||
		return "PyPI"
 | 
							return "PyPI"
 | 
				
			||||||
	case TypeRubyGems:
 | 
						case TypeRubyGems:
 | 
				
			||||||
| 
						 | 
					@ -78,12 +81,14 @@ func (pt Type) SVGName() string {
 | 
				
			||||||
		return "octicon-container"
 | 
							return "octicon-container"
 | 
				
			||||||
	case TypeGeneric:
 | 
						case TypeGeneric:
 | 
				
			||||||
		return "octicon-package"
 | 
							return "octicon-package"
 | 
				
			||||||
	case TypeNuGet:
 | 
						case TypeHelm:
 | 
				
			||||||
		return "gitea-nuget"
 | 
							return "gitea-helm"
 | 
				
			||||||
	case TypeNpm:
 | 
					 | 
				
			||||||
		return "gitea-npm"
 | 
					 | 
				
			||||||
	case TypeMaven:
 | 
						case TypeMaven:
 | 
				
			||||||
		return "gitea-maven"
 | 
							return "gitea-maven"
 | 
				
			||||||
 | 
						case TypeNpm:
 | 
				
			||||||
 | 
							return "gitea-npm"
 | 
				
			||||||
 | 
						case TypeNuGet:
 | 
				
			||||||
 | 
							return "gitea-nuget"
 | 
				
			||||||
	case TypePyPI:
 | 
						case TypePyPI:
 | 
				
			||||||
		return "gitea-python"
 | 
							return "gitea-python"
 | 
				
			||||||
	case TypeRubyGems:
 | 
						case TypeRubyGems:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										131
									
								
								modules/packages/helm/metadata.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								modules/packages/helm/metadata.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3051,6 +3051,9 @@ container.labels.key = Key
 | 
				
			||||||
container.labels.value = Value
 | 
					container.labels.value = Value
 | 
				
			||||||
generic.download = Download package from the command line:
 | 
					generic.download = Download package from the command line:
 | 
				
			||||||
generic.documentation = For more information on the generic registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">the documentation</a>.
 | 
					generic.documentation = For more information on the generic registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">the documentation</a>.
 | 
				
			||||||
 | 
					helm.registry = Setup this registry from the command line:
 | 
				
			||||||
 | 
					helm.install = To install the package, run the following command:
 | 
				
			||||||
 | 
					helm.documentation = For more information on the Helm registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">the documentation</a>.
 | 
				
			||||||
maven.registry = Setup this registry in your project <code>pom.xml</code> file:
 | 
					maven.registry = Setup this registry in your project <code>pom.xml</code> file:
 | 
				
			||||||
maven.install = To use the package include the following in the <code>dependencies</code> block in the <code>pom.xml</code> file:
 | 
					maven.install = To use the package include the following in the <code>dependencies</code> block in the <code>pom.xml</code> file:
 | 
				
			||||||
maven.install2 = Run via command line:
 | 
					maven.install2 = Run via command line:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								public/img/svg/gitea-helm.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/img/svg/gitea-helm.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					<svg viewBox="0 0 62 65" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" class="svg gitea-helm" width="16" height="16" aria-hidden="true"><path fill="#3f7a9c" d="m41.868 16.659.248.19a19.17 19.17 0 0 1 3.919 4.128l.9 1.414 2.774-1.601-1.05-1.65a22.373 22.373 0 0 0-3.304-3.748 17.143 17.143 0 0 0 2.215-2.403c2.158-2.813 3.141-5.657 2.204-6.376s-3.43.966-5.589 3.779a17.048 17.048 0 0 0-1.748 2.762 22.297 22.297 0 0 0-10.308-3.48c.189-.957.298-2.076.298-3.273 0-3.546-.951-6.4-2.133-6.4S28.16 2.854 28.16 6.4c0 1.198.109 2.317.298 3.273a22.293 22.293 0 0 0-10.308 3.48 17.117 17.117 0 0 0-1.748-2.762c-2.158-2.813-4.651-4.498-5.589-3.779s.045 3.563 2.204 6.376a17.203 17.203 0 0 0 2.215 2.403 22.373 22.373 0 0 0-3.304 3.748 22.33 22.33 0 0 0-1.05 1.65l2.774 1.601a19.13 19.13 0 0 1 .9-1.414 19.21 19.21 0 0 1 3.919-4.128l.248-.19c3.214-2.424 7.221-3.859 11.574-3.859s8.36 1.435 11.574 3.859zM14.551 43.023A19.15 19.15 0 0 0 30.293 51.2a19.15 19.15 0 0 0 15.742-8.177l2.624 1.837a22.373 22.373 0 0 1-3.304 3.748 17.143 17.143 0 0 1 2.215 2.403c2.158 2.813 3.141 5.657 2.204 6.376s-3.43-.966-5.589-3.779a17.048 17.048 0 0 1-1.748-2.762 22.297 22.297 0 0 1-10.308 3.48c.189.957.298 2.076.298 3.273 0 3.546-.951 6.4-2.133 6.4s-2.133-2.854-2.133-6.4c0-1.198.109-2.317.298-3.273a22.293 22.293 0 0 1-10.308-3.48 17.117 17.117 0 0 1-1.748 2.762c-2.158 2.813-4.651 4.498-5.589 3.779s.045-3.563 2.204-6.376a17.203 17.203 0 0 1 2.215-2.403 22.373 22.373 0 0 1-3.304-3.748zm30.249-2.49V24.32h4.693l3.413 9.813 3.413-9.813h4.267v16.213h-3.84v-5.12l.853-5.973-3.84 9.813h-2.133l-3.84-9.813.853 5.973v5.12zM31.147 24.32v16.213h10.667V37.12h-6.4v-12.8zm-14.08 0v16.213H28.16V37.12h-6.827v-2.987h5.547V30.72h-5.547v-2.987h6.4V24.32zm-12.8 16.213v-6.4h5.12v6.4h4.267V24.32H9.387v5.973h-5.12V24.32H0v16.213z" stroke="none"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.8 KiB  | 
| 
						 | 
					@ -17,6 +17,7 @@ import (
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/packages/conan"
 | 
						"code.gitea.io/gitea/routers/api/packages/conan"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/packages/container"
 | 
						"code.gitea.io/gitea/routers/api/packages/container"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/packages/generic"
 | 
						"code.gitea.io/gitea/routers/api/packages/generic"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/routers/api/packages/helm"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/packages/maven"
 | 
						"code.gitea.io/gitea/routers/api/packages/maven"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/packages/npm"
 | 
						"code.gitea.io/gitea/routers/api/packages/npm"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/packages/nuget"
 | 
						"code.gitea.io/gitea/routers/api/packages/nuget"
 | 
				
			||||||
| 
						 | 
					@ -162,6 +163,11 @@ func Routes() *web.Route {
 | 
				
			||||||
				}, reqPackageAccess(perm.AccessModeWrite))
 | 
									}, reqPackageAccess(perm.AccessModeWrite))
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
							r.Group("/helm", func() {
 | 
				
			||||||
 | 
								r.Get("/index.yaml", helm.Index)
 | 
				
			||||||
 | 
								r.Get("/{filename}", helm.DownloadPackageFile)
 | 
				
			||||||
 | 
								r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
		r.Group("/maven", func() {
 | 
							r.Group("/maven", func() {
 | 
				
			||||||
			r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
 | 
								r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
 | 
				
			||||||
			r.Get("/*", maven.DownloadPackageFile)
 | 
								r.Get("/*", maven.DownloadPackageFile)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										205
									
								
								routers/api/packages/helm/helm.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								routers/api/packages/helm/helm.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ func ListPackages(ctx *context.APIContext) {
 | 
				
			||||||
	//   in: query
 | 
						//   in: query
 | 
				
			||||||
	//   description: package type filter
 | 
						//   description: package type filter
 | 
				
			||||||
	//   type: string
 | 
						//   type: string
 | 
				
			||||||
	//   enum: [composer, conan, generic, maven, npm, nuget, pypi, rubygems]
 | 
						//   enum: [composer, conan, container, generic, helm, maven, npm, nuget, pypi, rubygems]
 | 
				
			||||||
	// - name: q
 | 
						// - name: q
 | 
				
			||||||
	//   in: query
 | 
						//   in: query
 | 
				
			||||||
	//   description: name filter
 | 
						//   description: name filter
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@
 | 
				
			||||||
						<option value="conan" {{if eq .PackageType "conan"}}selected="selected"{{end}}>Conan</option>
 | 
											<option value="conan" {{if eq .PackageType "conan"}}selected="selected"{{end}}>Conan</option>
 | 
				
			||||||
						<option value="container" {{if eq .PackageType "container"}}selected="selected"{{end}}>Container</option>
 | 
											<option value="container" {{if eq .PackageType "container"}}selected="selected"{{end}}>Container</option>
 | 
				
			||||||
						<option value="generic" {{if eq .PackageType "generic"}}selected="selected"{{end}}>Generic</option>
 | 
											<option value="generic" {{if eq .PackageType "generic"}}selected="selected"{{end}}>Generic</option>
 | 
				
			||||||
 | 
											<option value="helm" {{if eq .PackageType "helm"}}selected="selected"{{end}}>Helm</option>
 | 
				
			||||||
						<option value="maven" {{if eq .PackageType "maven"}}selected="selected"{{end}}>Maven</option>
 | 
											<option value="maven" {{if eq .PackageType "maven"}}selected="selected"{{end}}>Maven</option>
 | 
				
			||||||
						<option value="npm" {{if eq .PackageType "npm"}}selected="selected"{{end}}>npm</option>
 | 
											<option value="npm" {{if eq .PackageType "npm"}}selected="selected"{{end}}>npm</option>
 | 
				
			||||||
						<option value="nuget" {{if eq .PackageType "nuget"}}selected="selected"{{end}}>NuGet</option>
 | 
											<option value="nuget" {{if eq .PackageType "nuget"}}selected="selected"{{end}}>NuGet</option>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										57
									
								
								templates/package/content/helm.tmpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								templates/package/content/helm.tmpl
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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}}
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	{{end}}
 | 
						{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{{if or .PackageDescriptor.Metadata.Keywords}}
 | 
						{{if .PackageDescriptor.Metadata.Keywords}}
 | 
				
			||||||
		<h4 class="ui top attached header">{{.i18n.Tr "packages.keywords"}}</h4>
 | 
							<h4 class="ui top attached header">{{.i18n.Tr "packages.keywords"}}</h4>
 | 
				
			||||||
		<div class="ui attached segment">
 | 
							<div class="ui attached segment">
 | 
				
			||||||
			{{range .PackageDescriptor.Metadata.Keywords}}
 | 
								{{range .PackageDescriptor.Metadata.Keywords}}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								templates/package/metadata/helm.tmpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								templates/package/metadata/helm.tmpl
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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}}
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@
 | 
				
			||||||
				<option value="conan" {{if eq .PackageType "conan"}}selected="selected"{{end}}>Conan</option>
 | 
									<option value="conan" {{if eq .PackageType "conan"}}selected="selected"{{end}}>Conan</option>
 | 
				
			||||||
				<option value="container" {{if eq .PackageType "container"}}selected="selected"{{end}}>Container</option>
 | 
									<option value="container" {{if eq .PackageType "container"}}selected="selected"{{end}}>Container</option>
 | 
				
			||||||
				<option value="generic" {{if eq .PackageType "generic"}}selected="selected"{{end}}>Generic</option>
 | 
									<option value="generic" {{if eq .PackageType "generic"}}selected="selected"{{end}}>Generic</option>
 | 
				
			||||||
 | 
									<option value="helm" {{if eq .PackageType "helm"}}selected="selected"{{end}}>Helm</option>
 | 
				
			||||||
				<option value="maven" {{if eq .PackageType "maven"}}selected="selected"{{end}}>Maven</option>
 | 
									<option value="maven" {{if eq .PackageType "maven"}}selected="selected"{{end}}>Maven</option>
 | 
				
			||||||
				<option value="npm" {{if eq .PackageType "npm"}}selected="selected"{{end}}>npm</option>
 | 
									<option value="npm" {{if eq .PackageType "npm"}}selected="selected"{{end}}>npm</option>
 | 
				
			||||||
				<option value="nuget" {{if eq .PackageType "nuget"}}selected="selected"{{end}}>NuGet</option>
 | 
									<option value="nuget" {{if eq .PackageType "nuget"}}selected="selected"{{end}}>NuGet</option>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,9 +23,10 @@
 | 
				
			||||||
					{{template "package/content/conan" .}}
 | 
										{{template "package/content/conan" .}}
 | 
				
			||||||
					{{template "package/content/container" .}}
 | 
										{{template "package/content/container" .}}
 | 
				
			||||||
					{{template "package/content/generic" .}}
 | 
										{{template "package/content/generic" .}}
 | 
				
			||||||
					{{template "package/content/nuget" .}}
 | 
										{{template "package/content/helm" .}}
 | 
				
			||||||
					{{template "package/content/npm" .}}
 | 
					 | 
				
			||||||
					{{template "package/content/maven" .}}
 | 
										{{template "package/content/maven" .}}
 | 
				
			||||||
 | 
										{{template "package/content/npm" .}}
 | 
				
			||||||
 | 
										{{template "package/content/nuget" .}}
 | 
				
			||||||
					{{template "package/content/pypi" .}}
 | 
										{{template "package/content/pypi" .}}
 | 
				
			||||||
					{{template "package/content/rubygems" .}}
 | 
										{{template "package/content/rubygems" .}}
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
| 
						 | 
					@ -43,9 +44,10 @@
 | 
				
			||||||
							{{template "package/metadata/conan" .}}
 | 
												{{template "package/metadata/conan" .}}
 | 
				
			||||||
							{{template "package/metadata/container" .}}
 | 
												{{template "package/metadata/container" .}}
 | 
				
			||||||
							{{template "package/metadata/generic" .}}
 | 
												{{template "package/metadata/generic" .}}
 | 
				
			||||||
							{{template "package/metadata/nuget" .}}
 | 
												{{template "package/metadata/helm" .}}
 | 
				
			||||||
							{{template "package/metadata/npm" .}}
 | 
					 | 
				
			||||||
							{{template "package/metadata/maven" .}}
 | 
												{{template "package/metadata/maven" .}}
 | 
				
			||||||
 | 
												{{template "package/metadata/npm" .}}
 | 
				
			||||||
 | 
												{{template "package/metadata/nuget" .}}
 | 
				
			||||||
							{{template "package/metadata/pypi" .}}
 | 
												{{template "package/metadata/pypi" .}}
 | 
				
			||||||
							{{template "package/metadata/rubygems" .}}
 | 
												{{template "package/metadata/rubygems" .}}
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1904,7 +1904,9 @@
 | 
				
			||||||
            "enum": [
 | 
					            "enum": [
 | 
				
			||||||
              "composer",
 | 
					              "composer",
 | 
				
			||||||
              "conan",
 | 
					              "conan",
 | 
				
			||||||
 | 
					              "container",
 | 
				
			||||||
              "generic",
 | 
					              "generic",
 | 
				
			||||||
 | 
					              "helm",
 | 
				
			||||||
              "maven",
 | 
					              "maven",
 | 
				
			||||||
              "npm",
 | 
					              "npm",
 | 
				
			||||||
              "nuget",
 | 
					              "nuget",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								web_src/svg/gitea-helm.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web_src/svg/gitea-helm.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 62 65" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round">
 | 
				
			||||||
 | 
					<g><path fill="#3f7a9c" d="M41.868 16.659l.248.19c1.503 1.172 2.825 2.564 3.919 4.128l.9 1.414 2.774-1.601-1.05-1.65c-.959-1.37-2.068-2.628-3.304-3.748.728-.642 1.491-1.459 2.215-2.403 2.158-2.813 3.141-5.657 2.204-6.376s-3.43.966-5.589 3.779c-.725.944-1.317 1.892-1.748 2.762-3.013-1.94-6.525-3.176-10.308-3.48.189-.957.298-2.076.298-3.273 0-3.546-.951-6.4-2.133-6.4S28.16 2.854 28.16 6.4c0 1.198.109 2.317.298 3.273-3.783.304-7.296 1.54-10.308 3.48-.431-.87-1.024-1.818-1.748-2.762-2.158-2.813-4.651-4.498-5.589-3.779s.045 3.563 2.204 6.376c.725.944 1.487 1.761 2.215 2.403-1.236 1.12-2.345 2.378-3.304 3.748a22.33 22.33 0 0 0-1.05 1.65l2.774 1.601a19.13 19.13 0 0 1 .9-1.414 19.21 19.21 0 0 1 3.919-4.128l.248-.19c3.214-2.424 7.221-3.859 11.574-3.859s8.36 1.435 11.574 3.859zM14.551 43.023A19.15 19.15 0 0 0 30.293 51.2a19.15 19.15 0 0 0 15.742-8.177l2.624 1.837c-.959 1.37-2.068 2.628-3.304 3.748.728.642 1.491 1.459 2.215 2.403 2.158 2.813 3.141 5.657 2.204 6.376s-3.43-.966-5.589-3.779c-.725-.944-1.317-1.892-1.748-2.762-3.013 1.94-6.525 3.176-10.308 3.48.189.957.298 2.076.298 3.273 0 3.546-.951 6.4-2.133 6.4s-2.133-2.854-2.133-6.4c0-1.198.109-2.317.298-3.273-3.783-.304-7.296-1.54-10.308-3.48-.431.87-1.024 1.818-1.748 2.762-2.158 2.813-4.651 4.498-5.589 3.779s.045-3.563 2.204-6.376c.725-.944 1.487-1.761 2.215-2.403-1.236-1.12-2.345-2.378-3.304-3.748zM44.8 40.533V24.32h4.693l3.413 9.813 3.413-9.813h4.267v16.213h-3.84v-5.12l.853-5.973-3.84 9.813h-2.133l-3.84-9.813.853 5.973v5.12zM31.147 24.32v16.213h10.667V37.12h-6.4v-12.8zm-14.08 0v16.213H28.16V37.12h-6.827v-2.987h5.547V30.72h-5.547v-2.987h6.4V24.32zm-12.8 16.213v-6.4h5.12v6.4h4.267V24.32H9.387v5.973h-5.12V24.32H0v16.213z" stroke="none"/></g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.8 KiB  | 
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue