Skip to content

Commit 9173e07

Browse files
KN4CK3RtechknowlogicksilverwindGiteaBot
authored
Add Alpine package registry (#23714)
This PR adds an Alpine package registry. You can follow [this tutorial](https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package) to build a *.apk package for testing. This functionality is similar to the Debian registry (#22854) and therefore shares some methods. I marked this PR as blocked because it should be merged after #22854. ![grafik](https://user-images.githubusercontent.com/1666336/227779595-b76163aa-eea1-4a79-9583-775c24ad74e8.png) --------- Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Giteabot <teabot@gitea.io>
1 parent 80bde01 commit 9173e07

File tree

30 files changed

+1631
-52
lines changed

30 files changed

+1631
-52
lines changed

custom/conf/app.example.ini

+2
Original file line numberDiff line numberDiff line change
@@ -2442,6 +2442,8 @@ ROUTER = console
24422442
;LIMIT_TOTAL_OWNER_COUNT = -1
24432443
;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
24442444
;LIMIT_TOTAL_OWNER_SIZE = -1
2445+
;; Maximum size of an Alpine upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
2446+
;LIMIT_SIZE_ALPINE = -1
24452447
;; Maximum size of a Cargo upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
24462448
;LIMIT_SIZE_CARGO = -1
24472449
;; Maximum size of a Chef upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)

docs/content/doc/administration/config-cheat-sheet.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
12131213
- `CHUNKED_UPLOAD_PATH`: **tmp/package-upload**: Path for chunked uploads. Defaults to `APP_DATA_PATH` + `tmp/package-upload`
12141214
- `LIMIT_TOTAL_OWNER_COUNT`: **-1**: Maximum count of package versions a single owner can have (`-1` means no limits)
12151215
- `LIMIT_TOTAL_OWNER_SIZE`: **-1**: Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
1216+
- `LIMIT_SIZE_ALPINE`: **-1**: Maximum size of an Alpine upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12161217
- `LIMIT_SIZE_CARGO`: **-1**: Maximum size of a Cargo upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12171218
- `LIMIT_SIZE_CHEF`: **-1**: Maximum size of a Chef upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12181219
- `LIMIT_SIZE_COMPOSER`: **-1**: Maximum size of a Composer upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
date: "2023-03-25T00:00:00+00:00"
3+
title: "Alpine Packages Repository"
4+
slug: "packages/alpine"
5+
draft: false
6+
toc: false
7+
menu:
8+
sidebar:
9+
parent: "packages"
10+
name: "Alpine"
11+
weight: 4
12+
identifier: "alpine"
13+
---
14+
15+
# Alpine Packages Repository
16+
17+
Publish [Alpine](https://pkgs.alpinelinux.org/) packages for your user or organization.
18+
19+
**Table of Contents**
20+
21+
{{< toc >}}
22+
23+
## Requirements
24+
25+
To work with the Alpine registry, you need to use a HTTP client like `curl` to upload and a package manager like `apk` to consume packages.
26+
27+
The following examples use `apk`.
28+
29+
## Configuring the package registry
30+
31+
To register the Alpine registry add the url to the list of known apk sources (`/etc/apk/repositories`):
32+
33+
```
34+
https://gitea.example.com/api/packages/{owner}/alpine/<branch>/<repository>
35+
```
36+
37+
| Placeholder | Description |
38+
| ------------ | ----------- |
39+
| `owner` | The owner of the packages. |
40+
| `branch` | The branch to use. |
41+
| `repository` | The repository to use. |
42+
43+
If the registry is private, provide credentials in the url. You can use a password or a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}):
44+
45+
```
46+
https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/alpine/<branch>/<repository>
47+
```
48+
49+
The Alpine registry files are signed with a RSA key which must be known to apk. Download the public key and store it in `/etc/apk/keys/`:
50+
51+
```shell
52+
curl -JO https://gitea.example.com/api/packages/{owner}/alpine/key
53+
```
54+
55+
Afterwards update the local package index:
56+
57+
```shell
58+
apk update
59+
```
60+
61+
## Publish a package
62+
63+
To publish an Alpine package (`*.apk`), perform a HTTP `PUT` operation with the package content in the request body.
64+
65+
```
66+
PUT https://gitea.example.com/api/packages/{owner}/alpine/{branch}/{repository}
67+
```
68+
69+
| Parameter | Description |
70+
| ------------ | ----------- |
71+
| `owner` | The owner of the package. |
72+
| `branch` | The branch may match the release version of the OS, ex: `v3.17`. |
73+
| `repository` | The repository can be used [to group packages](https://wiki.alpinelinux.org/wiki/Repositories) or just `main` or similar. |
74+
75+
Example request using HTTP Basic authentication:
76+
77+
```shell
78+
curl --user your_username:your_password_or_token \
79+
--upload-file path/to/file.apk \
80+
https://gitea.example.com/api/packages/testuser/alpine/v3.17/main
81+
```
82+
83+
If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}) instead of the password.
84+
You cannot publish a file with the same name twice to a package. You must delete the existing package file first.
85+
86+
The server responds with the following HTTP Status codes.
87+
88+
| HTTP Status Code | Meaning |
89+
| ----------------- | ------- |
90+
| `201 Created` | The package has been published. |
91+
| `400 Bad Request` | The package name, version, branch, repository or architecture are invalid. |
92+
| `409 Conflict` | A package file with the same combination of parameters exist already in the package. |
93+
94+
## Delete a package
95+
96+
To delete an Alpine package perform a HTTP `DELETE` operation. This will delete the package version too if there is no file left.
97+
98+
```
99+
DELETE https://gitea.example.com/api/packages/{owner}/alpine/{branch}/{repository}/{architecture}/{filename}
100+
```
101+
102+
| Parameter | Description |
103+
| -------------- | ----------- |
104+
| `owner` | The owner of the package. |
105+
| `branch` | The branch to use. |
106+
| `repository` | The repository to use. |
107+
| `architecture` | The package architecture. |
108+
| `filename` | The file to delete.
109+
110+
Example request using HTTP Basic authentication:
111+
112+
```shell
113+
curl --user your_username:your_token_or_password -X DELETE \
114+
https://gitea.example.com/api/packages/testuser/alpine/v3.17/main/test-package-1.0.0.apk
115+
```
116+
117+
The server responds with the following HTTP Status codes.
118+
119+
| HTTP Status Code | Meaning |
120+
| ----------------- | ------- |
121+
| `204 No Content` | Success |
122+
| `404 Not Found` | The package or file was not found. |
123+
124+
## Install a package
125+
126+
To install a package from the Alpine registry, execute the following commands:
127+
128+
```shell
129+
# use latest version
130+
apk add {package_name}
131+
# use specific version
132+
apk add {package_name}={package_version}
133+
```

docs/content/doc/usage/packages/overview.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The following package managers are currently supported:
2727

2828
| Name | Language | Package client |
2929
| ---- | -------- | -------------- |
30+
| [Alpine]({{< relref "doc/usage/packages/alpine.en-us.md" >}}) | - | `apk` |
3031
| [Cargo]({{< relref "doc/usage/packages/cargo.en-us.md" >}}) | Rust | `cargo` |
3132
| [Chef]({{< relref "doc/usage/packages/chef.en-us.md" >}}) | - | `knife` |
3233
| [Composer]({{< relref "doc/usage/packages/composer.en-us.md" >}}) | PHP | `composer` |

docs/content/doc/usage/packages/storage.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ menu:
99
sidebar:
1010
parent: "packages"
1111
name: "Storage"
12-
weight: 5
12+
weight: 2
1313
identifier: "storage"
1414
---
1515

models/packages/alpine/search.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package alpine
5+
6+
import (
7+
"context"
8+
9+
packages_model "code.gitea.io/gitea/models/packages"
10+
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
11+
)
12+
13+
// GetBranches gets all available branches
14+
func GetBranches(ctx context.Context, ownerID int64) ([]string, error) {
15+
return packages_model.GetDistinctPropertyValues(
16+
ctx,
17+
packages_model.TypeAlpine,
18+
ownerID,
19+
packages_model.PropertyTypeFile,
20+
alpine_module.PropertyBranch,
21+
nil,
22+
)
23+
}
24+
25+
// GetRepositories gets all available repositories for the given branch
26+
func GetRepositories(ctx context.Context, ownerID int64, branch string) ([]string, error) {
27+
return packages_model.GetDistinctPropertyValues(
28+
ctx,
29+
packages_model.TypeAlpine,
30+
ownerID,
31+
packages_model.PropertyTypeFile,
32+
alpine_module.PropertyRepository,
33+
&packages_model.DistinctPropertyDependency{
34+
Name: alpine_module.PropertyBranch,
35+
Value: branch,
36+
},
37+
)
38+
}
39+
40+
// GetArchitectures gets all available architectures for the given repository
41+
func GetArchitectures(ctx context.Context, ownerID int64, repository string) ([]string, error) {
42+
return packages_model.GetDistinctPropertyValues(
43+
ctx,
44+
packages_model.TypeAlpine,
45+
ownerID,
46+
packages_model.PropertyTypeFile,
47+
alpine_module.PropertyArchitecture,
48+
&packages_model.DistinctPropertyDependency{
49+
Name: alpine_module.PropertyRepository,
50+
Value: repository,
51+
},
52+
)
53+
}

models/packages/debian/search.go

+30-32
Original file line numberDiff line numberDiff line change
@@ -88,44 +88,42 @@ func SearchLatestPackages(ctx context.Context, opts *PackageSearchOptions) ([]*p
8888

8989
// GetDistributions gets all available distributions
9090
func GetDistributions(ctx context.Context, ownerID int64) ([]string, error) {
91-
return getDistinctPropertyValues(ctx, ownerID, "", debian_module.PropertyDistribution)
91+
return packages.GetDistinctPropertyValues(
92+
ctx,
93+
packages.TypeDebian,
94+
ownerID,
95+
packages.PropertyTypeFile,
96+
debian_module.PropertyDistribution,
97+
nil,
98+
)
9299
}
93100

94101
// GetComponents gets all available components for the given distribution
95102
func GetComponents(ctx context.Context, ownerID int64, distribution string) ([]string, error) {
96-
return getDistinctPropertyValues(ctx, ownerID, distribution, debian_module.PropertyComponent)
103+
return packages.GetDistinctPropertyValues(
104+
ctx,
105+
packages.TypeDebian,
106+
ownerID,
107+
packages.PropertyTypeFile,
108+
debian_module.PropertyComponent,
109+
&packages.DistinctPropertyDependency{
110+
Name: debian_module.PropertyDistribution,
111+
Value: distribution,
112+
},
113+
)
97114
}
98115

99116
// GetArchitectures gets all available architectures for the given distribution
100117
func GetArchitectures(ctx context.Context, ownerID int64, distribution string) ([]string, error) {
101-
return getDistinctPropertyValues(ctx, ownerID, distribution, debian_module.PropertyArchitecture)
102-
}
103-
104-
func getDistinctPropertyValues(ctx context.Context, ownerID int64, distribution, propName string) ([]string, error) {
105-
var cond builder.Cond = builder.Eq{
106-
"package_property.ref_type": packages.PropertyTypeFile,
107-
"package_property.name": propName,
108-
"package.type": packages.TypeDebian,
109-
"package.owner_id": ownerID,
110-
}
111-
if distribution != "" {
112-
innerCond := builder.
113-
Expr("pp.ref_id = package_property.ref_id").
114-
And(builder.Eq{
115-
"pp.ref_type": packages.PropertyTypeFile,
116-
"pp.name": debian_module.PropertyDistribution,
117-
"pp.value": distribution,
118-
})
119-
cond = cond.And(builder.Exists(builder.Select("pp.ref_id").From("package_property pp").Where(innerCond)))
120-
}
121-
122-
values := make([]string, 0, 5)
123-
return values, db.GetEngine(ctx).
124-
Table("package_property").
125-
Distinct("package_property.value").
126-
Join("INNER", "package_file", "package_file.id = package_property.ref_id").
127-
Join("INNER", "package_version", "package_version.id = package_file.version_id").
128-
Join("INNER", "package", "package.id = package_version.package_id").
129-
Where(cond).
130-
Find(&values)
118+
return packages.GetDistinctPropertyValues(
119+
ctx,
120+
packages.TypeDebian,
121+
ownerID,
122+
packages.PropertyTypeFile,
123+
debian_module.PropertyArchitecture,
124+
&packages.DistinctPropertyDependency{
125+
Name: debian_module.PropertyDistribution,
126+
Value: distribution,
127+
},
128+
)
131129
}

models/packages/descriptor.go

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
repo_model "code.gitea.io/gitea/models/repo"
1313
user_model "code.gitea.io/gitea/models/user"
1414
"code.gitea.io/gitea/modules/json"
15+
"code.gitea.io/gitea/modules/packages/alpine"
1516
"code.gitea.io/gitea/modules/packages/cargo"
1617
"code.gitea.io/gitea/modules/packages/chef"
1718
"code.gitea.io/gitea/modules/packages/composer"
@@ -136,6 +137,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
136137

137138
var metadata interface{}
138139
switch p.Type {
140+
case TypeAlpine:
141+
metadata = &alpine.VersionMetadata{}
139142
case TypeCargo:
140143
metadata = &cargo.Metadata{}
141144
case TypeChef:

models/packages/package.go

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Type string
3030

3131
// List of supported packages
3232
const (
33+
TypeAlpine Type = "alpine"
3334
TypeCargo Type = "cargo"
3435
TypeChef Type = "chef"
3536
TypeComposer Type = "composer"
@@ -51,6 +52,7 @@ const (
5152
)
5253

5354
var TypeList = []Type{
55+
TypeAlpine,
5456
TypeCargo,
5557
TypeChef,
5658
TypeComposer,
@@ -74,6 +76,8 @@ var TypeList = []Type{
7476
// Name gets the name of the package type
7577
func (pt Type) Name() string {
7678
switch pt {
79+
case TypeAlpine:
80+
return "Alpine"
7781
case TypeCargo:
7882
return "Cargo"
7983
case TypeChef:
@@ -117,6 +121,8 @@ func (pt Type) Name() string {
117121
// SVGName gets the name of the package type svg image
118122
func (pt Type) SVGName() string {
119123
switch pt {
124+
case TypeAlpine:
125+
return "gitea-alpine"
120126
case TypeCargo:
121127
return "gitea-cargo"
122128
case TypeChef:

models/packages/package_property.go

+38
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"context"
88

99
"code.gitea.io/gitea/models/db"
10+
11+
"xorm.io/builder"
1012
)
1113

1214
func init() {
@@ -81,3 +83,39 @@ func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64
8183
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
8284
return err
8385
}
86+
87+
type DistinctPropertyDependency struct {
88+
Name string
89+
Value string
90+
}
91+
92+
// GetDistinctPropertyValues returns all distinct property values for a given type.
93+
// Optional: Search only in dependence of another property.
94+
func GetDistinctPropertyValues(ctx context.Context, packageType Type, ownerID int64, refType PropertyType, propertyName string, dep *DistinctPropertyDependency) ([]string, error) {
95+
var cond builder.Cond = builder.Eq{
96+
"package_property.ref_type": refType,
97+
"package_property.name": propertyName,
98+
"package.type": packageType,
99+
"package.owner_id": ownerID,
100+
}
101+
if dep != nil {
102+
innerCond := builder.
103+
Expr("pp.ref_id = package_property.ref_id").
104+
And(builder.Eq{
105+
"pp.ref_type": refType,
106+
"pp.name": dep.Name,
107+
"pp.value": dep.Value,
108+
})
109+
cond = cond.And(builder.Exists(builder.Select("pp.ref_id").From("package_property pp").Where(innerCond)))
110+
}
111+
112+
values := make([]string, 0, 5)
113+
return values, db.GetEngine(ctx).
114+
Table("package_property").
115+
Distinct("package_property.value").
116+
Join("INNER", "package_file", "package_file.id = package_property.ref_id").
117+
Join("INNER", "package_version", "package_version.id = package_file.version_id").
118+
Join("INNER", "package", "package.id = package_version.package_id").
119+
Where(cond).
120+
Find(&values)
121+
}

0 commit comments

Comments
 (0)