mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-01-11 03:11:50 +00:00
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:
committed by
André Roth
parent
767bc6bd0b
commit
bd64232eb6
2
Makefile
2
Makefile
@@ -59,7 +59,7 @@ flake8: ## run flake8 on system test python files
|
||||
|
||||
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
|
||||
@PATH=$(BINPATH)/:$(PATH) golangci-lint run
|
||||
|
||||
|
||||
530
api/publish.go
530
api/publish.go
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
@@ -13,17 +14,29 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SigningOptions is a shared between publish API GPG options structure
|
||||
type SigningOptions struct {
|
||||
Skip bool
|
||||
GpgKey string
|
||||
Keyring string
|
||||
SecretKeyring string
|
||||
Passphrase string
|
||||
PassphraseFile string
|
||||
type signingParams struct {
|
||||
// Don't sign published repository
|
||||
Skip bool ` json:"Skip"`
|
||||
// GPG key ID to use when signing the release, if not specified default key is used
|
||||
GpgKey string ` json:"GpgKey"`
|
||||
// GPG keyring to use (instead of default)
|
||||
Keyring string ` json:"Keyring"`
|
||||
// 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 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -54,10 +67,10 @@ func slashEscape(path string) string {
|
||||
return result
|
||||
}
|
||||
|
||||
// @Summary Get publish points
|
||||
// @Description Get list of available publish points. Each publish point is returned as in “show” API.
|
||||
// @Summary List published repositories
|
||||
// @Description **Get a list of all published repositories**
|
||||
// @Tags Publish
|
||||
// @Produce json
|
||||
// @Produce json
|
||||
// @Success 200 {array} deb.PublishedRepo
|
||||
// @Failure 500 {object} Error "Internal Error"
|
||||
// @Router /api/publish [get]
|
||||
@@ -79,37 +92,97 @@ func apiPublishList(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 500, err)
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, result)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// POST /publish/:prefix
|
||||
func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
// @Summary Show published repository
|
||||
// @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"))
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
distribution := parseEscapedPath(c.Params.ByName("distribution"))
|
||||
|
||||
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
|
||||
SkipBz2 *bool
|
||||
Architectures []string
|
||||
Signing SigningOptions
|
||||
AcquireByHash *bool
|
||||
MultiDist bool
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.PublishedRepoCollection()
|
||||
|
||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err))
|
||||
return
|
||||
}
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to show: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, published)
|
||||
}
|
||||
|
||||
type publishedRepoCreateParams struct {
|
||||
// 'local' for local repositories and 'snapshot' for snapshots
|
||||
SourceKind string `binding:"required" json:"SourceKind" example:"snapshot"`
|
||||
// List of 'Component/Name' objects, 'Name' is either local repository or snapshot name
|
||||
Sources []sourceParams `binding:"required" json:"Sources"`
|
||||
// Distribution name, if missing Aptly would try to guess from sources
|
||||
Distribution string ` json:"Distribution"`
|
||||
// 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 {
|
||||
return
|
||||
@@ -119,12 +192,12 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
|
||||
signer, err := getSigner(&b.Signing)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -145,12 +218,11 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
|
||||
snapshot, err = snapshotCollection.ByName(source.Name)
|
||||
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
|
||||
}
|
||||
|
||||
resources = append(resources, string(snapshot.ResourceKey()))
|
||||
|
||||
sources = append(sources, snapshot)
|
||||
}
|
||||
} else if b.SourceKind == deb.SourceLocalRepo {
|
||||
@@ -164,18 +236,24 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
|
||||
localRepo, err = localCollection.ByName(source.Name)
|
||||
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
|
||||
}
|
||||
|
||||
resources = append(resources, string(localRepo.Key()))
|
||||
sources = append(sources, localRepo)
|
||||
}
|
||||
} else {
|
||||
AbortWithJSONError(c, 400, fmt.Errorf("unknown SourceKind"))
|
||||
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unknown SourceKind"))
|
||||
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) {
|
||||
taskDetail := task.PublishDetail{
|
||||
Detail: detail,
|
||||
@@ -233,7 +311,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
published.AcquireByHash = *b.AcquireByHash
|
||||
}
|
||||
|
||||
collection := collectionFactory.PublishedRepoCollection()
|
||||
if b.MultiDist != nil {
|
||||
published.MultiDist = *b.MultiDist
|
||||
}
|
||||
|
||||
duplicate := collection.CheckDuplicate(published)
|
||||
if duplicate != nil {
|
||||
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) {
|
||||
param := slashEscape(c.Params.ByName("prefix"))
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
@@ -262,7 +354,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
|
||||
var b struct {
|
||||
ForceOverwrite bool
|
||||
Signing SigningOptions
|
||||
Signing signingParams
|
||||
SkipContents *bool
|
||||
SkipBz2 *bool
|
||||
SkipCleanup *bool
|
||||
@@ -280,7 +372,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
|
||||
signer, err := getSigner(&b.Signing)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -290,12 +382,13 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
|
||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
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
|
||||
}
|
||||
|
||||
var updatedComponents []string
|
||||
var updatedSnapshots []string
|
||||
|
||||
if published.SourceKind == deb.SourceLocalRepo {
|
||||
if len(b.Snapshots) > 0 {
|
||||
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
|
||||
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) {
|
||||
err = collection.LoadComplete(published, collectionFactory)
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
updatedComponents := result.UpdatedComponents()
|
||||
removedComponents := result.RemovedComponents()
|
||||
|
||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||
err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents,
|
||||
context.GetPublishedStorage(storage), collectionFactory, out)
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
force := c.Request.URL.Query().Get("force") == "1"
|
||||
skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1"
|
||||
|
||||
param := slashEscape(c.Params.ByName("prefix"))
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
distribution := c.Params.ByName("distribution")
|
||||
distribution := parseEscapedPath(c.Params.ByName("distribution"))
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
collection := collectionFactory.PublishedRepoCollection()
|
||||
@@ -404,7 +529,7 @@ func apiPublishDrop(c *gin.Context) {
|
||||
|
||||
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) {
|
||||
err := collection.Remove(context, storage, prefix, distribution,
|
||||
collectionFactory, out, force, skipCleanup)
|
||||
@@ -415,3 +540,304 @@ func apiPublishDrop(c *gin.Context) {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -185,12 +185,18 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
api.GET("/publish", apiPublishList)
|
||||
api.GET("/publish/:prefix/:distribution", apiPublishShow)
|
||||
api.POST("/publish", apiPublishRepoOrSnapshot)
|
||||
api.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
|
||||
api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
|
||||
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)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -34,10 +34,25 @@ func makeCmdPublish() *commander.Command {
|
||||
makeCmdPublishDrop(),
|
||||
makeCmdPublishList(),
|
||||
makeCmdPublishRepo(),
|
||||
makeCmdPublishShow(),
|
||||
makeCmdPublishSnapshot(),
|
||||
makeCmdPublishSource(),
|
||||
makeCmdPublishSwitch(),
|
||||
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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ func aptlyPublishShowTxt(_ *commander.Command, args []string) error {
|
||||
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, " "))
|
||||
|
||||
fmt.Printf("Sources:\n")
|
||||
for component, sourceID := range repo.Sources {
|
||||
for _, component := range repo.Components() {
|
||||
sourceID := repo.Sources[component]
|
||||
var name string
|
||||
if repo.SourceKind == deb.SourceSnapshot {
|
||||
source, e := collectionFactory.SnapshotCollection().ByUUID(sourceID)
|
||||
|
||||
@@ -150,6 +150,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
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)
|
||||
if duplicate != nil {
|
||||
collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
||||
|
||||
89
cmd/publish_source_add.go
Normal file
89
cmd/publish_source_add.go
Normal 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
|
||||
}
|
||||
62
cmd/publish_source_drop.go
Normal file
62
cmd/publish_source_drop.go
Normal 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
|
||||
}
|
||||
89
cmd/publish_source_list.go
Normal file
89
cmd/publish_source_list.go
Normal 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
|
||||
}
|
||||
81
cmd/publish_source_remove.go
Normal file
81
cmd/publish_source_remove.go
Normal 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
|
||||
}
|
||||
86
cmd/publish_source_update.go
Normal file
86
cmd/publish_source_update.go
Normal 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
|
||||
}
|
||||
@@ -5,12 +5,16 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
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(), ",")
|
||||
|
||||
@@ -22,11 +26,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
distribution := args[0]
|
||||
param := "."
|
||||
|
||||
var (
|
||||
names []string
|
||||
snapshot *deb.Snapshot
|
||||
)
|
||||
|
||||
if len(args) == len(components)+2 {
|
||||
param = args[1]
|
||||
names = args[2:]
|
||||
@@ -41,16 +40,12 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
published, err = collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
if published.SourceKind != deb.SourceSnapshot {
|
||||
return fmt.Errorf("unable to update: not a snapshot publish")
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
|
||||
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
for i, component := range components {
|
||||
snapshot, err = collectionFactory.SnapshotCollection().ByName(names[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
if published.SourceKind == deb.SourceLocalRepo {
|
||||
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||
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)
|
||||
}
|
||||
|
||||
err = collectionFactory.SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
localRepo, err := localRepoCollection.ByName(names[i])
|
||||
if err != nil {
|
||||
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())
|
||||
@@ -114,11 +137,11 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components,
|
||||
context.GetPublishedStorage(storage), collectionFactory, context.Progress())
|
||||
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
|
||||
}
|
||||
@@ -126,15 +149,15 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
func makeCmdPublishSwitch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSwitch,
|
||||
UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-snapshot>",
|
||||
Short: "update published repository by switching to new snapshot",
|
||||
UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-source>",
|
||||
Short: "update published repository by switching to new source",
|
||||
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,
|
||||
component).
|
||||
|
||||
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.:
|
||||
|
||||
aptly publish switch -component=main,contrib wheezy wh-main wh-contrib
|
||||
|
||||
@@ -31,18 +31,14 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
components := published.Components()
|
||||
for _, component := range components {
|
||||
published.UpdateLocalRepo(component)
|
||||
result, err := published.Update(collectionFactory, context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
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)
|
||||
if !skipCleanup {
|
||||
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components,
|
||||
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, result.UpdatedComponents(),
|
||||
context.GetPublishedStorage(storage), collectionFactory, context.Progress())
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
229
deb/publish.go
229
deb/publish.go
@@ -21,6 +21,12 @@ import (
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
)
|
||||
|
||||
type PublishedRepoUpdateResult struct {
|
||||
AddedSources map[string]string
|
||||
UpdatedSources map[string]string
|
||||
RemovedSources map[string]string
|
||||
}
|
||||
|
||||
type repoSourceItem struct {
|
||||
// Pointer to snapshot if SourceKind == "snapshot"
|
||||
snapshot *Snapshot
|
||||
@@ -73,6 +79,168 @@ type PublishedRepo struct {
|
||||
|
||||
// Support multiple distributions
|
||||
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
|
||||
@@ -281,14 +449,42 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MarshalJSON requires object to filled by "LoadShallow" or "LoadComplete"
|
||||
func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
||||
type sourceInfo struct {
|
||||
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{}
|
||||
for component, item := range p.sourceItems {
|
||||
for _, component := range p.Components() {
|
||||
item := p.sourceItems[component]
|
||||
name := ""
|
||||
if item.snapshot != nil {
|
||||
name = item.snapshot.Name
|
||||
@@ -444,20 +640,25 @@ func (p *PublishedRepo) SourceNames() []string {
|
||||
return sources
|
||||
}
|
||||
|
||||
// UpdateLocalRepo updates content from local repo in component
|
||||
func (p *PublishedRepo) UpdateLocalRepo(component string) {
|
||||
// UpdateLocalRepo inserts/updates local repository source for component
|
||||
func (p *PublishedRepo) UpdateLocalRepo(component string, localRepo *LocalRepo) {
|
||||
if p.SourceKind != SourceLocalRepo {
|
||||
panic("not local repo publish")
|
||||
}
|
||||
|
||||
item := p.sourceItems[component]
|
||||
item.packageRefs = item.localRepo.RefList()
|
||||
item, exists := p.sourceItems[component]
|
||||
if !exists {
|
||||
item = repoSourceItem{}
|
||||
}
|
||||
item.localRepo = localRepo
|
||||
item.packageRefs = localRepo.RefList()
|
||||
p.sourceItems[component] = item
|
||||
|
||||
p.Sources[component] = localRepo.UUID
|
||||
p.rePublishing = true
|
||||
}
|
||||
|
||||
// UpdateSnapshot switches snapshot for component
|
||||
// UpdateSnapshot inserts/updates snapshot source for component
|
||||
func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) {
|
||||
if p.SourceKind != SourceSnapshot {
|
||||
panic("not snapshot publish")
|
||||
@@ -474,6 +675,14 @@ func (p *PublishedRepo) UpdateSnapshot(component string, snapshot *Snapshot) {
|
||||
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
|
||||
func (p *PublishedRepo) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
package docs
|
||||
|
||||
import _ "github.com/swaggo/swag" // make sure swag is in go.mod
|
||||
|
||||
@@ -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
|
||||
'
|
||||
@@ -319,40 +319,36 @@ class BaseTest(object):
|
||||
return subprocess.Popen(command, stderr=stderr, stdout=stdout, env=environ)
|
||||
|
||||
def run_cmd(self, command, expected_code=0):
|
||||
try:
|
||||
proc = self._start_process(command, stdout=subprocess.PIPE)
|
||||
raw_output, _ = proc.communicate()
|
||||
proc = self._start_process(command, stdout=subprocess.PIPE)
|
||||
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]
|
||||
is_aptly_command = False
|
||||
if isinstance(command, str):
|
||||
is_aptly_command = command.startswith("aptly")
|
||||
returncodes = [proc.returncode]
|
||||
is_aptly_command = False
|
||||
if isinstance(command, str):
|
||||
is_aptly_command = command.startswith("aptly")
|
||||
|
||||
if isinstance(command, list):
|
||||
is_aptly_command = command[0] == "aptly"
|
||||
if isinstance(command, list):
|
||||
is_aptly_command = command[0] == "aptly"
|
||||
|
||||
if is_aptly_command:
|
||||
# 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
|
||||
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output)
|
||||
if not matches:
|
||||
raise Exception("no matches found in output '%s'" % raw_output)
|
||||
if is_aptly_command:
|
||||
# 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
|
||||
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output)
|
||||
if not matches:
|
||||
raise Exception("no matches found in command output '%s'" % raw_output)
|
||||
|
||||
output, _, returncode = matches[0]
|
||||
returncodes.append(int(returncode))
|
||||
else:
|
||||
output = raw_output
|
||||
output, _, returncode = matches[0]
|
||||
returncodes.append(int(returncode))
|
||||
else:
|
||||
output = raw_output
|
||||
|
||||
if expected_code is not None:
|
||||
if expected_code not in returncodes:
|
||||
raise Exception("exit code %d != %d (output: %s)" % (
|
||||
proc.returncode, expected_code, raw_output))
|
||||
return output
|
||||
except Exception as e:
|
||||
raise Exception("Running command '%s' failed: %s" %
|
||||
(command, str(e)))
|
||||
if expected_code is not None:
|
||||
if expected_code not in returncodes:
|
||||
raise Exception("command expected to return %d, but returned %d: \n%s" % (
|
||||
expected_code, proc.returncode, raw_output))
|
||||
return output
|
||||
|
||||
def gold_processor(self, gold):
|
||||
return gold
|
||||
@@ -379,6 +375,8 @@ class BaseTest(object):
|
||||
return s
|
||||
|
||||
def check_output(self):
|
||||
gold_file = self.get_gold_filename()
|
||||
print(f"Verifying gold file: {gold_file}")
|
||||
try:
|
||||
self.verify_match(self.get_gold(), self.output,
|
||||
match_prepare=self.outputMatchPrepare)
|
||||
@@ -464,11 +462,11 @@ class BaseTest(object):
|
||||
|
||||
def check_in(self, item, 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):
|
||||
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):
|
||||
diff = ''
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -4,4 +4,4 @@ Finalizing metadata files...
|
||||
Signing 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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ERROR: unable to update: not a snapshot publish
|
||||
ERROR: unable to switch: local repo with name snap1 not found
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -4,4 +4,4 @@ Finalizing metadata files...
|
||||
Signing 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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -3,4 +3,4 @@ Generating metadata files and linking package files...
|
||||
Finalizing metadata files...
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user