diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17f28d64..7e3c40a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,7 +142,7 @@ jobs: - name: "Install packages" run: | apt-get update - apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang binutils-i686-linux-gnu binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf jq + apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang binutils-i686-linux-gnu binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf jq bash-completion git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: "Checkout repository" diff --git a/.github/workflows/scripts/upload-artifacts.sh b/.github/workflows/scripts/upload-artifacts.sh index e41697df..e81f2e0b 100755 --- a/.github/workflows/scripts/upload-artifacts.sh +++ b/.github/workflows/scripts/upload-artifacts.sh @@ -67,6 +67,44 @@ cleanup() { } trap cleanup EXIT +wait_task() +{ + _id=$1 + _success=0 + for t in `seq 180` + do + jsonret=`curl -fsS -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_id` + _state=`echo $jsonret | jq .State` + if [ "$_state" = "2" ]; then + _success=1 + curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_id + break + fi + if [ "$_state" = "3" ]; then + echo Error: task failed + return 1 + fi + sleep 1 + done + if [ "$_success" -ne 1 ]; then + echo Error: task timeout + return 1 + fi + return 0 +} + +add_packages() { + _aptly_repository=$1 + _folder=$2 + jsonret=`curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$_aptly_repository/file/$_folder?_async=true` + _task_id=`echo $jsonret | jq .ID` + wait_task $_task_id + if [ "$?" -ne 0 ]; then + echo "Error: adding packages to $_aptly_repository failed" + exit 1 + fi +} + update_publish() { _publish=$1 _dist=$2 @@ -75,24 +113,9 @@ update_publish() { "Signing": {"Batch": true, "Keyring": "aptly.repo/aptly.pub", "secretKeyring": "aptly.repo/aptly.sec", "PassphraseFile": "aptly.repo/passphrase"}}' \ -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$_publish/$_dist?_async=true` _task_id=`echo $jsonret | jq .ID` - _success=0 - for t in `seq 180` - do - jsonret=`curl -fsS -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_task_id` - _state=`echo $jsonret | jq .State` - if [ "$_state" = "2" ]; then - _success=1 - curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_task_id - break - fi - if [ "$_state" = "3" ]; then - echo Error: publish failed - exit 1 - fi - sleep 1 - done - if [ "$_success" -ne 1 ]; then - echo "Error: publish failed (timeout)" + wait_task $_task_id + if [ "$?" -ne 0 ]; then + echo "Error: publish failed" exit 1 fi } @@ -114,7 +137,7 @@ fi upload echo "\nAdding packages to $aptly_repository ..." -jsonret=`curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder` +add_packages $aptly_repository $folder echo "\nUpdating published repo $aptly_published ..." update_publish $aptly_published $dist diff --git a/AUTHORS b/AUTHORS index 70a75464..0abdc0b0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -60,6 +60,8 @@ List of contributors, in chronological order: * Nic Waller (https://github.com/sf-nwaller) * iofq (https://github.com/iofq) * Noa Resare (https://github.com/nresare) -* Ramón N.Rodriguez (https://github.com/runitonmetal) +* Ramon N.Rodriguez (https://github.com/runitonmetal) * Golf Hu (https://github.com/hudeng-go) * Cookie Fei (https://github.com/wuhuang26) +* Andrey Loukhnov (https://github.com/aol-nnov) +* Christoph Fiehe (https://github.com/cfiehe) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edebdf22..d4ab7e55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,22 +68,13 @@ following [PR template](.github/PULL_REQUEST_TEMPLATE.md). Make sure that purpose of your change is clear, all the tests and checks pass, and all new code is covered with tests if that is possible. -### Forking and Cloning - -As aptly is using Go modules, aptly repository could be cloned to any location on the file system: - - git clone git@github.com:aptly-dev/aptly.git - cd aptly - -For main repo under your GitHub user and add it as another Git remote: - - git remote add git@github.com:/aptly.git - -That way you can continue to build project as is (you don't need to adjust import paths), but you would need -to specify your remote name when pushing branches: - - git push +### Get the Source +To clone the git repo, run the following commands: +``` +git clone git@github.com:aptly-dev/aptly.git +cd aptly +``` ## Development Setup diff --git a/Makefile b/Makefile index 363b2101..27ccdde1 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 @@ -100,7 +100,7 @@ serve: prepare swagger-install ## Run development server (auto recompiling) test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3 cp debian/aptly.conf ~/.aptly.conf sed -i /enableSwaggerEndpoint/s/false/true/ ~/.aptly.conf - PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,cmd,systemd -- api serve -listen 0.0.0.0:3142 + PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142 dpkg: prepare swagger ## Build debian packages @test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1) diff --git a/README.rst b/README.rst index 1b09a88e..a53990db 100644 --- a/README.rst +++ b/README.rst @@ -39,8 +39,8 @@ Current limitations: * translations are not supported yet -Download --------- +Install Stable Version +----------------------- To install aptly on Debian/Ubuntu, add new repository to ``/etc/apt/sources.list``:: @@ -58,19 +58,28 @@ After that you can install aptly as any other software package:: Don't worry about squeeze part in repo name: aptly package should work on Debian squeeze+, Ubuntu 10.0+. Package contains aptly binary, man page and bash completion. -If you would like to use nightly builds (unstable), please use following repository:: - - deb http://repo.aptly.info/ nightly main +Other Binaries +~~~~~~~~~~~~~~~~~ Binary executables (depends almost only on libc) are available for download from `GitHub Releases `_. -If you have Go environment set up, you can build aptly from source by running (go 1.14+ required):: +Install CI Version +-------------------- - git clone https://github.com/aptly-dev/aptly - cd aptly - make modules install +More recent versions are available as CI builds (development, might be unstable). -Binary would be installed to ``$GOPATH/bin/aptly``. +Debian GNU/Linux +~~~~~~~~~~~~~~~~~ + +Install the following APT key:: + + sudo wget -O /etc/apt/keyrings/aptly.asc https://www.aptly.info/pubkey.txt + +Define CI APT sources in ``/etc/apt/sources.list.d/aptly-ci.list``:: + + deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/ci DIST main + +Where DIST is one of: ``buster``, ``bullseye``, ``bookworm``, ``focal``, ``jammy``, ``noble`` Contributing ------------ diff --git a/api/publish.go b/api/publish.go index 9873cd58..b2f4ffad 100644 --- a/api/publish.go +++ b/api/publish.go @@ -13,17 +13,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" example:"false"` + // GPG key ID to use when signing the release, if not specified default key is used + GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"` + // GPG keyring to use (instead of default) + Keyring string ` json:"Keyring" example:"trustedkeys.gpg"` + // GPG secret keyring to use (instead of default) Note: depreciated with gpg2 + SecretKeyring string ` json:"SecretKeyring" example:""` + // GPG passphrase to unlock private key (possibly insecure) + Passphrase string ` json:"Passphrase" example:"verysecure"` + // GPG passphrase file to unlock private key (possibly insecure) + PassphraseFile string ` json:"PassphraseFile" example:"/etc/aptly.passphrase"` } -func getSigner(options *SigningOptions) (pgp.Signer, error) { +type sourceParams struct { + // Name of the component + Component string `binding:"required" json:"Component" example:"main"` + // Name of the local repository/snapshot + Name string `binding:"required" json:"Name" example:"snap1"` +} + +func getSigner(options *signingParams) (pgp.Signer, error) { if options.Skip { return nil, nil } @@ -54,10 +66,14 @@ 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 list of published repositories** +// @Description +// @Description Return list of published repositories including detailed information. +// @Description +// @Description See also: `aptly publish list` // @Tags Publish -// @Produce json +// @Produce json // @Success 200 {array} deb.PublishedRepo // @Failure 500 {object} Error "Internal Error" // @Router /api/publish [get] @@ -65,7 +81,7 @@ func apiPublishList(c *gin.Context) { collectionFactory := context.NewCollectionFactory() collection := collectionFactory.PublishedRepoCollection() - result := make([]*deb.PublishedRepo, 0, collection.Len()) + repos := make([]*deb.PublishedRepo, 0, collection.Len()) err := collection.ForEach(func(repo *deb.PublishedRepo) error { err := collection.LoadShallow(repo, collectionFactory) @@ -73,65 +89,142 @@ func apiPublishList(c *gin.Context) { return err } - result = append(result, repo) + repos = append(repos, repo) return nil }) if err != nil { - AbortWithJSONError(c, 500, err) + AbortWithJSONError(c, http.StatusInternalServerError, err) return } - c.JSON(200, result) + c.JSON(http.StatusOK, repos) } -// POST /publish/:prefix -func apiPublishRepoOrSnapshot(c *gin.Context) { +// @Summary Show Published Repository +// @Description **Get published repository information** +// @Description +// @Description Show detailed information of a published repository. +// @Description +// @Description See also: `aptly publish show` +// @Tags Publish +// @Produce json +// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambigious in URLs" +// @Param distribution path string true "distribution name" +// @Success 200 {object} deb.PublishedRepo +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution} [get] +func apiPublishShow(c *gin.Context) { param := slashEscape(c.Params.ByName("prefix")) storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) - 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" example:"bookworm"` + // Value of Label: field in published repository stanza + Label string ` json:"Label" example:""` + // Value of Origin: field in published repository stanza + Origin string ` json:"Origin" example:""` + // when publishing, overwrite files in pool/ directory without notice + ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` + // Override list of published architectures + Architectures []string ` json:"Architectures" example:"amd64,armhf"` + // GPG options + Signing signingParams ` json:"Signing"` + // Setting to yes indicates to the package manager to not install or upgrade packages from the repository without user consent + NotAutomatic string ` json:"NotAutomatic" example:""` + // setting to yes excludes upgrades from the NotAutomic setting + ButAutomaticUpgrades string ` json:"ButAutomaticUpgrades" example:""` + // Don't generate contents indexes + SkipContents *bool ` json:"SkipContents" example:"false"` + // Don't remove unreferenced files in prefix/component + SkipCleanup *bool ` json:"SkipCleanup" example:"false"` + // Skip bz2 compression for index files + SkipBz2 *bool ` json:"SkipBz2" example:"false"` + // Provide index files by hash + AcquireByHash *bool ` json:"AcquireByHash" example:"false"` + // Enable multiple packages with the same filename in different distributions + MultiDist *bool ` json:"MultiDist" example:"false"` +} + +// @Summary Create Published Repository +// @Description **Publish a local repository or snapshot** +// @Description +// @Description Create a published repository. +// @Description +// @Description The prefix may contain a storage specifier, e.g. `s3:packages/`, or it may also be empty to publish to the root directory. +// @Description +// @Description See also: `aptly publish create` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Consume json +// @Param request body publishedRepoCreateParams true "Parameters" +// @Produce json +// @Success 201 {object} deb.PublishedRepo +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Source not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix} [post] +func apiPublishRepoOrSnapshot(c *gin.Context) { + var ( + b publishedRepoCreateParams + components []string + names []string + sources []interface{} + resources []string + ) + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + if c.Bind(&b) != nil { return } b.Distribution = utils.SanitizePath(b.Distribution) + var archs []string + for _, arch := range b.Architectures { + archs = append(archs, utils.SanitizePath(arch)) + } + b.Architectures = archs + signer, err := getSigner(&b.Signing) if err != nil { - AbortWithJSONError(c, 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: sources are empty")) return } - var components []string - var names []string - var sources []interface{} - var resources []string collectionFactory := context.NewCollectionFactory() if b.SourceKind == deb.SourceSnapshot { @@ -145,17 +238,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())) - err = snapshotCollection.LoadComplete(snapshot) - if err != nil { - AbortWithJSONError(c, 500, fmt.Errorf("unable to publish: %s", err)) - return - } - sources = append(sources, snapshot) } } else if b.SourceKind == deb.SourceLocalRepo { @@ -169,33 +256,27 @@ 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())) - err = localCollection.LoadComplete(localRepo) - if err != nil { - AbortWithJSONError(c, 500, fmt.Errorf("unable to publish: %s", err)) - } - sources = append(sources, localRepo) } } else { - AbortWithJSONError(c, 400, fmt.Errorf("unknown SourceKind")) + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unknown SourceKind")) return } - published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, b.MultiDist) - if err != nil { - AbortWithJSONError(c, 500, fmt.Errorf("unable to publish: %s", err)) - return + multiDist := false + if b.MultiDist != nil { + multiDist = *b.MultiDist } - resources = append(resources, string(published.Key())) collection := collectionFactory.PublishedRepoCollection() - 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, param, b.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`)) maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) { taskDetail := task.PublishDetail{ Detail: detail, @@ -205,6 +286,29 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { PublishDetail: taskDetail, } + for _, source := range sources { + switch s := source.(type) { + case *deb.Snapshot: + snapshotCollection := collectionFactory.SnapshotCollection() + err = snapshotCollection.LoadComplete(s) + case *deb.LocalRepo: + localCollection := collectionFactory.LocalRepoCollection() + err = localCollection.LoadComplete(s) + default: + err = fmt.Errorf("unexpected type for source: %T", source) + } + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) + } + } + + published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, multiDist) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) + } + + resources = append(resources, string(published.Key())) + if b.Origin != "" { published.Origin = b.Origin } @@ -236,7 +340,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate) } - err := published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite) + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) } @@ -250,25 +354,55 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { }) } -// PUT /publish/:prefix/:distribution +type publishedRepoUpdateSwitchParams struct { + // when publishing, overwrite files in pool/ directory without notice + ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` + // GPG options + Signing signingParams ` json:"Signing"` + // Don't generate contents indexes + SkipContents *bool ` json:"SkipContents" example:"false"` + // Skip bz2 compression for index files + SkipBz2 *bool ` json:"SkipBz2" example:"false"` + // Don't remove unreferenced files in prefix/component + SkipCleanup *bool ` json:"SkipCleanup" example:"false"` + // only when updating published snapshots, list of objects 'Component/Name' + Snapshots []sourceParams ` json:"Snapshots"` + // Provide index files by hash + AcquireByHash *bool ` json:"AcquireByHash" example:"false"` + // Enable multiple packages with the same filename in different distributions + MultiDist *bool ` json:"MultiDist" example:"false"` +} + +// @Summary Update Published Repository +// @Description **Update a published repository** +// @Description +// @Description Update a published local repository or switch snapshot. +// @Description +// @Description For published local repositories: +// @Description * update to match local repository contents +// @Description +// @Description For published snapshots: +// @Description * switch components to new snapshot +// @Description +// @Description See also: `aptly publish update` / `aptly publish switch` +// @Tags Publish +// @Produce json +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Consume json +// @Param request body publishedRepoUpdateSwitchParams true "Parameters" +// @Produce json +// @Success 200 {object} deb.PublishedRepo +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository or source not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution} [put] func apiPublishUpdateSwitch(c *gin.Context) { + var b publishedRepoUpdateSwitchParams + param := slashEscape(c.Params.ByName("prefix")) storage, prefix := deb.ParsePrefix(param) - distribution := utils.SanitizePath(c.Params.ByName("distribution")) - - var b struct { - ForceOverwrite bool - Signing SigningOptions - SkipContents *bool - SkipBz2 *bool - SkipCleanup *bool - Snapshots []struct { - Component string `binding:"required"` - Name string `binding:"required"` - } - AcquireByHash *bool - MultiDist *bool - } + distribution := slashEscape(c.Params.ByName("distribution")) if c.Bind(&b) != nil { return @@ -276,58 +410,35 @@ 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 } collectionFactory := context.NewCollectionFactory() collection := collectionFactory.PublishedRepoCollection() + snapshotCollection := collectionFactory.SnapshotCollection() 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 } - err = collection.LoadComplete(published, collectionFactory) - if err != nil { - AbortWithJSONError(c, 500, fmt.Errorf("unable to update: %s", err)) - return - } - - var updatedComponents []string - var updatedSnapshots []string - var resources []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")) + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo")) return } - updatedComponents = published.Components() - for _, component := range updatedComponents { - published.UpdateLocalRepo(component) - } - } else if published.SourceKind == "snapshot" { + } else if published.SourceKind == deb.SourceSnapshot { for _, snapshotInfo := range b.Snapshots { - snapshotCollection := collectionFactory.SnapshotCollection() - snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name) + _, err2 := snapshotCollection.ByName(snapshotInfo.Name) if err2 != nil { - AbortWithJSONError(c, 404, err2) + AbortWithJSONError(c, http.StatusNotFound, err2) return } - - err2 = snapshotCollection.LoadComplete(snapshot) - if err2 != nil { - AbortWithJSONError(c, 500, err2) - return - } - - published.UpdateSnapshot(snapshotInfo.Component, snapshot) - updatedComponents = append(updatedComponents, snapshotInfo.Component) - updatedSnapshots = append(updatedSnapshots, snapshot.Name) } } else { - AbortWithJSONError(c, 500, fmt.Errorf("unknown published repository type")) + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type")) return } @@ -347,12 +458,33 @@ func apiPublishUpdateSwitch(c *gin.Context) { published.MultiDist = *b.MultiDist } - resources = append(resources, string(published.Key())) - taskName := fmt.Sprintf("Update published %s (%s): %s", published.SourceKind, strings.Join(updatedComponents, " "), strings.Join(updatedSnapshots, ", ")) + 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 := published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite) + err = collection.LoadComplete(published, collectionFactory) if err != nil { - return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err) + } + + revision := published.ObtainRevision() + sources := revision.Sources + + if published.SourceKind == deb.SourceSnapshot { + for _, snapshotInfo := range b.Snapshots { + component := snapshotInfo.Component + name := snapshotInfo.Name + sources[component] = name + } + } + + result, err := published.Update(collectionFactory, out) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err) + } + + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("Unable to update: %s", err) } err = collection.Update(published) @@ -361,8 +493,9 @@ func apiPublishUpdateSwitch(c *gin.Context) { } if b.SkipCleanup == nil || !*b.SkipCleanup { - err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents, - context.GetPublishedStorage(storage), collectionFactory, out) + cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources)) + cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...) + err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out) if err != nil { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) } @@ -372,14 +505,32 @@ func apiPublishUpdateSwitch(c *gin.Context) { }) } -// DELETE /publish/:prefix/:distribution +// @Summary Delete Published Repository +// @Description **Delete a published repository** +// @Description +// @Description Delete a distribution of a published repository and remove associated files. +// @Description +// @Description If no other published repositories share the same prefix, all files inside the prefix will be removed. +// @Description +// @Description See also: `aptly publish drop` +// @Tags Publish +// @Produce json +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param force query int true "force: 1 to enable" +// @Param skipCleanup query int true "skipCleanup: 1 to enable" +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution} [delete] func apiPublishDrop(c *gin.Context) { - 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 := slashEscape(c.Params.ByName("distribution")) + + force := c.Request.URL.Query().Get("force") == "1" + skipCleanup := c.Request.URL.Query().Get("SkipCleanup") == "1" collectionFactory := context.NewCollectionFactory() collection := collectionFactory.PublishedRepoCollection() @@ -391,8 +542,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) @@ -403,3 +553,490 @@ func apiPublishDrop(c *gin.Context) { return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil }) } + +// @Summary Add Source Component +// @Description **Add a source component to a published repo** +// @Description +// @Description Add a component of a snapshot or local repository to be published. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source add` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Consume json +// @Param request body sourceParams true "Parameters" +// @Produce json +// @Success 201 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [post] +func apiPublishAddSource(c *gin.Context) { + var b sourceParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to create: %s", err)) + return + } + + if c.Bind(&b) != nil { + return + } + + revision := published.ObtainRevision() + sources := revision.Sources + + component := b.Component + name := b.Name + + _, exists := sources[component] + if exists { + AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to create: Component '%s' already exists", component)) + return + } + + sources[component] = name + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusCreated, Value: gin.H{}}, nil + }) +} + +// @Summary List pending changes +// @Description **List source component changes to be applied** +// @Description +// @Description Return added, removed or changed components of snapshots or local repository to be published. +// @Description +// @Description The changes will be applied by a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source list` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Produce json +// @Success 200 {array} []deb.SourceEntry +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository pending changes not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [get] +func apiPublishListChanges(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to show: %s", err)) + return + } + + revision := published.Revision + if revision == nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to show: No source changes exist")) + return + } + + c.JSON(http.StatusOK, revision.SourceList()) +} + +// @Summary Replace Source Components +// @Description **Replace the source components of a published repository** +// @Description +// @Description Sets the components of snapshots or local repositories to be published. Existing Sourced will be replaced. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source replace` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Consume json +// @Param request body []sourceParams true "Parameters" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [put] +func apiPublishSetSources(c *gin.Context) { + var b []sourceParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err)) + return + } + + if c.Bind(&b) != nil { + return + } + + revision := published.ObtainRevision() + sources := make(map[string]string, len(b)) + revision.Sources = sources + + for _, source := range b { + component := source.Component + name := source.Name + sources[component] = name + } + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: revision.SourceList()}, nil + }) +} + +// @Summary Drop pending changes +// @Description **Drop pending source component changes of a published repository** +// @Description +// @Description Remove all pending changes what would be applied with a subsequent publish update call (i.e. `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source drop` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources [delete] +func apiPublishDropChanges(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err)) + return + } + + published.DropRevision() + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +// @Summary Update Source Component +// @Description **Update the source component of a published repository** +// @Description +// @Description Update a component of a snapshot or local repository to be published. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source update` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param component path string true "component name" +// @Consume json +// @Param request body sourceParams true "Parameters" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository/component not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources/{component} [put] +func apiPublishUpdateSource(c *gin.Context) { + var b sourceParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + component := slashEscape(c.Params.ByName("component")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err)) + return + } + + revision := published.ObtainRevision() + sources := revision.Sources + + _, exists := sources[component] + if !exists { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: Component '%s' does not exist", component)) + return + } + + b.Component = component + b.Name = revision.Sources[component] + + if c.Bind(&b) != nil { + return + } + + if b.Component != component { + delete(sources, component) + } + + component = b.Component + name := b.Name + sources[component] = name + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +// @Summary Remove Source Component +// @Description **Remove a source component from a published repo** +// @Description +// @Description Remove a source component (snapshot / local repo) from a published repository. +// @Description +// @Description This call does not publish the changes, but rather schedules them for a subsequent publish update call (i.e `PUT /api/publish/{prefix}/{distribution}` / `POST /api/publish/{prefix}/{distribution}/update`). +// @Description +// @Description See also: `aptly publish source remove` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Param component path string true "component name" +// @Produce json +// @Success 200 +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/sources/{component} [delete] +func apiPublishRemoveSource(c *gin.Context) { + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + component := slashEscape(c.Params.ByName("component")) + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err)) + return + } + + revision := published.ObtainRevision() + sources := revision.Sources + + _, exists := sources[component] + if !exists { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: Component '%s' does not exist", component)) + return + } + + delete(sources, component) + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil + }) +} + +type publishedRepoUpdateParams struct { + // when publishing, overwrite files in pool/ directory without notice + ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` + // GPG options + Signing signingParams ` json:"Signing"` + // Don't generate contents indexes + SkipContents *bool ` json:"SkipContents" example:"false"` + // Skip bz2 compression for index files + SkipBz2 *bool ` json:"SkipBz2" example:"false"` + // Don't remove unreferenced files in prefix/component + SkipCleanup *bool ` json:"SkipCleanup" example:"false"` + // Provide index files by hash + AcquireByHash *bool ` json:"AcquireByHash" example:"false"` + // Enable multiple packages with the same filename in different distributions + MultiDist *bool ` json:"MultiDist" example:"false"` +} + +// @Summary Update Published Repository +// @Description **Update a published repository** +// @Description +// @Description Publish pending source component changes which were added with `Add/Remove/Replace Source Components` +// @Description +// @Description See also: `aptly publish update` +// @Tags Publish +// @Param prefix path string true "publishing prefix" +// @Param distribution path string true "distribution name" +// @Consume json +// @Param request body publishedRepoUpdateParams true "Parameters" +// @Produce json +// @Success 200 {object} deb.PublishedRepo +// @Failure 400 {object} Error "Bad Request" +// @Failure 404 {object} Error "Published repository/component not found" +// @Failure 500 {object} Error "Internal Error" +// @Router /api/publish/{prefix}/{distribution}/update [post] +func apiPublishUpdate(c *gin.Context) { + var b publishedRepoUpdateParams + + param := slashEscape(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := slashEscape(c.Params.ByName("distribution")) + + if c.Bind(&b) != nil { + return + } + + signer, err := getSigner(&b.Signing) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to initialize GPG signer: %s", err)) + return + } + + collectionFactory := context.NewCollectionFactory() + collection := collectionFactory.PublishedRepoCollection() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err)) + return + } + + err = collection.LoadComplete(published, collectionFactory) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err)) + return + } + + if b.SkipContents != nil { + published.SkipContents = *b.SkipContents + } + + if b.SkipBz2 != nil { + published.SkipBz2 = *b.SkipBz2 + } + + if b.AcquireByHash != nil { + published.AcquireByHash = *b.AcquireByHash + } + + if b.MultiDist != nil { + published.MultiDist = *b.MultiDist + } + + resources := []string{string(published.Key())} + taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) + maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + result, err := published.Update(collectionFactory, out) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) + } + + err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) + } + + err = collection.Update(published) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err) + } + + if b.SkipCleanup == nil || !*b.SkipCleanup { + cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources)) + cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...) + err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err) + } + } + + return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil + }) +} diff --git a/api/repos.go b/api/repos.go index bc55dd9f..005c2aaa 100644 --- a/api/repos.go +++ b/api/repos.go @@ -69,25 +69,32 @@ func apiReposList(c *gin.Context) { c.JSON(200, result) } +type repoCreateParams struct { + // Name of repository to create + Name string `binding:"required" json:"Name" example:"repo1"` + // Text describing the repository (optional) + Comment string ` json:"Comment" example:"this is a repo"` + // Default distribution when publishing from this local repo + DefaultDistribution string ` json:"DefaultDistribution" example:"stable"` + // Default component when publishing from this local repo + DefaultComponent string ` json:"DefaultComponent" example:"main"` + // Snapshot name to create repoitory from (optional) + FromSnapshot string ` json:"FromSnapshot" example:"snapshot1"` +} + // @Summary Create repository // @Description Create a local repository. // @Tags Repos // @Produce json // @Consume json -// @Param Name query string false "Name of repository to be created." -// @Param Comment query string false "Text describing local repository, for the user" -// @Param DefaultDistribution query string false "Default distribution when publishing from this local repo" -// @Param DefaultComponent query string false "Default component when publishing from this local repo" +// @Param request body repoCreateParams true "Parameters" // @Success 201 {object} deb.LocalRepo -// @Failure 400 {object} Error "Repository already exists" +// @Failure 404 {object} Error "Source snapshot not found" +// @Failure 409 {object} Error "Local repo already exists" +// @Failure 500 {object} Error "Internal error" // @Router /api/repos [post] func apiReposCreate(c *gin.Context) { - var b struct { - Name string `binding:"required"` - Comment string - DefaultDistribution string - DefaultComponent string - } + var b repoCreateParams if c.Bind(&b) != nil { return @@ -98,14 +105,41 @@ func apiReposCreate(c *gin.Context) { repo.DefaultDistribution = b.DefaultDistribution collectionFactory := context.NewCollectionFactory() - collection := collectionFactory.LocalRepoCollection() - err := collection.Add(repo) - if err != nil { - AbortWithJSONError(c, 400, err) + + if b.FromSnapshot != "" { + var snapshot *deb.Snapshot + + snapshotCollection := collectionFactory.SnapshotCollection() + + snapshot, err := snapshotCollection.ByName(b.FromSnapshot) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err)) + return + } + + err = snapshotCollection.LoadComplete(snapshot) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to load source snapshot: %s", err)) + return + } + + repo.UpdateRefList(snapshot.RefList()) + } + + localRepoCollection := collectionFactory.LocalRepoCollection() + + if _, err := localRepoCollection.ByName(b.Name); err == nil { + AbortWithJSONError(c, http.StatusConflict, fmt.Errorf("local repo with name %s already exists", b.Name)) return } - c.JSON(201, repo) + err := localRepoCollection.Add(repo) + if err != nil { + AbortWithJSONError(c, http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusCreated, repo) } // PUT /api/repos/:name @@ -254,14 +288,14 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li return } - err = collection.LoadComplete(repo) - if err != nil { - AbortWithJSONError(c, 500, err) - return - } - resources := []string{string(repo.Key())} + maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.LoadComplete(repo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + out.Printf("Loading packages...\n") list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil) if err != nil { @@ -360,12 +394,6 @@ func apiReposPackageFromDir(c *gin.Context) { return } - err = collection.LoadComplete(repo) - if err != nil { - AbortWithJSONError(c, 500, err) - return - } - var taskName string var sources []string if fileParam == "" { @@ -379,6 +407,11 @@ func apiReposPackageFromDir(c *gin.Context) { resources := []string{string(repo.Key())} resources = append(resources, sources...) maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collection.LoadComplete(repo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + verifier := context.GetVerifier() var ( @@ -480,17 +513,7 @@ func apiReposCopyPackage(c *gin.Context) { return } - err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo) - if err != nil { - AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err)) - return - } - - var ( - srcRefList *deb.PackageRefList - srcRepo *deb.LocalRepo - ) - + var srcRepo *deb.LocalRepo srcRepo, err = collectionFactory.LocalRepoCollection().ByName(srcRepoName) if err != nil { AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err)) @@ -502,17 +525,22 @@ func apiReposCopyPackage(c *gin.Context) { return } - err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo) - if err != nil { - AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err)) - return - } - - srcRefList = srcRepo.RefList() taskName := fmt.Sprintf("Copy packages from repo %s to repo %s", srcRepoName, dstRepoName) resources := []string{string(dstRepo.Key()), string(srcRepo.Key())} maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err) + } + + err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err) + } + + srcRefList := srcRepo.RefList() + reporter := &aptly.RecordingResultReporter{ Warnings: []string{}, AddedLines: []string{}, diff --git a/api/router.go b/api/router.go index d5b6e612..ada14b88 100644 --- a/api/router.go +++ b/api/router.go @@ -185,12 +185,19 @@ 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.POST("/publish/:prefix/:distribution/sources", apiPublishAddSource) + api.GET("/publish/:prefix/:distribution/sources", apiPublishListChanges) + api.PUT("/publish/:prefix/:distribution/sources", apiPublishSetSources) + api.DELETE("/publish/:prefix/:distribution/sources", apiPublishDropChanges) + api.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishUpdateSource) + api.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishRemoveSource) + api.POST("/publish/:prefix/:distribution/update", apiPublishUpdate) } { diff --git a/api/snapshot.go b/api/snapshot.go index 3e84b1f2..1a9c2278 100644 --- a/api/snapshot.go +++ b/api/snapshot.go @@ -135,16 +135,17 @@ func apiSnapshotsCreate(c *gin.Context) { return } - err = snapshotCollection.LoadComplete(sources[i]) - if err != nil { - AbortWithJSONError(c, 500, err) - return - } - resources = append(resources, string(sources[i].ResourceKey())) } maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + for i := range sources { + err = snapshotCollection.LoadComplete(sources[i]) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + } + list := deb.NewPackageList() // verify package refs and build package list @@ -468,17 +469,20 @@ func apiSnapshotsMerge(c *gin.Context) { return } - err = snapshotCollection.LoadComplete(sources[i]) - if err != nil { - AbortWithJSONError(c, http.StatusInternalServerError, err) - return - } resources[i] = string(sources[i].ResourceKey()) } maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = snapshotCollection.LoadComplete(sources[0]) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } result := sources[0].RefList() for i := 1; i < len(sources); i++ { + err = snapshotCollection.LoadComplete(sources[i]) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } result = result.Merge(sources[i].RefList(), overrideMatching, false) } @@ -566,11 +570,6 @@ func apiSnapshotsPull(c *gin.Context) { AbortWithJSONError(c, http.StatusNotFound, err) return } - err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot) - if err != nil { - AbortWithJSONError(c, http.StatusInternalServerError, err) - return - } // Load snapshot sourceSnapshot, err := collectionFactory.SnapshotCollection().ByName(body.Source) @@ -578,15 +577,19 @@ func apiSnapshotsPull(c *gin.Context) { AbortWithJSONError(c, http.StatusNotFound, err) return } - err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot) - if err != nil { - AbortWithJSONError(c, http.StatusInternalServerError, err) - return - } resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())} taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination) maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { + err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + // convert snapshots to package list toPackageList, err := deb.NewPackageListFromRefList(toSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress()) if err != nil { @@ -650,7 +653,7 @@ func apiSnapshotsPull(c *gin.Context) { // If we haven't seen such name-architecture pair and were instructed to remove, remove it if !noRemove && !seen { // Remove all packages with the same name and architecture - packageSearchResults := toPackageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true) + packageSearchResults := toPackageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true, false) for _, p := range packageSearchResults { toPackageList.Remove(p) removedPackages = append(removedPackages, p.String()) diff --git a/cmd/api_serve.go b/cmd/api_serve.go index 22af04a8..d3ccdf77 100644 --- a/cmd/api_serve.go +++ b/cmd/api_serve.go @@ -65,6 +65,8 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error { signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) go (func() { if _, ok := <-sigchan; ok { + fmt.Printf("\nShutdown signal received, waiting for background tasks...\n") + context.TaskList().Wait() server.Shutdown(stdcontext.Background()) } })() diff --git a/cmd/mirror_update.go b/cmd/mirror_update.go index 2e6df439..2aad8c53 100644 --- a/cmd/mirror_update.go +++ b/cmd/mirror_update.go @@ -267,7 +267,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } - context.Progress().Printf("\nMirror `%s` has been successfully updated.\n", repo.Name) + context.Progress().Printf("\nMirror `%s` has been updated successfully.\n", repo.Name) return err } diff --git a/cmd/publish.go b/cmd/publish.go index d74384e0..4217ff87 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -34,10 +34,26 @@ 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(), + makeCmdPublishSourceReplace(), + 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..b052c668 --- /dev/null +++ b/cmd/publish_source_add.go @@ -0,0 +1,94 @@ +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 '%s' has already been added", component) + } + context.Progress().Printf("Adding component '%s' with source '%s' [%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) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceAdd() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceAdd, + UsageLine: "add ", + Short: "add source components to a published repo", + Long: ` +The command adds components of a snapshot or local repository to be published. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +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 source add -component=main,contrib wheezy wheezy-main wheezy-contrib + +Example: + + $ aptly publish source 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-source-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..704e36e7 --- /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: "drop pending source component changes of a published repository", + Long: ` +Remove all pending changes what would be applied with a subsequent 'aptly publish update'. + +Example: + + $ aptly publish source drop wheezy +`, + Flag: *flag.NewFlagSet("aptly-publish-source-drop", 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..aa61a2b8 --- /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.SourceList(), "", " ") + 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..d291bcf0 --- /dev/null +++ b/cmd/publish_source_remove.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 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 '%s' does not exist", component) + } + context.Progress().Printf("Removing component '%s' with source '%s' [%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) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceRemove() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceRemove, + UsageLine: "remove [[:]] ", + Short: "remove source components from a published repo", + Long: ` +The command removes source components (snapshot / local repo) from a published repository. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +The flag -component is mandatory. Use a comma-separated list of components, if +multiple components should be removed, e.g.: + +Example: + + $ aptly publish source remove -component=contrib,non-free wheezy filesystem:symlink:debian +`, + Flag: *flag.NewFlagSet("aptly-publish-source-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_replace.go b/cmd/publish_source_replace.go new file mode 100644 index 00000000..17801e5a --- /dev/null +++ b/cmd/publish_source_replace.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 aptlyPublishSourceReplace(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 + context.Progress().Printf("Replacing source list...\n") + clear(sources) + + for i, component := range components { + name := names[i] + context.Progress().Printf("Adding component '%s' with source '%s' [%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) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceReplace() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceReplace, + UsageLine: "replace ", + Short: "replace the source components of a published repository", + Long: ` +The command replaces the source components of a snapshot or local repository to be published. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +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 source replace -component=main,contrib wheezy wheezy-main wheezy-contrib + +Example: + + $ aptly publish source replace -component=contrib wheezy ppa wheezy-contrib +`, + Flag: *flag.NewFlagSet("aptly-publish-source-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_update.go b/cmd/publish_source_update.go new file mode 100644 index 00000000..af207fb3 --- /dev/null +++ b/cmd/publish_source_update.go @@ -0,0 +1,91 @@ +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 '%s' does not exist", component) + } + context.Progress().Printf("Updating component '%s' with source '%s' [%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) + } + + context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n", + distribution, published.StoragePrefix()) + + return err +} + +func makeCmdPublishSourceUpdate() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPublishSourceUpdate, + UsageLine: "update ", + Short: "update the source components of a published repository", + Long: ` +The command updates the source components of a snapshot or local repository to be published. + +This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'. + +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 source update -component=main,contrib wheezy wheezy-main wheezy-contrib + +Example: + + $ aptly publish source update -component=contrib wheezy ppa wheezy-contrib +`, + Flag: *flag.NewFlagSet("aptly-publish-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..12d73f08 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,16 @@ 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) + return fmt.Errorf("unable to switch: %s", err) } if published.SourceKind != deb.SourceSnapshot { - return fmt.Errorf("unable to update: not a snapshot publish") + return fmt.Errorf("unable to switch: not a published snapshot repository") } 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,13 +61,18 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { return fmt.Errorf("mismatch in number of components (%d) and snapshots (%d)", len(components), len(names)) } + snapshotCollection := collectionFactory.SnapshotCollection() for i, component := range components { - snapshot, err = collectionFactory.SnapshotCollection().ByName(names[i]) + 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 = collectionFactory.SnapshotCollection().LoadComplete(snapshot) + err = snapshotCollection.LoadComplete(snapshot) if err != nil { return fmt.Errorf("unable to switch: %s", err) } @@ -111,14 +115,13 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool) if !skipCleanup { - err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components, - context.GetPublishedStorage(storage), collectionFactory, context.Progress()) + err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(context, published, components, 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 +129,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..4b38282f 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,15 @@ 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, - context.GetPublishedStorage(storage), collectionFactory, context.Progress()) + cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources)) + cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...) + err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(context, published, cleanComponents, 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 updated successfully.\n", published.SourceKind, published.String()) return err } @@ -96,15 +93,21 @@ func makeCmdPublishUpdate() *commander.Command { cmd := &commander.Command{ Run: aptlyPublishUpdate, UsageLine: "update [[:]]", - Short: "update published local repository", + Short: "update published repository", Long: ` -Command re-publishes (updates) published local repository. -and should be occupied with local repository published -using command aptly publish repo. Update happens in-place with -minimum possible downtime for published repository. +The command updates updates a published repository after applying pending changes to the sources. -For multiple component published repositories, all local repositories -are updated. +For published local repositories: + + * update to match local repository contents + +For published snapshots: + + * switch components to new snapshot + +The update happens in-place with minimum possible downtime for published repository. + +For multiple component published repositories, all local repositories are updated. Example: diff --git a/cmd/snapshot_pull.go b/cmd/snapshot_pull.go index 884b50ff..9c2559f5 100644 --- a/cmd/snapshot_pull.go +++ b/cmd/snapshot_pull.go @@ -112,7 +112,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { // If we haven't seen such name-architecture pair and were instructed to remove, remove it if !noRemove && !seen { // Remove all packages with the same name and architecture - pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true) + pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true, false) for _, p := range pS { packageList.Remove(p) context.Progress().ColoredPrintf("@r[-]@| %s removed", p) diff --git a/completion.d/aptly b/completion.d/aptly index 8caf47f8..3cb54ac5 100644 --- a/completion.d/aptly +++ b/completion.d/aptly @@ -54,12 +54,14 @@ _aptly() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" + prevprev="${COMP_WORDS[COMP_CWORD-2]}" commands="api config db graph mirror package publish repo serve snapshot task version" options="-architectures= -config= -db-open-attempts= -dep-follow-all-variants -dep-follow-recommends -dep-follow-source -dep-follow-suggests -dep-verbose-resolve -gpg-provider=" db_subcommands="cleanup recover" mirror_subcommands="create drop edit show list rename search update" - publish_subcommands="drop list repo snapshot switch update" + publish_subcommands="drop list repo snapshot switch update source" + publish_source_subcommands="drop list add remove update replace" snapshot_subcommands="create diff drop filter list merge pull rename search show verify" repo_subcommands="add copy create drop edit import include list move remove rename search show" package_subcommands="search show" @@ -148,6 +150,17 @@ _aptly() esac fi + case "$prevprev" in + "publish") + case "$prev" in + "source") + COMPREPLY=($(compgen -W "${publish_source_subcommands}" -- ${cur})) + return 0 + ;; + esac + ;; + esac + case "$cmd" in "mirror") case "$subcmd" in @@ -575,6 +588,19 @@ _aptly() ;; esac ;; + "source") + case "$subcmd" in + "add") + return 0 + ;; + "list") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "-raw" -- ${cur})) + return 0 + fi + ;; + esac + ;; "package") case "$subcmd" in "search") diff --git a/context/context.go b/context/context.go index 30e84d42..7e468b0f 100644 --- a/context/context.go +++ b/context/context.go @@ -13,6 +13,7 @@ import ( "runtime/pprof" "strings" "sync" + "syscall" "time" "github.com/aptly-dev/aptly/aptly" @@ -359,10 +360,7 @@ func (context *AptlyContext) ReOpenDatabase() error { // NewCollectionFactory builds factory producing all kinds of collections func (context *AptlyContext) NewCollectionFactory() *deb.CollectionFactory { - context.Lock() - defer context.Unlock() - - db, err := context._database() + db, err := context.Database() if err != nil { Fatal(err) } @@ -560,7 +558,7 @@ func (context *AptlyContext) GoContextHandleSignals() { // Catch ^C sigch := make(chan os.Signal, 1) - signal.Notify(sigch, os.Interrupt) + signal.Notify(sigch, syscall.SIGINT, syscall.SIGTERM) var cancel gocontext.CancelFunc diff --git a/deb/import.go b/deb/import.go index d0f65479..99d52be8 100644 --- a/deb/import.go +++ b/deb/import.go @@ -209,7 +209,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b } if forceReplace { - conflictingPackages := list.Search(Dependency{Pkg: p.Name, Version: p.Version, Relation: VersionEqual, Architecture: p.Architecture}, true) + conflictingPackages := list.Search(Dependency{Pkg: p.Name, Version: p.Version, Relation: VersionEqual, Architecture: p.Architecture}, true, false) for _, cp := range conflictingPackages { reporter.Removed("%s removed due to conflict with package being added", cp) list.Remove(cp) @@ -218,7 +218,7 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b err = list.Add(p) if err != nil { - reporter.Warning("Unable to add package to repo %s: %s", p, err) + reporter.Warning("Unable to add package: %s", err) failedFiles = append(failedFiles, file) continue } diff --git a/deb/list.go b/deb/list.go index 51c9ba7e..e88069f2 100644 --- a/deb/list.go +++ b/deb/list.go @@ -138,7 +138,7 @@ func (l *PackageList) Add(p *Package) error { existing, ok := l.packages[key] if ok { if !existing.Equals(p) { - return &PackageConflictError{fmt.Errorf("conflict in package %s", p)} + return &PackageConflictError{fmt.Errorf("package already exists and is different: %s", p)} } return nil } @@ -201,7 +201,7 @@ func (l *PackageList) Append(pl *PackageList) error { existing, ok := l.packages[k] if ok { if !existing.Equals(p) { - return fmt.Errorf("conflict in package %s", p) + return fmt.Errorf("package already exists and is different: %s", p) } } else { l.packages[k] = p @@ -347,7 +347,7 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so hash := dep.Hash() satisfied, ok := cache[hash] if !ok { - satisfied = sources.Search(dep, false) != nil + satisfied = sources.Search(dep, false, true) != nil cache[hash] = satisfied } @@ -459,7 +459,7 @@ func (l *PackageList) SearchByKey(arch, name, version string) (result *PackageLi } // Search searches package index for specified package(s) using optimized queries -func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*Package) { +func (l *PackageList) Search(dep Dependency, allMatches bool, searchProvided bool) (searchResults []*Package) { if !l.indexed { panic("list not indexed, can't search") } @@ -479,18 +479,20 @@ func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []* i++ } - providers, ok := l.providesIndex[dep.Pkg] - if !ok { - return - } - for _, p := range providers { - if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) { - if p.MatchesDependency(dep) { - searchResults = append(searchResults, p) - } + if searchProvided { + providers, ok := l.providesIndex[dep.Pkg] + if !ok { + return + } + for _, p := range providers { + if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) { + if p.MatchesDependency(dep) { + searchResults = append(searchResults, p) + } - if !allMatches { - return + if !allMatches { + return + } } } } @@ -544,15 +546,16 @@ func (l *PackageList) FilterWithProgress(queries []PackageQuery, withDependencie // // when follow-all-variants is enabled, we need to try to expand anyway, // as even if dependency is satisfied now, there might be other ways to satisfy dependency - if result.Search(dep, false) != nil { + // FIXME: do not search twice + if result.Search(dep, false, true) != nil { if dependencyOptions&DepVerboseResolve == DepVerboseResolve && progress != nil { - progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true)) + progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true)) } continue } } - searchResults := l.Search(dep, true) + searchResults := l.Search(dep, true, true) if len(searchResults) > 0 { for _, p := range searchResults { if result.Has(p) { diff --git a/deb/list_test.go b/deb/list_test.go index 03cca348..4209f125 100644 --- a/deb/list_test.go +++ b/deb/list_test.go @@ -131,7 +131,7 @@ func (s *PackageListSuite) TestAddLen(c *C) { c.Check(s.list.Len(), Equals, 1) c.Check(s.list.Add(s.p3), IsNil) c.Check(s.list.Len(), Equals, 2) - c.Check(s.list.Add(s.p4), ErrorMatches, "conflict in package.*") + c.Check(s.list.Add(s.p4), ErrorMatches, "package already exists and is different: .*") } func (s *PackageListSuite) TestRemove(c *C) { @@ -243,7 +243,7 @@ func (s *PackageListSuite) TestAppend(c *C) { list.Add(s.p4) err = s.list.Append(list) - c.Check(err, ErrorMatches, "conflict.*") + c.Check(err, ErrorMatches, "package already exists and is different: .*") s.list.PrepareIndex() c.Check(func() { s.list.Append(s.il) }, Panics, "Append not supported when indexed") @@ -251,63 +251,63 @@ func (s *PackageListSuite) TestAppend(c *C) { func (s *PackageListSuite) TestSearch(c *C) { //allMatches = False - c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}, false) }, Panics, "list not indexed, can't search") + c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}, false, true) }, Panics, "list not indexed, can't search") - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}, false), DeepEquals, []*Package{s.packages[4]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}, false, true), DeepEquals, []*Package{s.packages[4]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}, false, true), IsNil) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]}) - c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, false), IsNil) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, false, true), DeepEquals, []*Package{s.packages[3]}) + c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, false, true), IsNil) // search w/o version should return package with latest version - c.Check(s.il.Search(Dependency{Architecture: "source", Pkg: "dpkg"}, false), DeepEquals, []*Package{s.packages[13]}) + c.Check(s.il.Search(Dependency{Architecture: "source", Pkg: "dpkg"}, false, true), DeepEquals, []*Package{s.packages[13]}) // allMatches = True - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "mail-agent"}, true), Contains, []*Package{s.packages2[0], s.packages2[1]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "puppy"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "mail-agent"}, true, true), Contains, []*Package{s.packages2[0], s.packages2[1]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "puppy"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "3"}, true), Contains, []*Package{s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "3"}, true, true), Contains, []*Package{s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.1~"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.2"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.1~"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1-bp1"}, true), Contains, []*Package{s.packages2[2]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.0"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.2"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1-bp1"}, true, true), Contains, []*Package{s.packages2[2]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.0"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "5.0"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "1.1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "5.0"}, true, true), IsNil) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) - c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true), IsNil) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true, true), Contains, []*Package{s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]}) + c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true, true), IsNil) // Provides with version number python3CFFIBackend := &Package{Name: "python3-cffi-backend", Version: "1.15.1-5+b1", Architecture: "amd64", Provides: []string{"python3-cffi-backend-api-9729", "python3-cffi-backend-api-max (= 10495)", "python3-cffi-backend-api-min (= 9729)"}} err := s.il2.Add(python3CFFIBackend) c.Check(err, IsNil) - c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionGreaterOrEqual, Version: "9729"}, false), DeepEquals, []*Package{python3CFFIBackend}) - c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionLess, Version: "9729"}, false), IsNil) - c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionDontCare}, false), DeepEquals, []*Package{python3CFFIBackend}) + c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionGreaterOrEqual, Version: "9729"}, false, true), DeepEquals, []*Package{python3CFFIBackend}) + c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionLess, Version: "9729"}, false, true), IsNil) + c.Check(s.il2.Search(Dependency{Pkg: "python3-cffi-backend-api-max", Relation: VersionDontCare}, false, true), DeepEquals, []*Package{python3CFFIBackend}) } func (s *PackageListSuite) TestFilter(c *C) { diff --git a/deb/package.go b/deb/package.go index 82ac7885..cf6a98ca 100644 --- a/deb/package.go +++ b/deb/package.go @@ -362,24 +362,22 @@ func (p *Package) providesDependency(dep Dependency) (bool, error) { if err != nil { errs = append(errs, err) } - // The only relation allowed here is `=`. - // > The relations allowed are [...]. The exception is the Provides field, for which only = is allowed. - // > [...] - // > A Provides field may contain version numbers, and such a version number will be considered when - // > considering a dependency on or conflict with the virtual package name. - // -- https://www.debian.org/doc/debian-policy/ch-relationships.html - switch providedDep.Relation { - case VersionDontCare: - if providedDep.Pkg == dep.Pkg { - return true, nil - } - case VersionEqual: - providedVersion := providedDep.Version - if providedDep.Pkg == dep.Pkg && versionSatisfiesDependency(providedVersion, dep) { - return true, nil - } - default: + if providedDep.Relation != VersionEqual && providedDep.Relation != VersionDontCare { + // The only relation allowed here is `=`. + // > The relations allowed are [...]. The exception is the Provides field, for which only = is allowed. + // > [...] + // > A Provides field may contain version numbers, and such a version number will be considered when + // > considering a dependency on or conflict with the virtual package name. + // -- https://www.debian.org/doc/debian-policy/ch-relationships.html errs = append(errs, fmt.Errorf("unsupported relation in Provides: %s", providedDep.String())) + continue + } + providedVersion := providedDep.Version + if providedVersion == "" { + providedVersion = p.Version + } + if providedDep.Pkg == dep.Pkg && versionSatisfiesDependency(providedVersion, dep) { + return true, nil } } return false, JoinErrors(errs...) diff --git a/deb/package_collection.go b/deb/package_collection.go index c7e6a300..0027f1de 100644 --- a/deb/package_collection.go +++ b/deb/package_collection.go @@ -317,7 +317,7 @@ func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList) } // Search is not implemented -func (collection *PackageCollection) Search(_ Dependency, _ bool) (searchResults []*Package) { +func (collection *PackageCollection) Search(_ Dependency, _ bool, _ bool) (searchResults []*Package) { panic("Not implemented") } diff --git a/deb/publish.go b/deb/publish.go index 79c47cb8..9143e610 100644 --- a/deb/publish.go +++ b/deb/publish.go @@ -21,6 +21,16 @@ import ( "github.com/aptly-dev/aptly/utils" ) +type SourceEntry struct { + Component, Name string +} + +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 +83,192 @@ 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) SourceList() []SourceEntry { + sources := revision.Sources + components := revision.Components() + sourceList := make([]SourceEntry, 0, len(sources)) + for _, component := range components { + name := sources[component] + sourceList = append(sourceList, SourceEntry{ + Component: component, + Name: name, + }) + } + + return sourceList +} + +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.DropRevision() + if revision == nil { + if p.SourceKind == SourceLocalRepo { + // Re-fetch packages from local repository + for component, item := range p.sourceItems { + localRepo := item.localRepo + if localRepo != nil { + p.UpdateLocalRepo(component, localRepo) + result.UpdatedSources[component] = localRepo.Name + } + } + } + } else { + for _, component := range p.Components() { + 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 +477,28 @@ 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 +func (revision *PublishedRepoRevision) MarshalJSON() ([]byte, error) { + sources := revision.Sources + components := revision.Components() + sourceList := make([]SourceEntry, 0, len(sources)) + for _, component := range components { + name := sources[component] + sourceList = append(sourceList, SourceEntry{ + Component: component, + Name: name, + }) } - sources := []sourceInfo{} - for component, item := range p.sourceItems { + return json.Marshal(map[string]interface{}{ + "Sources": sourceList, + }) +} + +// MarshalJSON requires object to filled by "LoadShallow" or "LoadComplete" +func (p *PublishedRepo) MarshalJSON() ([]byte, error) { + sources := []SourceEntry{} + for _, component := range p.Components() { + item := p.sourceItems[component] name := "" if item.snapshot != nil { name = item.snapshot.Name @@ -297,7 +507,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) { } else { panic("no snapshot/local repo") } - sources = append(sources, sourceInfo{ + sources = append(sources, SourceEntry{ Component: component, Name: name, }) @@ -444,20 +654,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 +689,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 @@ -1177,7 +1400,7 @@ func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix processedComponentRefs := map[string]*PackageRefList{} for _, r := range collection.list { - if r.Prefix == prefix { + if r.Prefix == prefix && !r.MultiDist { matches := false repoComponents := r.Components() @@ -1238,42 +1461,128 @@ func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix } // CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair -func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(prefix string, components []string, - publishedStorage aptly.PublishedStorage, collectionFactory *CollectionFactory, progress aptly.Progress) error { +func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(publishedStorageProvider aptly.PublishedStorageProvider, + published *PublishedRepo, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error { + + var err error collection.loadList() + storage := published.Storage + prefix := published.Prefix + distribution := published.Distribution + + rootPath := filepath.Join(prefix, "dists", distribution) + publishedStorage := publishedStorageProvider.GetPublishedStorage(published.Storage) + + sort.Strings(cleanComponents) + publishedComponents := published.Components() + removedComponents := utils.StrSlicesSubstract(cleanComponents, publishedComponents) + updatedComponents := utils.StrSlicesSubstract(cleanComponents, removedComponents) + if progress != nil { - progress.Printf("Cleaning up prefix %#v components %s...\n", prefix, strings.Join(components, ", ")) + progress.Printf("Cleaning up published repository %s/%s...\n", published.StoragePrefix(), distribution) } - referencedFiles, err := collection.listReferencedFilesByComponent(prefix, components, collectionFactory, progress) - if err != nil { - return err + for _, component := range removedComponents { + if progress != nil { + progress.Printf("Removing component '%s'...\n", component) + } + + err = publishedStorage.RemoveDirs(filepath.Join(rootPath, component), progress) + if err != nil { + return err + } + + // Ensure that component does not exist in multi distribution pool + err = publishedStorage.RemoveDirs(filepath.Join(prefix, "pool", distribution, component), nil) + if err != nil { + return err + } } - for _, component := range components { + referencedFiles := map[string][]string{} + + if published.MultiDist { + rootPath = filepath.Join(prefix, "pool", distribution) + + // Get all referenced files by component for determining orphaned pool files. + for _, component := range publishedComponents { + packageList, err := NewPackageListFromRefList(published.RefList(component), collectionFactory.PackageCollection(), progress) + if err != nil { + return err + } + + packageList.ForEach(func(p *Package) error { + poolDir, err := p.PoolDirectory() + if err != nil { + return err + } + + for _, file := range p.Files() { + referencedFiles[component] = append(referencedFiles[component], filepath.Join(poolDir, file.Filename)) + } + + return nil + }) + } + } else { + rootPath = filepath.Join(prefix, "pool") + + // In case of a shared component pool directory, we must check, if a component is no longer referenced by any other + // published repository within the same prefix. + referencedComponents := map[string]struct{}{} + for _, p := range collection.list { + if p.Prefix == prefix && p.Storage == storage && !p.MultiDist { + for _, component := range p.Components() { + referencedComponents[component] = struct{}{} + } + } + } + + // Remove orphaned component pool directories in the prefix. + for _, component := range removedComponents { + _, exists := referencedComponents[component] + if !exists { + err := publishedStorage.RemoveDirs(filepath.Join(rootPath, component), progress) + if err != nil { + return err + } + } + } + + // Get all referenced files by component for determining orphaned pool files. + referencedFiles, err = collection.listReferencedFilesByComponent(prefix, publishedComponents, collectionFactory, progress) + if err != nil { + return err + } + } + + for _, component := range updatedComponents { + if progress != nil { + progress.Printf("Cleaning up component '%s'...\n", component) + } sort.Strings(referencedFiles[component]) - rootPath := filepath.Join(prefix, "pool", component) - existingFiles, err := publishedStorage.Filelist(rootPath) + path := filepath.Join(rootPath, component) + existingFiles, err := publishedStorage.Filelist(path) if err != nil { return err } sort.Strings(existingFiles) - filesToDelete := utils.StrSlicesSubstract(existingFiles, referencedFiles[component]) + orphanedFiles := utils.StrSlicesSubstract(existingFiles, referencedFiles[component]) - for _, file := range filesToDelete { - err = publishedStorage.Remove(filepath.Join(rootPath, file)) + for _, file := range orphanedFiles { + err = publishedStorage.Remove(filepath.Join(path, file)) if err != nil { return err } } } - return nil + return err } // Remove removes published repository, cleaning up directories, files @@ -1324,8 +1633,7 @@ func (collection *PublishedRepoCollection) Remove(publishedStorageProvider aptly nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1] if !skipCleanup && len(cleanComponents) > 0 { - err = collection.CleanupPrefixComponentFiles(repo.Prefix, cleanComponents, - publishedStorageProvider.GetPublishedStorage(storage), collectionFactory, progress) + err = collection.CleanupPrefixComponentFiles(publishedStorageProvider, repo, cleanComponents, collectionFactory, progress) if err != nil { if !force { return fmt.Errorf("cleanup failed, use -force-drop to override: %s", err) diff --git a/deb/publish_test.go b/deb/publish_test.go index 5243a41a..eec1601f 100644 --- a/deb/publish_test.go +++ b/deb/publish_test.go @@ -2,6 +2,7 @@ package deb import ( "bytes" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -359,6 +360,25 @@ func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) { c.Check(err, ErrorMatches, "duplicate component name: main") } +func (s *PublishedRepoSuite) TestUpdate(c *C) { + revision := s.repo2.ObtainRevision() + sources := revision.Sources + sources["test"] = "local1" + + result, err := s.repo2.Update(s.factory, nil) + c.Assert(err, IsNil) + c.Assert(result, NotNil) + c.Assert(s.repo2.Revision, IsNil) + + c.Assert(result.AddedSources, DeepEquals, map[string]string{"test": "local1"}) + c.Assert(result.UpdatedSources, DeepEquals, map[string]string{"main": "local1"}) + c.Assert(result.RemovedSources, DeepEquals, map[string]string{}) + + c.Assert(result.AddedComponents(), DeepEquals, []string{"test"}) + c.Assert(result.UpdatedComponents(), DeepEquals, []string{"main"}) + c.Assert(result.RemovedComponents(), DeepEquals, []string{}) +} + func (s *PublishedRepoSuite) TestPublish(c *C) { err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false) c.Assert(err, IsNil) @@ -489,6 +509,30 @@ func (s *PublishedRepoSuite) TestEncodeDecode(c *C) { c.Assert(repo2, DeepEquals, s.repo2) } +func (s *PublishedRepoSuite) TestPublishedRepoRevision(c *C) { + revision := s.repo2.ObtainRevision() + c.Assert(revision, NotNil) + + sources := revision.Sources + c.Assert(sources, NotNil) + c.Assert(sources, DeepEquals, map[string]string{"main": "local1"}) + + sources["test1"] = "snap1" + sources["test2"] = "snap2" + + c.Assert(revision.Components(), DeepEquals, []string{"main", "test1", "test2"}) + c.Assert(revision.SourceNames(), DeepEquals, []string{"local1", "snap1", "snap2"}) + + bytes, err := json.Marshal(revision) + c.Assert(err, IsNil) + + json_expected := `{"Sources":[{"Component":"main","Name":"local1"},{"Component":"test1","Name":"snap1"},{"Component":"test2","Name":"snap2"}]}` + c.Assert(string(bytes), Equals, json_expected) + + c.Assert(s.repo2.DropRevision(), DeepEquals, revision) + c.Assert(s.repo2.Revision, IsNil) +} + type PublishedRepoCollectionSuite struct { PackageListMixinSuite db database.Storage diff --git a/deb/query.go b/deb/query.go index 1de9e95b..ad0ad17a 100644 --- a/deb/query.go +++ b/deb/query.go @@ -20,7 +20,7 @@ type PackageLike interface { // PackageCatalog is abstraction on top of PackageCollection and PackageList type PackageCatalog interface { Scan(q PackageQuery) (result *PackageList) - Search(dep Dependency, allMatches bool) (searchResults []*Package) + Search(dep Dependency, allMatches bool, searchProvided bool) (searchResults []*Package) SearchSupported() bool SearchByKey(arch, name, version string) (result *PackageList) } @@ -244,7 +244,7 @@ func (q *DependencyQuery) Fast(list PackageCatalog) bool { func (q *DependencyQuery) Query(list PackageCatalog) (result *PackageList) { if q.Fast(list) { result = NewPackageList() - for _, pkg := range list.Search(q.Dep, true) { + for _, pkg := range list.Search(q.Dep, true, true) { result.Add(pkg) } } else { diff --git a/deb/reflist_test.go b/deb/reflist_test.go index ec7ed09f..bcabec3c 100644 --- a/deb/reflist_test.go +++ b/deb/reflist_test.go @@ -65,7 +65,7 @@ func (s *PackageRefListSuite) TestNewPackageListFromRefList(c *C) { list, err := NewPackageListFromRefList(reflist, coll, nil) c.Assert(err, IsNil) c.Check(list.Len(), Equals, 4) - c.Check(list.Add(s.p4), ErrorMatches, "conflict in package.*") + c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*") list, err = NewPackageListFromRefList(nil, coll, nil) c.Assert(err, IsNil) diff --git a/debian/control b/debian/control index 704b4603..09d0b824 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,7 @@ Build-Depends: bash-completion, golang-go, golang-github-aleksi-pointer-dev, golang-github-awalterschulze-gographviz-dev, - golang-github-aws-aws-sdk-go-v2-dev, + golang-github-aws-aws-sdk-go-v2-dev (>= 1.24.1), golang-github-aws-smithy-go-dev, golang-github-azure-azure-pipeline-go-dev, golang-github-azure-azure-storage-blob-go-dev, @@ -54,7 +54,7 @@ Build-Depends: bash-completion, golang-github-prometheus-procfs-dev, golang-github-protonmail-go-crypto-dev, golang-github-rivo-uniseg-dev, - golang-github-rs-zerolog-dev, + golang-github-rs-zerolog-dev (>= 1.29.1), golang-github-saracen-walker-dev, golang-github-smira-commander-dev, golang-github-smira-flag-dev, 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/http/download.go b/http/download.go index 5bb9d050..be7b2c78 100644 --- a/http/download.go +++ b/http/download.go @@ -133,6 +133,8 @@ func retryableError(err error) bool { } switch err { + case context.Canceled: + return false case io.EOF: return true case io.ErrUnexpectedEOF: diff --git a/http/download_test.go b/http/download_test.go index 9cde0716..d0feccf3 100644 --- a/http/download_test.go +++ b/http/download_test.go @@ -154,3 +154,13 @@ func (s *DownloaderSuite) TestGetLengthConnectError(c *C) { c.Assert(err, ErrorMatches, ".*no such host") } + +func (s *DownloaderSuite) TestContextCancel(c *C) { + ctx, cancel := context.WithCancel(s.ctx) + s.ctx = ctx + + cancel() + _, err := s.d.GetLength(s.ctx, "http://nosuch.host.invalid./") + + c.Assert(err, ErrorMatches, ".*context canceled.*") +} diff --git a/man/aptly.1 b/man/aptly.1 index fe986f0f..d49d2f6f 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "APTLY" "1" "January 2022" "" "" +.TH "APTLY" "1" "October 2024" "" "" . .SH "NAME" \fBaptly\fR \- Debian repository management tool @@ -25,7 +25,7 @@ aptly is a tool to create partial and full mirrors of remote repositories, manag aptly\(cqs goal is to establish repeatability and controlled changes in a package\-centric environment\. aptly allows one to fix a set of packages in a repository, so that package installation and upgrade becomes deterministic\. At the same time aptly allows one to perform controlled, fine\-grained changes in repository contents to transition your package environment to new version\. . .SH "CONFIGURATION" -aptly looks for configuration file first in \fB~/\.aptly\.conf\fR then in \fB/etc/aptly\.conf\fR and, if no config file found, new one is created in home directory\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly\fR, it will be created if missing\. +aptly looks for configuration file first in \fB~/\.aptly\.conf\fR then in \fB/usr/local/etc/aptly\.conf\fR and \fB/etc/aptly\.conf\fR\. If no config file found (or they are not readable), a new one is created in the home directory\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly/\fR, it will be created if missing\. . .P Configuration file is stored in JSON format (default values shown below): @@ -36,6 +36,10 @@ Configuration file is stored in JSON format (default values shown below): { "rootDir": "$HOME/\.aptly", + "databaseBackend": { + "type": "", + "url": "" + }, "downloadConcurrency": 4, "downloadSpeedLimit": 0, "downloadRetries": 0, @@ -51,6 +55,16 @@ Configuration file is stored in JSON format (default values shown below): "gpgDisableVerify": false, "gpgProvider": "gpg", "downloadSourcePackages": false, + "packagePoolStorage": { + "path": "$ROOTDIR/pool", + "azure": { + "accountName": "", + "accountKey": "", + "container": "repo", + "prefix": "", + "endpoint": "" + } + }, "skipLegacyPool": true, "ppaDistributorID": "ubuntu", "ppaCodename": "", @@ -84,7 +98,7 @@ Configuration file is stored in JSON format (default values shown below): "plusWorkaround": false, "disableMultiDel": false, "forceSigV2": false, - "forceVirtualHostedStyle": false, + "forceVirtualHostedStyle": true, "debug": false } }, @@ -104,8 +118,8 @@ Configuration file is stored in JSON format (default values shown below): "accountName": "", "accountKey": "", "container": "repo", - "prefix": "" - "endpoint": "blob.core.windows.net" + "prefix": "", + "endpoint": "blob\.core\.windows\.net" } } } @@ -117,85 +131,96 @@ Configuration file is stored in JSON format (default values shown below): .P Options: . -.TP -\fBrootDir\fR -is root of directory storage to store database (\fBrootDir\fR/db), downloaded packages (\fBrootDir\fR/pool) and the default for published repositories (\fBrootDir\fR/public) +.IP "\[ci]" 4 +\fBrootDir\fR: is root of directory storage to store database (\fBrootDir\fR/db), the default for downloaded packages (\fBrootDir\fR/pool) and the default for published repositories (\fBrootDir\fR/public) . -.TP -\fBdownloadConcurrency\fR -is a number of parallel download threads to use when downloading packages +.IP "\[ci]" 4 +\fBdatabaseBackend\fR: the database config; if this config is empty, use levledb backend by default . -.TP -\fBdownloadSpeedLimit\fR -limit in kbytes/sec on download speed while mirroring remote repositories +.IP "\[ci]" 4 +\fBdownloadConcurrency\fR: is a number of parallel download threads to use when downloading packages . -.TP -\fBdownloadRetries\fR -number of retries for download attempts +.IP "\[ci]" 4 +\fBdownloadSpeedLimit\fR: limit in kbytes/sec on download speed while mirroring remote repositories . -.TP -\fBdatabaseOpenAttempts\fR -number of attempts to open DB if it\(cqs locked by other instance; could be overridden with option \fB\-db\-open\-attempts\fR +.IP "\[ci]" 4 +\fBdownloadRetries\fR: number of retries for download attempts . -.TP -\fBarchitectures\fR -is a list of architectures to process; if left empty defaults to all available architectures; could be overridden with option \fB\-architectures\fR +.IP "\[ci]" 4 +\fBdatabaseOpenAttempts\fR: number of attempts to open DB if it\(cqs locked by other instance; could be overridden with option \fB\-db\-open\-attempts\fR . -.TP -\fBdependencyFollowSuggests\fR -follow contents of \fBSuggests:\fR field when processing dependencies for the package +.IP "\[ci]" 4 +\fBarchitectures\fR: is a list of architectures to process; if left empty defaults to all available architectures; could be overridden with option \fB\-architectures\fR . -.TP -\fBdependencyFollowRecommends\fR -follow contents of \fBRecommends:\fR field when processing dependencies for the package +.IP "\[ci]" 4 +\fBdependencyFollowSuggests\fR: follow contents of \fBSuggests:\fR field when processing dependencies for the package . -.TP -\fBdependencyFollowAllVariants\fR -when dependency looks like \fBpackage\-a | package\-b\fR, follow both variants always +.IP "\[ci]" 4 +\fBdependencyFollowRecommends\fR: follow contents of \fBRecommends:\fR field when processing dependencies for the package . -.TP -\fBdependencyFollowSource\fR -follow dependency from binary package to source package +.IP "\[ci]" 4 +\fBdependencyFollowAllVariants\fR: when dependency looks like \fBpackage\-a | package\-b\fR, follow both variants always . -.TP -\fBdependencyVerboseResolve\fR -print additional details while resolving dependencies (useful for debugging) +.IP "\[ci]" 4 +\fBdependencyFollowSource\fR: follow dependency from binary package to source package . -.TP -\fBgpgDisableSign\fR -don\(cqt sign published repositories with gpg(1), also can be disabled on per\-repo basis using \fB\-skip\-signing\fR flag when publishing +.IP "\[ci]" 4 +\fBdependencyVerboseResolve\fR: print additional details while resolving dependencies (useful for debugging) . -.TP -\fBgpgDisableVerify\fR -don\(cqt verify remote mirrors with gpg(1), also can be disabled on per\-mirror basis using \fB\-ignore\-signatures\fR flag when creating and updating mirrors +.IP "\[ci]" 4 +\fBgpgDisableSign\fR: don\(cqt sign published repositories with gpg(1), also can be disabled on per\-repo basis using \fB\-skip\-signing\fR flag when publishing . -.TP -\fBgpgProvider\fR -implementation of PGP signing/validation \- \fBgpg\fR for external \fBgpg\fR utility or \fBinternal\fR to use Go internal implementation; \fBgpg1\fR might be used to force use of GnuPG 1\.x, \fBgpg2\fR enables GnuPG 2\.x only; default is to use GnuPG 1\.x if available and GnuPG 2\.x otherwise +.IP "\[ci]" 4 +\fBgpgDisableVerify\fR: don\(cqt verify remote mirrors with gpg(1), also can be disabled on per\-mirror basis using \fB\-ignore\-signatures\fR flag when creating and updating mirrors . -.TP -\fBdownloadSourcePackages\fR -if enabled, all mirrors created would have flag set to download source packages; this setting could be controlled on per\-mirror basis with \fB\-with\-sources\fR flag +.IP "\[ci]" 4 +\fBgpgProvider\fR: implementation of PGP signing/validation \- \fBgpg\fR for external \fBgpg\fR utility or \fBinternal\fR to use Go internal implementation; \fBgpg1\fR might be used to force use of GnuPG 1\.x, \fBgpg2\fR enables GnuPG 2\.x only; default is to use GnuPG 1\.x if available and GnuPG 2\.x otherwise . -.TP -\fBskipLegacyPool\fR -in aptly up to version 1\.0\.0, package files were stored in internal package pool with MD5\-dervied path, since 1\.1\.0 package pool layout was changed; if option is enabled, aptly stops checking for legacy paths; by default option is enabled for new aptly installations and disabled when upgrading from older versions +.IP "\[ci]" 4 +\fBdownloadSourcePackages\fR: if enabled, all mirrors created would have flag set to download source packages; this setting could be controlled on per\-mirror basis with \fB\-with\-sources\fR flag . -.TP -\fBppaDistributorID\fR, \fBppaCodename\fR -specifies paramaters for short PPA url expansion, if left blank they default to output of \fBlsb_release\fR command +.IP "\[ci]" 4 +\fBpackagePoolStorage\fR: configures the location to store downloaded packages (defaults to the path \fB$ROOTDIR/pool\fR), by setting the value of the \fBtype\fR: . -.TP -\fBFileSystemPublishEndpoints\fR -configuration of local filesystem publishing endpoints (see below) +.IP "\[ci]" 4 +\fBpath\fR: store the packages in the given path . -.TP -\fBS3PublishEndpoints\fR -configuration of Amazon S3 publishing endpoints (see below) +.IP "\[ci]" 4 +\fBazure\fR: store the packages in the given Azure Blob Storage container (see the section on Azure publishing below for information on the configuration) . -.TP -\fBSwiftPublishEndpoints\fR -configuration of OpenStack Swift publishing endpoints (see below) +.IP "" 0 + +. +.IP "\[ci]" 4 +\fBskipLegacyPool\fR: in aptly up to version 1\.0\.0, package files were stored in internal package pool with MD5\-dervied path, since 1\.1\.0 package pool layout was changed; if option is enabled, aptly stops checking for legacy paths; by default option is enabled for new aptly installations and disabled when upgrading from older versions +. +.IP "\[ci]" 4 +\fBppaDistributorID\fR, \fBppaCodename\fR: specifies paramaters for short PPA url expansion, if left blank they default to output of \fBlsb_release\fR command +. +.IP "\[ci]" 4 +\fBFileSystemPublishEndpoints\fR: configuration of local filesystem publishing endpoints (see below) +. +.IP "\[ci]" 4 +\fBS3PublishEndpoints\fR: configuration of Amazon S3 publishing endpoints (see below) +. +.IP "\[ci]" 4 +\fBSwiftPublishEndpoints\fR: configuration of OpenStack Swift publishing endpoints (see below) +. +.IP "\[ci]" 4 +\fBAzurePublishEndpoints\fR: configuration of Azure publishing endpoints (see below) +. +.IP "" 0 +. +.SH "CUSTOM PACKAGE POOLS" +aptly defaults to storing downloaded packages at \fBrootDir/\fRpool\. In order to change this, you can set the \fBtype\fR key within \fBpackagePoolStorage\fR to one of two values: +. +.IP "\[ci]" 4 +\fBlocal\fR: Store the package pool locally (the default)\. In order to change the path, additionally set the \fBpath\fR key within \fBpackagePoolStorage\fR to the desired location\. +. +.IP "\[ci]" 4 +\fBazure\fR: Store the package pool in an Azure Blob Storage container\. Any keys in the below section on Azure publishing may be set on the \fBpackagePoolStorage\fR object in order to configure the Azure connection\. +. +.IP "" 0 . .SH "FILESYSTEM PUBLISHING ENDPOINTS" aptly defaults to publish to a single publish directory under \fBrootDir\fR/public\. For a more advanced publishing strategy, you can define one or more filesystem endpoints in the \fBFileSystemPublishEndpoints\fR list of the aptly configuration file\. Each endpoint has a name and the following associated settings: @@ -308,6 +333,25 @@ In order to publish to Swift, specify endpoint as \fBswift:endpoint\-name:\fR be .P \fBaptly publish snapshot jessie\-main swift:test:\fR . +.SH "AZURE PUBLISHING ENDPOINTS" +aptly can be configured to publish repositories directly to Microsoft Azure Blob Storage\. First, publishing endpoints should be described in the aptly configuration file\. Each endpoint has its name and associated settings: +. +.TP +\fBcontainer\fR +container name +. +.TP +\fBprefix\fR +(optional) do publishing under specified prefix in the container, defaults to no prefix (container root) +. +.TP +\fBaccountName\fR, \fBaccountKey\fR +Azure storage account access key to access blob storage +. +.TP +\fBendpoint\fR +endpoint URL to connect to, as described in the Azure documentation \fIhttps://docs\.microsoft\.com/en\-us/azure/storage/common/storage\-configure\-connection\-string\fR; defaults to \fBhttps://$accountName\.blob\.core\.windows\.net\fR +. .SH "PACKAGE QUERY" Some commands accept package queries to identify list of packages to process\. Package query syntax almost matches \fBreprepro\fR query language\. Query consists of the following simple terms: . @@ -431,7 +475,7 @@ list of architectures to consider during (comma\-separated), default to all avai . .TP \-\fBconfig\fR= -location of configuration file (default locations are /etc/aptly\.conf, ~/\.aptly\.conf) +location of configuration file (default locations in order: ~/\.aptly\.conf, /usr/local/etc/aptly\.conf, /etc/aptly\.conf) . .TP \-\fBdb\-open\-attempts\fR=10 @@ -507,6 +551,10 @@ disable verification of Release file signatures gpg keyring to use when verifying Release file (could be specified multiple times) . .TP +\-\fBmax\-tries\fR=1 +max download tries till process fails with download error +. +.TP \-\fBwith\-installer\fR download additional not packaged installer files . @@ -1009,13 +1057,13 @@ custom format for result printing include dependencies into search results . .SH "ADD PACKAGES TO LOCAL REPOSITORIES BASED ON \.CHANGES FILES" -\fBaptly\fR \fBrepo\fR \fBinclude\fR |\fIdirectory\fR \fB\|\.\|\.\|\.\fR +\fBaptly\fR \fBrepo\fR \fBinclude\fR . .P Command include looks for \.changes files in list of arguments or specified directories\. Each \.changes file is verified, parsed, referenced files are put into separate temporary directory and added into local repository\. Successfully imported files are removed by default\. . .P -Additionally uploads could be restricted with file\. Rules in this file control uploads based on GPG key ID of \.changes file signature and queries on \.changes file fields\. +Additionally uploads could be restricted with . .P Example: @@ -1450,6 +1498,10 @@ run GPG with detached tty set value for ButAutomaticUpgrades field . .TP +\-\fBcodename\fR= +codename to publish (defaults to distribution) +. +.TP \-\fBcomponent\fR= component name to publish (for multi\-component publishing, separate components with commas) . @@ -1474,6 +1526,10 @@ GPG keyring to use (instead of default) label to publish . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBnotautomatic\fR= set value for NotAutomatic field . @@ -1494,6 +1550,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-contents\fR don\(cqt generate Contents indexes . @@ -1505,9 +1565,31 @@ don\(cqt sign Release files with GPG \-\fBsuite\fR= suite to publish (defaults to distribution) . +.SH "SHOWS DETAILS OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBshow\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] +. +.P +Command show displays full information of a published repository\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish show wheezy +. +.fi +. +.IP "" 0 +. +.P +Options: +. .TP -\-\fBcodename\fR= -codename to publish (defaults to distribution) +\-\fBjson\fR +display record in JSON format . .SH "PUBLISH SNAPSHOT" \fBaptly\fR \fBpublish\fR \fBsnapshot\fR \fIname\fR [[\fIendpoint\fR:]\fIprefix\fR] @@ -1557,6 +1639,10 @@ run GPG with detached tty overwrite value for ButAutomaticUpgrades field . .TP +\-\fBcodename\fR= +codename to publish (defaults to distribution) +. +.TP \-\fBcomponent\fR= component name to publish (for multi\-component publishing, separate components with commas) . @@ -1581,6 +1667,10 @@ GPG keyring to use (instead of default) label to publish . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBnotautomatic\fR= overwrite value for NotAutomatic field . @@ -1601,6 +1691,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-contents\fR don\(cqt generate Contents indexes . @@ -1612,18 +1706,200 @@ don\(cqt sign Release files with GPG \-\fBsuite\fR= suite to publish (defaults to distribution) . +.SH "ADD SOURCE TO STAGED SOURCE LIST OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBadd\fR \fIdistribution\fR \fIsource\fR +. +.P +The command adds sources to the staged source list of the published repository\. +. +.P +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\.: +. +.IP "" 4 +. +.nf + +aptly publish add \-component=main,contrib wheezy wheezy\-main wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish add \-component=contrib wheezy ppa wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +This command assigns the snapshot wheezy\-contrib to the component contrib and adds it to published repository revision of ppa/wheezy\. +. +.P +Options: +. .TP -\-\fBcodename\fR= -codename to publish (defaults to distribution) +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) . -.SH "UPDATE PUBLISHED REPOSITORY BY SWITCHING TO NEW SNAPSHOT" -\fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-snapshot\fR +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "DROPS STAGED SOURCE CHANGES OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBdrop\fR \fIdistribution\fR . .P -Command switches in\-place published snapshots with new snapshot contents\. All publishing parameters are preserved (architecture list, distribution, component)\. +Command drops the staged source changes of the published repository\. . .P -For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding snapshots should be given in the same order, e\.g\.: +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source drop wheezy +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "LISTS REVISION OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBlist\fR \fIdistribution\fR +. +.P +Command lists sources of a published repository\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish source list wheezy +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBjson\fR +display record in JSON format +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "REMOVE SOURCE FROM STAGED SOURCE LIST OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBremove\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fIsource\fR +. +.P +The command removes sources from the staged source list of the published repository\. +. +.P +The flag \-component is mandatory\. Use a comma\-separated list of components, if multiple components should be removed, e\.g\.: +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish remove \-component=contrib,non\-free wheezy filesystem:symlink:debian +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to remove (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "UPDATE SOURCE IN STAGED SOURCE LIST OF PUBLISHED REPOSITORY" +\fBaptly\fR \fBpublish\fR \fBsource\fR \fBupdate\fR \fIdistribution\fR \fIsource\fR +. +.P +The command updates sources in the staged source list of the published repository\. +. +.P +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\.: +. +.IP "" 4 +. +.nf + +aptly publish update \-component=main,contrib wheezy wheezy\-main wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly publish update \-component=contrib wheezy ppa wheezy\-contrib +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBcomponent\fR= +component names to add (for multi\-component publishing, separate components with commas) +. +.TP +\-\fBprefix\fR=\. +publishing prefix in the form of [\fIendpoint\fR:]\fIprefix\fR +. +.SH "UPDATE PUBLISHED REPOSITORY BY SWITCHING TO NEW SOURCE" +\fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-source\fR +. +.P +Command switches in\-place published snapshots with new source contents\. All publishing parameters are preserved (architecture list, distribution, component)\. +. +.P +For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding sources should be given in the same order, e\.g\.: . .IP "" 4 . @@ -1675,6 +1951,10 @@ GPG key ID to use when signing the release GPG keyring to use (instead of default) . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBpassphrase\fR= GPG passphrase for the key (warning: could be insecure) . @@ -1687,6 +1967,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-cleanup\fR don\(cqt remove unreferenced files in prefix/component . @@ -1740,6 +2024,10 @@ GPG key ID to use when signing the release GPG keyring to use (instead of default) . .TP +\-\fBmulti\-dist\fR +enable multiple packages with the same filename in different distributions +. +.TP \-\fBpassphrase\fR= GPG passphrase for the key (warning: could be insecure) . @@ -1752,6 +2040,10 @@ GPG passphrase\-file for the key (warning: could be insecure) GPG secret keyring to use (instead of default) . .TP +\-\fBskip\-bz2\fR +don\(cqt generate bzipped indexes +. +.TP \-\fBskip\-cleanup\fR don\(cqt remove unreferenced files in prefix/component . @@ -1763,32 +2055,6 @@ don\(cqt generate Contents indexes \-\fBskip\-signing\fR don\(cqt sign Release files with GPG . -.SH "SHOWS DETAILS OF PUBLISHED REPOSITORY" -\fBaptly\fR \fBpublish\fR \fBshow\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] -. -.P -Command show displays full information of a published repository\. -. -.P -Example: -. -.IP "" 4 -. -.nf - -$ aptly publish show wheezy -. -.fi -. -.IP "" 0 -. -.P -Options: -. -.TP -\-\fBjson\fR -display record in JSON format -. .SH "SEARCH FOR PACKAGES MATCHING QUERY" \fBaptly\fR \fBpackage\fR \fBsearch\fR [\fIpackage\-query\fR] . @@ -2160,5 +2426,65 @@ Lorenzo Bolla (https://github\.com/lbolla) .IP "\[ci]" 4 Benj Fassbind (https://github\.com/randombenj) . +.IP "\[ci]" 4 +Markus Muellner (https://github\.com/mmianl) +. +.IP "\[ci]" 4 +Chuan Liu (https://github\.com/chuan) +. +.IP "\[ci]" 4 +Samuel Mutel (https://github\.com/smutel) +. +.IP "\[ci]" 4 +Russell Greene (https://github\.com/russelltg) +. +.IP "\[ci]" 4 +Wade Simmons (https://github\.com/wadey) +. +.IP "\[ci]" 4 +Steven Stone (https://github\.com/smstone) +. +.IP "\[ci]" 4 +Josh Bayfield (https://github\.com/jbayfield) +. +.IP "\[ci]" 4 +Boxjan (https://github\.com/boxjan) +. +.IP "\[ci]" 4 +Mauro Regli (https://github\.com/reglim) +. +.IP "\[ci]" 4 +Alexander Zubarev (https://github\.com/strike) +. +.IP "\[ci]" 4 +Nicolas Dostert (https://github\.com/acdn\-ndostert) +. +.IP "\[ci]" 4 +Ryan Gonzalez (https://github\.com/refi64) +. +.IP "\[ci]" 4 +Paul Cacheux (https://github\.com/paulcacheux) +. +.IP "\[ci]" 4 +Nic Waller (https://github\.com/sf\-nwaller) +. +.IP "\[ci]" 4 +iofq (https://github\.com/iofq) +. +.IP "\[ci]" 4 +Noa Resare (https://github\.com/nresare) +. +.IP "\[ci]" 4 +Ramon N\.Rodriguez (https://github\.com/runitonmetal) +. +.IP "\[ci]" 4 +Golf Hu (https://github\.com/hudeng\-go) +. +.IP "\[ci]" 4 +Cookie Fei (https://github\.com/wuhuang26) +. +.IP "\[ci]" 4 +Christoph Fiehe (https://github\.com/cfiehe) +. .IP "" 0 diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl index baab5ff6..e88ba35e 100644 --- a/man/aptly.1.ronn.tmpl +++ b/man/aptly.1.ronn.tmpl @@ -111,7 +111,7 @@ Configuration file is stored in JSON format (default values shown below): "accountKey": "", "container": "repo", "prefix": "", - "endpoint": "" + "endpoint": "blob.core.windows.net" } } } 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/docker-wrapper b/system/docker-wrapper index 3bee17e7..1d92f392 100755 --- a/system/docker-wrapper +++ b/system/docker-wrapper @@ -1,11 +1,14 @@ #!/bin/sh -e # make sure files are written with correct user ownership -usermod -u `stat -c %u /work/src` aptly >/dev/null -chown -R `stat -c %u /work/src` /var/lib/aptly +if [ `stat -c %u /work/src` -ne 0 ]; then + usermod -u `stat -c %u /work/src` aptly >/dev/null + chown -R `stat -c %u /work/src` /var/lib/aptly +fi args="$@" if [ -z "$args" ]; then + cp /work/src/completion.d/aptly /usr/share/bash-completion/completions/ cmd="bash" else cmd="make $@" 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/t04_mirror/UpdateMirror10Test_gold b/system/t04_mirror/UpdateMirror10Test_gold index fa0f41f2..54251a64 100644 --- a/system/t04_mirror/UpdateMirror10Test_gold +++ b/system/t04_mirror/UpdateMirror10Test_gold @@ -18,7 +18,7 @@ Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/d Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_i386.deb Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_amd64.deb Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_i386.deb -Mirror `flat-src` has been successfully updated. +Mirror `flat-src` has been updated successfully. Packages filtered: 110 -> 11. gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 gpgv: Good signature from "Johannes Ranke " diff --git a/system/t04_mirror/UpdateMirror11FTPTest_gold b/system/t04_mirror/UpdateMirror11FTPTest_gold index 24f61552..dc4bab13 100644 --- a/system/t04_mirror/UpdateMirror11FTPTest_gold +++ b/system/t04_mirror/UpdateMirror11FTPTest_gold @@ -13,7 +13,7 @@ Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/deb Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sensible-utils/sensible-utils_0.0.9+deb9u1_all.deb Downloading: http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/pool/main/s/sysvinit/sysvinit-utils_2.88dsf-59.9_i386.deb Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease -Mirror `stretch-main` has been successfully updated. +Mirror `stretch-main` has been updated successfully. Packages filtered: 50604 -> 3. Retrying 0 http://repo.aptly.info/system-tests/snapshot.debian.org/archive/debian/20220201T025006Z/dists/stretch/InRelease... gpgv: issuer "debian-release@lists.debian.org" diff --git a/system/t04_mirror/UpdateMirror12Test_gold b/system/t04_mirror/UpdateMirror12Test_gold index eff42e76..f9ad32d5 100644 --- a/system/t04_mirror/UpdateMirror12Test_gold +++ b/system/t04_mirror/UpdateMirror12Test_gold @@ -37,7 +37,7 @@ Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archi Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_amd64.deb Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/g/gnupg2/scdaemon_2.1.18-8~deb9u4_i386.deb Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease -Mirror `stretch` has been successfully updated. +Mirror `stretch` has been updated successfully. Packages filtered: 78248 -> 20. Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... gpgv: issuer "debian-release@lists.debian.org" diff --git a/system/t04_mirror/UpdateMirror13Test_gold b/system/t04_mirror/UpdateMirror13Test_gold index 63c166c8..b00c4ce3 100644 --- a/system/t04_mirror/UpdateMirror13Test_gold +++ b/system/t04_mirror/UpdateMirror13Test_gold @@ -58,4 +58,4 @@ Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/va Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb -Mirror `varnish` has been successfully updated. \ No newline at end of file +Mirror `varnish` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror14Test_gold b/system/t04_mirror/UpdateMirror14Test_gold index 0c1b5c59..edcc3695 100644 --- a/system/t04_mirror/UpdateMirror14Test_gold +++ b/system/t04_mirror/UpdateMirror14Test_gold @@ -6,4 +6,4 @@ Downloading & parsing package files... Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2 Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2 -Mirror `varnish` has been successfully updated. \ No newline at end of file +Mirror `varnish` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror15Test_gold b/system/t04_mirror/UpdateMirror15Test_gold index 3c11f1ff..d218085c 100644 --- a/system/t04_mirror/UpdateMirror15Test_gold +++ b/system/t04_mirror/UpdateMirror15Test_gold @@ -6,4 +6,4 @@ Downloading & parsing package files... Downloading https://dl.bintray.com/smira/deb/Packages.bz2... Downloading https://dl.bintray.com/smira/deb/Release... Downloading https://dl.bintray.com/smira/deb/libboost-program-options-dev_1.49.0.1_i386.deb... -Mirror `bintray` has been successfully updated. \ No newline at end of file +Mirror `bintray` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror16Test_gold b/system/t04_mirror/UpdateMirror16Test_gold index 3c11f1ff..d218085c 100644 --- a/system/t04_mirror/UpdateMirror16Test_gold +++ b/system/t04_mirror/UpdateMirror16Test_gold @@ -6,4 +6,4 @@ Downloading & parsing package files... Downloading https://dl.bintray.com/smira/deb/Packages.bz2... Downloading https://dl.bintray.com/smira/deb/Release... Downloading https://dl.bintray.com/smira/deb/libboost-program-options-dev_1.49.0.1_i386.deb... -Mirror `bintray` has been successfully updated. \ No newline at end of file +Mirror `bintray` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror17Test_gold b/system/t04_mirror/UpdateMirror17Test_gold index 6341c1de..cddc0d3e 100644 --- a/system/t04_mirror/UpdateMirror17Test_gold +++ b/system/t04_mirror/UpdateMirror17Test_gold @@ -6,5 +6,5 @@ Download queue: 0 items (0 B) Downloading & parsing package files... Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz -Mirror `stretch` has been successfully updated. +Mirror `stretch` has been updated successfully. Packages filtered: 50604 -> 1. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror18Test_gold b/system/t04_mirror/UpdateMirror18Test_gold index 0e0deb88..30dfc184 100644 --- a/system/t04_mirror/UpdateMirror18Test_gold +++ b/system/t04_mirror/UpdateMirror18Test_gold @@ -7,5 +7,5 @@ Downloading & parsing package files... Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb -Mirror `stretch` has been successfully updated. +Mirror `stretch` has been updated successfully. Packages filtered: 50604 -> 1. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror19Test_gold b/system/t04_mirror/UpdateMirror19Test_gold index fcf7c33c..ef4a8634 100644 --- a/system/t04_mirror/UpdateMirror19Test_gold +++ b/system/t04_mirror/UpdateMirror19Test_gold @@ -12,4 +12,4 @@ Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/ Building download queue... Download queue: 23 items (3.46 MiB) -Mirror `pagerduty` has been successfully updated. +Mirror `pagerduty` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror1Test_gold b/system/t04_mirror/UpdateMirror1Test_gold index 63c166c8..b00c4ce3 100644 --- a/system/t04_mirror/UpdateMirror1Test_gold +++ b/system/t04_mirror/UpdateMirror1Test_gold @@ -58,4 +58,4 @@ Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/va Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish/varnish_3.0.3-1~wheezy_i386.deb -Mirror `varnish` has been successfully updated. \ No newline at end of file +Mirror `varnish` has been updated successfully. \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror20Test_gold b/system/t04_mirror/UpdateMirror20Test_gold index 3353fc5b..726afd93 100644 --- a/system/t04_mirror/UpdateMirror20Test_gold +++ b/system/t04_mirror/UpdateMirror20Test_gold @@ -8,7 +8,7 @@ Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/d Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/Packages.bz2 Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_amd64.deb Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/r-cran-class_7.3-22-2~bullseyecran.0_i386.deb -Mirror `flat` has been successfully updated. +Mirror `flat` has been updated successfully. Packages filtered: 89 -> 2. openpgp: Good signature from "Johannes Ranke " openpgp: RSA key ID B8F25A8A73EACF41 \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror21Test_gold b/system/t04_mirror/UpdateMirror21Test_gold index 869ec65b..e864bf91 100644 --- a/system/t04_mirror/UpdateMirror21Test_gold +++ b/system/t04_mirror/UpdateMirror21Test_gold @@ -11,4 +11,4 @@ Downloading: http://repo.aptly.info/system-tests/packages.pagerduty.com/pdagent/ Building download queue... Download queue: 23 items (3.46 MiB) -Mirror `pagerduty` has been successfully updated. +Mirror `pagerduty` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror22Test_gold b/system/t04_mirror/UpdateMirror22Test_gold index 6724bab1..9c76ac64 100644 --- a/system/t04_mirror/UpdateMirror22Test_gold +++ b/system/t04_mirror/UpdateMirror22Test_gold @@ -8,4 +8,4 @@ Applying filter... Building download queue... Download queue: 0 items (0 B) -Mirror `libnvidia-container` has been successfully updated. +Mirror `libnvidia-container` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror23Test_gold b/system/t04_mirror/UpdateMirror23Test_gold index 5e6157c7..5b172366 100644 --- a/system/t04_mirror/UpdateMirror23Test_gold +++ b/system/t04_mirror/UpdateMirror23Test_gold @@ -23,7 +23,7 @@ Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archi Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS -Mirror `stretch` has been successfully updated. +Mirror `stretch` has been updated successfully. Packages filtered: 49256 -> 1. Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease... Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/non-free/installer-s390x/current/images/SHA256SUMS... diff --git a/system/t04_mirror/UpdateMirror24Test_gold b/system/t04_mirror/UpdateMirror24Test_gold index e8ec96b9..dcc27631 100644 --- a/system/t04_mirror/UpdateMirror24Test_gold +++ b/system/t04_mirror/UpdateMirror24Test_gold @@ -53,7 +53,7 @@ Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/di Downloading: http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease Error (retrying): HTTP code 404 while fetching http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS -Mirror `trusty` has been successfully updated. +Mirror `trusty` has been updated successfully. Packages filtered: 8616 -> 1. Retrying 0 http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/InRelease... Retrying 0 http://repo.aptly.info/system-tests/us.archive.ubuntu.com/ubuntu/dists/trusty/restricted/installer-amd64/current/images/SHA256SUMS... diff --git a/system/t04_mirror/UpdateMirror26Test_gold b/system/t04_mirror/UpdateMirror26Test_gold index aa6d212e..b460083f 100644 --- a/system/t04_mirror/UpdateMirror26Test_gold +++ b/system/t04_mirror/UpdateMirror26Test_gold @@ -14,7 +14,7 @@ Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archi Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/main/binary-i386/Packages.gz Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/pool/main/b/boost-defaults/libboost-program-options-dev_1.62.0.1_i386.deb -Mirror `grab` has been successfully updated. +Mirror `grab` has been updated successfully. Packages filtered: 50604 -> 1. Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease Retrying 0 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/InRelease diff --git a/system/t04_mirror/UpdateMirror6Test_gold b/system/t04_mirror/UpdateMirror6Test_gold index 84cb9694..000a38aa 100644 --- a/system/t04_mirror/UpdateMirror6Test_gold +++ b/system/t04_mirror/UpdateMirror6Test_gold @@ -18,4 +18,4 @@ Download queue: 1 items (30 B) Downloading: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb WARNING: ${url}pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb: sha1 hash mismatch "8d3a014000038725d6daf8771b42a0784253688f" != "66b27417d37e024c46526c2f6d358a754fc552f3" -Mirror `failure` has been successfully updated. +Mirror `failure` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror7Test_gold b/system/t04_mirror/UpdateMirror7Test_gold index 14090853..bc1279d8 100644 --- a/system/t04_mirror/UpdateMirror7Test_gold +++ b/system/t04_mirror/UpdateMirror7Test_gold @@ -94,7 +94,7 @@ Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/d Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward-dbgsym_0.7.5-1~bullseyecran.0_i386.deb Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_amd64.deb Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/rkward_0.7.5-1~bullseyecran.0_i386.deb -Mirror `flat` has been successfully updated. +Mirror `flat` has been updated successfully. gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 gpgv: Good signature from "Johannes Ranke " gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC \ No newline at end of file diff --git a/system/t04_mirror/UpdateMirror8Test_gold b/system/t04_mirror/UpdateMirror8Test_gold index 30dbccb9..382b15e2 100644 --- a/system/t04_mirror/UpdateMirror8Test_gold +++ b/system/t04_mirror/UpdateMirror8Test_gold @@ -15,4 +15,4 @@ Downloading: http://repo.aptly.info/system-tests/ppa.launchpad.net/gladky-anton/ Building download queue... Download queue: 0 items (0 B) -Mirror `gnuplot-maverick-src` has been successfully updated. +Mirror `gnuplot-maverick-src` has been updated successfully. diff --git a/system/t04_mirror/UpdateMirror9Test_gold b/system/t04_mirror/UpdateMirror9Test_gold index 1d898874..2c2dba87 100644 --- a/system/t04_mirror/UpdateMirror9Test_gold +++ b/system/t04_mirror/UpdateMirror9Test_gold @@ -157,7 +157,7 @@ Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/d Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/survival_3.5-7-1~bullseyecran.0.debian.tar.xz Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/survival_3.5-7-1~bullseyecran.0.dsc Downloading: http://repo.aptly.info/system-tests/cloud.r-project.org/bin/linux/debian/bullseye-cran40/survival_3.5-7.orig.tar.gz -Mirror `flat-src` has been successfully updated. +Mirror `flat-src` has been updated successfully. gpgv: using RSA key 7BA040A510E4E66ED3743EC1B8F25A8A73EACF41 gpgv: Good signature from "Johannes Ranke " gpgv: Signature made Thu Nov 2 07:43:52 2023 UTC \ No newline at end of file diff --git a/system/t05_snapshot/VerifySnapshot7Test_gold b/system/t05_snapshot/VerifySnapshot7Test_gold index 85e3ce21..8d2420c7 100644 --- a/system/t05_snapshot/VerifySnapshot7Test_gold +++ b/system/t05_snapshot/VerifySnapshot7Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (625): +Missing dependencies (635): abrowser [amd64] abrowser [i386] apache [amd64] @@ -106,6 +106,8 @@ Missing dependencies (625): ghostcript-x (= 9.05~dfsg-6.3+deb7u1) [i386] gij [amd64] gij [i386] + git-core (<= 1:1.7.0.4-1) [amd64] + git-core (<= 1:1.7.0.4-1) [i386] gkrellm2 [amd64] gkrellm2 [i386] gnome-themes-more [amd64] @@ -302,6 +304,8 @@ Missing dependencies (625): libgnomeprint-data [i386] libgtk2.0-dev (<< 2.21) [amd64] libgtk2.0-dev (<< 2.21) [i386] + libhaml-ruby (<< 3.1) [amd64] + libhaml-ruby (<< 3.1) [i386] libicu36-dev [amd64] libicu36-dev [i386] libjasper-1.701-dev [amd64] @@ -328,6 +332,8 @@ Missing dependencies (625): libsvn-core-perl [i386] libswt-mozilla-gtk-3-jni [amd64] libswt-mozilla-gtk-3-jni [i386] + libtest-harness-perl (= 3.23-1) [amd64] + libtest-harness-perl (= 3.23-1) [i386] libwww-perl (<< 6) [amd64] libwww-perl (<< 6) [i386] libzephyr4-krb (= 3.0.2-2) [amd64] @@ -424,6 +430,8 @@ Missing dependencies (625): puredata (<< 0.43) [i386] python-celementtree [amd64] python-celementtree [i386] + python-codespeak-lib (<< 1.0) [amd64] + python-codespeak-lib (<< 1.0) [i386] python-elementtree (>= 1.2) [amd64] python-elementtree (>= 1.2) [i386] python-elementtree [amd64] @@ -576,6 +584,8 @@ Missing dependencies (625): xmessage [i386] xpdf-reader (<< 3.02-2) [amd64] xpdf-reader (<< 3.02-2) [i386] + xpdf-utils (>= 3.02-2) [amd64] + xpdf-utils (>= 3.02-2) [i386] xrandr [amd64] xrandr [i386] xulrunner-1.9 [amd64] diff --git a/system/t06_publish/AzurePublish2Test_gold b/system/t06_publish/AzurePublish2Test_gold index d9fa9ada..0ac6b345 100644 --- a/system/t06_publish/AzurePublish2Test_gold +++ b/system/t06_publish/AzurePublish2Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository azure:test1:./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/AzurePublish3Test_gold b/system/t06_publish/AzurePublish3Test_gold index fbcd2e79..a346b469 100644 --- a/system/t06_publish/AzurePublish3Test_gold +++ b/system/t06_publish/AzurePublish3Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository azure:test1:./maverick... +Cleaning up component '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/AzurePublish5Test_gold b/system/t06_publish/AzurePublish5Test_gold index cadce02c..bef652cb 100644 --- a/system/t06_publish/AzurePublish5Test_gold +++ b/system/t06_publish/AzurePublish5Test_gold @@ -1,3 +1,4 @@ -Cleaning up prefix "." components main... +Cleaning up published repository azure:test1:./sq2... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishDrop3Test_gold b/system/t06_publish/PublishDrop3Test_gold index eb8ce9c0..1da15e9a 100644 --- a/system/t06_publish/PublishDrop3Test_gold +++ b/system/t06_publish/PublishDrop3Test_gold @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/sq1... -Cleaning up prefix "." components main... +Cleaning up published repository ./sq1... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishDrop5Test_gold b/system/t06_publish/PublishDrop5Test_gold index 79be1a16..2313ec65 100644 --- a/system/t06_publish/PublishDrop5Test_gold +++ b/system/t06_publish/PublishDrop5Test_gold @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/sq2... -Cleaning up prefix "." components main... +Cleaning up published repository ./sq2... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishDrop9Test_gold b/system/t06_publish/PublishDrop9Test_gold index eb8ce9c0..1da15e9a 100644 --- a/system/t06_publish/PublishDrop9Test_gold +++ b/system/t06_publish/PublishDrop9Test_gold @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/sq1... -Cleaning up prefix "." components main... +Cleaning up published repository ./sq1... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/PublishShow5Test_gold b/system/t06_publish/PublishShow5Test_gold new file mode 100644 index 00000000..7e6f4a2c --- /dev/null +++ b/system/t06_publish/PublishShow5Test_gold @@ -0,0 +1,5 @@ +Prefix: . +Distribution: wheezy +Architectures: i386 +Sources: + main: local-repo [local] diff --git a/system/t06_publish/PublishSnapshot42Test_gold b/system/t06_publish/PublishSnapshot42Test_gold new file mode 100644 index 00000000..8cf10f19 --- /dev/null +++ b/system/t06_publish/PublishSnapshot42Test_gold @@ -0,0 +1,13 @@ +Loading packages... +Generating metadata files and linking package files... +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: + +Snapshot snap1 has been successfully published. +Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. +Now you can add following line to apt sources: + deb http://your-server/ maverick main +Don't forget to add your GPG key to apt with apt-key. + +You can also use `aptly serve` to publish your repositories over HTTP quickly. diff --git a/system/t06_publish/PublishSourceAdd1Test_gold b/system/t06_publish/PublishSourceAdd1Test_gold new file mode 100644 index 00000000..12c4aa07 --- /dev/null +++ b/system/t06_publish/PublishSourceAdd1Test_gold @@ -0,0 +1,3 @@ +Adding component 'test' with source 'snap2' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceAdd2Test_gold b/system/t06_publish/PublishSourceAdd2Test_gold new file mode 100644 index 00000000..fcdf6d02 --- /dev/null +++ b/system/t06_publish/PublishSourceAdd2Test_gold @@ -0,0 +1,4 @@ +Adding component 'test' with source 'snap2' [snapshot]... +Adding component 'other-test' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceAdd3Test_gold b/system/t06_publish/PublishSourceAdd3Test_gold new file mode 100644 index 00000000..dd31661c --- /dev/null +++ b/system/t06_publish/PublishSourceAdd3Test_gold @@ -0,0 +1 @@ +ERROR: unable to add: component 'main' has already been added diff --git a/system/t06_publish/PublishSourceDrop1Test_gold b/system/t06_publish/PublishSourceDrop1Test_gold new file mode 100644 index 00000000..5f05ba78 --- /dev/null +++ b/system/t06_publish/PublishSourceDrop1Test_gold @@ -0,0 +1 @@ +Source changes have been removed successfully. diff --git a/system/t06_publish/PublishSourceList1Test_gold b/system/t06_publish/PublishSourceList1Test_gold new file mode 100644 index 00000000..364ef5c2 --- /dev/null +++ b/system/t06_publish/PublishSourceList1Test_gold @@ -0,0 +1,3 @@ +Sources: + main: snap1 [snapshot] + test: snap2 [snapshot] diff --git a/system/t06_publish/PublishSourceList2Test_gold b/system/t06_publish/PublishSourceList2Test_gold new file mode 100644 index 00000000..ba30e4af --- /dev/null +++ b/system/t06_publish/PublishSourceList2Test_gold @@ -0,0 +1,10 @@ +[ + { + "Component": "main", + "Name": "snap1" + }, + { + "Component": "test", + "Name": "snap2" + } +] diff --git a/system/t06_publish/PublishSourceList3Test_gold b/system/t06_publish/PublishSourceList3Test_gold new file mode 100644 index 00000000..03e2a99a --- /dev/null +++ b/system/t06_publish/PublishSourceList3Test_gold @@ -0,0 +1 @@ +ERROR: unable to list: no source changes exist diff --git a/system/t06_publish/PublishSourceRemove1Test_gold b/system/t06_publish/PublishSourceRemove1Test_gold new file mode 100644 index 00000000..40fcf4de --- /dev/null +++ b/system/t06_publish/PublishSourceRemove1Test_gold @@ -0,0 +1,3 @@ +Removing component 'test' with source 'snap2' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceRemove2Test_gold b/system/t06_publish/PublishSourceRemove2Test_gold new file mode 100644 index 00000000..f8270658 --- /dev/null +++ b/system/t06_publish/PublishSourceRemove2Test_gold @@ -0,0 +1,4 @@ +Removing component 'test' with source 'snap2' [snapshot]... +Removing component 'other-test' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceRemove3Test_gold b/system/t06_publish/PublishSourceRemove3Test_gold new file mode 100644 index 00000000..d4ebcbb1 --- /dev/null +++ b/system/t06_publish/PublishSourceRemove3Test_gold @@ -0,0 +1 @@ +ERROR: unable to remove: component 'not-existent' does not exist diff --git a/system/t06_publish/PublishSourceReplace1Test_gold b/system/t06_publish/PublishSourceReplace1Test_gold new file mode 100644 index 00000000..c3b03ff9 --- /dev/null +++ b/system/t06_publish/PublishSourceReplace1Test_gold @@ -0,0 +1,5 @@ +Replacing source list... +Adding component 'main-new' with source 'snap2' [snapshot]... +Adding component 'test-new' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceUpdate1Test_gold b/system/t06_publish/PublishSourceUpdate1Test_gold new file mode 100644 index 00000000..2f218bf2 --- /dev/null +++ b/system/t06_publish/PublishSourceUpdate1Test_gold @@ -0,0 +1,3 @@ +Updating component 'main' with source 'snap2' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceUpdate2Test_gold b/system/t06_publish/PublishSourceUpdate2Test_gold new file mode 100644 index 00000000..88178e48 --- /dev/null +++ b/system/t06_publish/PublishSourceUpdate2Test_gold @@ -0,0 +1,4 @@ +Updating component 'main' with source 'snap2' [snapshot]... +Updating component 'test' with source 'snap3' [snapshot]... + +You can run 'aptly publish update maverick .' to update the content of the published repository. diff --git a/system/t06_publish/PublishSourceUpdate3Test_gold b/system/t06_publish/PublishSourceUpdate3Test_gold new file mode 100644 index 00000000..c3e6eb88 --- /dev/null +++ b/system/t06_publish/PublishSourceUpdate3Test_gold @@ -0,0 +1 @@ +ERROR: unable to update: component 'not-existent' does not exist diff --git a/system/t06_publish/PublishSwitch11Test_gold b/system/t06_publish/PublishSwitch11Test_gold index 2385a0ed..5f37e805 100644 --- a/system/t06_publish/PublishSwitch11Test_gold +++ b/system/t06_publish/PublishSwitch11Test_gold @@ -5,6 +5,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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/PublishSwitch12Test_gold b/system/t06_publish/PublishSwitch12Test_gold index 8a50172b..ce8636a4 100644 --- a/system/t06_publish/PublishSwitch12Test_gold +++ b/system/t06_publish/PublishSwitch12Test_gold @@ -1,8 +1 @@ -Loading packages... -Generating metadata files and linking package files... -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: -Cleaning up prefix "." components a, c... - -Publish for snapshot ./maverick [i386] publishes {a: [snap2]: Created as empty}, {b: [snap2]: Created as empty}, {c: [snap3]: Created as empty} has been successfully switched to new snapshot. +ERROR: unable to switch: component c does not exist in published repository diff --git a/system/t06_publish/PublishSwitch13Test_gold b/system/t06_publish/PublishSwitch13Test_gold index 3f440f06..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch13Test_gold +++ b/system/t06_publish/PublishSwitch13Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch15Test_gold +++ b/system/t06_publish/PublishSwitch15Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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..6794c13d 100644 --- a/system/t06_publish/PublishSwitch16Test_gold +++ b/system/t06_publish/PublishSwitch16Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./bookworm... +Cleaning up component '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..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch1Test_gold +++ b/system/t06_publish/PublishSwitch1Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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..7cfc4f75 100644 --- a/system/t06_publish/PublishSwitch2Test_gold +++ b/system/t06_publish/PublishSwitch2Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "ppa" components main... +Cleaning up published repository ppa/maverick... +Cleaning up component '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..b9e7a75a 100644 --- a/system/t06_publish/PublishSwitch3Test_gold +++ b/system/t06_publish/PublishSwitch3Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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..74451164 100644 --- a/system/t06_publish/PublishSwitch4Test_gold +++ b/system/t06_publish/PublishSwitch4Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "ppa" components main... +Cleaning up published repository ppa/maverick... +Cleaning up component '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..27a20838 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: not a published snapshot repository diff --git a/system/t06_publish/PublishSwitch8Test_gold b/system/t06_publish/PublishSwitch8Test_gold index d9870ff9..44d1fdbe 100644 --- a/system/t06_publish/PublishSwitch8Test_gold +++ b/system/t06_publish/PublishSwitch8Test_gold @@ -3,6 +3,8 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components b, c... +Cleaning up published repository ./maverick... +Cleaning up component 'b'... +Cleaning up component '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..010d5ec4 100644 --- a/system/t06_publish/PublishUpdate10Test_gold +++ b/system/t06_publish/PublishUpdate10Test_gold @@ -5,6 +5,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate11Test_gold b/system/t06_publish/PublishUpdate11Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate11Test_gold +++ b/system/t06_publish/PublishUpdate11Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate12Test_gold b/system/t06_publish/PublishUpdate12Test_gold index 3b7a5709..d3a202df 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 updated successfully. diff --git a/system/t06_publish/PublishUpdate13Test_gold b/system/t06_publish/PublishUpdate13Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate13Test_gold +++ b/system/t06_publish/PublishUpdate13Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate14Test_gold b/system/t06_publish/PublishUpdate14Test_gold index 6bd929ca..12ddf71c 100644 --- a/system/t06_publish/PublishUpdate14Test_gold +++ b/system/t06_publish/PublishUpdate14Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./bookworm... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate15Test_gold b/system/t06_publish/PublishUpdate15Test_gold new file mode 100644 index 00000000..a0624c8e --- /dev/null +++ b/system/t06_publish/PublishUpdate15Test_gold @@ -0,0 +1,8 @@ +Loading packages... +Generating metadata files and linking package files... +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: +Cleaning up published repository ./maverick... + +Published snapshot repository ./maverick (origin: LP-PPA-gladky-anton-gnuplot) [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {other-test: [snap3]: Created as empty}, {test: [snap2]: Created as empty} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate16Test_gold b/system/t06_publish/PublishUpdate16Test_gold new file mode 100644 index 00000000..927ac538 --- /dev/null +++ b/system/t06_publish/PublishUpdate16Test_gold @@ -0,0 +1,14 @@ +Loading packages... +Generating metadata files and linking package files... +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: +Cleaning up published repository ./maverick... +Removing component 'other-test'... +Removing ${HOME}/.aptly/public/dists/maverick/other-test... +Removing component 'test'... +Removing ${HOME}/.aptly/public/dists/maverick/test... +Removing ${HOME}/.aptly/public/pool/other-test... +Removing ${HOME}/.aptly/public/pool/test... + +Published snapshot repository ./maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate17Test_gold b/system/t06_publish/PublishUpdate17Test_gold new file mode 100644 index 00000000..69c0182f --- /dev/null +++ b/system/t06_publish/PublishUpdate17Test_gold @@ -0,0 +1,10 @@ +Loading packages... +Generating metadata files and linking package files... +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: +Cleaning up published repository ./maverick... +Cleaning up component 'other-test'... +Cleaning up component 'test'... + +Published snapshot repository ./maverick [i386] publishes {main: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {other-test: [snap5]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {test: [snap4]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate18Test_gold b/system/t06_publish/PublishUpdate18Test_gold new file mode 100644 index 00000000..ca805720 --- /dev/null +++ b/system/t06_publish/PublishUpdate18Test_gold @@ -0,0 +1,12 @@ +Loading packages... +Generating metadata files and linking package files... +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: +Cleaning up published repository ./maverick... +Removing component 'main'... +Removing ${HOME}/.aptly/public/dists/maverick/main... +Removing ${HOME}/.aptly/public/pool/main... +Cleaning up component 'test'... + +Published snapshot repository ./maverick [i386] publishes {other-test: [snap1]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick}, {test: [snap3]: Snapshot from mirror [gnuplot-maverick]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick} has been updated successfully. diff --git a/system/t06_publish/PublishUpdate1Test_gold b/system/t06_publish/PublishUpdate1Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate1Test_gold +++ b/system/t06_publish/PublishUpdate1Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate2Test_gold b/system/t06_publish/PublishUpdate2Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate2Test_gold +++ b/system/t06_publish/PublishUpdate2Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate3Test_gold b/system/t06_publish/PublishUpdate3Test_gold index 72e92234..3be101c4 100644 --- a/system/t06_publish/PublishUpdate3Test_gold +++ b/system/t06_publish/PublishUpdate3Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository ./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate4Test_gold b/system/t06_publish/PublishUpdate4Test_gold index dce245d5..c996227a 100644 --- a/system/t06_publish/PublishUpdate4Test_gold +++ b/system/t06_publish/PublishUpdate4Test_gold @@ -3,6 +3,6 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up component 'main' in prefix '.'... -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 updated successfully. diff --git a/system/t06_publish/PublishUpdate7Test_gold b/system/t06_publish/PublishUpdate7Test_gold index 813d7055..b0f13d5c 100644 --- a/system/t06_publish/PublishUpdate7Test_gold +++ b/system/t06_publish/PublishUpdate7Test_gold @@ -3,6 +3,8 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components contrib, main... +Cleaning up published repository ./maverick... +Cleaning up component 'contrib'... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/PublishUpdate8Test_gold b/system/t06_publish/PublishUpdate8Test_gold index 09aa9845..b69df45f 100644 --- a/system/t06_publish/PublishUpdate8Test_gold +++ b/system/t06_publish/PublishUpdate8Test_gold @@ -1,6 +1,8 @@ Loading packages... Generating metadata files and linking package files... Finalizing metadata files... -Cleaning up prefix "." components contrib, main... +Cleaning up published repository ./squeeze... +Cleaning up component 'contrib'... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/S3Publish2Test_gold b/system/t06_publish/S3Publish2Test_gold index 12c9c0e6..cd68115c 100644 --- a/system/t06_publish/S3Publish2Test_gold +++ b/system/t06_publish/S3Publish2Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/S3Publish3Test_gold b/system/t06_publish/S3Publish3Test_gold index 7d609f0f..348fbe81 100644 --- a/system/t06_publish/S3Publish3Test_gold +++ b/system/t06_publish/S3Publish3Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./maverick... +Cleaning up component '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/S3Publish5Test_gold b/system/t06_publish/S3Publish5Test_gold index cadce02c..0b8f635f 100644 --- a/system/t06_publish/S3Publish5Test_gold +++ b/system/t06_publish/S3Publish5Test_gold @@ -1,3 +1,4 @@ -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./sq2... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t06_publish/S3Publish6Test_gold b/system/t06_publish/S3Publish6Test_gold index 12c9c0e6..cd68115c 100644 --- a/system/t06_publish/S3Publish6Test_gold +++ b/system/t06_publish/S3Publish6Test_gold @@ -3,6 +3,7 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up published repository s3:test1:./maverick... +Cleaning up component '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 updated successfully. diff --git a/system/t06_publish/SwiftPublish2Test_gold b/system/t06_publish/SwiftPublish2Test_gold index 5beaf677..40ce0140 100644 --- a/system/t06_publish/SwiftPublish2Test_gold +++ b/system/t06_publish/SwiftPublish2Test_gold @@ -3,6 +3,6 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up prefix '.' components main... -Publish for local repo swift:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. +Published local repository swift:test1:./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully. diff --git a/system/t06_publish/SwiftPublish3Test_gold b/system/t06_publish/SwiftPublish3Test_gold index 16c322a5..d8334a59 100644 --- a/system/t06_publish/SwiftPublish3Test_gold +++ b/system/t06_publish/SwiftPublish3Test_gold @@ -3,6 +3,6 @@ Generating metadata files and linking package files... 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: -Cleaning up prefix "." components main... +Cleaning up prefix '.' components main... -Publish for snapshot swift: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 swift: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. diff --git a/system/t06_publish/SwiftPublish5Test_gold b/system/t06_publish/SwiftPublish5Test_gold index cadce02c..b8a740ad 100644 --- a/system/t06_publish/SwiftPublish5Test_gold +++ b/system/t06_publish/SwiftPublish5Test_gold @@ -1,3 +1,3 @@ -Cleaning up prefix "." components main... +Cleaning up prefix '.' components main... Published repository has been removed successfully. diff --git a/system/t06_publish/show.py b/system/t06_publish/show.py index 7616b78e..a6f06824 100644 --- a/system/t06_publish/show.py +++ b/system/t06_publish/show.py @@ -51,3 +51,17 @@ class PublishShow4Test(BaseTest): "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1 ppa/smira", ] runCmd = "aptly publish show -json maverick ppa/smira" + + +class PublishShow5Test(BaseTest): + """ + publish show: existing local repo + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly repo create -distribution=wheezy local-repo", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -architectures=i386 local-repo" + ] + runCmd = "aptly publish show wheezy" + gold_processor = BaseTest.expand_environ diff --git a/system/t06_publish/snapshot.py b/system/t06_publish/snapshot.py index 4514571d..aa3fb54e 100644 --- a/system/t06_publish/snapshot.py +++ b/system/t06_publish/snapshot.py @@ -1344,3 +1344,23 @@ class PublishSnapshot41Test(BaseTest): self.check_exists('public/pool/main/libx/libxslt/libxslt1.1_1.1.32-2.2~deb10u2_i386.deb') self.check_exists('public/pool/main/libz/libzstd/libzstd1_1.3.8+dfsg-3+deb10u2_i386.deb') self.check_exists('public/pool/main/z/zlib/zlib1g_1.2.11.dfsg-1+deb10u2_i386.deb') + + +class PublishSnapshot42Test(BaseTest): + """ + publish snapshot: mirror with multi-dist + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + ] + runCmd = "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -multi-dist snap1" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishSnapshot42Test, self).check() + self.check_not_exists( + 'public/pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb') + self.check_exists( + 'public/pool/maverick/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb') diff --git a/system/t06_publish/source.py b/system/t06_publish/source.py new file mode 100644 index 00000000..78c81c60 --- /dev/null +++ b/system/t06_publish/source.py @@ -0,0 +1,218 @@ +from lib import BaseTest + + +class PublishSourceAdd1Test(BaseTest): + """ + publish source add: add single source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source add -component=test maverick snap2" + gold_processor = BaseTest.expand_environ + + +class PublishSourceAdd2Test(BaseTest): + """ + publish source add: add multiple sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source add -component=test,other-test maverick snap2 snap3" + gold_processor = BaseTest.expand_environ + + +class PublishSourceAdd3Test(BaseTest): + """ + publish source add: (re-)add already added source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source add -component=main maverick snap2" + expectedCode = 1 + gold_processor = BaseTest.expand_environ + + +class PublishSourceList1Test(BaseTest): + """ + publish source list: show source changes + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + "aptly publish source add -component=test maverick snap2", + ] + runCmd = "aptly publish source list maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceList2Test(BaseTest): + """ + publish source list: show source changes as JSON + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + "aptly publish source add -component=test maverick snap2", + ] + runCmd = "aptly publish source list -json maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceList3Test(BaseTest): + """ + publish source list: show source changes (empty) + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source list maverick" + expectedCode = 1 + gold_processor = BaseTest.expand_environ + + +class PublishSourceDrop1Test(BaseTest): + """ + publish source drop: drop source changes + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source drop maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceUpdate1Test(BaseTest): + """ + publish source update: Update single source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source update -component=main maverick snap2" + gold_processor = BaseTest.expand_environ + + +class PublishSourceUpdate2Test(BaseTest): + """ + publish source update: Update multiple sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test snap1 snap2", + ] + runCmd = "aptly publish source update -component=main,test maverick snap2 snap3" + gold_processor = BaseTest.expand_environ + + +class PublishSourceUpdate3Test(BaseTest): + """ + publish source update: Update not existing source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source update -component=not-existent maverick snap1" + gold_processor = BaseTest.expand_environ + + +class PublishSourceReplace1Test(BaseTest): + """ + publish source replace: Replace existing sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test snap1 snap2", + ] + runCmd = "aptly publish source replace -component=main-new,test-new maverick snap2 snap3" + gold_processor = BaseTest.expand_environ + + +class PublishSourceRemove1Test(BaseTest): + """ + publish source remove: Remove single source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test snap1 snap2", + ] + runCmd = "aptly publish source remove -component=test maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceRemove2Test(BaseTest): + """ + publish source remove: Remove multiple sources + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main,test,other-test snap1 snap2 snap3", + ] + runCmd = "aptly publish source remove -component=test,other-test maverick" + gold_processor = BaseTest.expand_environ + + +class PublishSourceRemove3Test(BaseTest): + """ + publish source remove: Remove not-existing source + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=main snap1", + ] + runCmd = "aptly publish source remove -component=not-existent maverick" + expectedCode = 1 + gold_processor = BaseTest.expand_environ diff --git a/system/t06_publish/switch.py b/system/t06_publish/switch.py index dc777b91..4243a978 100644 --- a/system/t06_publish/switch.py +++ b/system/t06_publish/switch.py @@ -424,22 +424,15 @@ class PublishSwitch11Test(BaseTest): class PublishSwitch12Test(BaseTest): """ - publish switch: add new component to publish + publish switch: wrong component names """ fixtureCmds = [ "aptly snapshot create snap1 empty", "aptly snapshot create snap2 empty", - "aptly snapshot create snap3 empty", "aptly publish snapshot -architectures=i386 -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=a,b snap1 snap2", ] - runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -component=a,c maverick snap2 snap3" - gold_processor = BaseTest.expand_environ - - def check(self): - super(PublishSwitch12Test, self).check() - - self.check_exists('public/dists/maverick/a/binary-i386/Packages') - self.check_exists('public/dists/maverick/c/binary-i386/Packages') + runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -component=a,c maverick snap2 snap1" + expectedCode = 1 class PublishSwitch13Test(BaseTest): diff --git a/system/t06_publish/update.py b/system/t06_publish/update.py index 7757715a..3a24ec16 100644 --- a/system/t06_publish/update.py +++ b/system/t06_publish/update.py @@ -175,39 +175,6 @@ class PublishUpdate3Test(BaseTest): self.check_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') -class PublishUpdate4Test(BaseTest): - """ - publish update: added some packages, but list of published archs doesn't change - """ - fixtureCmds = [ - "aptly repo create local-repo", - "aptly repo add local-repo ${files}/pyspi_0.6.1-1.3.dsc", - "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick local-repo", - "aptly repo add local-repo ${files}/libboost-program-options-dev_1.49.0.1_i386.deb" - ] - runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" - gold_processor = BaseTest.expand_environ - - def check(self): - super(PublishUpdate4Test, self).check() - - self.check_exists('public/dists/maverick/InRelease') - self.check_exists('public/dists/maverick/Release') - self.check_exists('public/dists/maverick/Release.gpg') - - self.check_not_exists('public/dists/maverick/main/binary-i386/Packages') - self.check_not_exists('public/dists/maverick/main/binary-i386/Packages.gz') - self.check_not_exists('public/dists/maverick/main/binary-i386/Packages.bz2') - self.check_exists('public/dists/maverick/main/source/Sources') - self.check_exists('public/dists/maverick/main/source/Sources.gz') - self.check_exists('public/dists/maverick/main/source/Sources.bz2') - - self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') - self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') - self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') - self.check_not_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') - - class PublishUpdate5Test(BaseTest): """ publish update: no such publish @@ -216,20 +183,6 @@ class PublishUpdate5Test(BaseTest): expectedCode = 1 -class PublishUpdate6Test(BaseTest): - """ - publish update: not a local repo - """ - fixtureDB = True - fixturePool = True - fixtureCmds = [ - "aptly snapshot create snap1 from mirror gnuplot-maverick", - "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec snap1", - ] - runCmd = "aptly publish update maverick" - expectedCode = 1 - - class PublishUpdate7Test(BaseTest): """ publish update: multiple components, add some packages @@ -487,3 +440,169 @@ class PublishUpdate14Test(BaseTest): self.check_exists('public/dists/bookworm/main/binary-i386/Packages.gz') self.check_exists('public/pool/bookworm/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + + +class PublishUpdate15Test(BaseTest): + """ + publish update: source added + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main snap1", + "aptly publish source add -component=test,other-test maverick snap2 snap3" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate15Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + + self.check_exists('public/dists/maverick/test/binary-i386/Packages') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.bz2') + + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.bz2') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['main', 'other-test', 'test'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'main other-test test')) + + +class PublishUpdate16Test(BaseTest): + """ + publish update: source removed + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main,test,other-test snap1 snap2 snap3", + "aptly publish source remove -component=test,other-test maverick" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate16Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/Contents-i386.gz') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['main'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'main')) + + +class PublishUpdate17Test(BaseTest): + """ + publish update: source updated + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 empty", + "aptly snapshot create snap4 from mirror gnuplot-maverick", + "aptly snapshot create snap5 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main,test,other-test snap1 snap2 snap3", + "aptly publish source update -component=test,other-test maverick snap4 snap5" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate17Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/Contents-i386.gz') + + self.check_exists('public/dists/maverick/test/binary-i386/Packages') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/test/Contents-i386.gz') + + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/other-test/Contents-i386.gz') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['main', 'other-test', 'test'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'main other-test test')) + + +class PublishUpdate18Test(BaseTest): + """ + publish update: source added, updated and removed + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot create snap3 from mirror gnuplot-maverick", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -architectures=i386 -component=main,test snap1 snap2", + "aptly publish source remove -component=main maverick", + "aptly publish source update -component=test maverick snap3", + "aptly publish source add -component=other-test maverick snap1" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick" + + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishUpdate18Test, self).check() + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/test/binary-i386/Packages') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/test/Contents-i386.gz') + + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/other-test/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/other-test/Contents-i386.gz') + + release = self.read_file('public/dists/maverick/Release').split('\n') + components = next((e.split(': ')[1] for e in release if e.startswith('Components')), None) + components = sorted(components.split(' ')) + if ['other-test', 'test'] != components: + raise Exception("value of 'Components' in release file is '%s' and does not match '%s'." % (' '.join(components), 'other-test test')) diff --git a/system/t08_db/CleanupDB9Test_publish_drop b/system/t08_db/CleanupDB9Test_publish_drop index c9303809..ee4bb6e3 100644 --- a/system/t08_db/CleanupDB9Test_publish_drop +++ b/system/t08_db/CleanupDB9Test_publish_drop @@ -1,4 +1,5 @@ Removing ${HOME}/.aptly/public/dists/def... -Cleaning up prefix "." components main... +Cleaning up published repository ./def... +Cleaning up component 'main'... Published repository has been removed successfully. diff --git a/system/t09_repo/AddRepo17Test/mesa-stable-no-march_24.2.6-101pika1_amd64.deb b/system/t09_repo/AddRepo17Test/mesa-stable-no-march_24.2.6-101pika1_amd64.deb new file mode 100644 index 00000000..b021ea8e Binary files /dev/null and b/system/t09_repo/AddRepo17Test/mesa-stable-no-march_24.2.6-101pika1_amd64.deb differ diff --git a/system/t09_repo/AddRepo17Test/mesa-stable_24.2.6-101pika1_amd64.deb b/system/t09_repo/AddRepo17Test/mesa-stable_24.2.6-101pika1_amd64.deb new file mode 100644 index 00000000..1114ba60 Binary files /dev/null and b/system/t09_repo/AddRepo17Test/mesa-stable_24.2.6-101pika1_amd64.deb differ diff --git a/system/t09_repo/AddRepo17Test_gold b/system/t09_repo/AddRepo17Test_gold new file mode 100644 index 00000000..687c9eb4 --- /dev/null +++ b/system/t09_repo/AddRepo17Test_gold @@ -0,0 +1,3 @@ +Loading packages... +[+] mesa-stable-no-march_24.2.6-101pika1_amd64 added +[+] mesa-stable_24.2.6-101pika1_amd64 added diff --git a/system/t09_repo/AddRepo17Test_repo_show b/system/t09_repo/AddRepo17Test_repo_show new file mode 100644 index 00000000..7be66585 --- /dev/null +++ b/system/t09_repo/AddRepo17Test_repo_show @@ -0,0 +1,8 @@ +Name: repo17 +Comment: Repo17 +Default Distribution: squeeze +Default Component: main +Number of packages: 2 +Packages: + mesa-stable_24.2.6-101pika1_amd64 + mesa-stable-no-march_24.2.6-101pika1_amd64 diff --git a/system/t09_repo/AddRepo8Test_gold b/system/t09_repo/AddRepo8Test_gold index f0cf6eee..51e9ca08 100644 --- a/system/t09_repo/AddRepo8Test_gold +++ b/system/t09_repo/AddRepo8Test_gold @@ -1,5 +1,5 @@ Loading packages... -[!] Unable to add package to repo pyspi_0.6.1-1.3_source: conflict in package pyspi_0.6.1-1.3_source +[!] Unable to add package: package already exists and is different: pyspi_0.6.1-1.3_source [!] Some files were skipped due to errors: /pyspi_0.6.1-1.3.conflict.dsc ERROR: some files failed to be added diff --git a/system/t09_repo/add.py b/system/t09_repo/add.py index 61c64d1e..08274f77 100644 --- a/system/t09_repo/add.py +++ b/system/t09_repo/add.py @@ -344,3 +344,21 @@ class AddRepo16Test(BaseTest): self.check_cmd_output("aptly repo show repo2", "repo_show") shutil.rmtree(self.tempSrcDir) + + +class AddRepo17Test(BaseTest): + """ + add packages to local repo: .deb file with provides + """ + fixtureCmds = [ + "aptly repo create -comment=Repo17 -distribution=squeeze repo17", + ] + runCmd = "aptly repo add repo17 ${testfiles}/mesa-stable_24.2.6-101pika1_amd64.deb ${testfiles}/mesa-stable-no-march_24.2.6-101pika1_amd64.deb" + + def check(self): + self.check_output() + self.check_cmd_output("aptly repo show -with-packages repo17", "repo_show") + + # check pool + self.check_exists('pool/e3/f6/51297bd4bd0ef999296ef0a28299_mesa-stable-no-march_24.2.6-101pika1_amd64.deb') + self.check_exists('pool/01/6b/3d864229761eff49a8680c9987ab_mesa-stable_24.2.6-101pika1_amd64.deb') diff --git a/system/t12_api/publish.py b/system/t12_api/publish.py index 325802d2..82751579 100644 --- a/system/t12_api/publish.py +++ b/system/t12_api/publish.py @@ -905,6 +905,58 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest): self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") +class PublishShowAPITestRepo(APITest): + """ + GET /publish/:prefix/:distribution + """ + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal( + self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + self.check_equal(self.post_task("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Origin': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SkipContents': False, + 'MultiDist': False, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo1_name}], + 'Storage': '', + 'Suite': ''} + repo = self.get("/api/publish/" + prefix + "/wheezy") + self.check_equal(repo.status_code, 200) + self.check_equal(repo_expected, repo.json()) + + class ServePublishedListTestRepo(APITest): """ GET /repos @@ -1036,3 +1088,472 @@ class ServePublishedNotFoundTestRepo(APITest): get = self.get("/repos/apiandserve/pool/main/b/boost-defaults/i-dont-exist") if get.status_code != 404: raise Exception(f"Expected status 404 != {get.status_code}") + + +class PublishSourcesAddAPITestRepo(APITest): + """ + POST /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # Actual test + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/sources", + json={"Component": "test", "Name": repo2_name} + ).status_code, 201) + + sources_expected = [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourceUpdateAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution/sources/main + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # Actual test + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/main", + json={"Component": "main", "Name": repo2_name} + ).status_code, 200) + + sources_expected = [{"Component": "main", "Name": repo2_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourcesUpdateAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources", + json=[{"Component": "test", "Name": repo1_name}, {"Component": "other-test", "Name": repo2_name}] + ).status_code, 200) + + sources_expected = [{"Component": "other-test", "Name": repo2_name}, {"Component": "test", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourceRemoveAPITestRepo(APITest): + """ + DELETE /publish/:prefix/:distribution/sources/test + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/test").status_code, 200) + + sources_expected = [{"Component": "main", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourcesDropAPITestRepo(APITest): + """ + DELETE /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/test").status_code, 200) + + # Actual test + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources").status_code, 200) + + self.check_equal(self.get("/api/publish/" + prefix + "/wheezy/sources").status_code, 404) + + +class PublishSourcesListAPITestRepo(APITest): + """ + GET /publish/:prefix/:distribution/sources + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/sources", + json={"Component": "test", "Name": repo1_name} + ).status_code, 201) + + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/main", + json={"Component": "main", "Name": repo2_name} + ).status_code, 200) + + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/main").status_code, 200) + + sources_expected = [{"Component": "test", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishSourceReplaceAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution/sources/main + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}], + "Signing": DefaultSigningOptions, + } + ).status_code, 201) + + # Actual test + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/main", + json={"Component": "test", "Name": repo2_name} + ).status_code, 200) + + sources_expected = [{"Component": "test", "Name": repo2_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + +class PublishUpdateSourcesAPITestRepo(APITest): + """ + POST /publish/:prefix/:distribution/update + """ + fixtureGpg = True + + def check(self): + repo1_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo1_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo1_name + "/file/" + d).status_code, 200) + + repo2_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo2_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo2_name + "/file/" + d).status_code, 200) + + repo3_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo3_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo3_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + self.check_equal(self.post( + "/api/publish/" + prefix, + json={ + "Signing": DefaultSigningOptions, + "SourceKind": "local", + "Sources": [{"Component": "main", "Name": repo1_name}, {"Component": "test", "Name": repo2_name}], + } + ).status_code, 201) + + # remove 'main' component + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy/sources/main").status_code, 200) + + # update 'test' component + self.check_equal(self.put( + "/api/publish/" + prefix + "/wheezy/sources/test", + json={"Component": "test", "Name": repo1_name} + ).status_code, 200) + + # add 'other-test' component + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/sources", + json={"Component": "other-test", "Name": repo3_name} + ).status_code, 201) + + sources_expected = [{"Component": "other-test", "Name": repo3_name}, {"Component": "test", "Name": repo1_name}] + sources = self.get("/api/publish/" + prefix + "/wheezy/sources") + self.check_equal(sources.status_code, 200) + self.check_equal(sources_expected, sources.json()) + + # update published repository and publish new content + self.check_equal(self.post( + "/api/publish/" + prefix + "/wheezy/update", + json={ + "AcquireByHash": True, + "MultiDist": False, + "Signing": DefaultSigningOptions, + "SkipBz2": True, + "SkipContents": True, + } + ).status_code, 200) + + repo_expected = { + 'AcquireByHash': True, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SkipContents': True, + 'MultiDist': False, + 'SourceKind': 'local', + 'Sources': [{"Component": "other-test", "Name": repo3_name}, {"Component": "test", "Name": repo1_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) diff --git a/system/t12_api/repos.py b/system/t12_api/repos.py index 8cc8e4e5..424f9f49 100644 --- a/system/t12_api/repos.py +++ b/system/t12_api/repos.py @@ -28,6 +28,51 @@ class ReposAPITestCreateShow(APITest): self.check_equal(self.get("/api/repos/" + self.random_name()).status_code, 404) +class ReposAPITestCreateFromSnapshot(APITest): + """ + Create repo from snapshot + """ + def check(self): + initial_repo = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": initial_repo}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + task = self.post_task("/api/repos/" + initial_repo + "/file/" + d) + self.check_task(task) + + snapshot_name = self.random_name() + task = self.post_task("/api/repos/" + initial_repo + '/snapshots', json={'Name': snapshot_name}) + self.check_task(task) + self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 200) + + repo_from_snapshot = self.random_name() + new_repo = {'Name': repo_from_snapshot, + 'FromSnapshot': snapshot_name} + + resp = self.post("/api/repos", json=new_repo) + self.check_equal(resp.status_code, 201) + + self.check_equal(self.get("/api/repos/" + repo_from_snapshot + "/packages").json(), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']) + + +class ReposAPITestCreateFromWrongSnapshot(APITest): + """ + Create repo from snapshot + """ + def check(self): + snapshot_name = self.random_name() # non-existing snapshot + repo_from_snapshot = self.random_name() + new_repo = {'Name': repo_from_snapshot, + 'FromSnapshot': snapshot_name} + + resp = self.post("/api/repos", json=new_repo) + self.check_equal(resp.status_code, 404) + + class ReposAPITestCreateIndexDelete(APITest): """ GET /api/repos, POST /api/repos, DELETE /api/repos/:name