cloudapi: Hook up the /search/packages handler
This connects all the pieces needed to implement the search.
If you POST a request to /search/packages like this:
{
"packages": [
"tmux"
],
"distribution": "fedora-41",
"architecture": "x86_64"
}
It will return details about the tmux packages that looks like this:
{
"packages": [
{
"arch": "x86_64",
"buildtime": "2024-10-10T00:19:06Z",
"description": "tmux is ...",
"license": "ISC AND BSD-2-Clause AND BSD-3-Clause AND SSH-short AND LicenseRef-Fedora-Public-Domain",
"name": "tmux",
"release": "2.fc41",
"summary": "A terminal multiplexer",
"url": "https://tmux.github.io/",
"version": "3.5a"
}
]
}
Resolves: RHEL-60136
This commit is contained in:
parent
234e8a09eb
commit
532f1b0396
2 changed files with 146 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
|
@ -1383,3 +1384,63 @@ func packageSpecToPackageMetadata(pkgspecs []rpmmd.PackageSpec) []PackageMetadat
|
|||
}
|
||||
return packages
|
||||
}
|
||||
|
||||
// packageListToPackageDetails converts the rpmmd.PackageList to PackageDetails
|
||||
// This is used to return detailed package information from the package search
|
||||
func packageListToPackageDetails(packages rpmmd.PackageList) []PackageDetails {
|
||||
details := make([]PackageDetails, 0)
|
||||
for _, rpm := range packages {
|
||||
d := PackageDetails{
|
||||
Name: rpm.Name,
|
||||
Version: rpm.Version,
|
||||
Release: rpm.Release,
|
||||
Arch: rpm.Arch,
|
||||
}
|
||||
|
||||
// Set epoch if it is not 0
|
||||
if rpm.Epoch > 0 {
|
||||
d.Epoch = common.ToPtr(strconv.FormatUint(uint64(rpm.Epoch), 10))
|
||||
}
|
||||
|
||||
// Set buildtime to a RFC3339 string
|
||||
d.Buildtime = common.ToPtr(rpm.BuildTime.Format(time.RFC3339))
|
||||
if len(rpm.Summary) > 0 {
|
||||
d.Summary = common.ToPtr(rpm.Summary)
|
||||
}
|
||||
if len(rpm.Description) > 0 {
|
||||
d.Description = common.ToPtr(rpm.Description)
|
||||
}
|
||||
if len(rpm.URL) > 0 {
|
||||
d.Url = common.ToPtr(rpm.URL)
|
||||
}
|
||||
if len(rpm.License) > 0 {
|
||||
d.License = common.ToPtr(rpm.License)
|
||||
}
|
||||
|
||||
details = append(details, d)
|
||||
}
|
||||
|
||||
return details
|
||||
}
|
||||
|
||||
// PostSearchPackages searches for packages and returns detailed
|
||||
// information about the matches.
|
||||
func (h *apiHandlers) PostSearchPackages(ctx echo.Context) error {
|
||||
var request SearchPackagesRequest
|
||||
err := ctx.Bind(&request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Search for the listed packages
|
||||
// Any errors returned are suitable as a response
|
||||
packages, err := request.Search(h.server.distros, h.server.repos, h.server.workers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK,
|
||||
SearchPackagesResponse{
|
||||
Packages: packageListToPackageDetails(packages),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
85
internal/cloudapi/v2/search.go
Normal file
85
internal/cloudapi/v2/search.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package v2
|
||||
|
||||
// SearchPackagesRequest methods
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/osbuild/images/pkg/distrofactory"
|
||||
"github.com/osbuild/images/pkg/reporegistry"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/worker"
|
||||
)
|
||||
|
||||
func (request *SearchPackagesRequest) Search(df *distrofactory.Factory, rr *reporegistry.RepoRegistry, workers *worker.Server) (rpmmd.PackageList, error) {
|
||||
distro := df.GetDistro(request.Distribution)
|
||||
if distro == nil {
|
||||
return nil, HTTPError(ErrorUnsupportedDistribution)
|
||||
}
|
||||
distroArch, err := distro.GetArch(request.Architecture)
|
||||
if err != nil {
|
||||
return nil, HTTPErrorWithInternal(ErrorUnsupportedArchitecture, err)
|
||||
}
|
||||
|
||||
var repos []rpmmd.RepoConfig
|
||||
if request.Repositories != nil {
|
||||
repos, err = convertRepos(*request.Repositories, []Repository{}, []string{})
|
||||
if err != nil {
|
||||
// Error comes from genRepoConfig and is already an HTTPError
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
repos, err = rr.ReposByArchName(request.Distribution, distroArch.Name(), false)
|
||||
if err != nil {
|
||||
return nil, HTTPErrorWithInternal(ErrorInvalidRepository, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send the search request to the worker
|
||||
searchJobID, err := workers.EnqueueSearchPackages(&worker.SearchPackagesJob{
|
||||
Packages: request.Packages,
|
||||
Repositories: repos,
|
||||
ModulePlatformID: distro.ModulePlatformID(),
|
||||
Arch: distroArch.Name(),
|
||||
Releasever: distro.Releasever(),
|
||||
}, "")
|
||||
if err != nil {
|
||||
return nil, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
|
||||
}
|
||||
|
||||
// Limit how long a search can take
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*depsolveTimeoutMin)
|
||||
defer cancel()
|
||||
|
||||
// Wait until search job is finished, fails, or is canceled
|
||||
var result worker.SearchPackagesJobResult
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
info, err := workers.SearchPackagesJobInfo(searchJobID, &result)
|
||||
if err != nil {
|
||||
return nil, HTTPErrorWithInternal(ErrorFailedToDepsolve, err)
|
||||
}
|
||||
if result.JobError != nil {
|
||||
return nil, HTTPErrorWithInternal(ErrorFailedToDepsolve, err)
|
||||
}
|
||||
if info.JobStatus != nil {
|
||||
if info.JobStatus.Canceled {
|
||||
return nil, HTTPErrorWithInternal(ErrorFailedToDepsolve, err)
|
||||
}
|
||||
|
||||
if !info.JobStatus.Finished.IsZero() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, HTTPErrorWithInternal(ErrorFailedToDepsolve, fmt.Errorf("Search job %q timed out", searchJobID))
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return result.Packages, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue