Add Helm Chart registry (#19406)
This commit is contained in:
		
							parent
							
								
									b74322dfce
								
							
						
					
					
						commit
						18727df73a
					
				
							
								
								
									
										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…
	
		Reference in New Issue
	
	Block a user