mirror of https://github.com/aptly-dev/aptly
381 lines
9.9 KiB
Go
381 lines
9.9 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/aptly-dev/aptly/deb"
|
|
"github.com/aptly-dev/aptly/pgp"
|
|
"github.com/aptly-dev/aptly/utils"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// SigningOptions is a shared between publish API GPG options structure
|
|
type SigningOptions struct {
|
|
Skip bool
|
|
Batch bool
|
|
GpgKey string
|
|
Keyring string
|
|
SecretKeyring string
|
|
Passphrase string
|
|
PassphraseFile string
|
|
}
|
|
|
|
func getSigner(options *SigningOptions) (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)
|
|
signer.SetBatch(options.Batch)
|
|
|
|
err := signer.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return signer, nil
|
|
}
|
|
|
|
// Replace '_' with '/' and double '__' with single '_'
|
|
func parseEscapedPath(path string) string {
|
|
result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1)
|
|
if result == "" {
|
|
result = "."
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GET /publish
|
|
func apiPublishList(c *gin.Context) {
|
|
localCollection := context.CollectionFactory().LocalRepoCollection()
|
|
localCollection.RLock()
|
|
defer localCollection.RUnlock()
|
|
|
|
snapshotCollection := context.CollectionFactory().SnapshotCollection()
|
|
snapshotCollection.RLock()
|
|
defer snapshotCollection.RUnlock()
|
|
|
|
collection := context.CollectionFactory().PublishedRepoCollection()
|
|
collection.Lock()
|
|
defer collection.Unlock()
|
|
|
|
result := make([]*deb.PublishedRepo, 0, collection.Len())
|
|
|
|
err := collection.ForEach(func(repo *deb.PublishedRepo) error {
|
|
err := collection.LoadComplete(repo, context.CollectionFactory())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result = append(result, repo)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
c.AbortWithError(500, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(200, result)
|
|
}
|
|
|
|
// POST /publish/:prefix
|
|
func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|
param := parseEscapedPath(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
|
|
var b struct {
|
|
SourceKind string `binding:"required"`
|
|
Sources []struct {
|
|
Component string
|
|
Name string `binding:"required"`
|
|
} `binding:"required"`
|
|
Distribution string
|
|
Label string
|
|
Origin string
|
|
NotAutomatic string
|
|
ButAutomaticUpgrades string
|
|
ForceOverwrite bool
|
|
SkipContents *bool
|
|
Architectures []string
|
|
Signing SigningOptions
|
|
AcquireByHash *bool
|
|
}
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
signer, err := getSigner(&b.Signing)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
|
return
|
|
}
|
|
|
|
if len(b.Sources) == 0 {
|
|
c.AbortWithError(400, fmt.Errorf("unable to publish: soures are empty"))
|
|
return
|
|
}
|
|
|
|
var components []string
|
|
var sources []interface{}
|
|
|
|
if b.SourceKind == "snapshot" {
|
|
var snapshot *deb.Snapshot
|
|
|
|
snapshotCollection := context.CollectionFactory().SnapshotCollection()
|
|
snapshotCollection.Lock()
|
|
defer snapshotCollection.Unlock()
|
|
|
|
for _, source := range b.Sources {
|
|
components = append(components, source.Component)
|
|
|
|
snapshot, err = snapshotCollection.ByName(source.Name)
|
|
if err != nil {
|
|
c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
|
|
err = snapshotCollection.LoadComplete(snapshot)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
|
|
sources = append(sources, snapshot)
|
|
}
|
|
} else if b.SourceKind == deb.SourceLocalRepo {
|
|
var localRepo *deb.LocalRepo
|
|
|
|
localCollection := context.CollectionFactory().LocalRepoCollection()
|
|
localCollection.Lock()
|
|
defer localCollection.Unlock()
|
|
|
|
for _, source := range b.Sources {
|
|
components = append(components, source.Component)
|
|
|
|
localRepo, err = localCollection.ByName(source.Name)
|
|
if err != nil {
|
|
c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
|
|
err = localCollection.LoadComplete(localRepo)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
|
}
|
|
|
|
sources = append(sources, localRepo)
|
|
}
|
|
} else {
|
|
c.AbortWithError(400, fmt.Errorf("unknown SourceKind"))
|
|
return
|
|
}
|
|
|
|
collection := context.CollectionFactory().PublishedRepoCollection()
|
|
collection.Lock()
|
|
defer collection.Unlock()
|
|
|
|
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, context.CollectionFactory())
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
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
|
|
}
|
|
|
|
if b.AcquireByHash != nil {
|
|
published.AcquireByHash = *b.AcquireByHash
|
|
}
|
|
|
|
duplicate := collection.CheckDuplicate(published)
|
|
if duplicate != nil {
|
|
context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory())
|
|
c.AbortWithError(400, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate))
|
|
return
|
|
}
|
|
|
|
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.Add(published)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to save to DB: %s", err))
|
|
return
|
|
}
|
|
|
|
c.JSON(201, published)
|
|
}
|
|
|
|
// PUT /publish/:prefix/:distribution
|
|
func apiPublishUpdateSwitch(c *gin.Context) {
|
|
param := parseEscapedPath(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := c.Params.ByName("distribution")
|
|
|
|
var b struct {
|
|
ForceOverwrite bool
|
|
Signing SigningOptions
|
|
SkipContents *bool
|
|
SkipCleanup *bool
|
|
Snapshots []struct {
|
|
Component string `binding:"required"`
|
|
Name string `binding:"required"`
|
|
}
|
|
AcquireByHash *bool
|
|
}
|
|
|
|
if c.Bind(&b) != nil {
|
|
return
|
|
}
|
|
|
|
signer, err := getSigner(&b.Signing)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to initialize GPG signer: %s", err))
|
|
return
|
|
}
|
|
|
|
// published.LoadComplete would touch local repo collection
|
|
localRepoCollection := context.CollectionFactory().LocalRepoCollection()
|
|
localRepoCollection.Lock()
|
|
defer localRepoCollection.Unlock()
|
|
|
|
snapshotCollection := context.CollectionFactory().SnapshotCollection()
|
|
snapshotCollection.Lock()
|
|
defer snapshotCollection.Unlock()
|
|
|
|
collection := context.CollectionFactory().PublishedRepoCollection()
|
|
collection.Lock()
|
|
defer collection.Unlock()
|
|
|
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
|
if err != nil {
|
|
c.AbortWithError(404, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
err = collection.LoadComplete(published, context.CollectionFactory())
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
var updatedComponents []string
|
|
|
|
if published.SourceKind == deb.SourceLocalRepo {
|
|
if len(b.Snapshots) > 0 {
|
|
c.AbortWithError(400, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
|
return
|
|
}
|
|
updatedComponents = published.Components()
|
|
for _, component := range updatedComponents {
|
|
published.UpdateLocalRepo(component)
|
|
}
|
|
} else if published.SourceKind == "snapshot" {
|
|
publishedComponents := published.Components()
|
|
for _, snapshotInfo := range b.Snapshots {
|
|
if !utils.StrSliceHasItem(publishedComponents, snapshotInfo.Component) {
|
|
c.AbortWithError(404, fmt.Errorf("component %s is not in published repository", snapshotInfo.Component))
|
|
return
|
|
}
|
|
|
|
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
|
if err2 != nil {
|
|
c.AbortWithError(404, err2)
|
|
return
|
|
}
|
|
|
|
err2 = snapshotCollection.LoadComplete(snapshot)
|
|
if err2 != nil {
|
|
c.AbortWithError(500, err2)
|
|
return
|
|
}
|
|
|
|
published.UpdateSnapshot(snapshotInfo.Component, snapshot)
|
|
updatedComponents = append(updatedComponents, snapshotInfo.Component)
|
|
}
|
|
} else {
|
|
c.AbortWithError(500, fmt.Errorf("unknown published repository type"))
|
|
return
|
|
}
|
|
|
|
if b.SkipContents != nil {
|
|
published.SkipContents = *b.SkipContents
|
|
}
|
|
|
|
if b.AcquireByHash != nil {
|
|
published.AcquireByHash = *b.AcquireByHash
|
|
}
|
|
|
|
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
|
|
err = collection.Update(published)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to save to DB: %s", err))
|
|
return
|
|
}
|
|
|
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
|
err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents,
|
|
context.GetPublishedStorage(storage), context.CollectionFactory(), nil)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to update: %s", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(200, published)
|
|
}
|
|
|
|
// DELETE /publish/:prefix/:distribution
|
|
func apiPublishDrop(c *gin.Context) {
|
|
force := c.Request.URL.Query().Get("force") == "1"
|
|
skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1"
|
|
|
|
param := parseEscapedPath(c.Params.ByName("prefix"))
|
|
storage, prefix := deb.ParsePrefix(param)
|
|
distribution := c.Params.ByName("distribution")
|
|
|
|
// published.LoadComplete would touch local repo collection
|
|
localRepoCollection := context.CollectionFactory().LocalRepoCollection()
|
|
localRepoCollection.Lock()
|
|
defer localRepoCollection.Unlock()
|
|
|
|
collection := context.CollectionFactory().PublishedRepoCollection()
|
|
collection.Lock()
|
|
defer collection.Unlock()
|
|
|
|
err := collection.Remove(context, storage, prefix, distribution,
|
|
context.CollectionFactory(), context.Progress(), force, skipCleanup)
|
|
if err != nil {
|
|
c.AbortWithError(500, fmt.Errorf("unable to drop: %s", err))
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{})
|
|
}
|