Skip to content

Commit

Permalink
Add Chef package registry (#22554)
Browse files Browse the repository at this point in the history
This PR implements a [Chef registry](https://chef.io/) to manage
cookbooks. This package type was a bit complicated because Chef uses RSA
signed requests as authentication with the registry.


![grafik](https://user-images.githubusercontent.com/1666336/213747995-46819fd8-c3d6-45a2-afd4-a4c3c8505a4a.png)


![grafik](https://user-images.githubusercontent.com/1666336/213748145-d01c9e81-d4dd-41e3-a3cc-8241862c3166.png)

Co-authored-by: Lunny Xiao <[email protected]>
  • Loading branch information
KN4CK3R and lunny authored Feb 6, 2023
1 parent ff18d17 commit d987ac6
Show file tree
Hide file tree
Showing 31 changed files with 1,737 additions and 20 deletions.
2 changes: 2 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2460,6 +2460,8 @@ ROUTER = console
;LIMIT_TOTAL_OWNER_SIZE = -1
;; Maximum size of a Cargo upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_CARGO = -1
;; Maximum size of a Chef upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_CHEF = -1
;; Maximum size of a Composer upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_COMPOSER = -1
;; Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
- `LIMIT_TOTAL_OWNER_COUNT`: **-1**: Maximum count of package versions a single owner can have (`-1` means no limits)
- `LIMIT_TOTAL_OWNER_SIZE`: **-1**: Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CARGO`: **-1**: Maximum size of a Cargo upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CHEF`: **-1**: Maximum size of a Chef upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_COMPOSER`: **-1**: Maximum size of a Composer upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CONAN`: **-1**: Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CONDA`: **-1**: Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
Expand Down
96 changes: 96 additions & 0 deletions docs/content/doc/packages/chef.en-us.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
date: "2023-01-20T00:00:00+00:00"
title: "Chef Packages Repository"
slug: "packages/chef"
draft: false
toc: false
menu:
sidebar:
parent: "packages"
name: "Chef"
weight: 5
identifier: "chef"
---

# Chef Packages Repository

Publish [Chef](https://chef.io/) cookbooks for your user or organization.

**Table of Contents**

{{< toc >}}

## Requirements

To work with the Chef package registry, you have to use [`knife`](https://docs.chef.io/workstation/knife/).

## Authentication

The Chef package registry does not use an username:password authentication but signed requests with a private:public key pair.
Visit the package owner settings page to create the necessary key pair.
Only the public key is stored inside Gitea. if you loose access to the private key you must re-generate the key pair.
[Configure `knife`](https://docs.chef.io/workstation/knife_setup/) to use the downloaded private key with your Gitea username as `client_name`.

## Configure the package registry

To [configure `knife`](https://docs.chef.io/workstation/knife_setup/) to use the Gitea package registry add the url to the `~/.chef/config.rb` file.

```
knife[:supermarket_site] = 'https://gitea.example.com/api/packages/{owner}/chef'
```

| Parameter | Description |
| --------- | ----------- |
| `owner` | The owner of the package. |

## Publish a package

To publish a Chef package execute the following command:

```shell
knife supermarket share {package_name}
```

| Parameter | Description |
| -------------- | ----------- |
| `package_name` | The package name. |

You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first.

## Install a package

To install a package from the package registry, execute the following command:

```shell
knife supermarket install {package_name}
```

Optional you can specify the package version:

```shell
knife supermarket install {package_name} {package_version}
```

| Parameter | Description |
| ----------------- | ----------- |
| `package_name` | The package name. |
| `package_version` | The package version. |

## Delete a package

If you want to remove a package from the registry, execute the following command:

```shell
knife supermarket unshare {package_name}
```

Optional you can specify the package version:

```shell
knife supermarket unshare {package_name}/versions/{package_version}
```

| Parameter | Description |
| ----------------- | ----------- |
| `package_name` | The package name. |
| `package_version` | The package version. |
1 change: 1 addition & 0 deletions docs/content/doc/packages/overview.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The following package managers are currently supported:
| Name | Language | Package client |
| ---- | -------- | -------------- |
| [Cargo]({{< relref "doc/packages/cargo.en-us.md" >}}) | Rust | `cargo` |
| [Chef]({{< relref "doc/packages/chef.en-us.md" >}}) | - | `knife` |
| [Composer]({{< relref "doc/packages/composer.en-us.md" >}}) | PHP | `composer` |
| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` |
| [Conda]({{< relref "doc/packages/conda.en-us.md" >}}) | - | `conda` |
Expand Down
3 changes: 3 additions & 0 deletions models/packages/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/packages/cargo"
"code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/packages/conda"
Expand Down Expand Up @@ -132,6 +133,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
switch p.Type {
case TypeCargo:
metadata = &cargo.Metadata{}
case TypeChef:
metadata = &chef.Metadata{}
case TypeComposer:
metadata = &composer.Metadata{}
case TypeConan:
Expand Down
6 changes: 6 additions & 0 deletions models/packages/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Type string
// List of supported packages
const (
TypeCargo Type = "cargo"
TypeChef Type = "chef"
TypeComposer Type = "composer"
TypeConan Type = "conan"
TypeConda Type = "conda"
Expand All @@ -48,6 +49,7 @@ const (

var TypeList = []Type{
TypeCargo,
TypeChef,
TypeComposer,
TypeConan,
TypeConda,
Expand All @@ -68,6 +70,8 @@ func (pt Type) Name() string {
switch pt {
case TypeCargo:
return "Cargo"
case TypeChef:
return "Chef"
case TypeComposer:
return "Composer"
case TypeConan:
Expand Down Expand Up @@ -103,6 +107,8 @@ func (pt Type) SVGName() string {
switch pt {
case TypeCargo:
return "gitea-cargo"
case TypeChef:
return "gitea-chef"
case TypeComposer:
return "gitea-composer"
case TypeConan:
Expand Down
5 changes: 4 additions & 1 deletion modules/activitypub/user_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ package activitypub

import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
)

const rsaBits = 2048

// GetKeyPair function returns a user's private and public keys
func GetKeyPair(user *user_model.User) (pub, priv string, err error) {
var settings map[string]*user_model.Setting
settings, err = user_model.GetSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
if err != nil {
return
} else if len(settings) == 0 {
if priv, pub, err = GenerateKeyPair(); err != nil {
if priv, pub, err = util.GenerateKeyPair(rsaBits); err != nil {
return
}
if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPrivPem, priv); err != nil {
Expand Down
134 changes: 134 additions & 0 deletions modules/packages/chef/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package chef

import (
"archive/tar"
"compress/gzip"
"io"
"regexp"
"strings"

"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
)

const (
KeyBits = 4096
SettingPublicPem = "chef.public_pem"
)

var (
ErrMissingMetadataFile = util.NewInvalidArgumentErrorf("metadata.json file is missing")
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")

namePattern = regexp.MustCompile(`\A\S+\z`)
versionPattern = regexp.MustCompile(`\A\d+\.\d+(?:\.\d+)?\z`)
)

// Package represents a Chef package
type Package struct {
Name string
Version string
Metadata *Metadata
}

// Metadata represents the metadata of a Chef package
type Metadata struct {
Description string `json:"description,omitempty"`
LongDescription string `json:"long_description,omitempty"`
Author string `json:"author,omitempty"`
License string `json:"license,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"`
}

type chefMetadata struct {
Name string `json:"name"`
Description string `json:"description"`
LongDescription string `json:"long_description"`
Maintainer string `json:"maintainer"`
MaintainerEmail string `json:"maintainer_email"`
License string `json:"license"`
Platforms map[string]string `json:"platforms"`
Dependencies map[string]string `json:"dependencies"`
Providing map[string]string `json:"providing"`
Recipes map[string]string `json:"recipes"`
Version string `json:"version"`
SourceURL string `json:"source_url"`
IssuesURL string `json:"issues_url"`
Privacy bool `json:"privacy"`
ChefVersions [][]string `json:"chef_versions"`
Gems [][]string `json:"gems"`
EagerLoadLibraries bool `json:"eager_load_libraries"`
}

// ParsePackage parses the Chef package file
func ParsePackage(r io.Reader) (*Package, 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 strings.Count(hd.Name, "/") != 1 {
continue
}

if hd.FileInfo().Name() == "metadata.json" {
return ParseChefMetadata(tr)
}
}

return nil, ErrMissingMetadataFile
}

// ParseChefMetadata parses a metadata.json file to retrieve the metadata of a Chef package
func ParseChefMetadata(r io.Reader) (*Package, error) {
var cm chefMetadata
if err := json.NewDecoder(r).Decode(&cm); err != nil {
return nil, err
}

if !namePattern.MatchString(cm.Name) {
return nil, ErrInvalidName
}

if !versionPattern.MatchString(cm.Version) {
return nil, ErrInvalidVersion
}

if !validation.IsValidURL(cm.SourceURL) {
cm.SourceURL = ""
}

return &Package{
Name: cm.Name,
Version: cm.Version,
Metadata: &Metadata{
Description: cm.Description,
LongDescription: cm.LongDescription,
Author: cm.Maintainer,
License: cm.License,
RepositoryURL: cm.SourceURL,
Dependencies: cm.Dependencies,
},
}, nil
}
Loading

0 comments on commit d987ac6

Please sign in to comment.