mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Support for grouping RPMs using paths (#26984)
The current rpm repository places all packages in the same repository, and different systems (el7,f34) may hit packages that do not belong to this distribution ( #25304 ) , which now supports grouping of rpm.  Fixes #25304 . Fixes #27056 . Refactor: [#25866](https://github.com/go-gitea/gitea/pull/25866)
This commit is contained in:
		| @@ -27,17 +27,18 @@ The following examples use `dnf`. | ||||
| To register the RPM registry add the url to the list of known apt sources: | ||||
|  | ||||
| ```shell | ||||
| dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm.repo | ||||
| dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo | ||||
| ``` | ||||
|  | ||||
| | Placeholder | Description | | ||||
| | ----------- | ----------- | | ||||
| | `owner`     | The owner of the package. | | ||||
| | Placeholder | Description                                        | | ||||
| | ----------- |----------------------------------------------------| | ||||
| | `owner`     | The owner of the package.                          | | ||||
| | `group`     |  Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.| | ||||
|  | ||||
| If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication): | ||||
|  | ||||
| ```shell | ||||
| dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm.repo | ||||
| dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo | ||||
| ``` | ||||
|  | ||||
| You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.repos.d` too. | ||||
| @@ -47,19 +48,20 @@ You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum. | ||||
| To publish a RPM package (`*.rpm`), perform a HTTP PUT operation with the package content in the request body. | ||||
|  | ||||
| ``` | ||||
| PUT https://gitea.example.com/api/packages/{owner}/rpm/upload | ||||
| PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload | ||||
| ``` | ||||
|  | ||||
| | Parameter | Description | | ||||
| | --------- | ----------- | | ||||
| | `owner`   | The owner of the package. | | ||||
| | `group`   |  Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.| | ||||
|  | ||||
| Example request using HTTP Basic authentication: | ||||
|  | ||||
| ```shell | ||||
| curl --user your_username:your_password_or_token \ | ||||
|      --upload-file path/to/file.rpm \ | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/upload | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/centos/el7/upload | ||||
| ``` | ||||
|  | ||||
| If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. | ||||
| @@ -78,21 +80,22 @@ The server responds with the following HTTP Status codes. | ||||
| To delete an RPM package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. | ||||
|  | ||||
| ``` | ||||
| DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{package_version}/{architecture} | ||||
| DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture} | ||||
| ``` | ||||
|  | ||||
| | Parameter         | Description | | ||||
| | ----------------- | ----------- | | ||||
| | `owner`           | The owner of the package. | | ||||
| | `package_name`    | The package name. | | ||||
| | `package_version` | The package version. | | ||||
| | `architecture`    | The package architecture. | | ||||
| | Parameter         | Description                | | ||||
| |-------------------|----------------------------| | ||||
| | `owner`           | The owner of the package.  | | ||||
| | `group`           | The package group       .  | | ||||
| | `package_name`    | The package name.          | | ||||
| | `package_version` | The package version.       | | ||||
| | `architecture`    | The package architecture.  | | ||||
|  | ||||
| Example request using HTTP Basic authentication: | ||||
|  | ||||
| ```shell | ||||
| curl --user your_username:your_token_or_password -X DELETE \ | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/test-package/1.0.0/x86_64 | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64 | ||||
| ``` | ||||
|  | ||||
| The server responds with the following HTTP Status codes. | ||||
|   | ||||
| @@ -27,17 +27,18 @@ menu: | ||||
| 要注册RPM注册表,请将 URL 添加到已知 `apt` 源列表中: | ||||
|  | ||||
| ```shell | ||||
| dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm.repo | ||||
| dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo | ||||
| ``` | ||||
|  | ||||
| | 占位符  | 描述           | | ||||
| | ------- | -------------- | | ||||
| | `owner` | 软件包的所有者 | | ||||
| | 占位符  | 描述                                   | | ||||
| | ------- |--------------------------------------| | ||||
| | `owner` | 软件包的所有者                           | | ||||
| | `group` | 任何名称,例如 `centos/7`、`el-7`、`fc38` | | ||||
|  | ||||
| 如果注册表是私有的,请在URL中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证): | ||||
|  | ||||
| ```shell | ||||
| dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm.repo | ||||
| dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo | ||||
| ``` | ||||
|  | ||||
| 您还必须将凭据添加到 `/etc/yum.repos.d` 中的 `rpm.repo` 文件中的URL中。 | ||||
| @@ -47,19 +48,20 @@ dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea. | ||||
| 要发布RPM软件包(`*.rpm`),请执行带有软件包内容的 HTTP `PUT` 操作。 | ||||
|  | ||||
| ``` | ||||
| PUT https://gitea.example.com/api/packages/{owner}/rpm/upload | ||||
| PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload | ||||
| ``` | ||||
|  | ||||
| | 参数    | 描述           | | ||||
| | ------- | -------------- | | ||||
| | `owner` | 软件包的所有者 | | ||||
| | ------- |--------------| | ||||
| | `owner` | 软件包的所有者      | | ||||
| | `group` | 软件包自定义分组名称 | | ||||
|  | ||||
| 使用HTTP基本身份验证的示例请求: | ||||
|  | ||||
| ```shell | ||||
| curl --user your_username:your_password_or_token \ | ||||
|      --upload-file path/to/file.rpm \ | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/upload | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/centos/el7/version/upload | ||||
| ``` | ||||
|  | ||||
| 如果您使用 2FA 或 OAuth,请使用[个人访问令牌](development/api-usage.md#通过-api-认证)替代密码。您无法将具有相同名称的文件两次发布到软件包中。您必须先删除现有的软件包版本。 | ||||
| @@ -77,12 +79,13 @@ curl --user your_username:your_password_or_token \ | ||||
| 要删除 RPM 软件包,请执行 HTTP `DELETE` 操作。如果没有文件剩余,这也将删除软件包版本。 | ||||
|  | ||||
| ``` | ||||
| DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{package_version}/{architecture} | ||||
| DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture} | ||||
| ``` | ||||
|  | ||||
| | 参数              | 描述           | | ||||
| | ----------------- | -------------- | | ||||
| | `owner`           | 软件包的所有者 | | ||||
| | `group`         | 软件包自定义分组 | | ||||
| | `package_name`    | 软件包名称     | | ||||
| | `package_version` | 软件包版本     | | ||||
| | `architecture`    | 软件包架构     | | ||||
| @@ -91,7 +94,7 @@ DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{packag | ||||
|  | ||||
| ```shell | ||||
| curl --user your_username:your_token_or_password -X DELETE \ | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/test-package/1.0.0/x86_64 | ||||
|      https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64 | ||||
| ``` | ||||
|  | ||||
| 服务器将以以下HTTP状态码响应: | ||||
|   | ||||
| @@ -15,8 +15,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	PropertyMetadata = "rpm.metadata" | ||||
|  | ||||
| 	PropertyMetadata  = "rpm.metadata" | ||||
| 	SettingKeyPrivate = "rpm.key.private" | ||||
| 	SettingKeyPublic  = "rpm.key.public" | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| package templates | ||||
|  | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| @@ -25,6 +26,10 @@ func (su *StringUtils) Contains(s, substr string) bool { | ||||
| 	return strings.Contains(s, substr) | ||||
| } | ||||
|  | ||||
| func (su *StringUtils) ReplaceAllStringRegex(s, regex, new string) string { | ||||
| 	return regexp.MustCompile(regex).ReplaceAllString(s, new) | ||||
| } | ||||
|  | ||||
| func (su *StringUtils) Split(s, sep string) []string { | ||||
| 	return strings.Split(s, sep) | ||||
| } | ||||
|   | ||||
| @@ -512,19 +512,7 @@ func CommonRoutes() *web.Route { | ||||
| 			r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile) | ||||
| 			r.Get("/simple/{id}", pypi.PackageMetadata) | ||||
| 		}, reqPackageAccess(perm.AccessModeRead)) | ||||
| 		r.Group("/rpm", func() { | ||||
| 			r.Get(".repo", rpm.GetRepositoryConfig) | ||||
| 			r.Get("/repository.key", rpm.GetRepositoryKey) | ||||
| 			r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile) | ||||
| 			r.Group("/package/{name}/{version}/{architecture}", func() { | ||||
| 				r.Get("", rpm.DownloadPackageFile) | ||||
| 				r.Delete("", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile) | ||||
| 			}) | ||||
| 			r.Group("/repodata/{filename}", func() { | ||||
| 				r.Head("", rpm.CheckRepositoryFileExistence) | ||||
| 				r.Get("", rpm.GetRepositoryFile) | ||||
| 			}) | ||||
| 		}, reqPackageAccess(perm.AccessModeRead)) | ||||
| 		r.Group("/rpm", RpmRoutes(r), reqPackageAccess(perm.AccessModeRead)) | ||||
| 		r.Group("/rubygems", func() { | ||||
| 			r.Get("/specs.4.8.gz", rubygems.EnumeratePackages) | ||||
| 			r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest) | ||||
| @@ -589,6 +577,82 @@ func CommonRoutes() *web.Route { | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Support for uploading rpm packages with arbitrary depth paths | ||||
| func RpmRoutes(r *web.Route) func() { | ||||
| 	var ( | ||||
| 		groupRepoInfo = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)\.repo\z`) | ||||
| 		groupUpload   = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/upload\z`) | ||||
| 		groupRpm      = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`) | ||||
| 		groupMetadata = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/repodata/([^/]+)\z`) | ||||
| 	) | ||||
|  | ||||
| 	return func() { | ||||
| 		r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "*", func(ctx *context.Context) { | ||||
| 			path := ctx.Params("*") | ||||
| 			isHead := ctx.Req.Method == "HEAD" | ||||
| 			isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET" | ||||
| 			isPut := ctx.Req.Method == "PUT" | ||||
| 			isDelete := ctx.Req.Method == "DELETE" | ||||
|  | ||||
| 			if path == "/repository.key" && isGetHead { | ||||
| 				rpm.GetRepositoryKey(ctx) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			// get repo | ||||
| 			m := groupRepoInfo.FindStringSubmatch(path) | ||||
| 			if len(m) == 2 && isGetHead { | ||||
| 				ctx.SetParams("group", strings.Trim(m[1], "/")) | ||||
| 				rpm.GetRepositoryConfig(ctx) | ||||
| 				return | ||||
| 			} | ||||
| 			// get meta | ||||
| 			m = groupMetadata.FindStringSubmatch(path) | ||||
| 			if len(m) == 3 && isGetHead { | ||||
| 				ctx.SetParams("group", strings.Trim(m[1], "/")) | ||||
| 				ctx.SetParams("filename", m[2]) | ||||
| 				if isHead { | ||||
| 					rpm.CheckRepositoryFileExistence(ctx) | ||||
| 				} else { | ||||
| 					rpm.GetRepositoryFile(ctx) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 			// upload | ||||
| 			m = groupUpload.FindStringSubmatch(path) | ||||
| 			if len(m) == 2 && isPut { | ||||
| 				reqPackageAccess(perm.AccessModeWrite)(ctx) | ||||
| 				if ctx.Written() { | ||||
| 					return | ||||
| 				} | ||||
| 				ctx.SetParams("group", strings.Trim(m[1], "/")) | ||||
| 				rpm.UploadPackageFile(ctx) | ||||
| 				return | ||||
| 			} | ||||
| 			// rpm down/delete | ||||
| 			m = groupRpm.FindStringSubmatch(path) | ||||
| 			if len(m) == 6 { | ||||
| 				ctx.SetParams("group", strings.Trim(m[1], "/")) | ||||
| 				ctx.SetParams("name", m[2]) | ||||
| 				ctx.SetParams("version", m[3]) | ||||
| 				ctx.SetParams("architecture", m[4]) | ||||
| 				if isGetHead { | ||||
| 					rpm.DownloadPackageFile(ctx) | ||||
| 					return | ||||
| 				} else if isDelete { | ||||
| 					reqPackageAccess(perm.AccessModeWrite)(ctx) | ||||
| 					if ctx.Written() { | ||||
| 						return | ||||
| 					} | ||||
| 					rpm.DeletePackageFile(ctx) | ||||
| 				} | ||||
| 			} | ||||
| 			// default | ||||
| 			ctx.Status(http.StatusNotFound) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ContainerRoutes provides endpoints that implement the OCI API to serve containers | ||||
| // These have to be mounted on `/v2/...` to comply with the OCI spec: | ||||
| // https://github.com/opencontainers/distribution-spec/blob/main/spec.md | ||||
|   | ||||
| @@ -33,11 +33,14 @@ func apiError(ctx *context.Context, status int, obj any) { | ||||
|  | ||||
| // https://dnf.readthedocs.io/en/latest/conf_ref.html | ||||
| func GetRepositoryConfig(ctx *context.Context) { | ||||
| 	group := ctx.Params("group") | ||||
| 	if group != "" { | ||||
| 		group = fmt.Sprintf("/%s", group) | ||||
| 	} | ||||
| 	url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name) | ||||
|  | ||||
| 	ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`] | ||||
| name=`+ctx.Package.Owner.Name+` - `+setting.AppName+` | ||||
| baseurl=`+url+` | ||||
| 	ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+strings.ReplaceAll(group, "/", "-")+`] | ||||
| name=`+ctx.Package.Owner.Name+` - `+setting.AppName+strings.ReplaceAll(group, "/", " - ")+` | ||||
| baseurl=`+url+group+`/ | ||||
| enabled=1 | ||||
| gpgcheck=1 | ||||
| gpgkey=`+url+`/repository.key`) | ||||
| @@ -64,7 +67,7 @@ func CheckRepositoryFileExistence(ctx *context.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey) | ||||
| 	pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), ctx.Params("group")) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, util.ErrNotExist) { | ||||
| 			ctx.Status(http.StatusNotFound) | ||||
| @@ -93,7 +96,8 @@ func GetRepositoryFile(ctx *context.Context) { | ||||
| 		ctx, | ||||
| 		pv, | ||||
| 		&packages_service.PackageFileInfo{ | ||||
| 			Filename: ctx.Params("filename"), | ||||
| 			Filename:     ctx.Params("filename"), | ||||
| 			CompositeKey: ctx.Params("group"), | ||||
| 		}, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| @@ -145,7 +149,7 @@ func UploadPackageFile(ctx *context.Context) { | ||||
| 		apiError(ctx, http.StatusInternalServerError, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	group := ctx.Params("group") | ||||
| 	_, _, err = packages_service.CreatePackageOrAddFileToExisting( | ||||
| 		ctx, | ||||
| 		&packages_service.PackageCreationInfo{ | ||||
| @@ -153,14 +157,15 @@ func UploadPackageFile(ctx *context.Context) { | ||||
| 				Owner:       ctx.Package.Owner, | ||||
| 				PackageType: packages_model.TypeRpm, | ||||
| 				Name:        pck.Name, | ||||
| 				Version:     pck.Version, | ||||
| 				Version:     strings.Trim(fmt.Sprintf("%s/%s", group, pck.Version), "/"), | ||||
| 			}, | ||||
| 			Creator:  ctx.Doer, | ||||
| 			Metadata: pck.VersionMetadata, | ||||
| 		}, | ||||
| 		&packages_service.PackageFileCreationInfo{ | ||||
| 			PackageFileInfo: packages_service.PackageFileInfo{ | ||||
| 				Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), | ||||
| 				Filename:     fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), | ||||
| 				CompositeKey: group, | ||||
| 			}, | ||||
| 			Creator: ctx.Doer, | ||||
| 			Data:    buf, | ||||
| @@ -182,7 +187,7 @@ func UploadPackageFile(ctx *context.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil { | ||||
| 	if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil { | ||||
| 		apiError(ctx, http.StatusInternalServerError, err) | ||||
| 		return | ||||
| 	} | ||||
| @@ -191,19 +196,20 @@ func UploadPackageFile(ctx *context.Context) { | ||||
| } | ||||
|  | ||||
| func DownloadPackageFile(ctx *context.Context) { | ||||
| 	group := ctx.Params("group") | ||||
| 	name := ctx.Params("name") | ||||
| 	version := ctx.Params("version") | ||||
|  | ||||
| 	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( | ||||
| 		ctx, | ||||
| 		&packages_service.PackageInfo{ | ||||
| 			Owner:       ctx.Package.Owner, | ||||
| 			PackageType: packages_model.TypeRpm, | ||||
| 			Name:        name, | ||||
| 			Version:     version, | ||||
| 			Version:     strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"), | ||||
| 		}, | ||||
| 		&packages_service.PackageFileInfo{ | ||||
| 			Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), | ||||
| 			Filename:     fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), | ||||
| 			CompositeKey: group, | ||||
| 		}, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| @@ -219,14 +225,19 @@ func DownloadPackageFile(ctx *context.Context) { | ||||
| } | ||||
|  | ||||
| func DeletePackageFile(webctx *context.Context) { | ||||
| 	group := webctx.Params("group") | ||||
| 	name := webctx.Params("name") | ||||
| 	version := webctx.Params("version") | ||||
| 	architecture := webctx.Params("architecture") | ||||
|  | ||||
| 	var pd *packages_model.PackageDescriptor | ||||
|  | ||||
| 	err := db.WithTx(webctx, func(ctx stdctx.Context) error { | ||||
| 		pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version) | ||||
| 		pv, err := packages_model.GetVersionByNameAndVersion(ctx, | ||||
| 			webctx.Package.Owner.ID, | ||||
| 			packages_model.TypeRpm, | ||||
| 			name, | ||||
| 			strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -235,7 +246,7 @@ func DeletePackageFile(webctx *context.Context) { | ||||
| 			ctx, | ||||
| 			pv.ID, | ||||
| 			fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture), | ||||
| 			packages_model.EmptyFileKey, | ||||
| 			group, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @@ -275,7 +286,7 @@ func DeletePackageFile(webctx *context.Context) { | ||||
| 		notify_service.PackageDelete(webctx, webctx.Doer, pd) | ||||
| 	} | ||||
|  | ||||
| 	if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil { | ||||
| 	if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil { | ||||
| 		apiError(webctx, http.StatusInternalServerError, err) | ||||
| 		return | ||||
| 	} | ||||
|   | ||||
| @@ -125,17 +125,18 @@ type packageData struct { | ||||
|  | ||||
| type packageCache = map[*packages_model.PackageFile]*packageData | ||||
|  | ||||
| // BuildRepositoryFiles builds metadata files for the repository | ||||
| func BuildRepositoryFiles(ctx context.Context, ownerID int64) error { | ||||
| // BuildSpecificRepositoryFiles builds metadata files for the repository | ||||
| func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey string) error { | ||||
| 	pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ | ||||
| 		OwnerID:     ownerID, | ||||
| 		PackageType: packages_model.TypeRpm, | ||||
| 		Query:       "%.rpm", | ||||
| 		OwnerID:      ownerID, | ||||
| 		PackageType:  packages_model.TypeRpm, | ||||
| 		Query:        "%.rpm", | ||||
| 		CompositeKey: compositeKey, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -194,15 +195,15 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error { | ||||
| 		cache[pf] = pd | ||||
| 	} | ||||
|  | ||||
| 	primary, err := buildPrimary(ctx, pv, pfs, cache) | ||||
| 	primary, err := buildPrimary(ctx, pv, pfs, cache, compositeKey) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	filelists, err := buildFilelists(ctx, pv, pfs, cache) | ||||
| 	filelists, err := buildFilelists(ctx, pv, pfs, cache, compositeKey) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	other, err := buildOther(ctx, pv, pfs, cache) | ||||
| 	other, err := buildOther(ctx, pv, pfs, cache, compositeKey) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -216,11 +217,12 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error { | ||||
| 			filelists, | ||||
| 			other, | ||||
| 		}, | ||||
| 		compositeKey, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml | ||||
| func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData) error { | ||||
| func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, compositeKey string) error { | ||||
| 	type Repomd struct { | ||||
| 		XMLName  xml.Name    `xml:"repomd"` | ||||
| 		Xmlns    string      `xml:"xmlns,attr"` | ||||
| @@ -275,7 +277,8 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID | ||||
| 			pv, | ||||
| 			&packages_service.PackageFileCreationInfo{ | ||||
| 				PackageFileInfo: packages_service.PackageFileInfo{ | ||||
| 					Filename: file.Name, | ||||
| 					Filename:     file.Name, | ||||
| 					CompositeKey: compositeKey, | ||||
| 				}, | ||||
| 				Creator:           user_model.NewGhostUser(), | ||||
| 				Data:              file.Data, | ||||
| @@ -292,7 +295,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID | ||||
| } | ||||
|  | ||||
| // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml | ||||
| func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { | ||||
| func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { | ||||
| 	type Version struct { | ||||
| 		Epoch   string `xml:"epoch,attr"` | ||||
| 		Version string `xml:"ver,attr"` | ||||
| @@ -372,7 +375,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] | ||||
| 				files = append(files, f) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release) | ||||
| 		packages = append(packages, &Package{ | ||||
| 			Type:         "rpm", | ||||
| 			Name:         pd.Package.Name, | ||||
| @@ -401,7 +404,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] | ||||
| 				Archive:   pd.FileMetadata.ArchiveSize, | ||||
| 			}, | ||||
| 			Location: Location{ | ||||
| 				Href: fmt.Sprintf("package/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(pd.Version.Version), url.PathEscape(pd.FileMetadata.Architecture)), | ||||
| 				Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))), | ||||
| 			}, | ||||
| 			Format: Format{ | ||||
| 				License:   pd.VersionMetadata.License, | ||||
| @@ -431,11 +434,11 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] | ||||
| 		XmlnsRpm:     "http://linux.duke.edu/metadata/rpm", | ||||
| 		PackageCount: len(pfs), | ||||
| 		Packages:     packages, | ||||
| 	}) | ||||
| 	}, compositeKey) | ||||
| } | ||||
|  | ||||
| // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml | ||||
| func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { //nolint:dupl | ||||
| func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl | ||||
| 	type Version struct { | ||||
| 		Epoch   string `xml:"epoch,attr"` | ||||
| 		Version string `xml:"ver,attr"` | ||||
| @@ -478,11 +481,12 @@ func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs | ||||
| 		Xmlns:        "http://linux.duke.edu/metadata/other", | ||||
| 		PackageCount: len(pfs), | ||||
| 		Packages:     packages, | ||||
| 	}) | ||||
| 	}, | ||||
| 		compositeKey) | ||||
| } | ||||
|  | ||||
| // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml | ||||
| func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { //nolint:dupl | ||||
| func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl | ||||
| 	type Version struct { | ||||
| 		Epoch   string `xml:"epoch,attr"` | ||||
| 		Version string `xml:"ver,attr"` | ||||
| @@ -525,7 +529,7 @@ func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*p | ||||
| 		Xmlns:        "http://linux.duke.edu/metadata/other", | ||||
| 		PackageCount: len(pfs), | ||||
| 		Packages:     packages, | ||||
| 	}) | ||||
| 	}, compositeKey) | ||||
| } | ||||
|  | ||||
| // writtenCounter counts all written bytes | ||||
| @@ -545,10 +549,8 @@ func (wc *writtenCounter) Written() int64 { | ||||
| 	return wc.written | ||||
| } | ||||
|  | ||||
| func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any) (*repoData, error) { | ||||
| func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, compositeKey string) (*repoData, error) { | ||||
| 	content, _ := packages_module.NewHashedBuffer() | ||||
| 	defer content.Close() | ||||
|  | ||||
| 	gzw := gzip.NewWriter(content) | ||||
| 	wc := &writtenCounter{} | ||||
| 	h := sha256.New() | ||||
| @@ -571,7 +573,8 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, | ||||
| 		pv, | ||||
| 		&packages_service.PackageFileCreationInfo{ | ||||
| 			PackageFileInfo: packages_service.PackageFileInfo{ | ||||
| 				Filename: filename, | ||||
| 				Filename:     filename, | ||||
| 				CompositeKey: compositeKey, | ||||
| 			}, | ||||
| 			Creator:           user_model.NewGhostUser(), | ||||
| 			Data:              content, | ||||
|   | ||||
| @@ -4,19 +4,23 @@ | ||||
| 		<div class="ui form"> | ||||
| 			<div class="field"> | ||||
| 				<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.registry"}}</label> | ||||
| 				<div class="markup"><pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distro.redhat"}} | ||||
| dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm.repo"></gitea-origin-url> | ||||
| 				<div class="markup"><pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distros.redhat"}} | ||||
| {{$group_name:= StringUtils.ReplaceAllStringRegex .PackageDescriptor.Version.Version "(/[^/]+|[^/]*)\\z" "" -}} | ||||
| {{- if $group_name -}} | ||||
| {{- $group_name = (print "/" $group_name) -}} | ||||
| {{- end -}} | ||||
| dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group_name}}.repo"></gitea-origin-url> | ||||
|  | ||||
| # {{ctx.Locale.Tr "packages.rpm.distro.suse"}} | ||||
| zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm.repo"></gitea-origin-url></code></pre></div> | ||||
| # {{ctx.Locale.Tr "packages.rpm.distros.suse"}} | ||||
| zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group_name}}.repo"></gitea-origin-url></code></pre></div> | ||||
| 			</div> | ||||
| 			<div class="field"> | ||||
| 				<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.install"}}</label> | ||||
| 				<div class="markup"> | ||||
| 					<pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distro.redhat"}} | ||||
| 					<pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distros.redhat"}} | ||||
| dnf install {{$.PackageDescriptor.Package.Name}} | ||||
|  | ||||
| # {{ctx.Locale.Tr "packages.rpm.distro.suse"}} | ||||
| # {{ctx.Locale.Tr "packages.rpm.distros.suse"}} | ||||
| zypper install {{$.PackageDescriptor.Package.Name}}</code></pre> | ||||
| 				</div> | ||||
| 			</div> | ||||
|   | ||||
| @@ -76,12 +76,12 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5 | ||||
| 	t.Run("RepositoryConfig", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
|  | ||||
| 		req := NewRequest(t, "GET", rootURL+".repo") | ||||
| 		req := NewRequest(t, "GET", rootURL+"/el9/stable.repo") | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
|  | ||||
| 		expected := fmt.Sprintf(`[gitea-%s] | ||||
| name=%s - %s | ||||
| baseurl=%sapi/packages/%s/rpm | ||||
| 		expected := fmt.Sprintf(`[gitea-%s-el9-stable] | ||||
| name=%s - %s - el9 - stable | ||||
| baseurl=%sapi/packages/%s/rpm/el9/stable/ | ||||
| enabled=1 | ||||
| gpgcheck=1 | ||||
| gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppName, setting.AppURL, user.Name, setting.AppURL, user.Name) | ||||
| @@ -100,7 +100,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Upload", func(t *testing.T) { | ||||
| 		url := rootURL + "/upload" | ||||
| 		url := rootURL + "/el9/stable/upload" | ||||
|  | ||||
| 		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) | ||||
| 		MakeRequest(t, req, http.StatusUnauthorized) | ||||
| @@ -118,7 +118,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
| 		assert.Nil(t, pd.SemVer) | ||||
| 		assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata) | ||||
| 		assert.Equal(t, packageName, pd.Package.Name) | ||||
| 		assert.Equal(t, packageVersion, pd.Version.Version) | ||||
| 		assert.Equal(t, fmt.Sprintf("el9/stable/%s", packageVersion), pd.Version.Version) | ||||
|  | ||||
| 		pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) | ||||
| 		assert.NoError(t, err) | ||||
| @@ -138,7 +138,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
| 	t.Run("Download", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
|  | ||||
| 		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)) | ||||
| 		req := NewRequest(t, "GET", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)) | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
|  | ||||
| 		assert.Equal(t, content, resp.Body.Bytes()) | ||||
| @@ -147,7 +147,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
| 	t.Run("Repository", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
|  | ||||
| 		url := rootURL + "/repodata" | ||||
| 		url := rootURL + "/el9/stable/repodata" | ||||
|  | ||||
| 		req := NewRequest(t, "HEAD", url+"/dummy.xml") | ||||
| 		MakeRequest(t, req, http.StatusNotFound) | ||||
| @@ -201,8 +201,8 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
|  | ||||
| 				switch d.Type { | ||||
| 				case "primary": | ||||
| 					assert.EqualValues(t, 718, d.Size) | ||||
| 					assert.EqualValues(t, 1729, d.OpenSize) | ||||
| 					assert.EqualValues(t, 722, d.Size) | ||||
| 					assert.EqualValues(t, 1759, d.OpenSize) | ||||
| 					assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href) | ||||
| 				case "filelists": | ||||
| 					assert.EqualValues(t, 257, d.Size) | ||||
| @@ -311,7 +311,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
| 			assert.EqualValues(t, len(content), p.Size.Package) | ||||
| 			assert.EqualValues(t, 13, p.Size.Installed) | ||||
| 			assert.EqualValues(t, 272, p.Size.Archive) | ||||
| 			assert.Equal(t, fmt.Sprintf("package/%s/%s/%s", packageName, packageVersion, packageArchitecture), p.Location.Href) | ||||
| 			assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href) | ||||
| 			f := p.Format | ||||
| 			assert.Equal(t, "MIT", f.License) | ||||
| 			assert.Len(t, f.Provides.Entries, 2) | ||||
| @@ -401,18 +401,17 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN | ||||
| 	t.Run("Delete", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
|  | ||||
| 		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)) | ||||
| 		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)) | ||||
| 		MakeRequest(t, req, http.StatusUnauthorized) | ||||
|  | ||||
| 		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)). | ||||
| 		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)). | ||||
| 			AddBasicAuth(user.Name) | ||||
| 		MakeRequest(t, req, http.StatusNoContent) | ||||
|  | ||||
| 		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm) | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Empty(t, pvs) | ||||
|  | ||||
| 		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)). | ||||
| 		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)). | ||||
| 			AddBasicAuth(user.Name) | ||||
| 		MakeRequest(t, req, http.StatusNotFound) | ||||
| 	}) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user