mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-08 22:30:41 +00:00
committed by
Benj Fassbind
parent
90932cdac5
commit
77e02bf7a3
@@ -159,6 +159,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
root.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
|
root.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
|
||||||
root.DELETE("/snapshots/:name", apiSnapshotsDrop)
|
root.DELETE("/snapshots/:name", apiSnapshotsDrop)
|
||||||
root.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
|
root.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
|
||||||
|
root.POST("/snapshots/merge", apiSnapshotsMerge)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
@@ -397,3 +398,80 @@ func apiSnapshotsSearchPackages(c *gin.Context) {
|
|||||||
|
|
||||||
showPackages(c, snapshot.RefList(), collectionFactory)
|
showPackages(c, snapshot.RefList(), collectionFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/snapshots/merge
|
||||||
|
func apiSnapshotsMerge(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
snapshot *deb.Snapshot
|
||||||
|
)
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
Destination string `binding:"required"`
|
||||||
|
Sources []string `binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bind(&body) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body.Sources) < 1 {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("At least one source snapshot is required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
latest := c.Request.URL.Query().Get("latest") == "1"
|
||||||
|
noRemove := c.Request.URL.Query().Get("no-remove") == "1"
|
||||||
|
overrideMatching := !latest && !noRemove
|
||||||
|
|
||||||
|
if noRemove && latest {
|
||||||
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("no-remove and latest are mutually exclusive"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
|
sources := make([]*deb.Snapshot, len(body.Sources))
|
||||||
|
resources := make([]string, len(sources))
|
||||||
|
for i := range body.Sources {
|
||||||
|
sources[i], err = snapshotCollection.ByName(body.Sources[i])
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = snapshotCollection.LoadComplete(sources[i])
|
||||||
|
if err != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources[i] = string(sources[i].ResourceKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeRunTaskInBackground(c, "Merge snapshot "+body.Destination, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
result := sources[0].RefList()
|
||||||
|
for i := 1; i < len(sources); i++ {
|
||||||
|
result = result.Merge(sources[i].RefList(), overrideMatching, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if latest {
|
||||||
|
result.FilterLatestRefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceDescription := make([]string, len(sources))
|
||||||
|
for i, s := range sources {
|
||||||
|
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot = deb.NewSnapshotFromRefList(body.Destination, sources, result,
|
||||||
|
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
|
||||||
|
|
||||||
|
err = collectionFactory.SnapshotCollection().Add(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -262,9 +262,9 @@ class SnapshotsAPITestDiff(APITest):
|
|||||||
self.check_equal(self.upload("/api/files/" + d,
|
self.check_equal(self.upload("/api/files/" + d,
|
||||||
"libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200)
|
"libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200)
|
||||||
|
|
||||||
self.check_equal(self.post_task("/api/repos/" + repo_name + "/file/" + d).json()['State'], TASK_SUCCEEDED)
|
self.check_equal(self.post_task("/api/repos/" + repos[-1] + "/file/" + d).json()['State'], TASK_SUCCEEDED)
|
||||||
|
|
||||||
resp = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshots[0]})
|
resp = self.post_task("/api/repos/" + repos[-1] + '/snapshots', json={'Name': snapshots[0]})
|
||||||
self.check_equal(resp.json()['State'], TASK_SUCCEEDED)
|
self.check_equal(resp.json()['State'], TASK_SUCCEEDED)
|
||||||
|
|
||||||
resp = self.post_task("/api/snapshots", json={'Name': snapshots[1]})
|
resp = self.post_task("/api/snapshots", json={'Name': snapshots[1]})
|
||||||
@@ -287,3 +287,100 @@ class SnapshotsAPITestDiff(APITest):
|
|||||||
resp = self.get("/api/snapshots/" + snapshots[1] + "/diff/" + snapshots[1])
|
resp = self.get("/api/snapshots/" + snapshots[1] + "/diff/" + snapshots[1])
|
||||||
self.check_equal(resp.status_code, 200)
|
self.check_equal(resp.status_code, 200)
|
||||||
self.check_equal(resp.json(), [])
|
self.check_equal(resp.json(), [])
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotsAPITestMerge(APITest):
|
||||||
|
"""
|
||||||
|
POST /api/snapshots, GET /api/snapshots/merge, GET /api/snapshots/:name, DELETE /api/snapshots/:name
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
sources = [
|
||||||
|
{"Description": "fun snapshot", "Name": self.random_name()}
|
||||||
|
for _ in range(2)
|
||||||
|
]
|
||||||
|
|
||||||
|
# create source snapshots
|
||||||
|
for source in sources:
|
||||||
|
resp = self.post_task("/api/snapshots", json=source)
|
||||||
|
self.check_equal(resp.json()["State"], TASK_SUCCEEDED)
|
||||||
|
|
||||||
|
# create merge snapshot
|
||||||
|
merged_name = self.random_name()
|
||||||
|
resp = self.post_task(
|
||||||
|
"/api/snapshots/merge",
|
||||||
|
json={
|
||||||
|
"Destination": merged_name,
|
||||||
|
"Sources": [source["Name"] for source in sources],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.check_equal(resp.json()["State"], TASK_SUCCEEDED)
|
||||||
|
|
||||||
|
# check merge snapshot
|
||||||
|
resp = self.get(f"/api/snapshots/{merged_name}")
|
||||||
|
self.check_equal(resp.status_code, 200)
|
||||||
|
source_list = ", ".join(f"'{source['Name']}'" for source in sources)
|
||||||
|
self.check_subset(
|
||||||
|
{
|
||||||
|
"Name": merged_name,
|
||||||
|
"Description": f"Merged from sources: {source_list}",
|
||||||
|
},
|
||||||
|
resp.json(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove merge snapshot
|
||||||
|
self.check_equal(
|
||||||
|
self.delete_task(f"/api/snapshots/{merged_name}").json()["State"], TASK_SUCCEEDED
|
||||||
|
)
|
||||||
|
|
||||||
|
# create merge snapshot without sources
|
||||||
|
merged_name = self.random_name()
|
||||||
|
resp = self.post(
|
||||||
|
"/api/snapshots/merge", json={"Destination": merged_name, "Sources": []}
|
||||||
|
)
|
||||||
|
self.check_equal(resp.status_code, 400)
|
||||||
|
self.check_equal(
|
||||||
|
resp.json()["error"], "At least one source snapshot is required"
|
||||||
|
)
|
||||||
|
self.check_equal(self.get(f"/api/snapshots/{merged_name}").status_code, 404)
|
||||||
|
|
||||||
|
# create merge snapshot with non-existing source
|
||||||
|
merged_name = self.random_name()
|
||||||
|
non_existing_source = self.random_name()
|
||||||
|
resp = self.post(
|
||||||
|
"/api/snapshots/merge",
|
||||||
|
json={"Destination": merged_name, "Sources": [non_existing_source]},
|
||||||
|
)
|
||||||
|
self.check_equal(
|
||||||
|
resp.json()["error"], f"snapshot with name {non_existing_source} not found"
|
||||||
|
)
|
||||||
|
self.check_equal(resp.status_code, 404)
|
||||||
|
|
||||||
|
self.check_equal(self.get(f"/api/snapshots/{merged_name}").status_code, 404)
|
||||||
|
|
||||||
|
# create merge snapshot with used name
|
||||||
|
merged_name = sources[0]["Name"]
|
||||||
|
resp = self.post(
|
||||||
|
"/api/snapshots/merge",
|
||||||
|
json={"Destination": merged_name, "Sources": [source["Name"] for source in sources]},
|
||||||
|
)
|
||||||
|
self.check_equal(
|
||||||
|
resp.json()["error"],
|
||||||
|
f"unable to create snapshot: snapshot with name {sources[0]['Name']} already exists",
|
||||||
|
)
|
||||||
|
self.check_equal(resp.status_code, 500)
|
||||||
|
|
||||||
|
# create merge snapshot with "latest" and "no-remove" flags (should fail)
|
||||||
|
merged_name = self.random_name()
|
||||||
|
resp = self.post(
|
||||||
|
"/api/snapshots/merge",
|
||||||
|
json={
|
||||||
|
"Destination": merged_name,
|
||||||
|
"Sources": [source["Name"] for source in sources],
|
||||||
|
},
|
||||||
|
params={"latest": "1", "no-remove": "1"},
|
||||||
|
)
|
||||||
|
self.check_equal(
|
||||||
|
resp.json()["error"], "no-remove and latest are mutually exclusive"
|
||||||
|
)
|
||||||
|
self.check_equal(resp.status_code, 400)
|
||||||
|
|||||||
Reference in New Issue
Block a user