Allow management of components

This commit allows to add, remove and update components of published repositories without the need to recreate them.

Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
This commit is contained in:
Christoph Fiehe
2024-10-09 07:38:23 +02:00
committed by André Roth
parent 767bc6bd0b
commit bd64232eb6
45 changed files with 1248 additions and 179 deletions

View File

@@ -59,7 +59,7 @@ flake8: ## run flake8 on system test python files
lint: lint:
# Install golangci-lint # Install golangci-lint
go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) @test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
# Running lint # Running lint
@PATH=$(BINPATH)/:$(PATH) golangci-lint run @PATH=$(BINPATH)/:$(PATH) golangci-lint run

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"path/filepath"
"strings" "strings"
"github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/aptly"
@@ -13,17 +14,29 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// SigningOptions is a shared between publish API GPG options structure type signingParams struct {
type SigningOptions struct { // Don't sign published repository
Skip bool Skip bool ` json:"Skip"`
GpgKey string // GPG key ID to use when signing the release, if not specified default key is used
Keyring string GpgKey string ` json:"GpgKey"`
SecretKeyring string // GPG keyring to use (instead of default)
Passphrase string Keyring string ` json:"Keyring"`
PassphraseFile string // GPG secret keyring to use (instead of default)
SecretKeyring string ` json:"SecretKeyring"`
// GPG passphrase to unlock private key (possibly insecure)
Passphrase string ` json:"Passphrase"`
// GPG passphrase file to unlock private key (possibly insecure)
PassphraseFile string ` json:"PassphraseFile"`
} }
func getSigner(options *SigningOptions) (pgp.Signer, error) { type sourceParams struct {
// Name of the component
Component string `binding:"required" json:"Component" example:"contrib"`
// Name of the local repository/snapshot
Name string `binding:"required" json:"Name" example:"snap1"`
}
func getSigner(options *signingParams) (pgp.Signer, error) {
if options.Skip { if options.Skip {
return nil, nil return nil, nil
} }
@@ -54,10 +67,10 @@ func slashEscape(path string) string {
return result return result
} }
// @Summary Get publish points // @Summary List published repositories
// @Description Get list of available publish points. Each publish point is returned as in “show” API. // @Description **Get a list of all published repositories**
// @Tags Publish // @Tags Publish
// @Produce json // @Produce json
// @Success 200 {array} deb.PublishedRepo // @Success 200 {array} deb.PublishedRepo
// @Failure 500 {object} Error "Internal Error" // @Failure 500 {object} Error "Internal Error"
// @Router /api/publish [get] // @Router /api/publish [get]
@@ -79,37 +92,97 @@ func apiPublishList(c *gin.Context) {
}) })
if err != nil { if err != nil {
AbortWithJSONError(c, 500, err) AbortWithJSONError(c, http.StatusInternalServerError, err)
return return
} }
c.JSON(200, result) c.JSON(http.StatusOK, result)
} }
// POST /publish/:prefix // @Summary Show published repository
func apiPublishRepoOrSnapshot(c *gin.Context) { // @Description **Get published repository by name**
// @Tags Publish
// @Consume json
// @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.RemoteRepo
// @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")) param := slashEscape(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param) storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(c.Params.ByName("distribution"))
var b struct { collectionFactory := context.NewCollectionFactory()
SourceKind string `binding:"required"` collection := collectionFactory.PublishedRepoCollection()
Sources []struct {
Component string published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
Name string `binding:"required"` if err != nil {
} `binding:"required"` AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err))
Distribution string return
Label string
Origin string
NotAutomatic string
ButAutomaticUpgrades string
ForceOverwrite bool
SkipContents *bool
SkipBz2 *bool
Architectures []string
Signing SigningOptions
AcquireByHash *bool
MultiDist bool
} }
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"`
// Value of Label: field in published repository stanza
Label string ` json:"Label"`
// Value of Origin: field in published repository stanza
Origin string ` json:"Origin"`
// when publishing, overwrite files in pool/ directory without notice
ForceOverwrite bool ` json:"ForceOverwrite"`
// Override list of published architectures
Architectures []string ` json:"Architectures"`
// 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"`
// setting to yes excludes upgrades from the NotAutomic setting
ButAutomaticUpgrades string ` json:"ButAutomaticUpgrades"`
// Don't generate contents indexes
SkipContents *bool ` json:"SkipContents"`
// Don't remove unreferenced files in prefix/component
SkipCleanup *bool ` json:"SkipCleanup"`
// Skip bz2 compression for index files
SkipBz2 *bool ` json:"SkipBz2"`
// Provide index files by hash
AcquireByHash *bool ` json:"AcquireByHash"`
// Enable multiple packages with the same filename in different distributions
MultiDist *bool ` json:"MultiDist"`
}
// @Summary Create published repository
// @Description Publish local repository or snapshot under specified prefix. Storage might be passed in prefix as well, e.g. `s3:packages/`.
// @Description To supply empty prefix, just remove last part (`POST /api/publish`).
// @Tags Publish
// @Param prefix path string true "publishing prefix"
// @Consume json
// @Param request body publishedRepoCreateParams true "Parameters"
// @Produce json
// @Success 200 {object} deb.RemoteRepo
// @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
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
if c.Bind(&b) != nil { if c.Bind(&b) != nil {
return return
@@ -119,12 +192,12 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
signer, err := getSigner(&b.Signing) signer, err := getSigner(&b.Signing)
if err != nil { if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG signer: %s", err)) AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err))
return return
} }
if len(b.Sources) == 0 { if len(b.Sources) == 0 {
AbortWithJSONError(c, 400, fmt.Errorf("unable to publish: soures are empty")) AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to publish: soures are empty"))
return return
} }
@@ -145,12 +218,11 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
snapshot, err = snapshotCollection.ByName(source.Name) snapshot, err = snapshotCollection.ByName(source.Name)
if err != nil { if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to publish: %s", err)) AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err))
return return
} }
resources = append(resources, string(snapshot.ResourceKey())) resources = append(resources, string(snapshot.ResourceKey()))
sources = append(sources, snapshot) sources = append(sources, snapshot)
} }
} else if b.SourceKind == deb.SourceLocalRepo { } else if b.SourceKind == deb.SourceLocalRepo {
@@ -164,18 +236,24 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
localRepo, err = localCollection.ByName(source.Name) localRepo, err = localCollection.ByName(source.Name)
if err != nil { if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to publish: %s", err)) AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err))
return return
} }
resources = append(resources, string(localRepo.Key()))
sources = append(sources, localRepo) sources = append(sources, localRepo)
} }
} else { } else {
AbortWithJSONError(c, 400, fmt.Errorf("unknown SourceKind")) AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unknown SourceKind"))
return return
} }
taskName := fmt.Sprintf("Publish %s: %s", b.SourceKind, strings.Join(names, ", ")) taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"",
b.SourceKind, published.StoragePrefix(), published.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`))
resources = append(resources, string(published.Key()))
collection := collectionFactory.PublishedRepoCollection()
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
taskDetail := task.PublishDetail{ taskDetail := task.PublishDetail{
Detail: detail, Detail: detail,
@@ -233,7 +311,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
published.AcquireByHash = *b.AcquireByHash published.AcquireByHash = *b.AcquireByHash
} }
collection := collectionFactory.PublishedRepoCollection() if b.MultiDist != nil {
published.MultiDist = *b.MultiDist
}
duplicate := collection.CheckDuplicate(published) duplicate := collection.CheckDuplicate(published)
if duplicate != nil { if duplicate != nil {
collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory) collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
@@ -254,7 +335,18 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
}) })
} }
// PUT /publish/:prefix/:distribution // @Summary Update published repository
// @Description Update a published repository.
// @Tags Publish
// @Accept json
// @Produce json
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Success 200 {object} deb.RemoteRepo
// @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) { func apiPublishUpdateSwitch(c *gin.Context) {
param := slashEscape(c.Params.ByName("prefix")) param := slashEscape(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param) storage, prefix := deb.ParsePrefix(param)
@@ -262,7 +354,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
var b struct { var b struct {
ForceOverwrite bool ForceOverwrite bool
Signing SigningOptions Signing signingParams
SkipContents *bool SkipContents *bool
SkipBz2 *bool SkipBz2 *bool
SkipCleanup *bool SkipCleanup *bool
@@ -280,7 +372,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
signer, err := getSigner(&b.Signing) signer, err := getSigner(&b.Signing)
if err != nil { if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG signer: %s", err)) AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err))
return return
} }
@@ -290,12 +382,13 @@ func apiPublishUpdateSwitch(c *gin.Context) {
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil { if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to update: %s", err)) AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
return return
} }
var updatedComponents []string var updatedComponents []string
var updatedSnapshots []string var updatedSnapshots []string
if published.SourceKind == deb.SourceLocalRepo { if published.SourceKind == deb.SourceLocalRepo {
if len(b.Snapshots) > 0 { if len(b.Snapshots) > 0 {
AbortWithJSONError(c, 400, fmt.Errorf("snapshots shouldn't be given when updating local repo")) AbortWithJSONError(c, 400, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
@@ -335,7 +428,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
var resources []string var resources []string
resources = append(resources, string(published.Key())) resources = append(resources, string(published.Key()))
taskName := fmt.Sprintf("Update published %s (%s): %s", published.SourceKind, strings.Join(updatedComponents, " "), strings.Join(updatedSnapshots, ", ")) 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) { maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.LoadComplete(published, collectionFactory) err = collection.LoadComplete(published, collectionFactory)
if err != nil { if err != nil {
@@ -362,6 +455,11 @@ func apiPublishUpdateSwitch(c *gin.Context) {
} }
} }
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) err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite)
if err != nil { if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
@@ -372,26 +470,53 @@ func apiPublishUpdateSwitch(c *gin.Context) {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
} }
updatedComponents := result.UpdatedComponents()
removedComponents := result.RemovedComponents()
if b.SkipCleanup == nil || !*b.SkipCleanup { if b.SkipCleanup == nil || !*b.SkipCleanup {
err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents, publishedStorage := context.GetPublishedStorage(storage)
context.GetPublishedStorage(storage), collectionFactory, out)
err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents, publishedStorage, collectionFactory, out)
if err != nil { if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
} }
if len(removedComponents) > 0 {
// Cleanup files belonging to a removed component by dropping the component directory from the storage backend.
for _, component := range removedComponents {
err = publishedStorage.RemoveDirs(filepath.Join(prefix, "dists", distribution, component), 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 return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
}) })
} }
// DELETE /publish/:prefix/:distribution // @Summary Delete published repository
// @Description Delete a published repository.
// @Tags Publish
// @Accept json
// @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"
// @Success 200 {object} task.ProcessReturnValue
// @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) { func apiPublishDrop(c *gin.Context) {
force := c.Request.URL.Query().Get("force") == "1" force := c.Request.URL.Query().Get("force") == "1"
skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1" skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1"
param := slashEscape(c.Params.ByName("prefix")) param := slashEscape(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param) storage, prefix := deb.ParsePrefix(param)
distribution := c.Params.ByName("distribution") distribution := parseEscapedPath(c.Params.ByName("distribution"))
collectionFactory := context.NewCollectionFactory() collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection() collection := collectionFactory.PublishedRepoCollection()
@@ -404,7 +529,7 @@ func apiPublishDrop(c *gin.Context) {
resources := []string{string(published.Key())} resources := []string{string(published.Key())}
taskName := fmt.Sprintf("Delete published %s (%s)", prefix, distribution) 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) { maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err := collection.Remove(context, storage, prefix, distribution, err := collection.Remove(context, storage, prefix, distribution,
collectionFactory, out, force, skipCleanup) collectionFactory, out, force, skipCleanup)
@@ -415,3 +540,304 @@ func apiPublishDrop(c *gin.Context) {
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil
}) })
} }
// @Router /api/publish/{prefix}/{distribution}/sources/{component} [put]
func apiPublishSourceUpdate(c *gin.Context) {
var (
err error
b sourceParams
)
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(c.Params.ByName("distribution"))
component := parseEscapedPath(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 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.ObtainRevision()
sources := revision.Sources
b.Component = component
b.Name = revision.Sources[component]
if c.Bind(&b) != nil {
return
}
if b.Component != component {
delete(sources, component)
}
sources[b.Component] = b.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: published}, nil
})
}
// @Router /api/publish/{prefix}/{distribution}/sources [put]
func apiPublishSourcesUpdate(c *gin.Context) {
var (
err error
b []sourceParams
)
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(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
}
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: published}, nil
})
}
// @Router /api/publish/{prefix}/{distribution}/sources [get]
func apiPublishSourceList(c *gin.Context) {
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(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.ToJSON()["Sources"])
}
// @Router /api/publish/{prefix}/{distribution}/sources/{component} [delete]
func apiPublishSourceDelete(c *gin.Context) {
var err error
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(c.Params.ByName("distribution"))
component := parseEscapedPath(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 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.ObtainRevision()
sources := revision.Sources
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: published}, nil
})
}
// @Router /api/publish/{prefix}/{distribution}/sources [delete]
func apiPublishSourcesDelete(c *gin.Context) {
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(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
}
published.DropRevision()
}
// @Router /api/publish/{prefix}/{distribution}/update [post]
func apiPublishUpdate(c *gin.Context) {
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := parseEscapedPath(c.Params.ByName("distribution"))
var b struct {
AcquireByHash *bool
ForceOverwrite bool
Signing signingParams
SkipBz2 *bool
SkipCleanup *bool
SkipContents *bool
}
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
}
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)
}
updatedComponents := result.UpdatedComponents()
removedComponents := result.RemovedComponents()
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite)
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 {
publishedStorage := context.GetPublishedStorage(storage)
err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents, publishedStorage, collectionFactory, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
if len(removedComponents) > 0 {
// Cleanup files belonging to a removed component by dropping the component directory from the storage backend.
for _, component := range removedComponents {
err = publishedStorage.RemoveDirs(filepath.Join(prefix, "dists", distribution, component), 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
})
}

View File

@@ -185,12 +185,18 @@ func Router(c *ctx.AptlyContext) http.Handler {
} }
{ {
api.GET("/publish", apiPublishList) api.GET("/publish", apiPublishList)
api.GET("/publish/:prefix/:distribution", apiPublishShow)
api.POST("/publish", apiPublishRepoOrSnapshot) api.POST("/publish", apiPublishRepoOrSnapshot)
api.POST("/publish/:prefix", apiPublishRepoOrSnapshot) api.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch) api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
api.DELETE("/publish/:prefix/:distribution", apiPublishDrop) api.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
api.GET("/publish/:prefix/:distribution/sources", apiPublishSourceList)
api.PUT("/publish/:prefix/:distribution/sources", apiPublishSourcesUpdate)
api.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishSourceUpdate)
api.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishSourceDelete)
api.DELETE("/publish/:prefix/:distribution/sources", apiPublishSourcesDelete)
api.POST("/publish/:prefix/:distribution/update", apiPublishUpdate)
} }
{ {

View File

@@ -34,10 +34,25 @@ func makeCmdPublish() *commander.Command {
makeCmdPublishDrop(), makeCmdPublishDrop(),
makeCmdPublishList(), makeCmdPublishList(),
makeCmdPublishRepo(), makeCmdPublishRepo(),
makeCmdPublishShow(),
makeCmdPublishSnapshot(), makeCmdPublishSnapshot(),
makeCmdPublishSource(),
makeCmdPublishSwitch(), makeCmdPublishSwitch(),
makeCmdPublishUpdate(), makeCmdPublishUpdate(),
makeCmdPublishShow(), },
}
}
func makeCmdPublishSource() *commander.Command {
return &commander.Command{
UsageLine: "source",
Short: "manage sources of published repository",
Subcommands: []*commander.Command{
makeCmdPublishSourceAdd(),
makeCmdPublishSourceDrop(),
makeCmdPublishSourceList(),
makeCmdPublishSourceRemove(),
makeCmdPublishSourceUpdate(),
}, },
} }
} }

View File

@@ -52,7 +52,8 @@ func aptlyPublishShowTxt(_ *commander.Command, args []string) error {
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, " ")) fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, " "))
fmt.Printf("Sources:\n") fmt.Printf("Sources:\n")
for component, sourceID := range repo.Sources { for _, component := range repo.Components() {
sourceID := repo.Sources[component]
var name string var name string
if repo.SourceKind == deb.SourceSnapshot { if repo.SourceKind == deb.SourceSnapshot {
source, e := collectionFactory.SnapshotCollection().ByUUID(sourceID) source, e := collectionFactory.SnapshotCollection().ByUUID(sourceID)

View File

@@ -150,6 +150,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool) published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool)
} }
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published) duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
if duplicate != nil { if duplicate != nil {
collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory) collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)

89
cmd/publish_source_add.go Normal file
View File

@@ -0,0 +1,89 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceAdd(cmd *commander.Command, args []string) error {
if len(args) < 2 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
names := args[1:]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(names) != len(components) {
return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names))
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
for i, component := range components {
name := names[i]
_, exists := sources[component]
if exists {
return fmt.Errorf("unable to add: Component %q has already been added", component)
}
context.Progress().Printf("Adding component %q with source %q [%s]...\n", component, name, published.SourceKind)
sources[component] = names[i]
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
return err
}
func makeCmdPublishSourceAdd() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceAdd,
UsageLine: "add <distribution> <source>",
Short: "add package source to published repository",
Long: `
The command adds (in place) one or multiple package sources to a published repository.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be modified. The number of given components must be
equal to the number of given sources, e.g.:
aptly publish add -component=main,contrib wheezy wheezy-main wheezy-contrib
Example:
$ aptly publish add -component=contrib wheezy ppa wheezy-contrib
This command assigns the snapshot wheezy-contrib to the component contrib and
adds it to published repository revision of ppa/wheezy.
`,
Flag: *flag.NewFlagSet("aptly-publish-add", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}

View File

@@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceDrop(cmd *commander.Command, args []string) error {
if len(args) != 1 {
cmd.Usage()
return commander.ErrCommandError
}
prefix := context.Flags().Lookup("prefix").Value.String()
distribution := args[0]
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
published.DropRevision()
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
context.Progress().Printf("Source changes have been removed successfully.\n")
return err
}
func makeCmdPublishSourceDrop() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceDrop,
UsageLine: "drop <distribution>",
Short: "drops revision of published repository",
Long: `
Command drops revision of a published repository.
Example:
$ aptly publish revision drop wheezy
`,
Flag: *flag.NewFlagSet("aptly-publish-revision-create", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}

View File

@@ -0,0 +1,89 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceList(cmd *commander.Command, args []string) error {
if len(args) != 1 {
cmd.Usage()
return commander.ErrCommandError
}
prefix := context.Flags().Lookup("prefix").Value.String()
distribution := args[0]
storage, prefix := deb.ParsePrefix(prefix)
published, err := context.NewCollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to list: %s", err)
}
err = context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(published, context.NewCollectionFactory())
if err != nil {
return err
}
if published.Revision == nil {
return fmt.Errorf("unable to list: No source changes exist")
}
jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool)
if jsonFlag {
return aptlyPublishSourceListJSON(published)
}
return aptlyPublishSourceListTxt(published)
}
func aptlyPublishSourceListTxt(published *deb.PublishedRepo) error {
revision := published.Revision
fmt.Printf("Sources:\n")
for _, component := range revision.Components() {
name := revision.Sources[component]
fmt.Printf(" %s: %s [%s]\n", component, name, published.SourceKind)
}
return nil
}
func aptlyPublishSourceListJSON(published *deb.PublishedRepo) error {
revision := published.Revision
output, err := json.MarshalIndent(revision.ToJSON()["Sources"], "", " ")
if err != nil {
return fmt.Errorf("unable to list: %s", err)
}
fmt.Println(string(output))
return nil
}
func makeCmdPublishSourceList() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceList,
UsageLine: "list <distribution>",
Short: "lists revision of published repository",
Long: `
Command lists sources of a published repository.
Example:
$ aptly publish source list wheezy
`,
Flag: *flag.NewFlagSet("aptly-publish-source-list", flag.ExitOnError),
}
cmd.Flag.Bool("json", false, "display record in JSON format")
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}

View File

@@ -0,0 +1,81 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceRemove(cmd *commander.Command, args []string) error {
if len(args) < 1 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(components) == 0 {
return fmt.Errorf("unable to remove: Missing components, specify at least one component")
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to remove: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to remove: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
for _, component := range components {
name, exists := sources[component]
if !exists {
return fmt.Errorf("unable to remove: Component %q is not part of revision", component)
}
context.Progress().Printf("Removing component %q with source %q [%s]...\n", component, name, published.SourceKind)
delete(sources, component)
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
return err
}
func makeCmdPublishSourceRemove() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceRemove,
UsageLine: "remove <distribution> [[<endpoint>:]<prefix>] <source>",
Short: "remove package source to published repository",
Long: `
The command removes one or multiple components from a published repository.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be removed, e.g.:
Example:
$ aptly publish remove -component=contrib,non-free wheezy filesystem:symlink:debian
`,
Flag: *flag.NewFlagSet("aptly-publish-remove", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to remove (for multi-component publishing, separate components with commas)")
return cmd
}

View File

@@ -0,0 +1,86 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceUpdate(cmd *commander.Command, args []string) error {
if len(args) < 2 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
names := args[1:]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(names) != len(components) {
return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names))
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
for i, component := range components {
name := names[i]
_, exists := sources[component]
if !exists {
return fmt.Errorf("unable to update: Component %q does not exist", component)
}
context.Progress().Printf("Updating component %q with source %q [%s]...\n", component, name, published.SourceKind)
sources[component] = name
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
return err
}
func makeCmdPublishSourceUpdate() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceUpdate,
UsageLine: "update <distribution> <source>",
Short: "update package source to published repository",
Long: `
The command updates one or multiple components in a published repository.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be modified. The number of given components must be
equal to the number of given sources, e.g.:
aptly publish update -component=main,contrib wheezy wheezy-main wheezy-contrib
Example:
$ aptly publish update -component=contrib wheezy ppa wheezy-contrib
`,
Flag: *flag.NewFlagSet("aptly-publish-revision-source-update", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}

View File

@@ -5,12 +5,16 @@ import (
"strings" "strings"
"github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
) )
func aptlyPublishSwitch(cmd *commander.Command, args []string) error { func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
var err error var (
err error
names []string
)
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
@@ -22,11 +26,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
distribution := args[0] distribution := args[0]
param := "." param := "."
var (
names []string
snapshot *deb.Snapshot
)
if len(args) == len(components)+2 { if len(args) == len(components)+2 {
param = args[1] param = args[1]
names = args[2:] names = args[2:]
@@ -41,16 +40,12 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
collectionFactory := context.NewCollectionFactory() collectionFactory := context.NewCollectionFactory()
published, err = collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution) published, err = collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil { if err != nil {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to switch: %s", err)
}
if published.SourceKind != deb.SourceSnapshot {
return fmt.Errorf("unable to update: not a snapshot publish")
} }
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil { if err != nil {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to switch: %s", err)
} }
publishedComponents := published.Components() publishedComponents := published.Components()
@@ -62,18 +57,46 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
return fmt.Errorf("mismatch in number of components (%d) and snapshots (%d)", len(components), len(names)) return fmt.Errorf("mismatch in number of components (%d) and snapshots (%d)", len(components), len(names))
} }
for i, component := range components { if published.SourceKind == deb.SourceLocalRepo {
snapshot, err = collectionFactory.SnapshotCollection().ByName(names[i]) localRepoCollection := collectionFactory.LocalRepoCollection()
if err != nil { for i, component := range components {
return fmt.Errorf("unable to switch: %s", err) if !utils.StrSliceHasItem(publishedComponents, component) {
} return fmt.Errorf("unable to switch: component %s does not exist in published repository", component)
}
err = collectionFactory.SnapshotCollection().LoadComplete(snapshot) localRepo, err := localRepoCollection.ByName(names[i])
if err != nil { if err != nil {
return fmt.Errorf("unable to switch: %s", err) return fmt.Errorf("unable to switch: %s", err)
} }
published.UpdateSnapshot(component, snapshot) err = localRepoCollection.LoadComplete(localRepo)
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
published.UpdateLocalRepo(component, localRepo)
}
} else if published.SourceKind == deb.SourceSnapshot {
snapshotCollection := collectionFactory.SnapshotCollection()
for i, component := range components {
if !utils.StrSliceHasItem(publishedComponents, component) {
return fmt.Errorf("unable to switch: component %s does not exist in published repository", component)
}
snapshot, err := snapshotCollection.ByName(names[i])
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
published.UpdateSnapshot(component, snapshot)
}
} else {
return fmt.Errorf("unknown published repository type")
} }
signer, err := getSigner(context.Flags()) signer, err := getSigner(context.Flags())
@@ -114,11 +137,11 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components, err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components,
context.GetPublishedStorage(storage), collectionFactory, context.Progress()) context.GetPublishedStorage(storage), collectionFactory, context.Progress())
if err != nil { if err != nil {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to switch: %s", err)
} }
} }
context.Progress().Printf("\nPublish for snapshot %s has been successfully switched to new snapshot.\n", published.String()) context.Progress().Printf("\nPublished %s repository %s has been successfully switched to new source.\n", published.SourceKind, published.String())
return err return err
} }
@@ -126,15 +149,15 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
func makeCmdPublishSwitch() *commander.Command { func makeCmdPublishSwitch() *commander.Command {
cmd := &commander.Command{ cmd := &commander.Command{
Run: aptlyPublishSwitch, Run: aptlyPublishSwitch,
UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-snapshot>", UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-source>",
Short: "update published repository by switching to new snapshot", Short: "update published repository by switching to new source",
Long: ` Long: `
Command switches in-place published snapshots with new snapshot contents. All Command switches in-place published snapshots with new source contents. All
publishing parameters are preserved (architecture list, distribution, publishing parameters are preserved (architecture list, distribution,
component). component).
For multiple component repositories, flag -component should be given with For multiple component repositories, flag -component should be given with
list of components to update. Corresponding snapshots should be given in the list of components to update. Corresponding sources should be given in the
same order, e.g.: same order, e.g.:
aptly publish switch -component=main,contrib wheezy wh-main wh-contrib aptly publish switch -component=main,contrib wheezy wh-main wh-contrib

View File

@@ -31,18 +31,14 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to update: %s", err)
} }
if published.SourceKind != deb.SourceLocalRepo {
return fmt.Errorf("unable to update: not a local repository publish")
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory) err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil { if err != nil {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to update: %s", err)
} }
components := published.Components() result, err := published.Update(collectionFactory, context.Progress())
for _, component := range components { if err != nil {
published.UpdateLocalRepo(component) return fmt.Errorf("unable to update: %s", err)
} }
signer, err := getSigner(context.Flags()) signer, err := getSigner(context.Flags())
@@ -80,14 +76,14 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool) skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool)
if !skipCleanup { if !skipCleanup {
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components, err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, result.UpdatedComponents(),
context.GetPublishedStorage(storage), collectionFactory, context.Progress()) context.GetPublishedStorage(storage), collectionFactory, context.Progress())
if err != nil { if err != nil {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to update: %s", err)
} }
} }
context.Progress().Printf("\nPublish for local repo %s has been successfully updated.\n", published.String()) context.Progress().Printf("\nPublished %s repository %s has been successfully updated.\n", published.SourceKind, published.String())
return err return err
} }

View File

@@ -21,6 +21,12 @@ import (
"github.com/aptly-dev/aptly/utils" "github.com/aptly-dev/aptly/utils"
) )
type PublishedRepoUpdateResult struct {
AddedSources map[string]string
UpdatedSources map[string]string
RemovedSources map[string]string
}
type repoSourceItem struct { type repoSourceItem struct {
// Pointer to snapshot if SourceKind == "snapshot" // Pointer to snapshot if SourceKind == "snapshot"
snapshot *Snapshot snapshot *Snapshot
@@ -73,6 +79,168 @@ type PublishedRepo struct {
// Support multiple distributions // Support multiple distributions
MultiDist bool MultiDist bool
// Revision
Revision *PublishedRepoRevision
}
type PublishedRepoRevision struct {
// Map of sources: component name -> snapshot name/local repo Name
Sources map[string]string
}
func (result *PublishedRepoUpdateResult) AddedComponents() []string {
components := make([]string, 0, len(result.AddedSources))
for component := range result.AddedSources {
components = append(components, component)
}
sort.Strings(components)
return components
}
func (result *PublishedRepoUpdateResult) UpdatedComponents() []string {
components := make([]string, 0, len(result.UpdatedSources))
for component := range result.UpdatedSources {
components = append(components, component)
}
sort.Strings(components)
return components
}
func (result *PublishedRepoUpdateResult) RemovedComponents() []string {
components := make([]string, 0, len(result.RemovedSources))
for component := range result.RemovedSources {
components = append(components, component)
}
sort.Strings(components)
return components
}
func (revision *PublishedRepoRevision) Components() []string {
components := make([]string, 0, len(revision.Sources))
for component := range revision.Sources {
components = append(components, component)
}
sort.Strings(components)
return components
}
func (revision *PublishedRepoRevision) SourceNames() []string {
sources := revision.Sources
names := make([]string, 0, len(sources))
for _, component := range revision.Components() {
names = append(names, sources[component])
}
return names
}
func (p *PublishedRepo) DropRevision() *PublishedRepoRevision {
revision := p.Revision
p.Revision = nil
return revision
}
func (p *PublishedRepo) ObtainRevision() *PublishedRepoRevision {
revision := p.Revision
if revision == nil {
sources := make(map[string]string, len(p.Sources))
for _, component := range p.Components() {
item := p.sourceItems[component]
if item.snapshot != nil {
sources[component] = item.snapshot.Name
} else if item.localRepo != nil {
sources[component] = item.localRepo.Name
} else {
panic("no snapshot/localRepo")
}
}
revision = &PublishedRepoRevision{
Sources: sources,
}
p.Revision = revision
}
return revision
}
func (p *PublishedRepo) Update(collectionFactory *CollectionFactory, _ aptly.Progress) (*PublishedRepoUpdateResult, error) {
result := &PublishedRepoUpdateResult{
AddedSources: map[string]string{},
UpdatedSources: map[string]string{},
RemovedSources: map[string]string{},
}
revision := p.ObtainRevision()
p.DropRevision()
publishedComponents := p.Components()
for _, component := range publishedComponents {
name, exists := revision.Sources[component]
if !exists {
p.RemoveComponent(component)
result.RemovedSources[component] = name
}
}
if p.SourceKind == SourceLocalRepo {
localRepoCollection := collectionFactory.LocalRepoCollection()
for component, name := range revision.Sources {
localRepo, err := localRepoCollection.ByName(name)
if err != nil {
return result, fmt.Errorf("unable to update: %s", err)
}
err = localRepoCollection.LoadComplete(localRepo)
if err != nil {
return result, fmt.Errorf("unable to update: %s", err)
}
_, exists := p.Sources[component]
if exists {
// Even in the case, when the local repository has not been changed as package source,
// it may contain a modified set of packages that requires (re-)publication.
p.UpdateLocalRepo(component, localRepo)
result.UpdatedSources[component] = name
} else {
p.UpdateLocalRepo(component, localRepo)
result.AddedSources[component] = name
}
}
} else if p.SourceKind == SourceSnapshot {
snapshotCollection := collectionFactory.SnapshotCollection()
for component, name := range revision.Sources {
snapshot, err := snapshotCollection.ByName(name)
if err != nil {
return result, fmt.Errorf("unable to update: %s", err)
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
return result, fmt.Errorf("unable to update: %s", err)
}
sourceUUID, exists := p.Sources[component]
if exists {
if snapshot.UUID != sourceUUID {
p.UpdateSnapshot(component, snapshot)
result.UpdatedSources[component] = name
}
} else {
p.UpdateSnapshot(component, snapshot)
result.AddedSources[component] = name
}
}
} else {
return result, fmt.Errorf("unknown published repository type")
}
return result, nil
} }
// ParsePrefix splits [storage:]prefix into components // ParsePrefix splits [storage:]prefix into components
@@ -281,14 +449,42 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
return result, nil return result, nil
} }
// MarshalJSON requires object to filled by "LoadShallow" or "LoadComplete" type sourceInfo struct {
func (p *PublishedRepo) MarshalJSON() ([]byte, error) { Component, Name string
type sourceInfo struct { }
Component, Name string
func (revision *PublishedRepoRevision) ToJSON() map[string]any {
sources := []sourceInfo{}
for _, component := range revision.Components() {
name := revision.Sources[component]
sources = append(sources, sourceInfo{
Component: component,
Name: name,
})
}
return map[string]any{"Sources": sources}
}
func (revision *PublishedRepoRevision) MarshalJSON() ([]byte, error) {
sources := []sourceInfo{}
for _, component := range revision.Components() {
name := revision.Sources[component]
sources = append(sources, sourceInfo{
Component: component,
Name: name,
})
} }
return json.Marshal(map[string]interface{}{
"Sources": sources,
})
}
// MarshalJSON requires object to filled by "LoadShallow" or "LoadComplete"
func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
sources := []sourceInfo{} sources := []sourceInfo{}
for component, item := range p.sourceItems { for _, component := range p.Components() {
item := p.sourceItems[component]
name := "" name := ""
if item.snapshot != nil { if item.snapshot != nil {
name = item.snapshot.Name name = item.snapshot.Name
@@ -444,20 +640,25 @@ func (p *PublishedRepo) SourceNames() []string {
return sources return sources
} }
// UpdateLocalRepo updates content from local repo in component // UpdateLocalRepo inserts/updates local repository source for component
func (p *PublishedRepo) UpdateLocalRepo(component string) { func (p *PublishedRepo) UpdateLocalRepo(component string, localRepo *LocalRepo) {
if p.SourceKind != SourceLocalRepo { if p.SourceKind != SourceLocalRepo {
panic("not local repo publish") panic("not local repo publish")
} }
item := p.sourceItems[component] item, exists := p.sourceItems[component]
item.packageRefs = item.localRepo.RefList() if !exists {
item = repoSourceItem{}
}
item.localRepo = localRepo
item.packageRefs = localRepo.RefList()
p.sourceItems[component] = item p.sourceItems[component] = item
p.Sources[component] = localRepo.UUID
p.rePublishing = true p.rePublishing = true
} }
// UpdateSnapshot switches snapshot for component // UpdateSnapshot inserts/updates snapshot source for component
func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) { func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) {
if p.SourceKind != SourceSnapshot { if p.SourceKind != SourceSnapshot {
panic("not snapshot publish") panic("not snapshot publish")
@@ -474,6 +675,14 @@ func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) {
p.rePublishing = true p.rePublishing = true
} }
// RemoveComponent removes component from published repository
func (p *PublishedRepo) RemoveComponent(component string) {
delete(p.Sources, component)
delete(p.sourceItems, component)
p.rePublishing = true
}
// Encode does msgpack encoding of PublishedRepo // Encode does msgpack encoding of PublishedRepo
func (p *PublishedRepo) Encode() []byte { func (p *PublishedRepo) Encode() []byte {
var buf bytes.Buffer var buf bytes.Buffer

View File

@@ -1 +1,3 @@
package docs package docs
import _ "github.com/swaggo/swag" // make sure swag is in go.mod

View File

@@ -1,18 +0,0 @@
#!/bin/sh -e
usermod -u `stat -c %u /work/src` aptly >/dev/null
chown -R `stat -c %u /work/src` /var/lib/aptly
su aptly -c 'set -e; cd /work/src;
GOPATH=$PWD/.go go generate -v
# install and initialize swagger
GOPATH=$PWD/.go go install github.com/swaggo/swag/cmd/swag@latest
PATH=$PWD/.go/bin:$PATH swag init -q --markdownFiles docs
git checkout debian/changelog
DEBEMAIL="CI <runner@github>" dch -v `make version` "CI build"
dpkg-buildpackage -us -uc -b -d
mkdir -p build && mv ../*.deb build/
rm -rf obj-*-linux-gnu*
git checkout debian/changelog
cd build && ls -l *.deb
'

View File

@@ -319,40 +319,36 @@ class BaseTest(object):
return subprocess.Popen(command, stderr=stderr, stdout=stdout, env=environ) return subprocess.Popen(command, stderr=stderr, stdout=stdout, env=environ)
def run_cmd(self, command, expected_code=0): def run_cmd(self, command, expected_code=0):
try: proc = self._start_process(command, stdout=subprocess.PIPE)
proc = self._start_process(command, stdout=subprocess.PIPE) raw_output, _ = proc.communicate()
raw_output, _ = proc.communicate()
raw_output = raw_output.decode("utf-8", errors='replace') raw_output = raw_output.decode("utf-8", errors='replace')
returncodes = [proc.returncode] returncodes = [proc.returncode]
is_aptly_command = False is_aptly_command = False
if isinstance(command, str): if isinstance(command, str):
is_aptly_command = command.startswith("aptly") is_aptly_command = command.startswith("aptly")
if isinstance(command, list): if isinstance(command, list):
is_aptly_command = command[0] == "aptly" is_aptly_command = command[0] == "aptly"
if is_aptly_command: if is_aptly_command:
# remove the last two rows as go tests always print PASS/FAIL and coverage in those # remove the last two rows as go tests always print PASS/FAIL and coverage in those
# two lines. This would otherwise fail the tests as they would not match gold # two lines. This would otherwise fail the tests as they would not match gold
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output) matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output)
if not matches: if not matches:
raise Exception("no matches found in output '%s'" % raw_output) raise Exception("no matches found in command output '%s'" % raw_output)
output, _, returncode = matches[0] output, _, returncode = matches[0]
returncodes.append(int(returncode)) returncodes.append(int(returncode))
else: else:
output = raw_output output = raw_output
if expected_code is not None: if expected_code is not None:
if expected_code not in returncodes: if expected_code not in returncodes:
raise Exception("exit code %d != %d (output: %s)" % ( raise Exception("command expected to return %d, but returned %d: \n%s" % (
proc.returncode, expected_code, raw_output)) expected_code, proc.returncode, raw_output))
return output return output
except Exception as e:
raise Exception("Running command '%s' failed: %s" %
(command, str(e)))
def gold_processor(self, gold): def gold_processor(self, gold):
return gold return gold
@@ -379,6 +375,8 @@ class BaseTest(object):
return s return s
def check_output(self): def check_output(self):
gold_file = self.get_gold_filename()
print(f"Verifying gold file: {gold_file}")
try: try:
self.verify_match(self.get_gold(), self.output, self.verify_match(self.get_gold(), self.output,
match_prepare=self.outputMatchPrepare) match_prepare=self.outputMatchPrepare)
@@ -464,11 +462,11 @@ class BaseTest(object):
def check_in(self, item, l): def check_in(self, item, l):
if item not in l: if item not in l:
raise Exception("item %r not in %r", item, l) raise Exception("expected item: %r\nnot found in: %r" % (item, l))
def check_not_in(self, item, l): def check_not_in(self, item, l):
if item in l: if item in l:
raise Exception("item %r in %r", item, l) raise Exception("unexpected item: %r\n found in: %r" % (item, l))
def check_subset(self, a, b): def check_subset(self, a, b):
diff = '' diff = ''

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo azure:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository azure:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot azure:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository azure:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -7,4 +7,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot ./maverick [i386, source] publishes {main: [snap2]: Snapshot from local repo [local-repo2]} has been successfully switched to new snapshot. Published snapshot repository ./maverick [i386, source] publishes {main: [snap2]: Snapshot from local repo [local-repo2]} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -4,4 +4,4 @@ Finalizing metadata files...
Signing file 'Release' with gpg, please enter your passphrase when prompted: Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot ./bookworm (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository ./bookworm (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "ppa" components main... Cleaning up prefix "ppa" components main...
Publish for snapshot ppa/maverick [amd64, i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new snapshot. Published snapshot repository ppa/maverick [amd64, i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "ppa" components main... Cleaning up prefix "ppa" components main...
Publish for snapshot ppa/maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new snapshot. Published snapshot repository ppa/maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been successfully switched to new source.

View File

@@ -1 +1 @@
ERROR: unable to update: published repo with storage:prefix/distribution ppa/maverick not found ERROR: unable to switch: published repo with storage:prefix/distribution ppa/maverick not found

View File

@@ -1 +1 @@
ERROR: unable to update: not a snapshot publish ERROR: unable to switch: local repo with name snap1 not found

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components b, c... Cleaning up prefix "." components b, c...
Publish for snapshot ./maverick [amd64, i386, source] publishes {a: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {b: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'}, {c: [local2]: Snapshot from local repo [local-repo]} has been successfully switched to new snapshot. Published snapshot repository ./maverick [amd64, i386, source] publishes {a: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {b: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'}, {c: [local2]: Snapshot from local repo [local-repo]} has been successfully switched to new source.

View File

@@ -7,4 +7,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -4,4 +4,4 @@ Finalizing metadata files...
Signing file 'Release' with gpg, please enter your passphrase when prompted: Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./bookworm [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./bookworm [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo ./maverick [source] publishes {main: [local-repo]} has been successfully updated. Published local repository ./maverick [source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components contrib, main... Cleaning up prefix "." components contrib, main...
Publish for local repo ./maverick [i386, source] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated. Published local repository ./maverick [i386, source] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated.

View File

@@ -3,4 +3,4 @@ Generating metadata files and linking package files...
Finalizing metadata files... Finalizing metadata files...
Cleaning up prefix "." components contrib, main... Cleaning up prefix "." components contrib, main...
Publish for local repo ./squeeze [i386] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated. Published local repository ./squeeze [i386] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for snapshot s3:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. Published snapshot repository s3:test1:./maverick (origin: LP-PPA-gladky-anton-gnuplot) [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new source.

View File

@@ -5,4 +5,4 @@ Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up prefix "." components main... Cleaning up prefix "." components main...
Publish for local repo s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. Published local repository s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated.