mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-02-15 09:01:30 +00:00
In current aptly, each repository and snapshot has its own reflist in the database. This brings a few problems with it: - Given a sufficiently large repositories and snapshots, these lists can get enormous, reaching >1MB. This is a problem for LevelDB's overall performance, as it tends to prefer values around the confiruged block size (defaults to just 4KiB). - When you take these large repositories and snapshot them, you have a full, new copy of the reflist, even if only a few packages changed. This means that having a lot of snapshots with a few changes causes the database to basically be full of largely duplicate reflists. - All the duplication also means that many of the same refs are being loaded repeatedly, which can cause some slowdown but, more notably, eats up huge amounts of memory. - Adding on more and more new repositories and snapshots will cause the time and memory spent on things like cleanup and publishing to grow roughly linearly. At the core, there are two problems here: - Reflists get very big because there are just a lot of packages. - Different reflists can tend to duplicate much of the same contents. *Split reflists* aim at solving this by separating reflists into 64 *buckets*. Package refs are sorted into individual buckets according to the following system: - Take the first 3 letters of the package name, after dropping a `lib` prefix. (Using only the first 3 letters will cause packages with similar prefixes to end up in the same bucket, under the assumption that packages with similar names tend to be updated together.) - Take the 64-bit xxhash of these letters. (xxhash was chosen because it relatively good distribution across the individual bits, which is important for the next step.) - Use the first 6 bits of the hash (range [0:63]) as an index into the buckets. Once refs are placed in buckets, a sha256 digest of all the refs in the bucket is taken. These buckets are then stored in the database, split into roughly block-sized segments, and all the repositories and snapshots simply store an array of bucket digests. This approach means that *repositories and snapshots can share their reflist buckets*. If a snapshot is taken of a repository, it will have the same contents, so its split reflist will point to the same buckets as the base repository, and only one copy of each bucket is stored in the database. When some packages in the repository change, only the buckets containing those packages will be modified; all the other buckets will remain unchanged, and thus their contents will still be shared. Later on, when these reflists are loaded, each bucket is only loaded once, short-cutting loaded many megabytes of data. In effect, split reflists are essentially copy-on-write, with only the changed buckets stored individually. Changing the disk format means that a migration needs to take place, so that task is moved into the database cleanup step, which will migrate reflists over to split reflists, as well as delete any unused reflist buckets. All the reflist tests are also changed to additionally test out split reflists; although the internal logic is all shared (since buckets are, themselves, just normal reflists), some special additions are needed to have native versions of the various reflist helper methods. In our tests, we've observed the following improvements: - Memory usage during publish and database cleanup, with `GOMEMLIMIT=2GiB`, goes down from ~3.2GiB (larger than the memory limit!) to ~0.7GiB, a decrease of ~4.5x. - Database size decreases from 1.3GB to 367MB. *In my local tests*, publish times had also decreased down to mere seconds but the same effect wasn't observed on the server, with the times staying around the same. My suspicions are that this is due to I/O performance: my local system is an M1 MBP, which almost certainly has much faster disk speeds than our DigitalOcean block volumes. Split reflists include a side effect of requiring more random accesses from reading all the buckets by their keys, so if your random I/O performance is slower, it might cancel out the benefits. That being said, even in that case, the memory usage and database size advantages still persist. Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
1058 lines
40 KiB
Go
1058 lines
40 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/aptly-dev/aptly/aptly"
|
|
"github.com/aptly-dev/aptly/deb"
|
|
"github.com/aptly-dev/aptly/pgp"
|
|
"github.com/aptly-dev/aptly/task"
|
|
"github.com/aptly-dev/aptly/utils"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type signingParams struct {
|
|
// Don't sign published repository
|
|
Skip bool ` json:"Skip" example:"false"`
|
|
// GPG key ID to use when signing the release, if not specified default key is used
|
|
GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"`
|
|
// GPG keyring to use (instead of default)
|
|
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
|
|
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
|
|
SecretKeyring string ` json:"SecretKeyring" example:""`
|
|
// GPG passphrase to unlock private key (possibly insecure)
|
|
Passphrase string ` json:"Passphrase" example:"verysecure"`
|
|
// GPG passphrase file to unlock private key (possibly insecure)
|
|
PassphraseFile string ` json:"PassphraseFile" example:"/etc/aptly.passphrase"`
|
|
}
|
|
|
|
type sourceParams struct {
|
|
// Name of the component
|
|
Component string `binding:"required" json:"Component" example:"main"`
|
|
// Name of the local repository/snapshot
|
|
Name string `binding:"required" json:"Name" example:"snap1"`
|
|
}
|
|
|
|
func getSigner(options *signingParams) (pgp.Signer, error) {
|
|
if options.Skip {
|
|
return nil, nil
|
|
}
|
|
|
|
signer := context.GetSigner()
|
|
signer.SetKey(options.GpgKey)
|
|
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
|
|
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
|
|
|
|
// If Batch is false, GPG will ask for passphrase on stdin, which would block the api process
|
|
signer.SetBatch(true)
|
|
|
|
err := signer.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return signer, nil
|
|
}
|
|
|
|
// Replace '_' with '/' and double '__' with single '_', SanitizePath
|
|
func slashEscape(path string) string {
|
|
result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1)
|
|
result = utils.SanitizePath(result)
|
|
if result == "" {
|
|
result = "."
|
|
}
|
|
return result
|
|
}
|
|
|
|
// @Summary List Published Repositories
|
|
// @Description **Get list of published repositories**
|
|
// @Description
|
|
// @Description Return list of published repositories including detailed information.
|
|
// @Description
|
|
// @Description See also: `aptly publish list`
|
|
// @Tags Publish
|
|
// @Produce json
|
|
// @Success 200 {array} deb.PublishedRepo
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish [get]
|
|
func apiPublishList(c *gin.Context) {
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
repos := make([]*deb.PublishedRepo, 0, collection.Len())
|
|
|
|
err := collection.ForEach(func(repo *deb.PublishedRepo) error {
|
|
err := collection.LoadShallow(repo, collectionFactory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repos = append(repos, repo)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, repos)
|
|
}
|
|
|
|
// @Summary Show Published Repository
|
|
// @Description **Get published repository information**
|
|
// @Description
|
|
// @Description Show detailed information of a published repository.
|
|
// @Description
|
|
// @Description See also: `aptly publish show`
|
|
// @Tags Publish
|
|
// @Produce json
|
|
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambigious in URLs"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Success 200 {object} deb.PublishedRepo
|
|
// @Failure 404 {object} Error "Published repository not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution} [get]
|
|
func apiPublishShow(c *gin.Context) {
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to show: %s", err))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, published)
|
|
}
|
|
|
|
type publishedRepoCreateParams struct {
|
|
// 'local' for local repositories and 'snapshot' for snapshots
|
|
SourceKind string `binding:"required" json:"SourceKind" example:"snapshot"`
|
|
// List of 'Component/Name' objects, 'Name' is either local repository or snapshot name
|
|
Sources []sourceParams `binding:"required" json:"Sources"`
|
|
// Distribution name, if missing Aptly would try to guess from sources
|
|
Distribution string ` json:"Distribution" example:"bookworm"`
|
|
// Value of Label: field in published repository stanza
|
|
Label string ` json:"Label" example:""`
|
|
// Value of Origin: field in published repository stanza
|
|
Origin string ` json:"Origin" example:""`
|
|
// when publishing, overwrite files in pool/ directory without notice
|
|
ForceOverwrite bool ` json:"ForceOverwrite" example:"false"`
|
|
// Override list of published architectures
|
|
Architectures []string ` json:"Architectures" example:"amd64,armhf"`
|
|
// GPG options
|
|
Signing signingParams ` json:"Signing"`
|
|
// Setting to yes indicates to the package manager to not install or upgrade packages from the repository without user consent
|
|
NotAutomatic string ` json:"NotAutomatic" example:""`
|
|
// setting to yes excludes upgrades from the NotAutomic setting
|
|
ButAutomaticUpgrades string ` json:"ButAutomaticUpgrades" example:""`
|
|
// Don't generate contents indexes
|
|
SkipContents *bool ` json:"SkipContents" example:"false"`
|
|
// Don't remove unreferenced files in prefix/component
|
|
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
|
// Skip bz2 compression for index files
|
|
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
|
// Provide index files by hash
|
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
|
// Enable multiple packages with the same filename in different distributions
|
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
|
}
|
|
|
|
// @Summary Create Published Repository
|
|
// @Description **Publish a local repository or snapshot**
|
|
// @Description
|
|
// @Description Create a published repository.
|
|
// @Description
|
|
// @Description The prefix may contain a storage specifier, e.g. `s3:packages/`, or it may also be empty to publish to the root directory.
|
|
// @Description
|
|
// @Description **Example:**
|
|
// @Description ```
|
|
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos
|
|
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Label":"","Origin":"","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
|
|
// @Description ```
|
|
// @Description
|
|
// @Description See also: `aptly publish create`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Consume json
|
|
// @Param request body publishedRepoCreateParams true "Parameters"
|
|
// @Produce json
|
|
// @Success 201 {object} deb.PublishedRepo
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Source not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix} [post]
|
|
func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|
var (
|
|
b publishedRepoCreateParams
|
|
components []string
|
|
names []string
|
|
sources []interface{}
|
|
resources []string
|
|
)
|
|
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
b.Distribution = utils.SanitizePath(b.Distribution)
|
|
|
|
var archs []string
|
|
for _, arch := range b.Architectures {
|
|
archs = append(archs, utils.SanitizePath(arch))
|
|
}
|
|
b.Architectures = archs
|
|
|
|
signer, err := getSigner(&b.Signing)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
|
return
|
|
}
|
|
|
|
if len(b.Sources) == 0 {
|
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to publish: sources are empty"))
|
|
return
|
|
}
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
|
|
if b.SourceKind == deb.SourceSnapshot {
|
|
var snapshot *deb.Snapshot
|
|
|
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
|
|
for _, source := range b.Sources {
|
|
components = append(components, source.Component)
|
|
names = append(names, source.Name)
|
|
|
|
snapshot, err = snapshotCollection.ByName(source.Name)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
|
|
resources = append(resources, string(snapshot.ResourceKey()))
|
|
sources = append(sources, snapshot)
|
|
}
|
|
} else if b.SourceKind == deb.SourceLocalRepo {
|
|
var localRepo *deb.LocalRepo
|
|
|
|
localCollection := collectionFactory.LocalRepoCollection()
|
|
|
|
for _, source := range b.Sources {
|
|
components = append(components, source.Component)
|
|
names = append(names, source.Name)
|
|
|
|
localRepo, err = localCollection.ByName(source.Name)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
|
|
resources = append(resources, string(localRepo.Key()))
|
|
sources = append(sources, localRepo)
|
|
}
|
|
} else {
|
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unknown SourceKind"))
|
|
return
|
|
}
|
|
|
|
multiDist := false
|
|
if b.MultiDist != nil {
|
|
multiDist = *b.MultiDist
|
|
}
|
|
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"",
|
|
b.SourceKind, param, b.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`))
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
|
taskDetail := task.PublishDetail{
|
|
Detail: detail,
|
|
}
|
|
publishOutput := &task.PublishOutput{
|
|
Progress: out,
|
|
PublishDetail: taskDetail,
|
|
}
|
|
|
|
for _, source := range sources {
|
|
switch s := source.(type) {
|
|
case *deb.Snapshot:
|
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
err = snapshotCollection.LoadComplete(s, collectionFactory.RefListCollection())
|
|
case *deb.LocalRepo:
|
|
localCollection := collectionFactory.LocalRepoCollection()
|
|
err = localCollection.LoadComplete(s, collectionFactory.RefListCollection())
|
|
default:
|
|
err = fmt.Errorf("unexpected type for source: %T", source)
|
|
}
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
|
}
|
|
}
|
|
|
|
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, multiDist)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
|
}
|
|
|
|
resources = append(resources, string(published.Key()))
|
|
|
|
if b.Origin != "" {
|
|
published.Origin = b.Origin
|
|
}
|
|
if b.NotAutomatic != "" {
|
|
published.NotAutomatic = b.NotAutomatic
|
|
}
|
|
if b.ButAutomaticUpgrades != "" {
|
|
published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
|
|
}
|
|
published.Label = b.Label
|
|
|
|
published.SkipContents = context.Config().SkipContentsPublishing
|
|
if b.SkipContents != nil {
|
|
published.SkipContents = *b.SkipContents
|
|
}
|
|
|
|
published.SkipBz2 = context.Config().SkipBz2Publishing
|
|
if b.SkipBz2 != nil {
|
|
published.SkipBz2 = *b.SkipBz2
|
|
}
|
|
|
|
if b.AcquireByHash != nil {
|
|
published.AcquireByHash = *b.AcquireByHash
|
|
}
|
|
|
|
duplicate := collection.CheckDuplicate(published)
|
|
if duplicate != nil {
|
|
collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
|
}
|
|
|
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath())
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
|
}
|
|
|
|
err = collection.Add(published, collectionFactory.RefListCollection())
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: published}, nil
|
|
})
|
|
}
|
|
|
|
type publishedRepoUpdateSwitchParams struct {
|
|
// when publishing, overwrite files in pool/ directory without notice
|
|
ForceOverwrite bool ` json:"ForceOverwrite" example:"false"`
|
|
// GPG options
|
|
Signing signingParams ` json:"Signing"`
|
|
// Don't generate contents indexes
|
|
SkipContents *bool ` json:"SkipContents" example:"false"`
|
|
// Skip bz2 compression for index files
|
|
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
|
// Don't remove unreferenced files in prefix/component
|
|
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
|
// only when updating published snapshots, list of objects 'Component/Name'
|
|
Snapshots []sourceParams ` json:"Snapshots"`
|
|
// Provide index files by hash
|
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
|
// Enable multiple packages with the same filename in different distributions
|
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
|
}
|
|
|
|
// @Summary Update Published Repository
|
|
// @Description **Update a published repository**
|
|
// @Description
|
|
// @Description Update a published local repository or switch snapshot.
|
|
// @Description
|
|
// @Description For published local repositories:
|
|
// @Description * update to match local repository contents
|
|
// @Description
|
|
// @Description For published snapshots:
|
|
// @Description * switch components to new snapshot
|
|
// @Description
|
|
// @Description See also: `aptly publish update` / `aptly publish switch`
|
|
// @Tags Publish
|
|
// @Produce json
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Consume json
|
|
// @Param request body publishedRepoUpdateSwitchParams true "Parameters"
|
|
// @Produce json
|
|
// @Success 200 {object} deb.PublishedRepo
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository or source not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution} [put]
|
|
func apiPublishUpdateSwitch(c *gin.Context) {
|
|
var b publishedRepoUpdateSwitchParams
|
|
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
signer, err := getSigner(&b.Signing)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
|
return
|
|
}
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
if published.SourceKind == deb.SourceLocalRepo {
|
|
if len(b.Snapshots) > 0 {
|
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
|
return
|
|
}
|
|
} else if published.SourceKind == deb.SourceSnapshot {
|
|
for _, snapshotInfo := range b.Snapshots {
|
|
_, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
|
if err2 != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
|
|
return
|
|
}
|
|
|
|
if b.SkipContents != nil {
|
|
published.SkipContents = *b.SkipContents
|
|
}
|
|
|
|
if b.SkipBz2 != nil {
|
|
published.SkipBz2 = *b.SkipBz2
|
|
}
|
|
|
|
if b.AcquireByHash != nil {
|
|
published.AcquireByHash = *b.AcquireByHash
|
|
}
|
|
|
|
if b.MultiDist != nil {
|
|
published.MultiDist = *b.MultiDist
|
|
}
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err = collection.LoadComplete(published, collectionFactory, collectionFactory.RefListCollection())
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err)
|
|
}
|
|
|
|
revision := published.ObtainRevision()
|
|
sources := revision.Sources
|
|
|
|
if published.SourceKind == deb.SourceSnapshot {
|
|
for _, snapshotInfo := range b.Snapshots {
|
|
component := snapshotInfo.Component
|
|
name := snapshotInfo.Name
|
|
sources[component] = name
|
|
}
|
|
}
|
|
|
|
result, err := published.Update(collectionFactory, out)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err)
|
|
}
|
|
|
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err)
|
|
}
|
|
|
|
err = collection.Update(published, collectionFactory.RefListCollection())
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
|
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
|
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
|
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
|
}
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
|
})
|
|
}
|
|
|
|
// @Summary Delete Published Repository
|
|
// @Description **Delete a published repository**
|
|
// @Description
|
|
// @Description Delete a distribution of a published repository and remove associated files.
|
|
// @Description
|
|
// @Description If no other published repositories share the same prefix, all files inside the prefix will be removed.
|
|
// @Description
|
|
// @Description See also: `aptly publish drop`
|
|
// @Tags Publish
|
|
// @Produce json
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param force query int true "force: 1 to enable"
|
|
// @Param skipCleanup query int true "skipCleanup: 1 to enable"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Success 200
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution} [delete]
|
|
func apiPublishDrop(c *gin.Context) {
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
force := c.Request.URL.Query().Get("force") == "1"
|
|
skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1"
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to drop: %s", err))
|
|
return
|
|
}
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Delete published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err := collection.Remove(context, storage, prefix, distribution,
|
|
collectionFactory, out, force, skipCleanup)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil
|
|
})
|
|
}
|
|
|
|
// @Summary Add Source Component
|
|
// @Description **Add a source component to a published repo**
|
|
// @Description
|
|
// @Description Add a component of a snapshot or local repository to be published.
|
|
// @Description
|
|
// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
|
|
// @Description
|
|
// @Description See also: `aptly publish source add`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Consume json
|
|
// @Param request body sourceParams true "Parameters"
|
|
// @Produce json
|
|
// @Success 201
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/sources [post]
|
|
func apiPublishAddSource(c *gin.Context) {
|
|
var b sourceParams
|
|
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to create: %s", err))
|
|
return
|
|
}
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
revision := published.ObtainRevision()
|
|
sources := revision.Sources
|
|
|
|
component := b.Component
|
|
name := b.Name
|
|
|
|
_, exists := sources[component]
|
|
if exists {
|
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to create: Component '%s' already exists", component))
|
|
return
|
|
}
|
|
|
|
sources[component] = name
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: gin.H{}}, nil
|
|
})
|
|
}
|
|
|
|
// @Summary List Pending Changes
|
|
// @Description **List source component changes to be applied**
|
|
// @Description
|
|
// @Description Return added, removed or changed components of snapshots or local repository to be published.
|
|
// @Description
|
|
// @Description The changes will be applied by a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
|
|
// @Description
|
|
// @Description See also: `aptly publish source list`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Produce json
|
|
// @Success 200 {array} []deb.SourceEntry
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository pending changes not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/sources [get]
|
|
func apiPublishListChanges(c *gin.Context) {
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to show: %s", err))
|
|
return
|
|
}
|
|
|
|
revision := published.Revision
|
|
if revision == nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: No source changes exist"))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, revision.SourceList())
|
|
}
|
|
|
|
// @Summary Replace Source Components
|
|
// @Description **Replace the source components of a published repository**
|
|
// @Description
|
|
// @Description Sets the components of snapshots or local repositories to be published. Existing Sourced will be replaced.
|
|
// @Description
|
|
// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
|
|
// @Description
|
|
// @Description See also: `aptly publish source replace`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Consume json
|
|
// @Param request body []sourceParams true "Parameters"
|
|
// @Produce json
|
|
// @Success 200
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/sources [put]
|
|
func apiPublishSetSources(c *gin.Context) {
|
|
var b []sourceParams
|
|
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
revision := published.ObtainRevision()
|
|
sources := make(map[string]string, len(b))
|
|
revision.Sources = sources
|
|
|
|
for _, source := range b {
|
|
component := source.Component
|
|
name := source.Name
|
|
sources[component] = name
|
|
}
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: revision.SourceList()}, nil
|
|
})
|
|
}
|
|
|
|
// @Summary Discard Pending Changes
|
|
// @Description **Discard pending source component changes of a published repository**
|
|
// @Description
|
|
// @Description Remove all pending changes what would be applied with a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
|
|
// @Description
|
|
// @Description See also: `aptly publish source drop`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Produce json
|
|
// @Success 200
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/sources [delete]
|
|
func apiPublishDropChanges(c *gin.Context) {
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err))
|
|
return
|
|
}
|
|
|
|
published.DropRevision()
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil
|
|
})
|
|
}
|
|
|
|
// @Summary Update Source Component
|
|
// @Description **Update the source component of a published repository**
|
|
// @Description
|
|
// @Description Update a component of a snapshot or local repository to be published.
|
|
// @Description
|
|
// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
|
|
// @Description
|
|
// @Description See also: `aptly publish source update`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param component path string true "component name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Consume json
|
|
// @Param request body sourceParams true "Parameters"
|
|
// @Produce json
|
|
// @Success 200
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository/component not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/sources/{component} [put]
|
|
func apiPublishUpdateSource(c *gin.Context) {
|
|
var b sourceParams
|
|
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
component := slashEscape(c.Params.ByName("component"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
revision := published.ObtainRevision()
|
|
sources := revision.Sources
|
|
|
|
_, exists := sources[component]
|
|
if !exists {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: Component '%s' does not exist", component))
|
|
return
|
|
}
|
|
|
|
b.Component = component
|
|
b.Name = revision.Sources[component]
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
if b.Component != component {
|
|
delete(sources, component)
|
|
}
|
|
|
|
component = b.Component
|
|
name := b.Name
|
|
sources[component] = name
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil
|
|
})
|
|
}
|
|
|
|
// @Summary Remove Source Component
|
|
// @Description **Remove a source component from a published repo**
|
|
// @Description
|
|
// @Description Remove a source component (snapshot / local repo) from a published repository.
|
|
// @Description
|
|
// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`).
|
|
// @Description
|
|
// @Description See also: `aptly publish source remove`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param component path string true "component name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Produce json
|
|
// @Success 200
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/sources/{component} [delete]
|
|
func apiPublishRemoveSource(c *gin.Context) {
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
component := slashEscape(c.Params.ByName("component"))
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err))
|
|
return
|
|
}
|
|
|
|
revision := published.ObtainRevision()
|
|
sources := revision.Sources
|
|
|
|
_, exists := sources[component]
|
|
if !exists {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: Component '%s' does not exist", component))
|
|
return
|
|
}
|
|
|
|
delete(sources, component)
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil
|
|
})
|
|
}
|
|
|
|
type publishedRepoUpdateParams struct {
|
|
// when publishing, overwrite files in pool/ directory without notice
|
|
ForceOverwrite bool ` json:"ForceOverwrite" example:"false"`
|
|
// GPG options
|
|
Signing signingParams ` json:"Signing"`
|
|
// Don't generate contents indexes
|
|
SkipContents *bool ` json:"SkipContents" example:"false"`
|
|
// Skip bz2 compression for index files
|
|
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
|
// Don't remove unreferenced files in prefix/component
|
|
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
|
// Provide index files by hash
|
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
|
// Enable multiple packages with the same filename in different distributions
|
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
|
}
|
|
|
|
// @Summary Update Published Repository
|
|
// @Description **Update a published repository**
|
|
// @Description
|
|
// @Description Publish pending source component changes which were added with `Add/Remove/Replace Source Components`
|
|
// @Description
|
|
// @Description See also: `aptly publish update`
|
|
// @Tags Publish
|
|
// @Param prefix path string true "publishing prefix"
|
|
// @Param distribution path string true "distribution name"
|
|
// @Param _async query bool false "Run in background and return task object"
|
|
// @Consume json
|
|
// @Param request body publishedRepoUpdateParams true "Parameters"
|
|
// @Produce json
|
|
// @Success 200 {object} deb.PublishedRepo
|
|
// @Failure 400 {object} Error "Bad Request"
|
|
// @Failure 404 {object} Error "Published repository/component not found"
|
|
// @Failure 500 {object} Error "Internal Error"
|
|
// @Router /api/publish/{prefix}/{distribution}/update [post]
|
|
func apiPublishUpdate(c *gin.Context) {
|
|
var b publishedRepoUpdateParams
|
|
|
|
param := slashEscape(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
signer, err := getSigner(&b.Signing)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
|
return
|
|
}
|
|
|
|
collectionFactory := context.NewCollectionFactory()
|
|
collection := collectionFactory.PublishedRepoCollection()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.LoadComplete(published, collectionFactory)
|
|
if err != nil {
|
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
if b.SkipContents != nil {
|
|
published.SkipContents = *b.SkipContents
|
|
}
|
|
|
|
if b.SkipBz2 != nil {
|
|
published.SkipBz2 = *b.SkipBz2
|
|
}
|
|
|
|
if b.AcquireByHash != nil {
|
|
published.AcquireByHash = *b.AcquireByHash
|
|
}
|
|
|
|
if b.MultiDist != nil {
|
|
published.MultiDist = *b.MultiDist
|
|
}
|
|
|
|
resources := []string{string(published.Key())}
|
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
|
result, err := published.Update(collectionFactory, out)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
|
}
|
|
|
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
|
}
|
|
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
|
}
|
|
|
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
|
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
|
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
|
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
|
if err != nil {
|
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
|
}
|
|
}
|
|
|
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
|
})
|
|
}
|