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

Expose the copy package operation through REST API #1258

Merged
merged 3 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 17 additions & 5 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -38,7 +39,7 @@ func createTestConfig() *os.File {
return nil
}
jsonString, err := json.Marshal(gin.H{
"architectures": []string{},
"architectures": []string{},
"enableMetricsEndpoint": true,
})
if err != nil {
Expand All @@ -48,10 +49,12 @@ func createTestConfig() *os.File {
return file
}

func (s *ApiSuite) SetUpSuite(c *C) {
func (s *ApiSuite) setupContext() error {
aptly.Version = "testVersion"
file := createTestConfig()
c.Assert(file, NotNil)
if nil == file {
return fmt.Errorf("unable to create the test configuration file")
}
s.configFile = file

flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
Expand All @@ -62,10 +65,19 @@ func (s *ApiSuite) SetUpSuite(c *C) {
s.flags = flags

context, err := ctx.NewContext(s.flags)
c.Assert(err, IsNil)
if nil != err {
return err
}

s.context = context
s.router = Router(context)

return nil
}

func (s *ApiSuite) SetUpSuite(c *C) {
err := s.setupContext()
c.Assert(err, IsNil)
}

func (s *ApiSuite) TearDownSuite(c *C) {
Expand Down Expand Up @@ -98,7 +110,7 @@ func (s *ApiSuite) TestGetVersion(c *C) {
response, err := s.HTTPRequest("GET", "/api/version", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, "{\"Version\":\"" + aptly.Version + "\"}")
c.Check(response.Body.String(), Matches, "{\"Version\":\""+aptly.Version+"\"}")
}

func (s *ApiSuite) TestGetReadiness(c *C) {
Expand Down
140 changes: 140 additions & 0 deletions api/repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"text/template"

Expand Down Expand Up @@ -414,6 +415,145 @@ func apiReposPackageFromDir(c *gin.Context) {
})
}

// POST /repos/:name/copy/:src/:file
func apiReposCopyPackage(c *gin.Context) {
dstRepoName := c.Params.ByName("name")
srcRepoName := c.Params.ByName("src")

jsonBody := struct {
WithDeps bool `json:"with-deps,omitempty"`
DryRun bool `json:"dry-run,omitempty"`
}{
WithDeps: false,
DryRun: false,
}

err := c.Bind(&jsonBody)
if err != nil {
return
}

collectionFactory := context.NewCollectionFactory()
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
if err != nil {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err))
return
}

err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
if err != nil {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err))
return
}

var (
srcRefList *deb.PackageRefList
srcRepo *deb.LocalRepo
)

srcRepo, err = collectionFactory.LocalRepoCollection().ByName(srcRepoName)
if err != nil {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err))
return
}

if srcRepo.UUID == dstRepo.UUID {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest and source are identical"))
return
}

err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
if err != nil {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err))
return
}

srcRefList = srcRepo.RefList()
taskName := fmt.Sprintf("Copy packages from repo %s to repo %s", srcRepoName, dstRepoName)
resources := []string{string(dstRepo.Key()), string(srcRepo.Key())}

maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
reporter := &aptly.RecordingResultReporter{
Warnings: []string{},
AddedLines: []string{},
RemovedLines: []string{},
}

dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err)

}

srcList, err := deb.NewPackageListFromRefList(srcRefList, collectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
}

srcList.PrepareIndex()

var architecturesList []string

if jsonBody.WithDeps {
dstList.PrepareIndex()

// Calculate architectures
if len(context.ArchitecturesList()) > 0 {
architecturesList = context.ArchitecturesList()
} else {
architecturesList = dstList.Architectures(false)
}

sort.Strings(architecturesList)

if len(architecturesList) == 0 {
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to determine list of architectures, please specify explicitly")
}
}

// srcList.Filter|FilterWithProgress only accept query list
queries := make([]deb.PackageQuery, 1)
queries[0], err = query.Parse(c.Params.ByName("file"))
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to parse query: %s", err)
}

toProcess, err := srcList.FilterWithProgress(queries, jsonBody.WithDeps, dstList, context.DependencyOptions(), architecturesList, context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("filter error: %s", err)
}

err = toProcess.ForEach(func(p *deb.Package) error {
err = dstList.Add(p)
if err != nil {
return err
}

name := fmt.Sprintf("added %s-%s(%s)", p.Name, p.Version, p.Architecture)
reporter.AddedLines = append(reporter.AddedLines, name)
return nil
})
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("error processing dest add: %s", err)
}

if jsonBody.DryRun {
reporter.Warning("Changes not saved, as dry run has been requested")
} else {
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))

err = collectionFactory.LocalRepoCollection().Update(dstRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
}

return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{
"Report": reporter,
}}, nil
})
}

// POST /repos/:name/include/:dir/:file
func apiReposIncludePackageFromFile(c *gin.Context) {
// redirect all work to dir method
Expand Down
1 change: 1 addition & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func Router(c *ctx.AptlyContext) http.Handler {

api.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
api.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
api.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage)

api.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
api.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)
Expand Down
30 changes: 30 additions & 0 deletions system/t12_api/repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,33 @@ def check(self):
self.check_not_in(b"Warnings: ", resp.content)

self.check_equal(self.get("/api/repos/" + repo_name + "/packages?maximumVersion=1").json(), ['Pi386 libboost-program-options-dev 1.62.0.1 7760e62f99c551cb'])


class ReposAPITestCopyPackage(APITest):
"""
POST /api/repos/:name/copy/:src/:file
"""
def check(self):
pkg_name = "libboost-program-options-dev_1.49.0.1_i386"

# Creating origin repo
repo1_name = self.random_name()
self.check_equal(self.post("/api/repos", json={"Name": repo1_name, "Comment": "origin repo"}).status_code, 201)

# Uploading test package
d = self.random_name()
self.check_equal(self.upload(f"/api/files/{d}", f"{pkg_name}.deb").status_code, 200)
resp = self.post_task(f"/api/repos/{repo1_name}/file/{d}")
self.check_equal(resp.json()['State'], TASK_SUCCEEDED)

# Creating target repo
repo2_name = self.random_name()
self.check_equal(self.post("/api/repos", json={"Name": repo2_name, "Comment": "target repo"}).status_code, 201)

# Copy the package
resp = self.post_task(f"/api/repos/{repo2_name}/copy/{repo1_name}/{pkg_name}")
self.check_equal(resp.status_code, 200)
self.check_equal(resp.json()['State'], TASK_SUCCEEDED)

self.check_equal(self.get(f"/api/repos/{repo2_name}/packages").json(),
['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378'])
Loading