Skip to content

Commit 3548d25

Browse files
committed
init third part api
because if using token serve, it will be difficut to use docker registry api directly, try do someting to simplify it.` Signed-off-by: a1012112796 <[email protected]>
1 parent b1f5379 commit 3548d25

File tree

10 files changed

+270
-3
lines changed

10 files changed

+270
-3
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1298,4 +1298,6 @@ KEY_FILE = /path/to/server.key
12981298
; Expiration time for token, should biger than 60 (s)
12991299
EXPIRATION = 60
13001300
; notify token for notify check
1301-
NOTIFY_TOKEN = xxxxxxxxx
1301+
NOTIFY_TOKEN = xxxxxxxxx
1302+
; api base path
1303+
API_BASE_PATH = http://127.0.0.1:5000

modules/convert/package.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,14 @@ func ToPackage(pkg *models.Package) *api.Package {
2222
rs.Private = rs.Repo.Private
2323
return rs
2424
}
25+
26+
// DockerToVersionList convert docker version list
27+
func DockerToVersionList(vs []string) []*api.PackageVersion {
28+
rs := make([]*api.PackageVersion, 0, len(vs))
29+
for _, v := range vs {
30+
rs = append(rs, &api.PackageVersion{
31+
Name: v,
32+
})
33+
}
34+
return rs
35+
}

modules/packages/docker/api.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package docker
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"net/http"
11+
"time"
12+
)
13+
14+
// API docker api port
15+
type API struct {
16+
APIBasePath string
17+
Token string
18+
TimeOut time.Duration
19+
Ctx context.Context
20+
}
21+
22+
// ListImageTags Listing Image Tags
23+
// GET /v2/<name>/tags/list
24+
func (a *API) ListImageTags(name string) (*TagsAPIResponse, error) {
25+
client := &http.Client{}
26+
req, err := http.NewRequest("GET", a.APIBasePath+"/v2/"+name+"/tags/list", nil)
27+
if err != nil {
28+
return nil, err
29+
}
30+
req.Header.Set("Authorization", "Bearer "+a.Token)
31+
32+
ctx, cancel := context.WithTimeout(a.Ctx, a.TimeOut)
33+
defer cancel()
34+
35+
req = req.WithContext(ctx)
36+
37+
rsp, err := client.Do(req)
38+
if err != nil {
39+
return nil, err
40+
}
41+
defer rsp.Body.Close()
42+
43+
v := new(TagsAPIResponse)
44+
decoder := json.NewDecoder(rsp.Body)
45+
if err = decoder.Decode(v); err != nil {
46+
return nil, err
47+
}
48+
49+
return v, nil
50+
}
51+
52+
// TagsAPIResponse list tags response
53+
type TagsAPIResponse struct {
54+
Name string `json:"name"`
55+
Tags []string `json:"tags"`
56+
}

modules/setting/package.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package setting
77
import (
88
"crypto/tls"
99
"crypto/x509"
10+
"strings"
1011

1112
"code.gitea.io/gitea/modules/log"
1213

@@ -22,6 +23,8 @@ type dockerPluginConfig struct {
2223

2324
PublicKey libtrust.PublicKey
2425
PrivateKey libtrust.PrivateKey
26+
27+
APIBasePath string
2528
}
2629

2730
var (
@@ -59,6 +62,11 @@ func newPackage() {
5962
if len(Docker.NotifyToken) == 0 {
6063
log.Fatal("docker_registry_plugin: `NOTIFY_TOKEN` is requested")
6164
}
65+
Docker.APIBasePath = cfg.Key("API_BASE_PATH").String()
66+
if len(Docker.APIBasePath) == 0 {
67+
log.Fatal("docker_registry_plugin: `API_BASE_PATH` is requested")
68+
}
69+
Docker.APIBasePath = strings.TrimRight(Docker.APIBasePath, "/")
6270
}
6371

6472
// HasDockerPlugin has docker plugin

modules/structs/package.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ type Package struct {
1919
// swagger:strfmt date-time
2020
Updated *time.Time `json:"updated_at"`
2121
}
22+
23+
// PackageVersion represents a package version
24+
type PackageVersion struct {
25+
Name string `json:"name"`
26+
Detail string `json:"detail"`
27+
}

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ func Routes() *web.Route {
953953
m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages)
954954
m.Group("/packages", func() {
955955
m.Get("/{type}/{name}", repo.GetPackage)
956+
m.Get("/{type}/{name}/versions", repo.ListPackageVersions)
956957
}, reqRepoReader(models.UnitTypePackages))
957958
}, repoAssignment())
958959
})

routers/api/v1/repo/package.go

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ package repo
66

77
import (
88
"net/http"
9+
"time"
910

1011
"code.gitea.io/gitea/models"
1112
"code.gitea.io/gitea/modules/context"
1213
"code.gitea.io/gitea/modules/convert"
14+
"code.gitea.io/gitea/modules/packages/docker"
15+
"code.gitea.io/gitea/modules/setting"
1316
)
1417

1518
// GetPackage get a single package of a repository
@@ -47,7 +50,7 @@ func GetPackage(ctx *context.APIContext) {
4750
// "$ref": "#/responses/notFound"
4851
typ := ctx.Params("type")
4952
name := ctx.Params("name")
50-
if typ != models.PackageTypeDockerImage.String() {
53+
if typ != models.PackageTypeDockerImage.String() || !setting.HasDockerPlugin() {
5154
ctx.NotFound()
5255
return
5356
}
@@ -72,3 +75,102 @@ func GetPackage(ctx *context.APIContext) {
7275

7376
ctx.JSON(http.StatusOK, convert.ToPackage(pkg))
7477
}
78+
79+
// ListPackageVersions list versions of a package
80+
func ListPackageVersions(ctx *context.APIContext) {
81+
// swagger:operation GET /repos/{owner}/{repo}/packages/{type}/{name}/versions repository repoListPackageVersions
82+
// ---
83+
// summary: list versions of a package
84+
// produces:
85+
// - application/json
86+
// parameters:
87+
// - name: owner
88+
// in: path
89+
// description: owner of the repo
90+
// type: string
91+
// required: true
92+
// - name: repo
93+
// in: path
94+
// description: name of the repo
95+
// type: string
96+
// required: true
97+
// - name: type
98+
// in: path
99+
// description: type of package
100+
// type: string
101+
// required: true
102+
// - name: name
103+
// in: path
104+
// description: name of package
105+
// type: string
106+
// required: true
107+
// responses:
108+
// "200":
109+
// "$ref": "#/responses/PackageVersionList"
110+
// "404":
111+
// "$ref": "#/responses/notFound"
112+
typ := ctx.Params("type")
113+
name := ctx.Params("name")
114+
if typ != models.PackageTypeDockerImage.String() || !setting.HasDockerPlugin() {
115+
ctx.NotFound()
116+
return
117+
}
118+
pkg, err := models.GetPackage(ctx.Repo.Repository.ID, models.PackageTypeDockerImage, name)
119+
if err != nil {
120+
if models.IsErrPackageNotExist(err) {
121+
ctx.NotFound()
122+
}
123+
ctx.InternalServerError(err)
124+
return
125+
}
126+
127+
if err := pkg.LoadRepo(); err != nil {
128+
ctx.InternalServerError(err)
129+
return
130+
}
131+
132+
// generate token
133+
perms := []docker.AuthzResult{
134+
{
135+
Scope: docker.AuthScope{
136+
Type: "repository",
137+
Class: "",
138+
Name: pkg.Repo.OwnerName + "/" + pkg.Repo.Name + "/" + pkg.Name,
139+
Actions: []string{"pull"},
140+
},
141+
AutorizedActions: []string{"pull"},
142+
},
143+
}
144+
account := ""
145+
if ctx.User != nil {
146+
account = ctx.User.Name
147+
}
148+
token, err := docker.GenerateToken(docker.GenerateTokenOptions{
149+
Account: account,
150+
IssuerName: setting.Docker.IssuerName,
151+
AuthzResults: perms,
152+
PublicKey: &setting.Docker.PublicKey,
153+
PrivateKey: &setting.Docker.PrivateKey,
154+
ServiceName: setting.Docker.ServiceName,
155+
Expiration: setting.Docker.Expiration,
156+
})
157+
if err != nil {
158+
ctx.Error(http.StatusInternalServerError, "generateToken", err)
159+
return
160+
}
161+
162+
api := &docker.API{
163+
APIBasePath: setting.Docker.APIBasePath,
164+
Token: token,
165+
TimeOut: 5 * time.Minute,
166+
Ctx: ctx.Req.Context(),
167+
}
168+
169+
rs, err := api.ListImageTags(pkg.Repo.OwnerName + "/" + pkg.Repo.Name + "/" + pkg.Name)
170+
if err != nil {
171+
ctx.Error(500, "docker.ListImageTags", err)
172+
return
173+
}
174+
175+
ctx.JSON(http.StatusOK, convert.DockerToVersionList(rs.Tags))
176+
}

routers/api/v1/swagger/repo.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,10 @@ type swaggerResponsePackage struct {
330330
// in:body
331331
Body api.Package `json:"body"`
332332
}
333+
334+
// PackageVersionList
335+
// swagger:response PackageVersionList
336+
type swaggerResponsePackageVersionList struct {
337+
// in:body
338+
Body []api.PackageVersion `json:"body"`
339+
}

routers/repo/package.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func handleResult(ctx *context.Context, ares []docker.AuthzResult) {
7272
Expiration: setting.Docker.Expiration,
7373
})
7474
if err != nil {
75-
ctx.Error(http.StatusInternalServerError, "generateToken")
75+
ctx.Error(http.StatusInternalServerError, "")
7676
log.Error("Failed to generate token: %v", err)
7777
return
7878
}

templates/swagger/v1_json.tmpl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6952,6 +6952,56 @@
69526952
}
69536953
}
69546954
},
6955+
"/repos/{owner}/{repo}/packages/{type}/{name}/versions": {
6956+
"get": {
6957+
"produces": [
6958+
"application/json"
6959+
],
6960+
"tags": [
6961+
"repository"
6962+
],
6963+
"summary": "list versions of a package",
6964+
"operationId": "repoListPackageVersions",
6965+
"parameters": [
6966+
{
6967+
"type": "string",
6968+
"description": "owner of the repo",
6969+
"name": "owner",
6970+
"in": "path",
6971+
"required": true
6972+
},
6973+
{
6974+
"type": "string",
6975+
"description": "name of the repo",
6976+
"name": "repo",
6977+
"in": "path",
6978+
"required": true
6979+
},
6980+
{
6981+
"type": "string",
6982+
"description": "type of package",
6983+
"name": "type",
6984+
"in": "path",
6985+
"required": true
6986+
},
6987+
{
6988+
"type": "string",
6989+
"description": "name of package",
6990+
"name": "name",
6991+
"in": "path",
6992+
"required": true
6993+
}
6994+
],
6995+
"responses": {
6996+
"200": {
6997+
"$ref": "#/responses/PackageVersionList"
6998+
},
6999+
"404": {
7000+
"$ref": "#/responses/notFound"
7001+
}
7002+
}
7003+
}
7004+
},
69557005
"/repos/{owner}/{repo}/pulls": {
69567006
"get": {
69577007
"produces": [
@@ -15117,6 +15167,21 @@
1511715167
},
1511815168
"x-go-package": "code.gitea.io/gitea/modules/structs"
1511915169
},
15170+
"PackageVersion": {
15171+
"description": "PackageVersion represents a package version",
15172+
"type": "object",
15173+
"properties": {
15174+
"detail": {
15175+
"type": "string",
15176+
"x-go-name": "Detail"
15177+
},
15178+
"name": {
15179+
"type": "string",
15180+
"x-go-name": "Name"
15181+
}
15182+
},
15183+
"x-go-package": "code.gitea.io/gitea/modules/structs"
15184+
},
1512015185
"PayloadCommit": {
1512115186
"description": "PayloadCommit represents a commit",
1512215187
"type": "object",
@@ -16825,6 +16890,15 @@
1682516890
}
1682616891
}
1682716892
},
16893+
"PackageVersionList": {
16894+
"description": "PackageVersionList",
16895+
"schema": {
16896+
"type": "array",
16897+
"items": {
16898+
"$ref": "#/definitions/PackageVersion"
16899+
}
16900+
}
16901+
},
1682816902
"PublicKey": {
1682916903
"description": "PublicKey",
1683016904
"schema": {

0 commit comments

Comments
 (0)