Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Chef package registry #22554

Merged
merged 6 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2454,6 +2454,8 @@ ROUTER = console
;LIMIT_TOTAL_OWNER_COUNT = -1
;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_TOTAL_OWNER_SIZE = -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 @@ -1212,6 +1212,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
- `CHUNKED_UPLOAD_PATH`: **tmp/package-upload**: Path for chunked uploads. Defaults to `APP_DATA_PATH` + `tmp/package-upload`
- `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_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_CONTAINER`: **-1**: Maximum size of a Container 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 @@ -26,6 +26,7 @@ The following package managers are currently supported:

| Name | Language | Package client |
| ---- | -------- | -------------- |
| [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` |
| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client |
Expand Down
3 changes: 3 additions & 0 deletions models/packages/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"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/container"
Expand Down Expand Up @@ -128,6 +129,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc

var metadata interface{}
switch p.Type {
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 @@ -30,6 +30,7 @@ type Type string

// List of supported packages
const (
TypeChef Type = "chef"
TypeComposer Type = "composer"
TypeConan Type = "conan"
TypeContainer Type = "container"
Expand All @@ -45,6 +46,7 @@ const (
)

var TypeList = []Type{
TypeChef,
TypeComposer,
TypeConan,
TypeContainer,
Expand All @@ -62,6 +64,8 @@ var TypeList = []Type{
// Name gets the name of the package type
func (pt Type) Name() string {
switch pt {
case TypeChef:
return "Chef"
case TypeComposer:
return "Composer"
case TypeConan:
Expand Down Expand Up @@ -93,6 +97,8 @@ func (pt Type) Name() string {
// SVGName gets the name of the package type svg image
func (pt Type) SVGName() string {
switch pt {
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
zeripath marked this conversation as resolved.
Show resolved Hide resolved

// 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 = 2048
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