mirror of https://github.com/aptly-dev/aptly
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)
|
|
case *deb.LocalRepo:
|
|
localCollection := collectionFactory.LocalRepoCollection()
|
|
err = localCollection.LoadComplete(s)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
})
|
|
}
|