diff --git a/api/router.go b/api/router.go index 3c6badb0..67be831e 100644 --- a/api/router.go +++ b/api/router.go @@ -29,6 +29,12 @@ func Router(c *ctx.AptlyContext) http.Handler { root.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile) root.POST("/repos/:name/file/:dir", apiReposPackageFromDir) + + root.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository) + } + + { + root.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror) } { @@ -39,5 +45,14 @@ func Router(c *ctx.AptlyContext) http.Handler { root.DELETE("/files/:dir/:name", apiFilesDeleteFile) } + { + root.GET("/snapshots", apiSnapshotsList) + root.POST("/snapshots", apiSnapshotsCreateEmpty) + root.PUT("/snapshots/:name", apiSnapshotsRename) + root.GET("/snapshots/:name", apiSnapshotsShow) + root.DELETE("/snapshots/:name", apiSnapshotsDrop) + root.POST("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff) + } + return router } diff --git a/api/snapshot.go b/api/snapshot.go new file mode 100644 index 00000000..4cd5c0ce --- /dev/null +++ b/api/snapshot.go @@ -0,0 +1,320 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/smira/aptly/deb" +) + +// GET /api/snapshots +func apiSnapshotsList(c *gin.Context) { + SortMethodString := c.Request.URL.Query().Get("sort") + + collection := context.CollectionFactory().SnapshotCollection() + collection.Sort(SortMethodString) + + result := []*deb.Snapshot{} + collection.ForEach(func(snapshot *deb.Snapshot) error { + result = append(result, snapshot) + return nil + }) + + c.JSON(200, result) +} + +// POST /api/mirros/:name/snapshots/:snapname +func apiSnapshotsCreateFromMirror(c *gin.Context) { + var ( + err error + repo *deb.RemoteRepo + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().RemoteRepoCollection() + collection.Lock() + defer collection.Unlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + repo, err = collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = repo.CheckLock() + if err != nil { + c.Fail(409, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + snapshot, err = deb.NewSnapshotFromRepository(b.Name, repo) + if err != nil { + c.Fail(500, err) + return + } + + err = snapshotCollection.Add(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(201, snapshot) +} + +// POST /api/repos/:name/snapshots/:snapname +func apiSnapshotsCreateEmpty(c *gin.Context) { + var ( + err error + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + } + + if !c.Bind(&b) { + return + } + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + packageList := deb.NewPackageList() + + snapshot = deb.NewSnapshotFromPackageList(b.Name, nil, packageList, "Created as empty") + + err = snapshotCollection.Add(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(201, snapshot) +} + +// POST /api/repos/:name/snapshots/:snapname +func apiSnapshotsCreateFromRepository(c *gin.Context) { + var ( + err error + repo *deb.LocalRepo + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + repo, err = collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + snapshot, err = deb.NewSnapshotFromLocalRepo(b.Name, repo) + if err != nil { + c.Fail(500, err) + return + } + + err = snapshotCollection.Add(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(201, snapshot) +} + +// PUT /api/snapshots/:name +func apiSnapshotsRename(c *gin.Context) { + var ( + err error + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().SnapshotCollection() + collection.Lock() + defer collection.Unlock() + + snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + } + + _, err = context.CollectionFactory().SnapshotCollection().ByName(b.Name) + if err == nil { + c.Fail(409, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)) + } + + snapshot.Name = b.Name + err = context.CollectionFactory().SnapshotCollection().Update(snapshot) + if err != nil { + c.Fail(403, err) + } + + c.JSON(200, snapshot) +} + +// GET /api/snapshots/:name +func apiSnapshotsShow(c *gin.Context) { + collection := context.CollectionFactory().SnapshotCollection() + collection.RLock() + defer collection.RUnlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + c.JSON(200, repo) +} + +// DELETE /api/snapshots/:name +func apiSnapshotsDrop(c *gin.Context) { + name := c.Params.ByName("name") + force := c.Request.URL.Query().Get("force") == "1" + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.RLock() + defer snapshotCollection.RUnlock() + + publishedCollection := context.CollectionFactory().PublishedRepoCollection() + publishedCollection.RLock() + defer publishedCollection.RUnlock() + + snapshot, err := snapshotCollection.ByName(name) + if err != nil { + c.Fail(404, err) + return + } + + published := publishedCollection.BySnapshot(snapshot) + + if len(published) > 0 { + for _, repo := range published { + err = publishedCollection.LoadComplete(repo, context.CollectionFactory()) + if err != nil { + c.Fail(409, err) + return + } + } + + c.Fail(409, fmt.Errorf("unable to drop: snapshot is published")) + return + } + + if !force { + snapshots := snapshotCollection.BySnapshotSource(snapshot) + if len(snapshots) > 0 { + c.Fail(409, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override")) + return + } + } + + err = context.CollectionFactory().SnapshotCollection().Drop(snapshot) + if err != nil { + c.Fail(409, err) + return + } + + c.JSON(200, gin.H{}) +} + +// GET /api/snapshots/:name +func apiSnapshotsDiff(c *gin.Context) { + onlyMatching := c.Request.URL.Query().Get("onlyMatching") == "1" + + collection := context.CollectionFactory().SnapshotCollection() + collection.RLock() + defer collection.RUnlock() + + snapshotA, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot")) + if err != nil { + c.Fail(404, err) + return + } + + err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshotA) + if err != nil { + c.Fail(500, err) + } + + err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshotB) + if err != nil { + c.Fail(500, err) + } + + // Calculate diff + diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), context.CollectionFactory().PackageCollection()) + if err != nil { + c.Fail(500, err) + } + + result := []deb.PackageDiff{} + + for _, pdiff := range diff { + if onlyMatching && (pdiff.Left == nil || pdiff.Right == nil) { + continue + } + + result = append(result, pdiff) + } + + c.JSON(200, result) +}