From bd64232eb6f2da63a1e314888396ed5d09e1ae04 Mon Sep 17 00:00:00 2001 From: Christoph Fiehe Date: Wed, 9 Oct 2024 07:38:23 +0200 Subject: [PATCH] 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 --- Makefile | 2 +- api/publish.go | 530 ++++++++++++++++++-- api/router.go | 8 +- cmd/publish.go | 17 +- cmd/publish_show.go | 3 +- cmd/publish_snapshot.go | 4 + cmd/publish_source_add.go | 89 ++++ cmd/publish_source_drop.go | 62 +++ cmd/publish_source_list.go | 89 ++++ cmd/publish_source_remove.go | 81 +++ cmd/publish_source_update.go | 86 ++++ cmd/publish_switch.go | 79 +-- cmd/publish_update.go | 14 +- deb/publish.go | 229 ++++++++- docs/index.go | 2 + system/build-deb | 18 - system/lib.py | 58 ++- system/t06_publish/AzurePublish2Test_gold | 2 +- system/t06_publish/AzurePublish3Test_gold | 2 +- system/t06_publish/PublishSwitch11Test_gold | 2 +- system/t06_publish/PublishSwitch13Test_gold | 2 +- system/t06_publish/PublishSwitch14Test_gold | 2 +- system/t06_publish/PublishSwitch15Test_gold | 2 +- system/t06_publish/PublishSwitch16Test_gold | 2 +- system/t06_publish/PublishSwitch1Test_gold | 2 +- system/t06_publish/PublishSwitch2Test_gold | 2 +- system/t06_publish/PublishSwitch3Test_gold | 2 +- system/t06_publish/PublishSwitch4Test_gold | 2 +- system/t06_publish/PublishSwitch5Test_gold | 2 +- system/t06_publish/PublishSwitch6Test_gold | 2 +- system/t06_publish/PublishSwitch8Test_gold | 2 +- system/t06_publish/PublishUpdate10Test_gold | 2 +- system/t06_publish/PublishUpdate11Test_gold | 2 +- system/t06_publish/PublishUpdate12Test_gold | 2 +- system/t06_publish/PublishUpdate13Test_gold | 2 +- system/t06_publish/PublishUpdate14Test_gold | 2 +- system/t06_publish/PublishUpdate1Test_gold | 2 +- system/t06_publish/PublishUpdate2Test_gold | 2 +- system/t06_publish/PublishUpdate3Test_gold | 2 +- system/t06_publish/PublishUpdate4Test_gold | 2 +- system/t06_publish/PublishUpdate7Test_gold | 2 +- system/t06_publish/PublishUpdate8Test_gold | 2 +- system/t06_publish/S3Publish2Test_gold | 2 +- system/t06_publish/S3Publish3Test_gold | 2 +- system/t06_publish/S3Publish6Test_gold | 2 +- 45 files changed, 1248 insertions(+), 179 deletions(-) create mode 100644 cmd/publish_source_add.go create mode 100644 cmd/publish_source_drop.go create mode 100644 cmd/publish_source_list.go create mode 100644 cmd/publish_source_remove.go create mode 100644 cmd/publish_source_update.go delete mode 100755 system/build-deb diff --git a/Makefile b/Makefile index 363b2101..89aceb22 100644 --- a/Makefile +++ b/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 diff --git a/api/publish.go b/api/publish.go index c7aced61..9e04499c 100644 --- a/api/publish.go +++ b/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 + }) +} diff --git a/api/router.go b/api/router.go index d5b6e612..66353fc2 100644 --- a/api/router.go +++ b/api/router.go @@ -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) } { diff --git a/cmd/publish.go b/cmd/publish.go index d74384e0..887929d6 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -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(), }, } } diff --git a/cmd/publish_show.go b/cmd/publish_show.go index d943df3f..a0af446b 100644 --- a/cmd/publish_show.go +++ b/cmd/publish_show.go @@ -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) diff --git a/cmd/publish_snapshot.go b/cmd/publish_snapshot.go index 3d2e43e6..6312c4d4 100644 --- a/cmd/publish_snapshot.go +++ b/cmd/publish_snapshot.go @@ -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) diff --git a/cmd/publish_source_add.go b/cmd/publish_source_add.go new file mode 100644 index 00000000..52e98000 --- /dev/null +++ b/cmd/publish_source_add.go @@ -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 ", + 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 [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_drop.go b/cmd/publish_source_drop.go new file mode 100644 index 00000000..af5c1f84 --- /dev/null +++ b/cmd/publish_source_drop.go @@ -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 ", + 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 [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_list.go b/cmd/publish_source_list.go new file mode 100644 index 00000000..2a915439 --- /dev/null +++ b/cmd/publish_source_list.go @@ -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 ", + 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 [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_remove.go b/cmd/publish_source_remove.go new file mode 100644 index 00000000..6a6136c3 --- /dev/null +++ b/cmd/publish_source_remove.go @@ -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 [[:]] ", + 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 [:]") + cmd.Flag.String("component", "", "component names to remove (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_source_update.go b/cmd/publish_source_update.go new file mode 100644 index 00000000..6fe90c55 --- /dev/null +++ b/cmd/publish_source_update.go @@ -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 ", + 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 [:]") + cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)") + + return cmd +} diff --git a/cmd/publish_switch.go b/cmd/publish_switch.go index 0f1a620b..6d65db72 100644 --- a/cmd/publish_switch.go +++ b/cmd/publish_switch.go @@ -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 [[:]] ", - Short: "update published repository by switching to new snapshot", + UsageLine: "switch [[:]] ", + 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 diff --git a/cmd/publish_update.go b/cmd/publish_update.go index 28de8c67..decd4e2b 100644 --- a/cmd/publish_update.go +++ b/cmd/publish_update.go @@ -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 } diff --git a/deb/publish.go b/deb/publish.go index 79c47cb8..e5472093 100644 --- a/deb/publish.go +++ b/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 diff --git a/docs/index.go b/docs/index.go index 5604656f..da325d03 100644 --- a/docs/index.go +++ b/docs/index.go @@ -1 +1,3 @@ package docs + +import _ "github.com/swaggo/swag" // make sure swag is in go.mod diff --git a/system/build-deb b/system/build-deb deleted file mode 100755 index 3c70e0b0..00000000 --- a/system/build-deb +++ /dev/null @@ -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 " 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 -' diff --git a/system/lib.py b/system/lib.py index e162b964..6f76213f 100644 --- a/system/lib.py +++ b/system/lib.py @@ -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 = '' diff --git a/system/t06_publish/AzurePublish2Test_gold b/system/t06_publish/AzurePublish2Test_gold index d9fa9ada..ea8cafb9 100644 --- a/system/t06_publish/AzurePublish2Test_gold +++ b/system/t06_publish/AzurePublish2Test_gold @@ -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. diff --git a/system/t06_publish/AzurePublish3Test_gold b/system/t06_publish/AzurePublish3Test_gold index fbcd2e79..c1855169 100644 --- a/system/t06_publish/AzurePublish3Test_gold +++ b/system/t06_publish/AzurePublish3Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch11Test_gold b/system/t06_publish/PublishSwitch11Test_gold index 2385a0ed..9d87127b 100644 --- a/system/t06_publish/PublishSwitch11Test_gold +++ b/system/t06_publish/PublishSwitch11Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch13Test_gold b/system/t06_publish/PublishSwitch13Test_gold index 3f440f06..271997f3 100644 --- a/system/t06_publish/PublishSwitch13Test_gold +++ b/system/t06_publish/PublishSwitch13Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch14Test_gold b/system/t06_publish/PublishSwitch14Test_gold index 9b8ce8f1..1cdaf085 100644 --- a/system/t06_publish/PublishSwitch14Test_gold +++ b/system/t06_publish/PublishSwitch14Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch15Test_gold b/system/t06_publish/PublishSwitch15Test_gold index 3f440f06..271997f3 100644 --- a/system/t06_publish/PublishSwitch15Test_gold +++ b/system/t06_publish/PublishSwitch15Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch16Test_gold b/system/t06_publish/PublishSwitch16Test_gold index 122b2882..d3d1cd5b 100644 --- a/system/t06_publish/PublishSwitch16Test_gold +++ b/system/t06_publish/PublishSwitch16Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch1Test_gold b/system/t06_publish/PublishSwitch1Test_gold index 3f440f06..271997f3 100644 --- a/system/t06_publish/PublishSwitch1Test_gold +++ b/system/t06_publish/PublishSwitch1Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch2Test_gold b/system/t06_publish/PublishSwitch2Test_gold index c8464423..7cc00d44 100644 --- a/system/t06_publish/PublishSwitch2Test_gold +++ b/system/t06_publish/PublishSwitch2Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch3Test_gold b/system/t06_publish/PublishSwitch3Test_gold index 3f440f06..271997f3 100644 --- a/system/t06_publish/PublishSwitch3Test_gold +++ b/system/t06_publish/PublishSwitch3Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch4Test_gold b/system/t06_publish/PublishSwitch4Test_gold index 7256431a..899c3d53 100644 --- a/system/t06_publish/PublishSwitch4Test_gold +++ b/system/t06_publish/PublishSwitch4Test_gold @@ -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. diff --git a/system/t06_publish/PublishSwitch5Test_gold b/system/t06_publish/PublishSwitch5Test_gold index c28babdc..498bf248 100644 --- a/system/t06_publish/PublishSwitch5Test_gold +++ b/system/t06_publish/PublishSwitch5Test_gold @@ -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 diff --git a/system/t06_publish/PublishSwitch6Test_gold b/system/t06_publish/PublishSwitch6Test_gold index bb45822f..9253c44d 100644 --- a/system/t06_publish/PublishSwitch6Test_gold +++ b/system/t06_publish/PublishSwitch6Test_gold @@ -1 +1 @@ -ERROR: unable to update: not a snapshot publish +ERROR: unable to switch: local repo with name snap1 not found diff --git a/system/t06_publish/PublishSwitch8Test_gold b/system/t06_publish/PublishSwitch8Test_gold index d9870ff9..a10e335f 100644 --- a/system/t06_publish/PublishSwitch8Test_gold +++ b/system/t06_publish/PublishSwitch8Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate10Test_gold b/system/t06_publish/PublishUpdate10Test_gold index bda2f4dd..082275a3 100644 --- a/system/t06_publish/PublishUpdate10Test_gold +++ b/system/t06_publish/PublishUpdate10Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate11Test_gold b/system/t06_publish/PublishUpdate11Test_gold index 72e92234..05b56b2a 100644 --- a/system/t06_publish/PublishUpdate11Test_gold +++ b/system/t06_publish/PublishUpdate11Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate12Test_gold b/system/t06_publish/PublishUpdate12Test_gold index 3b7a5709..7488eec4 100644 --- a/system/t06_publish/PublishUpdate12Test_gold +++ b/system/t06_publish/PublishUpdate12Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate13Test_gold b/system/t06_publish/PublishUpdate13Test_gold index 72e92234..05b56b2a 100644 --- a/system/t06_publish/PublishUpdate13Test_gold +++ b/system/t06_publish/PublishUpdate13Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate14Test_gold b/system/t06_publish/PublishUpdate14Test_gold index 6bd929ca..e0d3ab8c 100644 --- a/system/t06_publish/PublishUpdate14Test_gold +++ b/system/t06_publish/PublishUpdate14Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate1Test_gold b/system/t06_publish/PublishUpdate1Test_gold index 72e92234..05b56b2a 100644 --- a/system/t06_publish/PublishUpdate1Test_gold +++ b/system/t06_publish/PublishUpdate1Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate2Test_gold b/system/t06_publish/PublishUpdate2Test_gold index 72e92234..05b56b2a 100644 --- a/system/t06_publish/PublishUpdate2Test_gold +++ b/system/t06_publish/PublishUpdate2Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate3Test_gold b/system/t06_publish/PublishUpdate3Test_gold index 72e92234..05b56b2a 100644 --- a/system/t06_publish/PublishUpdate3Test_gold +++ b/system/t06_publish/PublishUpdate3Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate4Test_gold b/system/t06_publish/PublishUpdate4Test_gold index dce245d5..29502a02 100644 --- a/system/t06_publish/PublishUpdate4Test_gold +++ b/system/t06_publish/PublishUpdate4Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate7Test_gold b/system/t06_publish/PublishUpdate7Test_gold index 813d7055..a13b260c 100644 --- a/system/t06_publish/PublishUpdate7Test_gold +++ b/system/t06_publish/PublishUpdate7Test_gold @@ -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. diff --git a/system/t06_publish/PublishUpdate8Test_gold b/system/t06_publish/PublishUpdate8Test_gold index 09aa9845..1a6ebd26 100644 --- a/system/t06_publish/PublishUpdate8Test_gold +++ b/system/t06_publish/PublishUpdate8Test_gold @@ -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. diff --git a/system/t06_publish/S3Publish2Test_gold b/system/t06_publish/S3Publish2Test_gold index 12c9c0e6..d6ffd382 100644 --- a/system/t06_publish/S3Publish2Test_gold +++ b/system/t06_publish/S3Publish2Test_gold @@ -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. diff --git a/system/t06_publish/S3Publish3Test_gold b/system/t06_publish/S3Publish3Test_gold index 7d609f0f..54c8a83e 100644 --- a/system/t06_publish/S3Publish3Test_gold +++ b/system/t06_publish/S3Publish3Test_gold @@ -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. diff --git a/system/t06_publish/S3Publish6Test_gold b/system/t06_publish/S3Publish6Test_gold index 12c9c0e6..d6ffd382 100644 --- a/system/t06_publish/S3Publish6Test_gold +++ b/system/t06_publish/S3Publish6Test_gold @@ -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.