Compare commits

..

376 Commits

Author SHA1 Message Date
André Roth 2d78c467b7 fix test 2026-06-15 00:50:41 +02:00
André Roth 909c5bb116 fix tests 2026-06-14 23:58:18 +02:00
André Roth 06bcce22d4 fix azure 2026-06-14 23:58:18 +02:00
André Roth b07665da50 remove swagger 2026-06-14 23:58:18 +02:00
André Roth 034131c7a9 fix ststem test 2026-06-14 23:34:25 +02:00
André Roth c9b1d723c3 fix test 2026-06-14 21:57:48 +02:00
André Roth 9346901001 disable swagger 2026-06-14 20:56:17 +02:00
André Roth 6416440ed3 go: mod tidy 2026-06-14 20:48:41 +02:00
André Roth cb3c0519d6 fix unit tests 2026-06-14 20:48:04 +02:00
André Roth be7c232204 swagger: remove test 2026-06-14 20:41:01 +02:00
André Roth 5ff470fd59 fix test 2026-06-14 20:14:32 +02:00
André Roth 8f76f4c24d go: mod tidy 2026-06-14 19:55:45 +02:00
André Roth 57c93177ad publish: support MultiDist toggle 2026-06-14 19:55:45 +02:00
André Roth 2a7a04ee32 tasks: fix race conditions
* show resources in task details
* fix task state locking
* return task object consistently

Race condition iexisted where task State, err, and processReturnValue fields
were written by consumer goroutine and read by concurrent accessors without
proper synchronization, causing torn reads and data races.
2026-06-14 19:55:45 +02:00
André Roth 7ccb256841 mirror: fix race conditions
* load data inside background tasks
  Perform collection.LoadComplete inside maybeRunTaskInBackground
  Have tasks use a fresh copy of taskCollectionFactory, taskCollection
2026-06-14 19:55:45 +02:00
André Roth 00e17d82fe snapshot: fix race conditions
* perform collection.LoadComplete inside maybeRunTaskInBackground
 * have tasks use a fresh copy of taskCollectionFactory, taskCollection
 * fix locking for snapshots of snapshots by locking SourceSnapshots
 * use uuids, since names can be renamed
2026-06-14 19:55:45 +02:00
André Roth 867310e8f1 repos: fix race conditions
* load data inside background tasks
  Perform collection.LoadComplete inside maybeRunTaskInBackground
  Have tasks use a fresh copy of taskCollectionFactory, taskCollection
* use uuids, since names can be renamed
2026-06-14 19:55:45 +02:00
André Roth fbac933c30 publish: fix race conditions
* remove useless resource lock
  Resource locks need to be before the background task. creating same publish endpoint at the same time is unlikely...
* load data inside background tasks
  This fixes a flaw in async apis, which loaded the published repo from the DB and mutated it outside the task closure, before the task lock was acquired.
  Perform collection.LoadComplete inside maybeRunTaskInBackground and have tasks use a fresh copy of taskCollectionFactory, taskCollection
* lock source repos/snapshots for publish operations
  Concurrent tasks were not properly locking their resources, leading to inconsistent published indexes:
  SourceLocalRepo: iterate published.Sources (component -> source UUID), look up each local repo via localRepoCollection.ByUUID and append string(repo.Key()) to resources
  SourceSnapshot: iterate b.Snapshots,look up each snapshot via snapshotCollection.ByName and append string(snapshot.ResourceKey()) to resources.
* lock pool on non MultiDist publish
* revert mutex on LinkFromPool
* use uuids, since names can be renamed
* add test for MultiDist change
2026-06-14 19:55:45 +02:00
André Roth 1ab61457a5 cleanup 2026-06-14 19:53:09 +02:00
André Roth 9b1a0ec2da ci: update codecov-action to 7.0.0 2026-06-14 19:53:09 +02:00
André Roth 5cfc5a6b8e ci: use correct ubuntu 26.04 codename 2026-06-14 19:53:09 +02:00
Catalin Muresan 4655d7c188 Added tests to please codeconv 2026-06-14 19:53:09 +02:00
Catalin Muresan 88ee47045f Fix crash in aptly db recover 2026-06-14 19:53:09 +02:00
André Roth 84c8e5cf22 docs: fix typos 2026-06-14 19:53:09 +02:00
André Roth 813d9c660d ci: build for ubuntu 26.04 2026-06-14 19:53:09 +02:00
André Roth 057fcaa598 system tests: do not depend on launchpad.net 2026-06-14 19:53:09 +02:00
André Roth 0b6af54eee config: allow setting PPA Base URL 2026-06-14 19:53:09 +02:00
André Roth 66e6d3ac6f document prometheus API
* enable in dev and test env
* fix api/repos doc
2026-06-14 19:53:09 +02:00
dependabot[bot] 0aebd14f13 build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3
Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.67.1 to 1.97.3.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.67.1...service/s3/v1.97.3)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.97.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 19:53:09 +02:00
dependabot[bot] b909bccfcd build(deps): bump github.com/cloudflare/circl from 1.6.1 to 1.6.3
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.1 to 1.6.3.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.1...v1.6.3)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 19:53:09 +02:00
Russell Greene 62c92e4256 fix docs for Serve in API mode 2026-06-14 19:53:09 +02:00
dependabot[bot] 2f0b2cf4de build(deps): bump requests from 2.32.4 to 2.33.0 in /system
Bumps [requests](https://github.com/psf/requests) from 2.32.4 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 19:53:09 +02:00
André Roth 79bd81e937 ci: do not upload coverage for dependabot 2026-06-14 19:53:09 +02:00
André Roth b1cb14e921 ci: fix coverage 2026-06-14 19:53:09 +02:00
Tim Foerster 091b6f9948 Add SOURCE_DATE_EPOCH support for reproducible builds
Implement support for the SOURCE_DATE_EPOCH environment variable as
specified by reproducible-builds.org. When set, this variable overrides
the current timestamp in the Release file's Date and Valid-Until fields,
enabling reproducible filesystem publishes.

- Read SOURCE_DATE_EPOCH environment variable in Publish()
- Use the epoch timestamp for both Date and Valid-Until fields
- Gracefully fallback to current time if unset or invalid
- Add comprehensive tests for valid and invalid SOURCE_DATE_EPOCH values
2026-06-14 19:53:09 +02:00
André Roth 213fbccead multi sign: add test 2026-06-14 19:53:09 +02:00
Ales Bregar f06e428caf clearer REST api docs, put whitespace to docs to show that keyId strings are trimmed 2026-06-14 19:53:09 +02:00
Ales Bregar 8b521fc722 updating REST api with multiple gpg keys support, due backwards compatibility introducing CSV under same key (gpg-key) 2026-06-14 19:53:09 +02:00
Ales Bregar 7895edd100 review fix 2026-06-14 19:53:09 +02:00
Ales Bregar 5e3460ae62 system test t12_api sends empty keyRef string, making gpg fail 2026-06-14 19:53:09 +02:00
Ales Bregar 58480d747f system test unexpected string fix (would be helpful, but not changing the test just for this) 2026-06-14 19:53:09 +02:00
Ales Bregar 7716d4236d system test configuration fix 2026-06-14 19:53:09 +02:00
Ales Bregar 6a8723484d documentation updated 2026-06-14 19:53:09 +02:00
Ales Bregar ec4503c941 white space revert to minimize change 2026-06-14 19:53:09 +02:00
Ales Bregar bd95012687 - #309 adding gpgKeys config key, accepting array of keyRef, cli args has precedence
- #691 adding handling of multiple keyRefs when signing with gpg
2026-06-14 19:53:09 +02:00
André Roth d4da3d5440 tasklist: fix deadlocks
* lock correct resources
* unlock list before queueing
2026-06-14 19:53:09 +02:00
André Roth dff6bbb165 ci: fail on failed coverage upload 2026-06-14 19:53:09 +02:00
André Roth c9bae4c454 unit-test: use /smallfs when non-root 2026-06-14 19:53:09 +02:00
André Roth 95ef905ca9 ci: provide 1MB /smallfs to docker 2026-06-14 19:53:09 +02:00
André Roth 6a5f494a1f ci: run unit tests in docker
- run separate unit-test job
- build docker
- allow make docker-unit-tests in ci
2026-06-14 19:53:09 +02:00
Brian Witt 009087e58f error on out of space 2026-06-14 19:53:09 +02:00
André Roth c73aa0e255 docs: update PR tempalte 2026-06-14 19:53:09 +02:00
Yury Bushmelev 90e7008b8d Update Puppet module references in README
Removed outdated Puppet module references and added an actively
maintained one.
2026-06-14 19:53:09 +02:00
dependabot[bot] d7c51530f5 build(deps): bump github.com/cloudflare/circl from 1.4.0 to 1.6.1
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.4.0 to 1.6.1.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.4.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 19:53:09 +02:00
dependabot[bot] c55c0f6e3c build(deps): bump golang.org/x/crypto from 0.36.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.36.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.36.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 19:53:09 +02:00
Linus Fischer d9099b7e6b Fix swagger property casing 2026-06-14 19:53:08 +02:00
Yaksh Bariya 444c7f8af1 give myself some credit as well
Cause I'm nice :)
2026-06-14 19:53:08 +02:00
Yaksh Bariya 9a1a401248 make version comparision more similar to that of dpkg
Initially found by automated repository health checks used by Termux
in https://github.com/termux/termux-packages/issues/27472

The root problem was 4.3.5a comparing less than 4.3.5-rc1-1 by aptly
According to debian "4.3.5a" > "4.3.5-rc1-1"

This is because dpkg splits hyphen for revision at the first hyphen,
whereas aptly was splitting at the last hyphen which is different from
dpkg's behaviour.

dpkg behaviour: https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/parsehelp.c#n242

Perhaps this wasn't detected as there was broken tests in the repository
since the initial commit of aptly. This also fixes those tests
2026-06-14 19:53:08 +02:00
Tobias Assarsson 56f5254aa9 fix repo edit api. 2026-06-14 19:53:08 +02:00
Ryan Gonzalez 4f1838bb74 system-test: Allow skipping coverage
Enabling coverage near-doubles the incremental build time and adds
overhead to individual tests on the order of **5-10x** or more. It's not
essential to have this for quick local system-test runs, so add an option
to disable it.
2026-06-14 19:53:08 +02:00
Ryan Gonzalez 37841fb205 system-test: Forward CAPTURE to docker
The code was only forwarding TEST, but CAPTURE is useful too.
2026-06-14 19:53:08 +02:00
Ryan Gonzalez 0164827907 docker: Preserve the go build cache
Otherwise, every `make docker-...` invocation will need to rebuild
everything from scratch.
2026-06-14 19:53:08 +02:00
Ryan Gonzalez fa8e8ab6fb docker: Fix usage with rootless podman and SELinux
When using rootless podman, the *current user* gets mapped to uid 0,
which results in the aptly user being unable to write to the build
directory. We can instead map the current user to the corresponding uid
in the container via `PODMAN_USERNS=keep-id`, which matches up with what
docker-wrapper wants...but then that will *enter the container as the
current uid*, which messes with the ability to set permissions on
`/var/lib/aptly`. That can be fixed by explicitly passing `--user 0:0`,
which should be a no-op on docker (since the container's default user is
already root).

Additionally, this adds `--security-opt label=disable` to avoid
permission errors when running on systems with SELinux enforcing.
2026-06-14 19:53:08 +02:00
Ryan Gonzalez 2a87554581 system-test: Fix crash when a comparison with a non-string value fails
`orig` isn't necessarily a string, so the string concatenation here can
raise a TypeError.
2026-06-14 19:53:08 +02:00
chesseed 298e09e0b9 fix comment 2026-06-14 19:53:08 +02:00
chesseed 4ecbaf5a62 fix swagger errors 2026-06-14 19:53:08 +02:00
JupiterRider 562820b625 ran "gofmt -s -w ." to format the code 2026-06-14 19:53:08 +02:00
André Roth 2d86506183 README: remove buster 2026-06-14 19:53:08 +02:00
Yye847 ef75ff8600 Update README.rst
add trixie in list of available dists also in CLI part of README
2026-06-14 19:53:08 +02:00
Yye847 62b324eb65 Update README.rst
add trixie in list of available dists
2026-06-14 19:53:08 +02:00
JupiterRider 4c40f4dc0a add JupiterRider to AUTHORS file 2026-06-14 19:53:08 +02:00
JupiterRider cfdb720ef4 remove tautological (unnecessary) nil condition 2026-06-14 19:53:08 +02:00
André Roth 5de38a987a ci: remove EOL debian/buster 2026-06-14 19:53:08 +02:00
André Roth c62670ea51 update Releasing.md 2026-06-14 19:53:08 +02:00
Alejandro Guijarro Monerris 9d0b3a186e chore: add name to AUTHORS 2026-06-14 19:53:08 +02:00
Alejandro Guijarro Monerris a5702371ef feat(s3): add publishedPrefix to pathCache to avoid reupload of files 2026-06-14 19:53:08 +02:00
Itay Porezky 02227d7233 Removing non related actions from mirror update 2026-06-14 19:53:08 +02:00
dependabot[bot] 877ffbe376 build(deps): bump requests from 2.28.2 to 2.32.4 in /system
Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.28.2...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 19:53:08 +02:00
André Roth 37b7c5aa91 Revert "use new azure-sdk"
This reverts commit e2cbd637b8.
2026-06-14 19:53:08 +02:00
André Roth 3e7978180e disable swagger 2026-06-14 19:53:08 +02:00
André Roth 4360fb00d7 Revert "tests: disable t04_mirror/create/CreateMirror18Test (Closes: #1135740)"
This reverts commit 24fcde56b6.
2026-06-14 19:40:17 +02:00
Sébastien Delafond d90825f4f0 Update changelog for 1.6.2-3 release 2026-05-05 18:24:43 +02:00
Sébastien Delafond 5be757e35e d/control: bump-up Standards-Version 2026-05-05 18:24:17 +02:00
Sébastien Delafond e51c1894bf tests: disable t12_api/gpg/GPGAPITestAddKey (Closes: #1135672) 2026-05-05 18:14:15 +02:00
Sébastien Delafond 24fcde56b6 tests: disable t04_mirror/create/CreateMirror18Test (Closes: #1135740) 2026-05-05 18:11:47 +02:00
Sébastien Delafond 7390e19e03 Update changelog for 1.6.2-2 release 2025-11-21 15:47:30 +01:00
Sébastien Delafond 0aa0c0a995 Rediff patches 2025-11-21 15:46:22 +01:00
Sébastien Delafond 1d10dd6ce7 Remove Built-Using 2025-09-24 10:36:58 +02:00
Sébastien Delafond e28fa416ab Update changelog for 1.6.2-1 release 2025-09-24 08:23:22 +02:00
Sébastien Delafond d6c7b1d770 tests: add dependencies, disable extra failing tests 2025-09-24 08:23:04 +02:00
Sébastien Delafond 92ea4a2505 Patch system-tests to not abort on first failure 2025-09-24 08:22:50 +02:00
Sébastien Delafond 3e5e0fc119 aptly-api is arch all 2025-09-24 06:50:13 +02:00
Sébastien Delafond 9fa4248e3b Add Static-Built-Using 2025-09-24 06:49:59 +02:00
Sébastien Delafond d958a146f7 d/watch: mangle & suffix 2025-09-24 06:30:52 +02:00
Sébastien Delafond 125a7c2c07 not-installed 2025-09-23 21:20:03 +02:00
Sébastien Delafond d403150d77 Update changelog for 1.6.1+ds1-4~1.gbp4e6c52 release 2025-09-23 20:51:26 +02:00
Sébastien Delafond 4e6c52ec2a Rediff patches
Add 0003-tests-no-upstream-s-etcd-install-as-it-s-arch-specif.patch: <REASON>
Drop 0004-tests-no-upstream-s-etcd-install-as-it-s-arch-specif.patch: <REASON>
Drop 0005-Fixes-Issue-1435.patch: <REASON>
2025-09-23 20:50:34 +02:00
Sébastien Delafond 90ffa6883a Merge tag 'upstream/1.6.2+ds1' into debian/master 2025-09-23 20:44:39 +02:00
Sébastien Delafond 4a85be68a0 Merge tag 'v1.6.2' into upstream/latest
aptly: release $version
2025-09-23 20:40:20 +02:00
Sébastien Delafond 19e4040b17 Bump up Standards-Version 2025-09-23 20:35:48 +02:00
Sébastien Delafond 767323fde9 d/watch: v5 2025-09-23 20:35:31 +02:00
Sébastien Delafond 5be8231598 Update changelog for 1.6.1+ds1-3 release 2025-07-08 14:13:48 +02:00
Sébastien Delafond e9f1947156 tests: disable system test CreateMirror31Test (Comment: #1108828) 2025-07-08 10:31:36 +02:00
Sébastien Delafond 3d8c906c7f tests: disable unit test TestVerifyClearsigned (Comment: #1108828) 2025-07-08 10:31:13 +02:00
Sébastien Delafond f1c0205e21 tests: declare needs-internet for system-test 2025-05-02 07:18:24 +02:00
Sébastien Delafond 7d124ac5c0 Update changelog for 1.6.1+ds1-2 release 2025-04-28 15:39:13 +02:00
Sébastien Delafond abebdb94a5 Do not re-publish unchanged files to S3 every single time (Closes: #1104299) 2025-04-28 15:38:08 +02:00
Sébastien Delafond 7b45c4d380 Update changelog for 1.6.1+ds1-1 release 2025-02-24 10:06:44 +01:00
Sébastien Delafond 6cbd80566f Rediff patches
Drop 0003-Revert-fix-empty-mirror-check.patch: <REASON>
2025-02-24 10:06:40 +01:00
Sébastien Delafond 274bb2732a Merge branch 'upstream/latest' into debian/master 2025-02-24 10:06:01 +01:00
Sébastien Delafond 5935c7d5a0 Merge tag 'v1.6.1' into upstream/latest
aptly: release $version
2025-02-24 10:03:35 +01:00
Sébastien Delafond 4ab07fef23 Update changelog for 1.6.0+ds1-8 release 2025-02-21 06:54:25 +01:00
Sébastien Delafond e7abc4d39a tests: disable riscv64-flaky EditRepo4Test 2025-02-21 06:53:27 +01:00
Sébastien Delafond 71b045366d Update changelog for 1.6.0+ds1-7 release 2025-02-20 08:01:02 +01:00
Sébastien Delafond 7abac9537f tests: clean way to disable single tests, disable s390-flaky CreateMirror35Test 2025-02-20 08:01:02 +01:00
Sébastien Delafond 9d64dc2fd9 Update changelog for 1.6.0+ds1-6 release 2025-02-19 14:02:23 +01:00
Sébastien Delafond 6e1b49daa8 tests: disable system-test's etcd tests as the corresponding fixture is also arch-specific 2025-02-19 14:01:27 +01:00
Sébastien Delafond 7bbfe88008 Update changelog for 1.6.0+ds1-5 release 2025-02-19 07:23:49 +01:00
Sébastien Delafond 4c5db7d98c tests: don't use upstream's etcd installer 2025-02-19 07:23:20 +01:00
Sébastien Delafond cab280ebc0 Update changelog for 1.6.0+ds1-4 release 2025-02-18 10:36:35 +01:00
Sébastien Delafond 2f78cf5129 Rediff patches
Add 0004-Don-t-run-swagger-related-or-modules-tasks-before-te.patch: <REASON>
Add 0003-Revert-fix-empty-mirror-check.patch: <REASON>
Drop 0003-Don-t-run-swagger-related-or-modules-tasks-before-te.patch: <REASON>
2025-02-18 10:05:07 +01:00
Sébastien Delafond bc3755dcf7 tests: use extensive coverage from make's test and system-test 2025-02-18 10:05:07 +01:00
Sébastien Delafond a5730feb9d postrm: remove aptly-api user and home directory on purge 2025-01-18 08:43:11 +01:00
Sébastien Delafond b8f084b1dc Update changelog for 1.6.0+ds1-3 release 2025-01-13 14:51:33 +01:00
Sébastien Delafond 9fc50e347b aptly.conf: fix s3 example 2025-01-13 14:49:26 +01:00
Sébastien Delafond 034ab23ff1 Update changelog for 1.6.0+ds1-2 release 2024-12-30 11:11:34 +01:00
Sébastien Delafond 865a0c92eb Rediff patches 2024-12-30 11:08:41 +01:00
Sébastien Delafond b3a4eb8897 Document disabled swagger in default config file 2024-12-30 11:04:31 +01:00
Sébastien Delafond 33536bd69f Update changelog for 1.6.0+ds1-1 release 2024-12-29 08:46:39 +01:00
Sébastien Delafond 10bed44763 Finalize d/NEWS 2024-12-29 08:46:39 +01:00
Sébastien Delafond 0111596d50 Merge branch 'upstream/1.6' into upstream/latest 2024-12-29 08:06:32 +01:00
Sébastien Delafond 1df6ce531c Rediff patches 2024-12-27 15:10:48 +01:00
Sébastien Delafond 1ad63d1786 Merge branch 'upstream/1.6' into debian/experimental 2024-12-27 14:20:57 +01:00
André Roth 895cf5e5c6 debian: embed yaml config 2024-12-27 14:15:50 +01:00
André Roth 9b53deb97f debian native build 2024-12-27 14:15:38 +01:00
Sébastien Delafond 6e9e9a6e31 Merge remote-tracking branch 'official/master' into upstream/1.6 2024-12-27 14:15:09 +01:00
André Roth 90343b21d3 Merge pull request #1406 from aptly-dev/release/1.6.0
Release/1.6.0
2024-12-24 21:30:01 +01:00
Sébastien Delafond 2369a2dadf Update changelog for 1.6.0+ds1~beta3-1 release 2024-12-11 18:16:42 +01:00
Sébastien Delafond 237f43f8ba Rediff patches
Add 0002-disable-new-azure-sdk.patch: <REASON>
Drop 0002-Disable-new-azure-sdk.patch: <REASON>
2024-12-11 18:16:03 +01:00
Sébastien Delafond 7fcac4ed49 Inject d/aptly.conf for build 2024-12-11 18:16:03 +01:00
Sébastien Delafond d52f325d99 Merge branch 'upstream/1.6' into debian/experimental 2024-12-11 17:48:54 +01:00
André Roth 921dfaddb9 debian: embed yaml config 2024-12-11 17:44:09 +01:00
André Roth f642f3fde4 debian native build 2024-12-11 17:43:36 +01:00
Sébastien Delafond f401cea76a Merge remote-tracking branch 'official/release/1.6.0' into upstream/1.6 2024-12-11 17:42:22 +01:00
André Roth 76d3b27842 update changelog 2024-12-11 13:03:55 +01:00
André Roth 35ad56ff7f Revert "debian: do not conflict with gnupg1"
This reverts commit 2f540a8026.
2024-12-11 13:02:43 +01:00
Sébastien Delafond 3bfc305df8 Update changelog for 1.6.0+ds1~beta2-1 release 2024-11-21 16:12:02 +01:00
Sébastien Delafond 5ab866f0db d/control: keep conflicting on gnupg1 & gpgv1 2024-11-21 16:11:40 +01:00
Sébastien Delafond 12855db1a0 Merge branch 'upstream/1.6' into debian/experimental 2024-11-21 16:07:55 +01:00
Sébastien Delafond e094d79b85 Merge remote-tracking branch 'official/fix/debianization' into upstream/1.6 2024-11-21 16:07:37 +01:00
André Roth 96394ecf38 debian: do not conflict with gnupg1 2024-11-20 13:49:47 +01:00
André Roth e9600f9d66 set systemd service file limit to 32768 2024-11-20 13:49:26 +01:00
Sébastien Delafond 94ec8c4548 Update changelog for 1.6.0+ds1~beta1-1 release 2024-11-17 18:56:40 +01:00
Sébastien Delafond bde3dcc5f5 Revert "use new azure-sdk" 2024-11-17 18:56:40 +01:00
Sébastien Delafond df10066c16 Merge branch 'upstream/1.6' into debian/experimental 2024-11-17 18:50:20 +01:00
Sébastien Delafond 6b52a72359 Merge remote-tracking branch 'official/master' into upstream/1.6 2024-11-17 18:49:51 +01:00
Sébastien Delafond 608e0d8610 Update changelog for 1.6.0+ds1~alpha5-1 release 2024-11-17 16:00:20 +01:00
Sébastien Delafond 94c1b2b755 Reduce diff with upstream some more 2024-11-17 16:00:20 +01:00
Sébastien Delafond c31ab7b43f Merge branch 'upstream/1.6' into debian/experimental 2024-11-17 15:59:13 +01:00
Sébastien Delafond 0bbf61df95 Merge remote-tracking branch 'official/improve/debianization' into upstream/16.0 2024-11-17 15:54:02 +01:00
Sébastien Delafond 1b58b88b02 Reduce diff with upstream some more 2024-11-17 15:25:50 +01:00
Sébastien Delafond 61ef1fe798 Sort out dependencies on gpg* 2024-11-17 15:11:03 +01:00
Sébastien Delafond 863ec4dae9 Update changelog for 1.6.0+ds1~alpha4-1 release 2024-11-17 14:52:19 +01:00
Sébastien Delafond 5aa5b8d9cb Remove build-dep on git 2024-11-17 14:51:34 +01:00
Sébastien Delafond 37750cefda Merge branch 'upstream/16.0' into debian/experimental 2024-11-17 14:50:20 +01:00
Sébastien Delafond 7be60cd8be Fix previous merge 2024-11-17 14:44:12 +01:00
Sébastien Delafond 606b701b00 Merge remote-tracking branch 'official/improve/debianization' into upstream/16.0 2024-11-17 14:43:32 +01:00
André Roth fe2f17d38a include more official debianization 2024-11-17 14:23:07 +01:00
Sébastien Delafond 4aeba31a6a Update changelog for 1.6.0+ds1~alpha3-1 release 2024-11-17 07:53:25 +01:00
Sébastien Delafond 2086d424bd Missed .github/workflows/ci.yml in merge 2024-11-17 07:53:25 +01:00
Sébastien Delafond 520eeea355 Merge branch 'upstream/16.0' into debian/experimental 2024-11-17 07:49:31 +01:00
Sébastien Delafond dad2527182 Remove aptly-api postrm 2024-11-17 07:47:51 +01:00
Sébastien Delafond 9a3922fe17 Revert "debian: disable etcd"
This reverts commit a0610292a7.
2024-11-17 07:47:45 +01:00
André Roth 9c2e95d614 debian: add lintian
and fix/improve cross building. build now with PIE and RELRO
2024-11-16 18:49:17 +01:00
André Roth baba1165ff adapt to official debian aptly packaging 2024-11-16 18:46:14 +01:00
Sébastien Delafond a0610292a7 debian: disable etcd 2024-11-16 17:11:59 +01:00
Sébastien Delafond de7f169043 changelog for 1.6.0+ds1~alpha2-1 2024-11-16 14:50:32 +01:00
Sébastien Delafond 97b7143f6d Rediff patches 2024-11-16 14:50:32 +01:00
Sébastien Delafond 520b50e49b copyright: add André Roth 2024-11-16 14:40:10 +01:00
Sébastien Delafond 70cbc12ac7 Simplify maintainer scripts 2024-11-16 14:40:06 +01:00
Sébastien Delafond 9a01c64f68 Merge branch 'upstream/16.0' into debian/experimental 2024-11-16 14:39:54 +01:00
Sébastien Delafond 673da76e55 aptly-api postrm: do not remove potentially valuable data 2024-11-15 17:44:21 +01:00
André Roth 146daa22a7 fix deb 2024-11-15 17:25:58 +01:00
André Roth 9150a75886 update from official debian packaging 2024-11-15 16:54:00 +01:00
André Roth 92bff40eb4 debian: use package versions from bookworm-backports 2024-11-15 16:52:39 +01:00
Sébastien Delafond 3fd90c74de Update changelog for 1.6.0+ds1~alpha1-2 release 2024-10-16 09:18:20 +02:00
Sébastien Delafond 5039f76fe8 aptly-api: revert arch:all, so mv_conffile does the right thing 2024-10-16 09:09:44 +02:00
Sébastien Delafond 15e14b2a93 Add zsh completion 2024-10-16 08:11:37 +02:00
Sébastien Delafond d41157bd54 Update changelog for 1.6.0+ds1~alpha1-1 release to experimental 2024-10-15 14:43:19 +02:00
Sébastien Delafond cfe853e791 rename conffile 2024-10-15 14:42:01 +02:00
Sébastien Delafond 4fa420699b Adjust packaging for 1.6.0~alpha1 2024-10-15 14:42:01 +02:00
Sébastien Delafond 2b9a7914fd d/control: bump up Standards-Version 2024-10-15 14:42:01 +02:00
Sébastien Delafond 3bf957c313 d/patches: remove old patch, and disable swagger support 2024-10-15 14:42:01 +02:00
Sébastien Delafond 25dfd98672 Merge tag 'upstream/1.6.0+ds1_alpha1' into debian/master 2024-10-15 11:08:00 +02:00
Sébastien Delafond a1fd350573 d/copyright: update Files-Excluded 2024-10-15 10:35:24 +02:00
Sébastien Delafond bbf5db745f Update changelog for 1.5.0+ds1-2 release 2023-09-04 10:07:43 +02:00
Sébastien Delafond 884d695273 debian/tests: pass HOME=/tmp to ensure success in schroot 2023-09-04 10:07:43 +02:00
Sebastien Delafond 65a984ec2b Merge branch 'ftbfs' into 'debian/master'
Fix FTBFS

See merge request debian/aptly!11
2023-09-04 06:48:10 +00:00
Shengjing Zhu e06ecf5092 Replace golang-gopkg-cheggaaa-pb.v1-dev with golang-github-cheggaaa-pb-dev
golang-gopkg-cheggaaa-pb.v1-dev no longer ships compatible symlink
2023-08-29 18:50:44 +08:00
Shengjing Zhu 3bbd61d75b Sort Build-Depends
Gbp-Dch: Ignore
2023-08-29 18:49:41 +08:00
Roland Mas 69f851124c Upload to unstable 2023-01-31 14:47:23 +01:00
Roland Mas 5fef06100c Also close #907121 (fixed upstream) 2023-01-02 15:22:09 +01:00
Roland Mas 40ba4ce958 Add missing build-depends 2023-01-02 14:47:15 +01:00
Roland Mas a8aeaff2a3 Refresh patches (and remove not relevant patches) 2023-01-02 14:47:15 +01:00
Roland Mas d8fea9f142 Start working on 1.5.0+ds1 2023-01-02 14:47:15 +01:00
Roland Mas f33a9dccf8 Update upstream source from tag 'upstream/1.5.0+ds1'
Update to upstream version '1.5.0+ds1'
with Debian dir 8f25149198
2023-01-02 14:19:51 +01:00
Roland Mas 5c4f97f88e New upstream version 1.5.0+ds1 2023-01-02 14:19:29 +01:00
Sébastien Delafond 4c7796ca56 salsa-ci: switch to recipes/debian.yml@salsa-ci-team/pipeline 2022-12-01 12:30:45 +01:00
Jelmer Vernooij e6e102a95c Merge branch 'lintian-fixes' into 'debian/master'
Fix some issues reported by lintian

See merge request debian/aptly!10
2022-11-28 10:44:25 +00:00
Debian Janitor fee581c722 Update standards version to 4.6.1, no changes needed.
Changes-By: lintian-brush
Fixes: lintian: out-of-date-standards-version
See-also: https://lintian.debian.org/tags/out-of-date-standards-version.html
2022-11-27 18:33:12 +00:00
Jelmer Vernooij f398ffb183 Merge branch 'lintian-fixes' into 'debian/master'
Fix some issues reported by lintian

See merge request debian/aptly!9
2022-11-27 14:21:43 +00:00
Debian Janitor 60e578bad9 Set upstream metadata fields: Repository.
Changes-By: lintian-brush
Fixes: lintian: upstream-metadata-missing-repository
See-also: https://lintian.debian.org/tags/upstream-metadata-missing-repository.html
2022-06-07 00:53:29 +00:00
Debian Janitor 55fc2f4d0c Update standards version to 4.6.0, no changes needed.
Changes-By: lintian-brush
Fixes: lintian: out-of-date-standards-version
See-also: https://lintian.debian.org/tags/out-of-date-standards-version.html
2022-05-19 08:31:42 +00:00
Debian Janitor dc74239275 Set upstream metadata fields: Bug-Database, Bug-Submit, Repository-Browse.
Changes-By: lintian-brush
Fixes: lintian: upstream-metadata-file-is-missing
See-also: https://lintian.debian.org/tags/upstream-metadata-file-is-missing.html
Fixes: lintian: upstream-metadata-missing-bug-tracking
See-also: https://lintian.debian.org/tags/upstream-metadata-missing-bug-tracking.html
2022-05-19 08:31:03 +00:00
Debian Janitor 75ad96e9ca Bump debhelper from old 12 to 13.
Changes-By: lintian-brush
Fixes: lintian: package-uses-old-debhelper-compat-version
See-also: https://lintian.debian.org/tags/package-uses-old-debhelper-compat-version.html
2022-05-19 08:30:53 +00:00
Sebastien Delafond 265d1373a1 Merge branch 'add_zstd_support' into 'debian/master'
Add zstd support

See merge request debian/aptly!8
2022-05-19 05:54:31 +00:00
Anton Gladky 814ee037d3 Add support for zstd compression (Closes: #1010465) 2022-05-18 16:32:36 +02:00
Sébastien Delafond 4ab22a5fc5 Update changelog for 1.4.0+ds1-6 release 2021-11-04 10:25:02 +01:00
Sébastien Delafond 6222250104 Conflict on gpgv1 (Closes: #990821) 2021-11-04 10:24:23 +01:00
Sébastien Delafond f2b328395d Update changelog for 1.4.0+ds1-5 release 2021-10-14 18:43:38 +02:00
Sébastien Delafond f3732b1683 Conflict on gnupg1 (Closes: #990821) 2021-10-14 18:36:49 +02:00
Sébastien Delafond 82ddc7f9ce Update changelog for 1.4.0+ds1-4 release 2021-03-11 15:21:03 +01:00
Sébastien Delafond 5031adcb7f Install correct bash completion snippet (Closes: #984979) 2021-03-11 15:20:27 +01:00
Sébastien Delafond c5322ff2f6 Update changelog for 1.4.0+ds1-3 release 2021-03-03 10:51:17 +01:00
Sébastien Delafond a1f1cf307b Bump-up Standards-Version 2021-03-03 10:50:49 +01:00
Sébastien Delafond 1b0cad0f1b Bump-up d/watch version 2021-03-03 10:50:49 +01:00
Sébastien Delafond 29c2603d61 Remove unused d/source/include-binaries 2021-03-03 10:50:49 +01:00
Sébastien Delafond ea9657dae0 Fix s3 etag issue (Closes: #983877) 2021-03-03 10:46:05 +01:00
Sébastien Delafond b61875fa9c Update changelog for 1.4.0+ds1-2 release 2020-08-21 16:29:22 +02:00
Sébastien Delafond 1308c2f774 Pass version from d/rules (Closes: #968585) 2020-08-21 16:29:22 +02:00
Sébastien Delafond 63bc8282a0 Allow reprotest failure 2019-12-22 16:00:25 +01:00
Sébastien Delafond cd0626e825 Use pipeline from salsa-ci-team 2019-12-22 15:47:01 +01:00
Sébastien Delafond dc4b4a86a4 Update changelog for 1.4.0+ds1-1 release 2019-12-22 15:16:51 +01:00
Sébastien Delafond bb6df8ee63 Depend on gnupg 2 2019-12-22 15:16:51 +01:00
Sébastien Delafond 199b5ab9b8 Rediff patches
Drop : <REASON>
Drop pborman-uuid.patch: <REASON>
Drop kjk-lzma.patch: <REASON>
2019-12-22 15:09:18 +01:00
Sébastien Delafond 5719d6fcdd New upstream version 1.4.0+ds1 2019-12-22 15:09:18 +01:00
Sébastien Delafond 29e4ea6ec0 New upstream version 1.4.0+ds1 2019-12-22 14:57:35 +01:00
Sébastien Delafond 491c8ebdd1 Update changelog for 1.3.0+ds1-4 release 2019-12-22 14:10:39 +01:00
Sébastien Delafond 3afb6e47bf Bump up Standards-Version 2019-12-22 14:10:04 +01:00
Sébastien Delafond f767136371 Update changelog for 1.3.0+ds1-4 release 2019-12-22 13:58:16 +01:00
Sébastien Delafond 86162f0ef5 Merge branch 'lintian-fixes' of salsa.debian.org:janitor-bot-guest/aptly into debian/master 2019-12-22 13:57:47 +01:00
Sébastien Delafond f03f8378b9 Revert "Force gz"
This reverts commit 074b35755d.
2019-12-22 10:22:44 +01:00
Sébastien Delafond 074b35755d Force gz 2019-12-21 10:51:08 +01:00
Sébastien Delafond a92de0d9bd Update changelog for 1.3.0+ds1-3 release 2019-12-21 10:29:34 +01:00
Sébastien Delafond f966258772 Lintian fix 2019-12-21 10:29:34 +01:00
Sébastien Delafond 7938eebdcd Build-Depend on golang-golang-x-tools-dev instead of golang-go.tools (Closes: #945884) 2019-12-21 10:21:18 +01:00
Debian Janitor dccf1acb78 Set debhelper-compat version in Build-Depends.
Fixes lintian: uses-debhelper-compat-file
See https://lintian.debian.org/tags/uses-debhelper-compat-file.html for more details.
2019-11-29 08:02:45 +00:00
Debian Janitor 20278a7b5d Bump debhelper from old 11 to 12.
Fixes lintian: package-uses-old-debhelper-compat-version
See https://lintian.debian.org/tags/package-uses-old-debhelper-compat-version.html for more details.
2019-11-29 08:02:25 +00:00
Debian Janitor d4268fd4c6 Use secure URI in Homepage field.
Fixes lintian: homepage-field-uses-insecure-uri
See https://lintian.debian.org/tags/homepage-field-uses-insecure-uri.html for more details.
2019-11-29 08:02:06 +00:00
Debian Janitor e7af54999f Rename obsolete path debian/tests/control.autodep8 to debian/tests/control.
Fixes lintian: debian-tests-control-autodep8-is-obsolete
See https://lintian.debian.org/tags/debian-tests-control-autodep8-is-obsolete.html for more details.
2019-11-29 08:01:46 +00:00
Alexandre Viau 916d5a22c2 remove myself from uploaders 2019-09-15 19:28:43 -04:00
Shengjing Zhu abdd341369 Update changelog for 1.3.0+ds1-2.2 release 2019-04-16 00:18:38 +08:00
Shengjing Zhu a802160318 Update debian/NEWS about DB compatibility 2019-04-16 00:18:08 +08:00
Shengjing Zhu a5c7bde1a3 Fix struct field tag typo 2019-04-16 00:12:28 +08:00
Shengjing Zhu 217a8a8e92 Add patch to fix DB backwards compatibility (Closes: #911924) 2019-04-16 00:04:05 +08:00
Tobias Frost e85459c529 NMU to fix #923866 2019-04-05 17:40:24 +02:00
aviau 2edaf38386 warn about db incompatibility 2018-10-26 13:22:48 -04:00
aviau cc7f75370f d/changelog: close #902128 (remove vendor/*) 2018-10-15 12:16:16 -04:00
aviau 829d9924c3 add more missing dependencies 2018-10-15 12:07:27 -04:00
aviau 164cefe2a2 s/UNRELEASED/unstable/ 2018-10-15 11:54:13 -04:00
aviau 9ac2e25739 d/rules: remove trailing whitespace 2018-10-15 11:53:50 -04:00
aviau 1de4d69922 Use Debian's uuid pacakge 2018-10-15 11:52:43 -04:00
aviau dbc5ba9458 add even more dependencies 2018-10-15 11:45:18 -04:00
aviau 1459919984 d/changelog: set version to 1.3.0+ds1-1 2018-10-15 11:40:42 -04:00
aviau 11b9382e56 d/control: add vendor/* build dependencies 2018-10-15 11:39:09 -04:00
aviau c49c3cac30 d/copyright: remove vendor sections 2018-10-15 11:36:32 -04:00
aviau 165c79394b Update upstream source from tag 'upstream/1.3.0+ds1'
Update to upstream version '1.3.0+ds1'
with Debian dir 3c94b99a81
2018-10-15 11:31:47 -04:00
aviau 8fa7bc9206 New upstream version 1.3.0+ds1 2018-10-15 11:31:46 -04:00
aviau 842cbc0e44 append +ds suffix and ignore vendor/* 2018-10-15 11:30:40 -04:00
Ondřej Nový 014f4c49d1 d/changelog: Remove trailing whitespaces 2018-10-01 10:22:56 +02:00
aviau c9f52ab9f4 combine autodep8 and autopkgtest 2018-06-29 18:11:55 -04:00
aviau c44eb676b0 Combine autodep8 and autopkgtest 2018-06-27 13:32:17 -04:00
aviau d29fbb8acf aptly test: allow-stderr 2018-06-26 23:28:52 -04:00
aviau 1566f9a229 fix broken autopkgtest 2018-06-26 23:20:02 -04:00
aviau b65434650c depend on gpgv1 2018-06-26 23:10:31 -04:00
aviau 4ba3e0b941 s/UNRELEASED/unstable/ 2018-06-26 23:01:49 -04:00
aviau 535d7149bd Fix unnecessary-testsuite-autopkgtest-field 2018-06-26 22:58:48 -04:00
aviau e8df80555c Fix syntax-error-in-dep5-copyright 2018-06-26 22:12:45 -04:00
Sébastien Delafond d978ae5a15 Update changelog for 1.3.0-4 release 2018-06-26 14:24:57 +02:00
Sébastien Delafond 8de7940335 Depend on gnupg1 (Closes: #902419) 2018-06-26 14:23:38 +02:00
Sebastien Delafond b6a1adf90e Merge branch 'master' into 'debian/master'
autopkgtest and gitlab-ci

See merge request debian/aptly!3
2018-06-26 12:15:52 +00:00
Hans-Christoph Steiner 910b969894 add debian/.gitlab-ci.yml 2018-06-26 13:06:53 +02:00
Hans-Christoph Steiner 476f17b84c add simple autopkgtest 2018-06-26 13:06:53 +02:00
Hans-Christoph Steiner 1fe6cbdb4c point debian/watch to new git repo on GitHub 2018-06-26 13:06:53 +02:00
Sébastien Delafond fb9f92e99e Document #902128 in debian/copyright 2018-06-26 10:39:12 +02:00
Sébastien Delafond 056182923a Target unstable 2018-06-25 14:52:03 +02:00
Sebastien Delafond be0e5f1dad Merge branch 'aptly-api' into 'debian/master'
create aptly-api package

See merge request debian/aptly!2
2018-06-25 12:48:41 +00:00
aviau 5add1af33b create aptly-api package 2018-06-22 15:58:42 -04:00
Sébastien Delafond bda3b8dad2 Merge remote-tracking branch 'salsa/dh-golang' into debian/master 2018-06-22 13:55:20 +02:00
aviau 5679b4b1db switch to dh_golang 2018-06-21 15:09:32 -04:00
Sébastien Delafond 7730890e7c Update changelog for 1.3.0-1 release 2018-06-21 16:09:01 +02:00
Sébastien Delafond 7d2ddd44c0 Bump-up Standards-Version 2018-06-21 16:09:01 +02:00
Sébastien Delafond ba8c42d70b Now hosted at https://github.com/aptly-dev/aptly 2018-06-21 16:07:02 +02:00
Sébastien Delafond 09ad0121c6 New upstream version 1.3.0 2018-06-21 16:07:02 +02:00
Sébastien Delafond 23e839c80b New upstream version 1.3.0 2018-06-21 15:14:48 +02:00
Sébastien Delafond bed9fffa94 Fix copyright 2018-04-12 13:12:40 +02:00
Sébastien Delafond 4c3e0f8b3c Update changelog for 1.2.0-4 release 2018-03-06 10:11:51 +01:00
Sébastien Delafond c3bd6a3eea Enable Built-Using in debian/control (thanks M. Staperberg) 2018-03-06 10:11:08 +01:00
Sébastien Delafond 93294dcb18 Update changelog for 1.2.0-3 release 2018-02-16 17:02:54 +01:00
Sébastien Delafond 9492994b8c Update Vcs-* to point to salsa.d.o 2018-02-16 17:00:02 +01:00
Sébastien Delafond 0394a30dd7 Switch to DEP 14 2018-02-16 16:33:00 +01:00
Sébastien Delafond 83c0c03257 Merge branch 'upstream' 2018-02-16 16:32:56 +01:00
Michael Stapelberg 4c5aa19a8f Update changelog for 1.2.0-2 release 2018-02-10 18:41:37 +01:00
Michael Stapelberg 8adb6e37eb Set XS-Go-Import-Path 2018-02-10 18:41:36 +01:00
Michael Stapelberg 12211e127c Build-Depend on golang-any, not golang 2018-02-10 18:41:21 +01:00
Sébastien Delafond 4345e93446 Update changelog for 1.2.0-1 release 2018-01-03 13:52:41 +01:00
Sébastien Delafond 29bdd9ff26 Remove patch that's been included upstream 2018-01-03 13:52:41 +01:00
Sébastien Delafond f6225c4983 New upstream version 1.2.0 2018-01-03 13:52:40 +01:00
Sébastien Delafond b49bcafd67 New upstream version 1.2.0 2018-01-03 12:07:20 +01:00
Sébastien Delafond 3cbca50cad Update changelog for 1.1.1-2 release 2017-11-02 13:32:09 +01:00
Sébastien Delafond 018a6bd2c7 Support both uncompressed control.tar, and xz-compressed control.tar.xz 2017-11-02 13:32:09 +01:00
Sébastien Delafond 8c2cd7117c Update changelog for 1.1.1-1 release 2017-11-02 09:42:00 +01:00
Sébastien Delafond 13e67ee7ca Update debian/copyright 2017-11-02 09:27:46 +01:00
Sébastien Delafond a40e6dd5d8 Bump up Standards-Version 2017-11-02 09:10:32 +01:00
Sébastien Delafond 89fd04febb Use bash-completion snippet from upstream 2017-11-02 09:00:25 +01:00
Sébastien Delafond ee9fb8dfec New upstream version 1.1.1 2017-11-02 08:56:43 +01:00
Sébastien Delafond 1949e77df8 Update upstream source from tag 'upstream/1.1.1'
Update to upstream version '1.1.1'
with Debian dir c0689398ae
2017-11-02 08:56:43 +01:00
Sébastien Delafond 44079e1ce2 1.0.1-1 2017-07-18 12:00:18 +02:00
Sébastien Delafond 76a9eeda7c Bump-up Standards-Version 2017-07-18 12:00:18 +02:00
Sébastien Delafond 2dd9a49f05 Update Vcs-Git 2017-07-18 12:00:18 +02:00
Sébastien Delafond aa7a862631 Adapt debian/rules 2017-07-18 12:00:18 +02:00
Sébastien Delafond 66d1f3878b Update debian/copyright 2017-07-18 11:53:04 +02:00
Sébastien Delafond 4f12259bc0 Imported Upstream version 1.0.1 2017-07-18 11:45:29 +02:00
Sébastien Delafond c926f2bf05 Imported Upstream version 1.0.1 2017-07-04 14:45:08 +02:00
Sébastien Delafond 1ee35b841d 0.9.7-1 2016-05-24 09:17:38 +02:00
Sébastien Delafond 1a802d7428 Add new copyright info 2016-05-24 09:16:58 +02:00
Sébastien Delafond 056c3d6dad Imported Upstream version 0.9.7 2016-05-24 09:05:22 +02:00
Sébastien Delafond 64760ea779 Merge tag 'upstream/0.9.7'
Upstream version 0.9.7
2016-05-24 09:05:22 +02:00
Sébastien Delafond d1027dd016 0.9.6-1 2016-02-10 18:53:29 +01:00
Sébastien Delafond 37862fdb5c Licenses and copyrights
- new libraries
  - libraries that moved from code.google.com to github
2016-02-10 18:53:29 +01:00
Sébastien Delafond bbc8eae484 Add dependency on xz-utils 2016-02-10 15:35:46 +01:00
Sébastien Delafond 8fd4a508f8 Merge tag 'upstream/0.9.6'
Upstream version 0.9.6
2016-02-10 15:34:40 +01:00
Sébastien Delafond 26ac72d142 Imported Upstream version 0.9.6 2016-02-10 15:34:39 +01:00
Sébastien Delafond bbebb43808 0.9.5-2 2015-08-16 18:42:31 +02:00
Sébastien Delafond 35deb90803 Remove empty component in GOPATH (Closes: #793838) 2015-08-16 18:41:50 +02:00
Sébastien Delafond 9fdc90cf13 0.9.5-1 2015-05-15 10:47:02 +02:00
Sébastien Delafond 6fe53d9508 Up-to-date bash completion 2015-05-15 10:46:21 +02:00
Sébastien Delafond 9141f1b343 Imported Upstream version 0.9.5 2015-05-15 10:45:09 +02:00
Sébastien Delafond 80804b9b49 Merge tag 'upstream/0.9.5'
Upstream version 0.9.5
2015-05-15 10:45:09 +02:00
Sébastien Delafond 034a67ff7e 0.9.1-1 2015-05-02 12:57:47 +02:00
Sébastien Delafond 8d6cc854aa Bump-up Standards-Version 2015-05-02 12:56:55 +02:00
Sébastien Delafond 0ed02327b0 Proper name for bash-completion file 2015-05-02 12:56:55 +02:00
Sébastien Delafond fac32a6def RC 2015-05-02 12:56:55 +02:00
Sébastien Delafond 1d0a51886f Suggest graphviz 2015-05-02 12:51:08 +02:00
Sébastien Delafond e65a50161d Document licenses and copyrights for new dependencies 2015-05-02 12:49:45 +02:00
Sébastien Delafond 950bfadaba Merge tag 'upstream/0.9.1'
Upstream version 0.9.1
2015-05-02 12:28:40 +02:00
Sébastien Delafond fbac926044 Imported Upstream version 0.9.1 2015-05-02 12:24:38 +02:00
Sébastien Delafond 7efd573010 Imported Upstream version 0.9.1 2015-05-02 12:18:01 +02:00
Sébastien Delafond 2b589d1ded 0.8-3 2014-10-09 18:41:46 +02:00
Sébastien Delafond 63762ba279 Correct dependency from "gpg" to "gnupg" (Closes: #764619) 2014-10-09 18:40:56 +02:00
Sébastien Delafond fa01fd6ed5 Correct Vcs-Browser entry (Closes: #764622) 2014-10-09 18:40:47 +02:00
Sébastien Delafond 57598ec9de 0.8-2 2014-10-09 11:15:36 +02:00
Sébastien Delafond 5ccd8663f6 Add missing dependencies on bzip2, gpg and gpgv 2014-10-09 11:14:59 +02:00
Sébastien Delafond d663c8e3cc 0.8-1 2014-10-04 17:58:35 +02:00
Sébastien Delafond 22a9d9a369 Reference full paths in debian/copyright, and remove unused references,
so lintian does not complain
2014-10-04 17:58:35 +02:00
Sébastien Delafond b6ebdd9c7b Add bash completion snippet
* This is hosted in another github repository, so for now I provide it
    directly in debian/bash-completion. At some later point it will come
    in the main source tarball, and I'll then just reference that in
    debian/bash-completion.
2014-10-04 17:58:35 +02:00
Sébastien Delafond 94ac71d4ce Document new copyrights and licenses 2014-10-04 17:58:31 +02:00
Sébastien Delafond 9666e1cf41 Imported Upstream version 0.8 2014-10-04 17:58:31 +02:00
Sébastien Delafond 35bd883d40 Imported Upstream version 0.8 2014-10-04 17:09:23 +02:00
Sébastien Delafond 8faca75d06 0.7.1-1 2014-09-10 10:04:34 +02:00
Sébastien Delafond b1deaba0bd Add copyright information for new libraries 2014-09-10 10:01:56 +02:00
Sébastien Delafond 32417bbb7e Merge tag 'upstream/0.7.1'
Upstream version 0.7.1

Conflicts:
	src/github.com/smira/aptly/_vendor/src/code.google.com/p/mxk/LICENSE
2014-09-10 09:42:37 +02:00
Sébastien Delafond b3596e7471 Imported Upstream version 0.7.1 2014-09-10 09:41:14 +02:00
Sébastien Delafond 9bbd6b21b9 0.5-5 2014-09-09 09:21:07 +02:00
Sébastien Delafond 7d35c5c5bb Add missing MIT license text 2014-09-08 20:08:55 +02:00
Sébastien Delafond 7d1f66e537 gocov is licensed MIT, not BSD-3 2014-09-08 20:08:09 +02:00
Sébastien Delafond 2e9f8b8064 Correct Vcs-* information 2014-07-14 11:52:04 +02:00
Sébastien Delafond 5703f81bac Turn off verbose mode 2014-07-11 11:12:27 +02:00
Sébastien Delafond 7f8edee078 Try and use packaged go.tools 2014-07-10 01:02:05 +02:00
Sébastien Delafond 991aa67efb Collect licenses by going over files one by one 2014-07-09 23:10:59 +02:00
Sébastien Delafond f6e8e05dad 0.5-3 2014-05-28 10:05:04 +02:00
Sébastien Delafond 09c07b24dc Licensing:
* goleveldb is BSD-2, not BSD-3
  * _vendor/src/code.google.com/p/gographviz/scanner/scanner is BSD-3,
    copyright 2009 The Go Authors
2014-05-28 10:02:40 +02:00
Sébastien Delafond 3699db53b5 0.5-2 2014-05-02 12:04:43 +02:00
Sébastien Delafond 0b2469eef2 Go interpreter not needed at runtime 2014-05-02 12:00:58 +02:00
Sébastien Delafond 1d892e6b99 Initial upload 2014-05-01 20:01:40 +02:00
Sébastien Delafond f4e87ed80b Imported Upstream version 0.5 2014-05-01 20:01:26 +02:00
162 changed files with 3953 additions and 4478 deletions
-2
View File
@@ -26,8 +26,6 @@ _testmain.go
*.test
coverage.txt
coverage.out
coverage.html
*.pyc
-8
View File
@@ -70,17 +70,9 @@ List of contributors, in chronological order:
* Gordian Schoenherr (https://github.com/schoenherrg)
* Silke Hofstra (https://github.com/silkeh)
* Itay Porezky (https://github.com/itayporezky)
* Alejandro Guijarro Monerris (https://github.com/alguimodd)
* JupiterRider (https://github.com/JupiterRider)
* Agustin Henze (https://github.com/agustinhenze)
* Tobias Assarsson (https://github.com/daedaluz)
* Yaksh Bariya (https://github.com/thunder-coding)
* Juan Calderon-Perez (https://github.com/gaby)
* Ato Araki (https://github.com/atotto)
* Roman Lebedev (https://github.com/LebedevRI)
* Brian Witt (https://github.com/bwitt)
* Ales Bregar (https://github.com/abregar)
* Tim Foerster (https://github.com/tonobo)
* Zhang Xiao (https://github.com/xzhang1)
* Tom Nguyen (https://github.com/lecafard)
* Philip Cramer (https://github.com/PhilipCramer)
+2 -2
View File
@@ -75,9 +75,9 @@ azurite-start:
azurite-stop:
@kill `cat ~/.azurite.pid`
swagger: swagger-install
swagger: #swagger-install
# Generate swagger docs
@PATH=$(BINPATH)/:$(PATH) swag init --propertyStrategy pascalcase --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
#@PATH=$(BINPATH)/:$(PATH) swag init --propertyStrategy pascalcase --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
etcd-install:
# Install etcd
-201
View File
@@ -1,7 +1,6 @@
package api
import (
"bufio"
"fmt"
"os"
"os/exec"
@@ -13,23 +12,6 @@ import (
"github.com/gin-gonic/gin"
)
type gpgKeyInfo struct {
// 16-character key ID (short form)
KeyID string `json:"KeyID" example:"8B48AD6246925553"`
// Full fingerprint
Fingerprint string `json:"Fingerprint" example:"D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0"`
// Key validity (u=unknown, f=fulltrust, m=marginal, n=never)
Validity string `json:"Validity" example:"u"`
// User ID(s) associated with this key
UserIDs []string `json:"UserIDs" example:"John Doe <john@example.com>"`
// Creation date (Unix timestamp format from gpg)
CreatedAt string `json:"CreatedAt" example:"2023-01-15"`
}
type gpgKeyListResponse struct {
Keys []gpgKeyInfo `json:"Keys"`
}
type gpgAddKeyParams struct {
// Keyring for adding the keys (default: trustedkeys.gpg)
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
@@ -43,14 +25,6 @@ type gpgAddKeyParams struct {
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500 8B48AD6246925553"`
}
type gpgDeleteKeyParams struct {
// Keyring to delete keys from (default: trustedkeys.gpg)
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
// Key ID or fingerprint to delete
GpgKeyID string `json:"GpgKeyID" example:"8B48AD6246925553"`
}
// @Summary Add GPG Keys
// @Description **Adds GPG keys to aptly keyring**
// @Description
@@ -134,178 +108,3 @@ func apiGPGAddKey(c *gin.Context) {
c.JSON(200, string(out))
}
// @Summary List GPG Keys
// @Description **Lists all GPG keys in aptly keyring**
// @Description
// @Description Returns all public keys currently installed in the aptly GPG keyring.
// @Description
// @Tags Mirrors
// @Param keyring query string false "Keyring file to list keys from (default: trustedkeys.gpg)" example(trustedkeys.gpg)
// @Produce json
// @Success 200 {object} gpgKeyListResponse "OK"
// @Failure 400 {object} Error "Bad Request"
// @Router /api/gpg/keys [get]
func apiGPGListKeys(c *gin.Context) {
keyring := c.DefaultQuery("keyring", "trustedkeys.gpg")
keyring = utils.SanitizePath(keyring)
finder := pgp.GPGDefaultFinder()
gpg, _, err := finder.FindGPG()
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
args := []string{
"--no-default-keyring",
"--with-colons",
"--keyring", keyring,
"--list-keys",
}
cmd := exec.Command(gpg, args...)
out, err := cmd.CombinedOutput()
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("failed to list keys: %s", string(out)))
return
}
keys := parseGPGOutput(string(out))
c.JSON(200, gpgKeyListResponse{Keys: keys})
}
// @Summary Delete GPG Key
// @Description **Deletes a GPG key from aptly keyring**
// @Description
// @Description Removes a public key from the aptly GPG keyring. This is useful for removing
// @Description compromised keys or cleaning up obsolete keys.
// @Description
// @Tags Mirrors
// @Consume json
// @Param request body gpgDeleteKeyParams true "Parameters"
// @Produce json
// @Success 200 {object} string "OK"
// @Failure 400 {object} Error "Bad Request"
// @Router /api/gpg/key [delete]
func apiGPGDeleteKey(c *gin.Context) {
b := gpgDeleteKeyParams{}
if c.Bind(&b) != nil {
AbortWithJSONError(c, 400, fmt.Errorf("invalid request body"))
return
}
if len(strings.TrimSpace(b.GpgKeyID)) == 0 {
AbortWithJSONError(c, 400, fmt.Errorf("GpgKeyID is required"))
return
}
b.GpgKeyID = utils.SanitizePath(b.GpgKeyID)
// b.Keyring can be an absolute path
finder := pgp.GPGDefaultFinder()
gpg, _, err := finder.FindGPG()
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
args := []string{
"--no-default-keyring",
"--allow-non-selfsigned-uid",
"--batch",
"--yes",
}
keyring := "trustedkeys.gpg"
if len(b.Keyring) > 0 {
keyring = b.Keyring
}
args = append(args, "--keyring", keyring)
args = append(args, "--delete-keys", b.GpgKeyID)
cmd := exec.Command(gpg, args...)
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
out, err := cmd.CombinedOutput()
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("failed to delete key: %s", string(out)))
return
}
c.JSON(200, string(out))
}
// parseGPGOutput parses the output of `gpg --with-colons --list-keys`
// and returns a structured list of keys
func parseGPGOutput(output string) []gpgKeyInfo {
var keys []gpgKeyInfo
var currentKey *gpgKeyInfo
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
parts := strings.Split(line, ":")
if len(parts) < 10 {
continue
}
recordType := parts[0]
// pub: public key record
if recordType == "pub" {
// Save previous key if it exists
if currentKey != nil && currentKey.KeyID != "" {
keys = append(keys, *currentKey)
}
// Create new key entry
// Format: pub:trust:length:algo:keyid:created:expires:uidhash:...
keyID := parts[4]
if len(keyID) >= 16 {
keyID = keyID[len(keyID)-16:] // Last 16 chars = short key ID
}
validity := parts[1]
createdAt := parts[5]
currentKey = &gpgKeyInfo{
KeyID: keyID,
Validity: validity,
CreatedAt: createdAt,
UserIDs: []string{},
Fingerprint: "",
}
}
// uid: user ID record
if recordType == "uid" && currentKey != nil {
// Format: uid:trust:created:expires:keyid:uidhash:uidtype:validity:userID:...
if len(parts) >= 10 {
userID := parts[9]
if userID != "" {
currentKey.UserIDs = append(currentKey.UserIDs, userID)
}
}
}
// fpr: fingerprint record
if recordType == "fpr" && currentKey != nil {
// Format: fpr:::::::::fingerprint:
if len(parts) >= 10 {
fingerprint := parts[9]
currentKey.Fingerprint = fingerprint
}
}
}
// Don't forget the last key
if currentKey != nil && currentKey.KeyID != "" {
keys = append(keys, *currentKey)
}
return keys
}
-451
View File
@@ -1,451 +0,0 @@
package api
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
. "gopkg.in/check.v1"
)
type GPGSuite struct {
APISuite
}
var _ = Suite(&GPGSuite{})
func (s *GPGSuite) withFakeGPG(c *C, scriptBody string, test func(scriptPath string)) {
tempDir, err := os.MkdirTemp("", "aptly-fake-gpg")
c.Assert(err, IsNil)
defer func() { _ = os.RemoveAll(tempDir) }()
scriptPath := filepath.Join(tempDir, "gpg")
err = os.WriteFile(scriptPath, []byte(scriptBody), 0o755)
c.Assert(err, IsNil)
oldPath := os.Getenv("PATH")
err = os.Setenv("PATH", tempDir+string(os.PathListSeparator)+oldPath)
c.Assert(err, IsNil)
defer func() { _ = os.Setenv("PATH", oldPath) }()
test(scriptPath)
}
func (s *GPGSuite) fakeGPGScript(c *C, listOutput string, deleteOutput string, deleteError string) string {
return "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then\n" +
" echo 'gpg (GnuPG) 2.2.27'\n" +
" exit 0\n" +
"fi\n" +
"args=\"$*\"\n" +
"if printf '%s' \"$args\" | grep -q -- '--list-keys'; then\n" +
" cat <<'EOF'\n" + listOutput + "\nEOF\n" +
" exit 0\n" +
"fi\n" +
"if printf '%s' \"$args\" | grep -q -- '--delete-keys'; then\n" +
" if [ -n \"" + strings.ReplaceAll(deleteError, "\n", "") + "\" ]; then\n" +
" echo '" + strings.ReplaceAll(deleteError, "'", "'\\''") + "'\n" +
" exit 1\n" +
" fi\n" +
" cat <<'EOF'\n" + deleteOutput + "\nEOF\n" +
" exit 0\n" +
"fi\n" +
"echo 'unexpected invocation' >&2\n" +
"exit 1\n"
}
// TestParseGPGOutputEmpty tests parsing of empty GPG output
func (s *GPGSuite) TestParseGPGOutputEmpty(c *C) {
output := ""
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 0)
}
// TestParseGPGOutputSingleKeyMinimal tests parsing a single key with minimal fields
func (s *GPGSuite) TestParseGPGOutputSingleKeyMinimal(c *C) {
// Minimal valid GPG output with one key
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
key := keys[0]
c.Check(key.KeyID, Equals, "8B48AD6246925553")
c.Check(key.Validity, Equals, "u")
c.Check(key.CreatedAt, Equals, "1611864000")
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
c.Check(key.UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
}
// TestParseGPGOutputMultipleKeys tests parsing multiple keys
func (s *GPGSuite) TestParseGPGOutputMultipleKeys(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
pub:f:2048:1:A1B2C3D4E5F67890:1580592000:1612128000:uidhash:::scESC:::::::23::0:
uid:f::::1580592000::0987654321::Jane Smith <jane@example.com>::::::::::0:
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 2)
// First key
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
c.Check(keys[0].Validity, Equals, "u")
c.Check(keys[0].UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
// Second key
c.Check(keys[1].KeyID, Equals, "A1B2C3D4E5F67890")
c.Check(keys[1].Validity, Equals, "f")
c.Check(keys[1].UserIDs, DeepEquals, []string{"Jane Smith <jane@example.com>"})
}
// TestParseGPGOutputMultipleUIDs tests a key with multiple user IDs
func (s *GPGSuite) TestParseGPGOutputMultipleUIDs(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
uid:u::::1611864000::1234567891::John Doe <john.doe@company.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
key := keys[0]
c.Check(key.UserIDs, HasLen, 2)
c.Check(key.UserIDs, DeepEquals, []string{
"John Doe <john@example.com>",
"John Doe <john.doe@company.com>",
})
}
// TestParseGPGOutputMalformedLines tests that malformed lines are skipped
func (s *GPGSuite) TestParseGPGOutputMalformedLines(c *C) {
// Mix of valid and invalid lines (too few fields)
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
invalid:line:with:only:three:fields
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
}
// TestParseGPGOutputEmptyLines tests that empty lines are skipped
func (s *GPGSuite) TestParseGPGOutputEmptyLines(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
}
// TestParseGPGOutputKeyWithoutUID tests a public key without user ID
func (s *GPGSuite) TestParseGPGOutputKeyWithoutUID(c *C) {
// Key without uid record (should still be included)
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
key := keys[0]
c.Check(key.KeyID, Equals, "8B48AD6246925553")
c.Check(key.UserIDs, HasLen, 0)
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
}
// TestParseGPGOutputVariousValidity tests different validity values
func (s *GPGSuite) TestParseGPGOutputVariousValidity(c *C) {
output := `pub:u:4096:1:KEY1111111111111:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::Key1::::::::::0:
fpr:::::::::1111111111111111111111111111111111111111:
pub:f:4096:1:KEY2222222222222:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:f::::1611864000::1234567891::Key2::::::::::0:
fpr:::::::::2222222222222222222222222222222222222222:
pub:m:4096:1:KEY3333333333333:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:m::::1611864000::1234567892::Key3::::::::::0:
fpr:::::::::3333333333333333333333333333333333333333:
pub:n:4096:1:KEY4444444444444:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:n::::1611864000::1234567893::Key4::::::::::0:
fpr:::::::::4444444444444444444444444444444444444444:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 4)
validities := []string{"u", "f", "m", "n"}
for i, validity := range validities {
c.Check(keys[i].Validity, Equals, validity)
}
}
// TestParseGPGOutputShortKeyID tests that key IDs are shortened to 16 chars
func (s *GPGSuite) TestParseGPGOutputShortKeyID(c *C) {
// 40-character key ID that should be shortened to last 16 chars
longKeyID := "0123456789ABCDEF0123456789ABCDEF8B48AD62"
output := `pub:u:4096:1:` + longKeyID + `:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should extract the last 16 characters: 89ABCDEF8B48AD62
c.Check(keys[0].KeyID, Equals, "89ABCDEF8B48AD62")
}
// TestParseGPGOutputSpecialCharactersInUID tests user IDs with special characters
func (s *GPGSuite) TestParseGPGOutputSpecialCharactersInUID(c *C) {
// UID with Unicode characters and special formatting
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::J\xc3\xb6hn D\xc3\xb6\xc3\xa9 (D\xc3\xbcss) <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should preserve the encoded special characters
c.Check(keys[0].UserIDs, HasLen, 1)
}
// TestAPIGPGListKeysDefaultKeyring tests the HTTP endpoint with default keyring
func (s *GPGSuite) TestAPIGPGListKeysDefaultKeyring(c *C) {
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:u:4096:1:8B48AD6246925553:1611864000:::::
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`, "", ""), func(_ string) {
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
var result gpgKeyListResponse
err = json.NewDecoder(response.Body).Decode(&result)
c.Assert(err, IsNil)
c.Check(result.Keys, HasLen, 1)
c.Check(result.Keys[0].KeyID, Equals, "8B48AD6246925553")
})
}
// TestAPIGPGListKeysWithKeyringParam tests the HTTP endpoint with custom keyring parameter
func (s *GPGSuite) TestAPIGPGListKeysWithKeyringParam(c *C) {
argFile, err := os.CreateTemp("", "aptly-gpg-args")
c.Assert(err, IsNil)
_ = argFile.Close()
defer func() { _ = os.Remove(argFile.Name()) }()
script := "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
"cat <<'EOF'\n" +
"pub:u:4096:1:8B48AD6246925553:1611864000:::::\n" +
"fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:\n" +
"EOF\n" +
"exit 0\n" +
"fi\n" +
"exit 1\n"
s.withFakeGPG(c, script, func(_ string) {
response, reqErr := s.HTTPRequest("GET", "/api/gpg/keys?keyring=/custom.gpg", nil)
c.Assert(reqErr, IsNil)
c.Check(response.Code, Equals, 200)
argBytes, readErr := os.ReadFile(argFile.Name())
c.Assert(readErr, IsNil)
c.Check(string(argBytes), Matches, `(?s).*--keyring\ncustom\.gpg\n.*`)
})
}
// TestAPIGPGListKeysResponseFormat tests that the response has the correct structure
func (s *GPGSuite) TestAPIGPGListKeysResponseFormat(c *C) {
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:f:4096:1:A1B2C3D4E5F67890:1611864000:::::
uid:f::::1611864000::1234567890::Jane Smith <jane@example.com>::::::::::0:
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`, "", ""), func(_ string) {
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
var result gpgKeyListResponse
err = json.NewDecoder(response.Body).Decode(&result)
c.Assert(err, IsNil)
c.Assert(result.Keys, HasLen, 1)
c.Check(result.Keys[0].KeyID, Equals, "A1B2C3D4E5F67890")
c.Check(result.Keys[0].Validity, Equals, "f")
c.Check(result.Keys[0].CreatedAt, Equals, "1611864000")
})
}
// TestParseGPGOutputEdgeCaseUIDWithoutFields tests UID record with missing fields
func (s *GPGSuite) TestParseGPGOutputEdgeCaseUIDWithoutFields(c *C) {
// UID record with fewer than 10 fields
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should not have user ID since it's in field 9 and this record is too short
c.Check(keys[0].UserIDs, HasLen, 0)
}
// TestParseGPGOutputFingerprintWithoutCurrentKey tests FPR record appearing before any PUB
func (s *GPGSuite) TestParseGPGOutputFingerprintWithoutCurrentKey(c *C) {
// FPR record without a preceding PUB (should be ignored)
output := `fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should only have one key with the correct fingerprint
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
}
// TestParseGPGOutputComplexRealWorldExample tests real-world-like GPG output
func (s *GPGSuite) TestParseGPGOutputComplexRealWorldExample(c *C) {
// Real-world GPG output with multiple keys, UIDs, and other record types (sig, sub)
// Note: sub and sig records are skipped as we only care about pub/uid/fpr
realWorldOutput := `tru::1:1611864000:0:3:1:5
pub:u:4096:1:8B48AD6246925553:1611864000:2023-01-15T00:00:00:::::scESC:::::::23::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
uid:u::::1611864100::1234567891::John Doe <john@work.com>::::::::::0:
pub:f:2048:1:1234567890123456:1580592000:2022-12-31T00:00:00::u:::scESC:::::::23::0:
fpr:::::::::F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9:
uid:f::::1580592000::0987654321::Maintainer Key <maint@example.com>::::::::::0:`
keys := parseGPGOutput(realWorldOutput)
c.Check(keys, HasLen, 2)
// First key should have 2 UIDs
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
c.Check(keys[0].UserIDs, HasLen, 2)
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
// Second key should have 1 UID
c.Check(keys[1].KeyID, Equals, "1234567890123456")
c.Check(keys[1].UserIDs, HasLen, 1)
c.Check(keys[1].Fingerprint, Equals, "F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9")
}
// TestParseGPGOutputConsecutiveEmptyUIDs tests handling of consecutive empty user ID fields
func (s *GPGSuite) TestParseGPGOutputConsecutiveEmptyUIDs(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890:::::::::::0:
uid:u::::1611864000::1234567891::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should skip empty UID but include the non-empty one
c.Check(keys[0].UserIDs, HasLen, 1)
c.Check(keys[0].UserIDs[0], Equals, "John Doe <john@example.com>")
}
// TestGPGDeleteKeyParamsValidation tests gpgDeleteKeyParams validation
func (s *GPGSuite) TestGPGDeleteKeyParamsValidation(c *C) {
// This is a unit test that validates parameter structure (no HTTP needed)
params := gpgDeleteKeyParams{
Keyring: "custom.gpg",
GpgKeyID: "8B48AD6246925553",
}
c.Check(params.Keyring, Equals, "custom.gpg")
c.Check(params.GpgKeyID, Equals, "8B48AD6246925553")
}
// TestAPIGPGDeleteKeyMissingKeyID tests delete with missing key ID parameter
func (s *GPGSuite) TestAPIGPGDeleteKeyMissingKeyID(c *C) {
body, err := json.Marshal(map[string]string{
"Keyring": "trustedkeys.gpg",
// GpgKeyID is missing
})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
}
// TestAPIGPGDeleteKeyInvalidJSON tests delete with invalid JSON request
func (s *GPGSuite) TestAPIGPGDeleteKeyInvalidJSON(c *C) {
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader([]byte("invalid json")))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
}
// TestAPIGPGDeleteKeySuccess tests successful key deletion
func (s *GPGSuite) TestAPIGPGDeleteKeySuccess(c *C) {
argFile, err := os.CreateTemp("", "aptly-gpg-delete-args")
c.Assert(err, IsNil)
_ = argFile.Close()
defer func() { _ = os.Remove(argFile.Name()) }()
script := "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
"if printf '%s' \"$*\" | grep -q -- '--delete-keys'; then\n" +
"echo 'deleted'\n" +
"exit 0\n" +
"fi\n" +
"exit 1\n"
s.withFakeGPG(c, script, func(_ string) {
body, marshalErr := json.Marshal(gpgDeleteKeyParams{
Keyring: "/trustedkeys.gpg",
GpgKeyID: "8B48AD6246925553",
})
c.Assert(marshalErr, IsNil)
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
c.Assert(reqErr, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, `"deleted\\n"`)
argBytes, readErr := os.ReadFile(argFile.Name())
c.Assert(readErr, IsNil)
argText := string(argBytes)
c.Check(argText, Matches, `(?s).*--batch\n--yes\n.*`)
c.Check(argText, Matches, `(?s).*--keyring\n/trustedkeys\.gpg\n.*`)
c.Check(argText, Matches, `(?s).*--delete-keys\n8B48AD6246925553\n.*`)
})
}
// TestAPIGPGListKeysCommandFailure tests list error propagation from gpg
func (s *GPGSuite) TestAPIGPGListKeysCommandFailure(c *C) {
script := "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
"echo 'keyring missing'\n" +
"exit 1\n" +
"fi\n" +
"exit 1\n"
s.withFakeGPG(c, script, func(_ string) {
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Matches, `(?s).*failed to list keys: keyring missing.*`)
})
}
// TestAPIGPGDeleteKeyCommandFailure tests delete error propagation from gpg
func (s *GPGSuite) TestAPIGPGDeleteKeyCommandFailure(c *C) {
s.withFakeGPG(c, s.fakeGPGScript(c, "", "", "delete failed"), func(_ string) {
body, err := json.Marshal(gpgDeleteKeyParams{
Keyring: "trustedkeys.gpg",
GpgKeyID: "8B48AD6246925553",
})
c.Assert(err, IsNil)
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
c.Assert(reqErr, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Matches, `(?s).*failed to delete key: delete failed.*`)
})
}
+51 -193
View File
@@ -31,61 +31,22 @@ func getVerifier(keyRings []string) (pgp.Verifier, error) {
return verifier, nil
}
// stringSlicesEqual compares two string slices for equality (order matters)
func stringSlicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// uniqueStrings returns a new slice with only unique strings from the input, sorted
func uniqueStrings(input []string) []string {
if len(input) == 0 {
return input
}
seen := make(map[string]struct{}, len(input))
result := make([]string, 0, len(input))
for _, s := range input {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
}
}
sort.Strings(result)
return result
}
// @Summary List Mirrors
// @Description **Show list of currently available mirrors**
// @Description Each mirror is returned as in “show” API.
// @Tags Mirrors
// @Produce json
// @Success 200 {array} remoteRepoResponse
// @Success 200 {array} deb.RemoteRepo
// @Router /api/mirrors [get]
func apiMirrorsList(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
result := []remoteRepoResponse{}
err := collection.ForEach(func(repo *deb.RemoteRepo) error {
err := collection.LoadComplete(repo)
if err != nil {
return err
}
result = append(result, newRemoteRepoResponse(repo))
result := []*deb.RemoteRepo{}
_ = collection.ForEach(func(repo *deb.RemoteRepo) error {
result = append(result, repo)
return nil
})
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
return
}
c.JSON(200, result)
}
@@ -111,8 +72,6 @@ type mirrorCreateParams struct {
DownloadUdebs bool ` json:"DownloadUdebs"`
// Set "true" to mirror installer files
DownloadInstaller bool ` json:"DownloadInstaller"`
// Set "true" to mirror AppStream (DEP-11) metadata
DownloadAppStream bool ` json:"DownloadAppStream"`
// Set "true" to include dependencies of matching packages when filtering
FilterWithDeps bool ` json:"FilterWithDeps"`
// Set "true" to skip if the given components are in the Release file
@@ -164,7 +123,7 @@ func apiMirrorsCreate(c *gin.Context) {
}
repo, err := deb.NewRemoteRepo(b.Name, b.ArchiveURL, b.Distribution, b.Components, b.Architectures,
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller, b.DownloadAppStream)
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
@@ -216,9 +175,9 @@ func apiMirrorsDrop(c *gin.Context) {
name := c.Params.ByName("name")
force := c.Request.URL.Query().Get("force") == "1"
// Phase 1: Pre-task validation (shallow load for 404 check only)
collectionFactory := context.NewCollectionFactory()
mirrorCollection := collectionFactory.RemoteRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
repo, err := mirrorCollection.ByName(name)
if err != nil {
@@ -228,21 +187,34 @@ func apiMirrorsDrop(c *gin.Context) {
resources := []string{string(repo.Key())}
taskName := fmt.Sprintf("Delete mirror %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err := repo.CheckLock()
// Phase 2: Inside task lock - create fresh collections
taskCollectionFactory := context.NewCollectionFactory()
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
// Fresh load after lock acquired
repo, err := taskMirrorCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
}
err = repo.CheckLock()
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
}
if !force {
snapshots := snapshotCollection.ByRemoteRepoSource(repo)
// Fresh checks with current collections
snapshots := taskSnapshotCollection.ByRemoteRepoSource(repo)
if len(snapshots) > 0 {
return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
}
}
err = mirrorCollection.Drop(repo)
err = taskMirrorCollection.Drop(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
}
@@ -273,7 +245,6 @@ func apiMirrorsShow(c *gin.Context) {
err = collection.LoadComplete(repo)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
return
}
c.JSON(200, repo)
@@ -372,128 +343,6 @@ func apiMirrorsPackages(c *gin.Context) {
}
}
type mirrorEditParams struct {
// Package query that is applied to mirror packages
Filter *string ` json:"Filter" example:"xserver-xorg"`
// Set "true" to include dependencies of matching packages when filtering
FilterWithDeps *bool ` json:"FilterWithDeps"`
// Set "true" to mirror installer files
DownloadInstaller *bool `json:"DownloadInstaller"`
// Set "true" to mirror source packages
DownloadSources *bool ` json:"DownloadSources"`
// Set "true" to mirror udeb files
DownloadUdebs *bool ` json:"DownloadUdebs"`
// URL of the archive to mirror
ArchiveURL *string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
// Comma separated list of architectures
Architectures *[]string `json:"Architectures" example:"amd64"`
// Gpg keyring(s) for verifying Release file if a mirror update is required.
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
// Set "true" to skip the verification of Release file signatures
IgnoreSignatures *bool ` json:"IgnoreSignatures"`
}
// @Summary Edit Mirror
// @Description **Edit mirror config**
// @Tags Mirrors
// @Param name path string true "mirror name to edit"
// @Consume json
// @Param request body mirrorEditParams true "Parameters"
// @Produce json
// @Success 200 {object} deb.RemoteRepo "Mirror was edited successfully"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Mirror not found"
// @Failure 409 {object} Error "Aptly db locked"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/mirrors/{name} [post]
func apiMirrorsEdit(c *gin.Context) {
var (
err error
b mirrorEditParams
repo *deb.RemoteRepo
)
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
name := c.Params.ByName("name")
repo, err = collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to edit: %s", err))
return
}
err = repo.CheckLock()
if err != nil {
AbortWithJSONError(c, 409, fmt.Errorf("unable to edit: %s", err))
return
}
if c.Bind(&b) != nil {
return
}
fetchMirror := false
ignoreSignatures := context.Config().GpgDisableVerify
if b.Filter != nil {
repo.Filter = *b.Filter
}
if b.FilterWithDeps != nil {
repo.FilterWithDeps = *b.FilterWithDeps
}
if b.DownloadInstaller != nil {
repo.DownloadInstaller = *b.DownloadInstaller
}
if b.DownloadSources != nil {
repo.DownloadSources = *b.DownloadSources
}
if b.DownloadUdebs != nil {
repo.DownloadUdebs = *b.DownloadUdebs
}
if b.ArchiveURL != nil && *b.ArchiveURL != repo.ArchiveRoot {
repo.SetArchiveRoot(*b.ArchiveURL)
fetchMirror = true
}
if b.Architectures != nil {
uniqueArchitectures := uniqueStrings(*b.Architectures)
if !stringSlicesEqual(uniqueArchitectures, uniqueStrings(repo.Architectures)) {
repo.Architectures = uniqueArchitectures
fetchMirror = true
}
}
if b.IgnoreSignatures != nil {
ignoreSignatures = *b.IgnoreSignatures
}
if repo.IsFlat() && repo.DownloadUdebs {
AbortWithJSONError(c, 400, fmt.Errorf("unable to edit: flat mirrors don't support udebs"))
return
}
if fetchMirror {
verifier, err := getVerifier(b.Keyrings)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG verifier: %s", err))
return
}
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
return
}
}
err = collection.Update(repo)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
return
}
c.JSON(200, repo)
}
type mirrorUpdateParams struct {
// Change mirror name to `Name`
Name string ` json:"Name" example:"mirror1"`
@@ -507,8 +356,6 @@ type mirrorUpdateParams struct {
ForceUpdate bool ` json:"ForceUpdate"`
// Set "true" to skip downloading already downloaded packages
SkipExistingPackages bool ` json:"SkipExistingPackages"`
// Set "true" to download only the latest version per package/architecture
LatestOnly bool ` json:"LatestOnly"`
}
// @Summary Update Mirror
@@ -535,7 +382,8 @@ func apiMirrorsUpdate(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
remote, err = collection.ByName(c.Params.ByName("name"))
name := c.Params.ByName("name")
remote, err = collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
@@ -550,6 +398,7 @@ func apiMirrorsUpdate(c *gin.Context) {
return
}
// Pre-task validation of new name if provided
if b.Name != remote.Name {
_, err = collection.ByName(b.Name)
if err == nil {
@@ -566,9 +415,26 @@ func apiMirrorsUpdate(c *gin.Context) {
resources := []string{string(remote.Key())}
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
// Phase 2: Inside task lock - create fresh factory
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.RemoteRepoCollection()
// Fresh load after lock acquired (use captured `name` variable, not gin context)
remote, err := taskCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
// Fresh rename check inside lock (if renaming)
if b.Name != remote.Name {
_, err := taskCollection.ByName(b.Name)
if err == nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)
}
}
downloader := context.NewDownloader(out)
err := remote.Fetch(downloader, verifier, b.IgnoreSignatures)
err = remote.Fetch(downloader, verifier, b.IgnoreSignatures)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
@@ -580,19 +446,11 @@ func apiMirrorsUpdate(c *gin.Context) {
}
}
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
err = remote.DownloadPackageIndexes(out, downloader, verifier, taskCollectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
if remote.DownloadAppStream && !remote.IsFlat() {
err = remote.DownloadAppStreamFiles(out, downloader,
context.PackagePool(), collectionFactory.ChecksumCollection(nil), b.IgnoreChecksums)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
}
if remote.Filter != "" {
var filterQuery deb.PackageQuery
@@ -607,8 +465,8 @@ func apiMirrorsUpdate(c *gin.Context) {
}
}
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages, b.LatestOnly)
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), taskCollectionFactory.PackageCollection(),
taskCollectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
@@ -618,12 +476,12 @@ func apiMirrorsUpdate(c *gin.Context) {
e := context.ReOpenDatabase()
if e == nil {
remote.MarkAsIdle()
_ = collection.Update(remote)
_ = taskCollection.Update(remote)
}
}()
remote.MarkAsUpdating()
err = collection.Update(remote)
err = taskCollection.Update(remote)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
@@ -727,7 +585,7 @@ func apiMirrorsUpdate(c *gin.Context) {
}
// and import it back to the pool
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, collectionFactory.ChecksumCollection(nil))
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, taskCollectionFactory.ChecksumCollection(nil))
if err != nil {
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
pushError(err)
@@ -780,8 +638,8 @@ func apiMirrorsUpdate(c *gin.Context) {
}
log.Info().Msgf("%s: Finalizing download...", b.Name)
_ = remote.FinalizeDownload(collectionFactory, out)
err = collectionFactory.RemoteRepoCollection().Update(remote)
_ = remote.FinalizeDownload(taskCollectionFactory, out)
err = taskCollection.Update(remote)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
+1 -66
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
. "gopkg.in/check.v1"
)
@@ -18,10 +17,7 @@ var _ = Suite(&MirrorSuite{})
func (s *MirrorSuite) TestGetMirrors(c *C) {
response, _ := s.HTTPRequest("GET", "/api/mirrors", nil)
c.Check(response.Code, Equals, 200)
var mirrors []map[string]interface{}
err := json.Unmarshal(response.Body.Bytes(), &mirrors)
c.Assert(err, IsNil)
c.Check(response.Body.String(), Equals, "[]")
}
func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
@@ -30,21 +26,6 @@ func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
c.Check(response.Body.String(), Equals, "{\"error\":\"unable to drop: mirror with name does-not-exist not found\"}")
}
func (s *MirrorSuite) TestCreateMirrorFlatWithAppStream(c *C) {
body, err := json.Marshal(gin.H{
"Name": "test-flat-appstream",
"ArchiveURL": "http://example.com/repo/",
"Distribution": "./",
"DownloadAppStream": true,
})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("POST", "/api/mirrors", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Matches, ".*AppStream.*flat.*")
}
func (s *MirrorSuite) TestCreateMirror(c *C) {
c.ExpectFailure("Need to mock downloads")
body, err := json.Marshal(gin.H{
@@ -57,49 +38,3 @@ func (s *MirrorSuite) TestCreateMirror(c *C) {
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Equals, "")
}
func (s *MirrorSuite) TestGetMirrorsIncludesNumPackages(c *C) {
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
repo, err := deb.NewRemoteRepo("count-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
c.Assert(err, IsNil)
err = collection.Add(repo)
c.Assert(err, IsNil)
putRawDBValue(c, &s.APISuite, repo.RefKey(), makePackageRefList(c).Encode())
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 200)
var mirrors []map[string]interface{}
err = json.Unmarshal(response.Body.Bytes(), &mirrors)
c.Assert(err, IsNil)
found := false
for _, mirror := range mirrors {
if mirror["Name"] == "count-mirror" {
found = true
value, ok := mirror["NumPackages"]
c.Assert(ok, Equals, true)
c.Assert(value, Equals, float64(2))
break
}
}
c.Assert(found, Equals, true)
}
func (s *MirrorSuite) TestGetMirrorsReturns500OnCorruptRefList(c *C) {
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
repo, err := deb.NewRemoteRepo("broken-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
c.Assert(err, IsNil)
c.Assert(collection.Add(repo), IsNil)
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 500)
c.Assert(response.Body.String(), Matches, ".*unable to show:.*")
}
-30
View File
@@ -1,30 +0,0 @@
package api
import "github.com/aptly-dev/aptly/deb"
type remoteRepoResponse struct {
*deb.RemoteRepo
NumPackages int `json:"NumPackages"`
}
type localRepoResponse struct {
*deb.LocalRepo
NumPackages int `json:"NumPackages"`
}
type snapshotResponse struct {
*deb.Snapshot
NumPackages int `json:"NumPackages"`
}
func newRemoteRepoResponse(repo *deb.RemoteRepo) remoteRepoResponse {
return remoteRepoResponse{RemoteRepo: repo, NumPackages: repo.NumPackages()}
}
func newLocalRepoResponse(repo *deb.LocalRepo) localRepoResponse {
return localRepoResponse{LocalRepo: repo, NumPackages: repo.NumPackages()}
}
func newSnapshotResponse(snapshot *deb.Snapshot) snapshotResponse {
return snapshotResponse{Snapshot: snapshot, NumPackages: snapshot.NumPackages()}
}
-19
View File
@@ -1,19 +0,0 @@
package api
import (
"github.com/aptly-dev/aptly/deb"
. "gopkg.in/check.v1"
)
func makePackageRefList(c *C) *deb.PackageRefList {
list := deb.NewPackageList()
c.Assert(list.Add(&deb.Package{Name: "libcount", Version: "1.0", Architecture: "amd64"}), IsNil)
c.Assert(list.Add(&deb.Package{Name: "appcount", Version: "2.0", Architecture: "all"}), IsNil)
return deb.NewPackageRefListFromPackageList(list)
}
func putRawDBValue(c *C, s *APISuite, key []byte, value []byte) {
db, err := s.context.Database()
c.Assert(err, IsNil)
c.Assert(db.Put(key, value), IsNil)
}
+321 -236
View File
@@ -160,10 +160,6 @@ type publishedRepoCreateParams struct {
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
@@ -182,12 +178,8 @@ type publishedRepoCreateParams struct {
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
// Provide index files by hash
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file.
SignedBy *string ` json:"SignedBy" example:""`
// Enable multiple packages with the same filename in different distributions
MultiDist *bool ` json:"MultiDist" example:"false"`
// Version of the release
Version string ` json:"Version" example:""`
}
// @Summary Create Published Repository
@@ -200,7 +192,7 @@ type publishedRepoCreateParams struct {
// @Description **Example:**
// @Description ```
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Label":"","Origin":"","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
// @Description ```
// @Description
// @Description See also: `aptly publish create`
@@ -267,7 +259,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
return
}
resources = append(resources, string(snapshot.ResourceKey()))
resources = append(resources, string(snapshot.Key()))
sources = append(sources, snapshot)
}
} else if b.SourceKind == deb.SourceLocalRepo {
@@ -298,11 +290,24 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
multiDist = *b.MultiDist
}
collection := collectionFactory.PublishedRepoCollection()
// Non-MultiDist publishes share a single pool/ directory under the
// prefix. Lock at the prefix level so that concurrent publish/drop
// operations on sibling distributions cannot race during cleanup.
if !multiDist {
storagePrefix := prefix
if storage != "" {
storagePrefix = storage + ":" + prefix
}
resources = append(resources, deb.PrefixPoolLockKey(storagePrefix))
}
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) {
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
taskDetail := task.PublishDetail{
Detail: detail,
}
@@ -314,10 +319,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
for _, source := range sources {
switch s := source.(type) {
case *deb.Snapshot:
snapshotCollection := collectionFactory.SnapshotCollection()
snapshotCollection := taskCollectionFactory.SnapshotCollection()
err = snapshotCollection.LoadComplete(s)
case *deb.LocalRepo:
localCollection := collectionFactory.LocalRepoCollection()
localCollection := taskCollectionFactory.LocalRepoCollection()
err = localCollection.LoadComplete(s)
default:
err = fmt.Errorf("unexpected type for source: %T", source)
@@ -327,23 +332,17 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
}
}
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, multiDist)
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, taskCollectionFactory, 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
}
if b.NotAutomatic != "" {
published.NotAutomatic = b.NotAutomatic
}
if b.ButAutomaticUpgrades != "" {
published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
}
published.Label = b.Label
published.SkipContents = context.Config().SkipContentsPublishing
if b.SkipContents != nil {
@@ -359,26 +358,18 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
published.AcquireByHash = *b.AcquireByHash
}
if b.SignedBy != nil {
published.SignedBy = *b.SignedBy
}
if b.Version != "" {
published.Version = b.Version
}
duplicate := collection.CheckDuplicate(published)
duplicate := taskCollection.CheckDuplicate(published)
if duplicate != nil {
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
_ = taskCollectionFactory.PublishedRepoCollection().LoadComplete(duplicate, taskCollectionFactory)
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, context.SkelPath())
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
}
err = collection.Add(published)
err = taskCollection.Add(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -402,16 +393,8 @@ type publishedRepoUpdateSwitchParams struct {
Snapshots []sourceParams ` json:"Snapshots"`
// Provide index files by hash
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file
SignedBy *string ` json:"SignedBy" example:""`
// Enable multiple packages with the same filename in different distributions
MultiDist *bool ` json:"MultiDist" example:"false"`
// Value of Label: field in published repository stanza
Label *string ` json:"Label" example:"Debian"`
// Value of Origin: field in published repository stanza
Origin *string ` json:"Origin" example:"Debian"`
// Version of the release: Optional
Version *string ` json:"Version" example:"13.3"`
}
// @Summary Update Published Repository
@@ -458,6 +441,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
localRepoCollection := collectionFactory.LocalRepoCollection()
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
@@ -465,64 +449,76 @@ func apiPublishUpdateSwitch(c *gin.Context) {
return
}
resources := []string{string(published.Key())}
if published.SourceKind == deb.SourceLocalRepo {
if len(b.Snapshots) > 0 {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
return
}
} else if published.SourceKind == deb.SourceSnapshot {
for _, snapshotInfo := range b.Snapshots {
_, err2 := snapshotCollection.ByName(snapshotInfo.Name)
for _, uuid := range published.Sources {
repo, err2 := localRepoCollection.ByUUID(uuid)
if err2 != nil {
AbortWithJSONError(c, http.StatusNotFound, err2)
return
}
resources = append(resources, string(repo.Key()))
}
} else if published.SourceKind == deb.SourceSnapshot {
for _, snapshotInfo := range b.Snapshots {
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
if err2 != nil {
AbortWithJSONError(c, http.StatusNotFound, err2)
return
}
resources = append(resources, string(snapshot.Key()))
}
} else {
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
return
}
if b.SkipContents != nil {
published.SkipContents = *b.SkipContents
// Non-MultiDist distributions share a single pool/ directory under the
// prefix. Acquire the prefix-level pool lock so that concurrent updates
// on sibling distributions are serialised and cannot race during cleanup.
if !published.MultiDist {
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
}
if b.SkipBz2 != nil {
published.SkipBz2 = *b.SkipBz2
}
if b.AcquireByHash != nil {
published.AcquireByHash = *b.AcquireByHash
}
if b.SignedBy != nil {
published.SignedBy = *b.SignedBy
}
if b.MultiDist != nil {
published.MultiDist = *b.MultiDist
}
if b.Label != nil {
published.Label = *b.Label
}
if b.Origin != nil {
published.Origin = *b.Origin
}
if b.Version != nil {
published.Version = *b.Version
}
resources := []string{string(published.Key())}
// Field mutations and fresh DB load are deferred to inside the task so
// they always operate on a consistent state after the lock is held.
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.LoadComplete(published, collectionFactory)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
// Capture MultiDist before mutations to detect a false→true transition.
prevMultiDist := published.MultiDist
// Apply field mutations on the freshly loaded object.
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
}
revision := published.ObtainRevision()
sources := revision.Sources
@@ -534,17 +530,17 @@ func apiPublishUpdateSwitch(c *gin.Context) {
}
}
result, err := published.Update(collectionFactory, out)
result, err := published.Update(taskCollectionFactory, 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, context.SkelPath())
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
err = collection.Update(published)
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -552,10 +548,19 @@ func apiPublishUpdateSwitch(c *gin.Context) {
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)
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
// When MultiDist is toggled, the old pool layout still has files that
// CleanupPrefixComponentFiles won't touch (it only scans the new layout).
// Run a second pass over the previous layout to remove stale files.
if prevMultiDist != published.MultiDist {
err = taskCollection.CleanupAfterMultiDistToggle(context, published, prevMultiDist, cleanComponents, taskCollectionFactory, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to clean legacy pool: %s", err)
}
}
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
@@ -600,10 +605,19 @@ func apiPublishDrop(c *gin.Context) {
}
resources := []string{string(published.Key())}
// Non-MultiDist distributions share a single pool/ directory under the
// prefix. Acquire the prefix-level pool lock so that a drop cannot race
// with a concurrent update or drop of a sibling distribution during cleanup.
if !published.MultiDist {
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
}
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)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
err := taskCollection.Remove(context, storage, prefix, distribution,
taskCollectionFactory, out, force, skipCleanup)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
}
@@ -639,43 +653,52 @@ func apiPublishAddSource(c *gin.Context) {
storage, prefix := deb.ParsePrefix(param)
distribution := slashEscape(c.Params.ByName("distribution"))
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
// Load shallowly (no LoadComplete) to verify existence and obtain the
// resource key and task name. The actual mutation is performed inside
// the task on a freshly loaded copy to prevent lost-update races.
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(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.Update(published)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to create: %s", err)
}
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
component := b.Component
name := b.Name
_, exists := sources[component]
if exists {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("unable to create: Component '%s' already exists", component)
}
sources[component] = name
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -757,39 +780,48 @@ func apiPublishSetSources(c *gin.Context) {
storage, prefix := deb.ParsePrefix(param)
distribution := slashEscape(c.Params.ByName("distribution"))
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
// Load shallowly for 404 check, resource key, and task name.
// Full load and mutation happen inside the task.
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(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.Update(published)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
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
}
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -822,24 +854,33 @@ func apiPublishDropChanges(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
// Load shallowly for 404 check, resource key, and task name.
// Full load and DropRevision happen inside the task.
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(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.Update(published)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: %s", err)
}
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to delete: %s", err)
}
published.DropRevision()
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -875,51 +916,58 @@ func apiPublishUpdateSource(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"))
urlComponent := slashEscape(c.Params.ByName("component"))
// Default component to the URL path segment; the body may rename it.
b.Component = urlComponent
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
// Load shallowly for 404 check, resource key, and task name.
// Full load and mutation happen inside the task.
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(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.Update(published)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
_, exists := sources[urlComponent]
if !exists {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: Component '%s' does not exist", urlComponent)
}
if b.Component != urlComponent {
delete(sources, urlComponent)
}
newComponent := b.Component
name := b.Name
sources[newComponent] = name
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -956,33 +1004,41 @@ func apiPublishRemoveSource(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
// Load shallowly for 404 check, resource key, and task name.
// Full load and mutation happen inside the task.
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(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.Update(published)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: %s", err)
}
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to delete: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
_, exists := sources[component]
if !exists {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: Component '%s' does not exist", component)
}
delete(sources, component)
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -1004,16 +1060,8 @@ type publishedRepoUpdateParams struct {
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
// Provide index files by hash
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file
SignedBy *string ` json:"SignedBy" example:""`
// Enable multiple packages with the same filename in different distributions
MultiDist *bool ` json:"MultiDist" example:"false"`
// Value of Label: field in published repository stanza
Label *string ` json:"Label" example:"Debian"`
// Value of Origin: field in published repository stanza
Origin *string ` json:"Origin" example:"Debian"`
// Version of the release: Optional
Version *string ` json:"Version" example:"13.3"`
}
// @Summary Update Published Repository
@@ -1054,64 +1102,92 @@ func apiPublishUpdate(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PublishedRepoCollection()
// Load shallowly for 404 check, resource key, and task name.
// Full load and field mutations happen inside the task.
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.SignedBy != nil {
published.SignedBy = *b.SignedBy
}
if b.MultiDist != nil {
published.MultiDist = *b.MultiDist
}
if b.Label != nil {
published.Label = *b.Label
}
if b.Origin != nil {
published.Origin = *b.Origin
}
if b.Version != nil {
published.Version = *b.Version
}
resources := []string{string(published.Key())}
// Non-MultiDist distributions share a single pool/ directory under the
// prefix. Acquire the prefix-level pool lock so that concurrent updates
// on sibling distributions are serialised and cannot race during cleanup.
if !published.MultiDist {
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
}
// Lock source repos / snapshots the same way apiPublishUpdateSwitch does,
// because published.Update() reads from them and concurrent modification
// would produce an inconsistent view.
snapshotCollection := collectionFactory.SnapshotCollection()
localRepoCollection := collectionFactory.LocalRepoCollection()
if published.SourceKind == deb.SourceLocalRepo {
for _, uuid := range published.Sources {
repo, err2 := localRepoCollection.ByUUID(uuid)
if err2 != nil {
AbortWithJSONError(c, http.StatusNotFound, err2)
return
}
resources = append(resources, string(repo.Key()))
}
} else if published.SourceKind == deb.SourceSnapshot {
for _, uuid := range published.Sources {
snapshot, err2 := snapshotCollection.ByUUID(uuid)
if err2 != nil {
AbortWithJSONError(c, http.StatusNotFound, err2)
return
}
resources = append(resources, string(snapshot.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)
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.PublishedRepoCollection()
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
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, context.SkelPath())
err = taskCollection.LoadComplete(published, taskCollectionFactory)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
err = collection.Update(published)
// Capture MultiDist before mutations to detect a false→true transition.
prevMultiDist := published.MultiDist
// Apply field mutations on the freshly loaded object.
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
}
result, err := published.Update(taskCollectionFactory, 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, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
err = taskCollection.Update(published)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
}
@@ -1119,10 +1195,19 @@ func apiPublishUpdate(c *gin.Context) {
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)
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
// When MultiDist is toggled, the old pool layout still has files that
// CleanupPrefixComponentFiles won't touch (it only scans the new layout).
// Run a second pass over the previous layout to remove stale files.
if prevMultiDist != published.MultiDist {
err = taskCollection.CleanupAfterMultiDistToggle(context, published, prevMultiDist, cleanComponents, taskCollectionFactory, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to clean legacy pool: %s", err)
}
}
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
+733
View File
@@ -0,0 +1,733 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"sync"
"time"
"github.com/aptly-dev/aptly/aptly"
ctx "github.com/aptly-dev/aptly/context"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
"github.com/smira/flag"
. "gopkg.in/check.v1"
)
// PublishedFileMissingSuite reproduces the exact bug where:
// - Package import succeeds
// - Metadata is updated (Packages.gz shows the package)
// - Publish reports success
// - BUT the .deb file is missing from the published pool directory
// - Result: apt-get returns 404 when trying to download the package
type PublishedFileMissingSuite struct {
context *ctx.AptlyContext
flags *flag.FlagSet
configFile *os.File
router http.Handler
tempDir string
poolPath string
publicPath string
}
var _ = Suite(&PublishedFileMissingSuite{})
func (s *PublishedFileMissingSuite) SetUpSuite(c *C) {
aptly.Version = "publishedFileMissingTest"
tempDir, err := os.MkdirTemp("", "aptly-published-missing-test")
c.Assert(err, IsNil)
s.tempDir = tempDir
s.poolPath = filepath.Join(tempDir, "pool")
s.publicPath = filepath.Join(tempDir, "public")
file, err := os.CreateTemp("", "aptly-published-missing-config")
c.Assert(err, IsNil)
s.configFile = file
config := gin.H{
"rootDir": tempDir,
"downloadDir": filepath.Join(tempDir, "download"),
"architectures": []string{"amd64"},
"dependencyFollowSuggests": false,
"dependencyFollowRecommends": false,
"gpgDisableSign": true,
"gpgDisableVerify": true,
"gpgProvider": "internal",
"skipLegacyPool": true,
"enableMetricsEndpoint": false,
}
jsonString, err := json.Marshal(config)
c.Assert(err, IsNil)
_, err = file.Write(jsonString)
c.Assert(err, IsNil)
flags := flag.NewFlagSet("publishedFileMissingTestFlags", flag.ContinueOnError)
flags.Bool("no-lock", true, "disable database locking for test")
flags.Int("db-open-attempts", 3, "dummy")
flags.String("config", s.configFile.Name(), "config file")
flags.String("architectures", "", "dummy")
s.flags = flags
context, err := ctx.NewContext(s.flags)
c.Assert(err, IsNil)
s.context = context
s.router = Router(context)
}
func (s *PublishedFileMissingSuite) TearDownSuite(c *C) {
if s.configFile != nil {
_ = os.Remove(s.configFile.Name())
}
if s.context != nil {
s.context.Shutdown()
}
if s.tempDir != "" {
_ = os.RemoveAll(s.tempDir)
}
}
func (s *PublishedFileMissingSuite) SetUpTest(c *C) {
collectionFactory := s.context.NewCollectionFactory()
localRepoCollection := collectionFactory.LocalRepoCollection()
_ = localRepoCollection.ForEach(func(repo *deb.LocalRepo) error {
_ = localRepoCollection.Drop(repo)
return nil
})
publishedCollection := collectionFactory.PublishedRepoCollection()
_ = publishedCollection.ForEach(func(published *deb.PublishedRepo) error {
_ = publishedCollection.Remove(s.context, published.Storage, published.Prefix,
published.Distribution, collectionFactory, nil, true, true)
return nil
})
}
func (s *PublishedFileMissingSuite) TearDownTest(c *C) {
s.SetUpTest(c)
}
func (s *PublishedFileMissingSuite) httpRequest(c *C, method string, url string, body []byte) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
var req *http.Request
var err error
if body != nil {
req, err = http.NewRequest(method, url, bytes.NewReader(body))
} else {
req, err = http.NewRequest(method, url, nil)
}
c.Assert(err, IsNil)
req.Header.Add("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
return w
}
func (s *PublishedFileMissingSuite) createDebPackage(c *C, uploadID, packageName, version string) {
uploadPath := s.context.UploadPath()
uploadDir := filepath.Join(uploadPath, uploadID)
err := os.MkdirAll(uploadDir, 0755)
c.Assert(err, IsNil)
tempDir, err := os.MkdirTemp("", "deb-build")
c.Assert(err, IsNil)
defer func() { _ = os.RemoveAll(tempDir) }()
debianDir := filepath.Join(tempDir, "DEBIAN")
err = os.MkdirAll(debianDir, 0755)
c.Assert(err, IsNil)
controlContent := fmt.Sprintf(`Package: %s
Version: %s
Section: libs
Priority: optional
Architecture: amd64
Maintainer: Test <test@example.com>
Description: Test package
Test package for published file missing bug.
`, packageName, version)
err = os.WriteFile(filepath.Join(debianDir, "control"), []byte(controlContent), 0644)
c.Assert(err, IsNil)
usrDir := filepath.Join(tempDir, "usr", "lib")
err = os.MkdirAll(usrDir, 0755)
c.Assert(err, IsNil)
err = os.WriteFile(filepath.Join(usrDir, "lib.so"), []byte("library"), 0644)
c.Assert(err, IsNil)
debFile := filepath.Join(uploadDir, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
cmd := exec.Command("dpkg-deb", "--build", tempDir, debFile)
err = cmd.Run()
c.Assert(err, IsNil)
}
// TestPublishedFileGoMissing reproduces the exact production bug
func (s *PublishedFileMissingSuite) TestPublishedFileGoMissing(c *C) {
c.Log("=== Reproducing: Package in metadata but 404 on download ===")
// Create and publish a repository
repoName := "test-repo"
distribution := "bullseye"
createBody, _ := json.Marshal(gin.H{
"Name": repoName,
"DefaultDistribution": distribution,
"DefaultComponent": "main",
})
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create repo: %s", resp.Body.String()))
publishBody, _ := json.Marshal(gin.H{
"SourceKind": "local",
"Distribution": distribution,
"Architectures": []string{"amd64"},
"Sources": []gin.H{
{"Component": "main", "Name": repoName},
},
"Signing": gin.H{"Skip": true},
})
resp = s.httpRequest(c, "POST", "/api/publish/hrt", publishBody)
c.Assert(resp.Code, Equals, 201, Commentf("Failed to publish: %s", resp.Body.String()))
// Create package
packageName := "hrt-libblobbyclient1"
version := "20250926.152427+hrtdeb11"
uploadID := "test-upload-1"
s.createDebPackage(c, uploadID, packageName, version)
// Add package
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoName, uploadID), nil)
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package: %s", resp.Body.String()))
// Update publish
updateBody, _ := json.Marshal(gin.H{
"Signing": gin.H{"Skip": true},
"ForceOverwrite": true,
})
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/hrt/%s", distribution), updateBody)
c.Assert(resp.Code, Equals, 200, Commentf("Failed to update publish: %s", resp.Body.String()))
// Now check if the file is actually accessible in the published location
publishedStorage := s.context.GetPublishedStorage("")
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
// Expected file path: hrt/pool/main/h/hrt-libblobbyclient1/hrt-libblobbyclient1_20250926.152427+hrtdeb11_amd64.deb
expectedPath := filepath.Join(publicPath, "hrt", "pool", "main", "h", packageName,
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
c.Logf("Checking for published file at: %s", expectedPath)
fileInfo, err := os.Stat(expectedPath)
fileExists := err == nil
c.Logf("File exists: %v", fileExists)
if fileExists {
c.Logf("File size: %d bytes", fileInfo.Size())
}
// Check metadata
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repoName), nil)
var packages []string
err = json.Unmarshal(resp.Body.Bytes(), &packages)
c.Assert(err, IsNil)
c.Logf("Packages in metadata: %d", len(packages))
// THE BUG: Metadata says package exists, but file is missing from published location
if len(packages) > 0 && !fileExists {
c.Logf("★★★ BUG REPRODUCED! ★★★")
c.Logf("Metadata shows %d package(s) but file is missing at: %s", len(packages), expectedPath)
c.Logf("This is exactly what causes: 404 Not Found [IP: 10.20.72.62 3142]")
c.Fatal("BUG CONFIRMED: Package in metadata but missing from published directory!")
}
c.Assert(fileExists, Equals, true, Commentf(
"Published file should exist at %s when package is in metadata", expectedPath))
}
// TestConcurrentPublishRace tries to trigger the race with concurrent publishes
func (s *PublishedFileMissingSuite) TestConcurrentPublishRace(c *C) {
c.Log("=== Testing concurrent publish race condition ===")
const numIterations = 4
for iteration := 0; iteration < numIterations; iteration++ {
c.Logf("--- Iteration %d/%d ---", iteration+1, numIterations)
// Create repo
repoName := fmt.Sprintf("race-repo-%d", iteration)
distribution := fmt.Sprintf("dist-%d", iteration)
createBody, _ := json.Marshal(gin.H{
"Name": repoName,
"DefaultDistribution": distribution,
"DefaultComponent": "main",
})
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
c.Assert(resp.Code, Equals, 201)
publishBody, _ := json.Marshal(gin.H{
"SourceKind": "local",
"Distribution": distribution,
"Architectures": []string{"amd64"},
"Sources": []gin.H{
{"Component": "main", "Name": repoName},
},
"Signing": gin.H{"Skip": true},
})
resp = s.httpRequest(c, "POST", "/api/publish/concurrent", publishBody)
c.Assert(resp.Code, Equals, 201)
// Create multiple packages
var wg sync.WaitGroup
numPackages := 5
for i := 0; i < numPackages; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
packageName := fmt.Sprintf("pkg-%d-%d", iteration, idx)
version := "1.0.0"
uploadID := fmt.Sprintf("upload-%d-%d", iteration, idx)
s.createDebPackage(c, uploadID, packageName, version)
// Add package
resp := s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoName, uploadID), nil)
c.Logf("Package %d add: %d", idx, resp.Code)
// Small delay
time.Sleep(time.Duration(5+idx*2) * time.Millisecond)
// Publish
updateBody, _ := json.Marshal(gin.H{
"Signing": gin.H{"Skip": true},
"ForceOverwrite": true,
})
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/concurrent/%s", distribution), updateBody)
c.Logf("Publish %d: %d", idx, resp.Code)
}(i)
}
wg.Wait()
time.Sleep(100 * time.Millisecond)
// Check all packages
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repoName), nil)
var packages []string
err := json.Unmarshal(resp.Body.Bytes(), &packages)
c.Assert(err, IsNil)
// Check published files
publishedStorage := s.context.GetPublishedStorage("")
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
missingFiles := []string{}
for i := 0; i < numPackages; i++ {
packageName := fmt.Sprintf("pkg-%d-%d", iteration, i)
version := "1.0.0"
// Calculate pool path
poolSubdir := string(packageName[0])
expectedPath := filepath.Join(publicPath, "concurrent", "pool", "main", poolSubdir, packageName,
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
missingFiles = append(missingFiles, expectedPath)
}
}
if len(missingFiles) > 0 {
c.Logf("★★★ BUG DETECTED in iteration %d/%d! ★★★", iteration+1, numIterations)
c.Logf("Metadata shows %d packages, but %d files are MISSING:", len(packages), len(missingFiles))
for i, f := range missingFiles {
c.Logf(" [iter %d] File MISSING %d/%d: %s", iteration+1, i+1, len(missingFiles), f)
}
c.Fatalf("BUG REPRODUCED in iteration %d/%d: %d published files missing", iteration+1, numIterations, len(missingFiles))
} else {
c.Logf("[iter %d/%d] All %d files present - OK", iteration+1, numIterations, numPackages)
}
}
c.Logf("All %d iterations passed - bug not reproduced with current timing", numIterations)
}
// TestIdenticalPackageRace tests the specific case of identical SHA256 packages
func (s *PublishedFileMissingSuite) TestIdenticalPackageRace(c *C) {
c.Log("=== AGGRESSIVE test: identical package (same SHA256) race ===")
const numIterations = 4
packageName := "shared-package"
for iter := 0; iter < numIterations; iter++ {
c.Logf("Iteration %d/%d", iter+1, numIterations)
// Create two repos that will get the SAME package (unique per iteration)
repos := []string{fmt.Sprintf("identical-a-%d", iter), fmt.Sprintf("identical-b-%d", iter)}
dists := []string{fmt.Sprintf("dist-a-%d", iter), fmt.Sprintf("dist-b-%d", iter)}
for i := range repos {
createBody, _ := json.Marshal(gin.H{
"Name": repos[i],
"DefaultDistribution": dists[i],
"DefaultComponent": "main",
})
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
c.Assert(resp.Code, Equals, 201)
publishBody, _ := json.Marshal(gin.H{
"SourceKind": "local",
"Distribution": dists[i],
"Architectures": []string{"amd64"},
"Sources": []gin.H{
{"Component": "main", "Name": repos[i]},
},
"Signing": gin.H{"Skip": true},
"SkipBz2": true,
})
resp = s.httpRequest(c, "POST", "/api/publish/identical", publishBody)
c.Assert(resp.Code, Equals, 201)
}
// Create IDENTICAL package file with UNIQUE VERSION per iteration
version := fmt.Sprintf("1.0.%d", iter)
uploadID1 := fmt.Sprintf("identical-upload-1-%d", iter)
uploadID2 := fmt.Sprintf("identical-upload-2-%d", iter)
s.createDebPackage(c, uploadID1, packageName, version)
// Copy to second upload (same SHA256)
uploadPath := s.context.UploadPath()
src := filepath.Join(uploadPath, uploadID1, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
destDir := filepath.Join(uploadPath, uploadID2)
err := os.MkdirAll(destDir, 0755)
c.Assert(err, IsNil)
dest := filepath.Join(destDir, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
srcData, readErr := os.ReadFile(src)
c.Assert(readErr, IsNil)
err = os.WriteFile(dest, srcData, 0644)
c.Assert(err, IsNil)
// Race: add and publish both simultaneously
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repos[0], uploadID1), nil)
updateBody, _ := json.Marshal(gin.H{"Signing": gin.H{"Skip": true}, "ForceOverwrite": true, "SkipBz2": true})
s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/identical/%s", dists[0]), updateBody)
}()
go func() {
defer wg.Done()
s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repos[1], uploadID2), nil)
updateBody, _ := json.Marshal(gin.H{"Signing": gin.H{"Skip": true}, "ForceOverwrite": true, "SkipBz2": true})
s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/identical/%s", dists[1]), updateBody)
}()
wg.Wait()
time.Sleep(200 * time.Millisecond)
c.Logf("[iter %d] All operations complete", iter)
// Check the shared pool location
publishedStorage := s.context.GetPublishedStorage("")
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
poolSubdir := string(packageName[0])
sharedPoolPath := filepath.Join(publicPath, "identical", "pool", "main", poolSubdir, packageName,
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
fileInfo, err := os.Stat(sharedPoolPath)
fileExists := err == nil
if fileExists {
c.Logf("[iter %d] File EXISTS at %s (size: %d)", iter, sharedPoolPath, fileInfo.Size())
} else {
c.Logf("[iter %d] File MISSING at %s (error: %v)", iter, sharedPoolPath, err)
}
// Check metadata
var packagesA, packagesB []string
resp := s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repos[0]), nil)
err = json.Unmarshal(resp.Body.Bytes(), &packagesA)
c.Assert(err, IsNil)
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repos[1]), nil)
err = json.Unmarshal(resp.Body.Bytes(), &packagesB)
c.Assert(err, IsNil)
c.Logf("[iter %d] Packages in metadata: A=%d, B=%d", iter, len(packagesA), len(packagesB))
// THE BUG: Both repos show packages in metadata, but the shared pool file is missing
if (len(packagesA) > 0 || len(packagesB) > 0) && !fileExists {
c.Logf("★★★ BUG REPRODUCED in iteration %d! ★★★", iter+1)
c.Logf("Packages in metadata A: %d, B: %d", len(packagesA), len(packagesB))
c.Logf("Shared pool file exists: %v", fileExists)
c.Logf("Pool path: %s", sharedPoolPath)
// List what files ARE in the pool directory
poolDir := filepath.Dir(sharedPoolPath)
if entries, err := os.ReadDir(poolDir); err == nil {
c.Logf("Files in pool directory %s:", poolDir)
for _, entry := range entries {
c.Logf(" - %s", entry.Name())
}
}
c.Fatalf("Metadata shows packages but shared pool file is missing (iteration %d)", iter+1)
}
}
c.Logf("All %d iterations passed - bug not reproduced", numIterations)
}
// TestConcurrentSnapshotPublishToSamePrefix reproduces the EXACT production bug:
// Multiple snapshots are published concurrently to the SAME prefix but different distributions.
// Example from production logs:
// - trixie-pgdg published to "external/postgres-auto/trixie"
// - bullseye-pgdg published to "external/postgres-auto/bullseye"
// Both share the same pool directory, causing cleanup race conditions.
func (s *PublishedFileMissingSuite) TestConcurrentSnapshotPublishToSamePrefix(c *C) {
const numIterations = 4
for iter := 0; iter < numIterations; iter++ {
c.Logf("--- Iteration %d/%d ---", iter+1, numIterations)
// Create two repos with different packages (simulating trixie-pgdg and bullseye-pgdg)
repoTrixie := fmt.Sprintf("trixie-pgdg-%d", iter)
repoBullseye := fmt.Sprintf("bullseye-pgdg-%d", iter)
// Create trixie repo
createBody, _ := json.Marshal(gin.H{
"Name": repoTrixie,
"DefaultDistribution": "trixie",
"DefaultComponent": "main",
})
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create trixie repo"))
// Create bullseye repo
createBody, _ = json.Marshal(gin.H{
"Name": repoBullseye,
"DefaultDistribution": "bullseye",
"DefaultComponent": "main",
})
resp = s.httpRequest(c, "POST", "/api/repos", createBody)
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create bullseye repo"))
// Add packages to both repos
numPackages := 3
// Add packages to trixie repo
for i := 0; i < numPackages; i++ {
packageName := fmt.Sprintf("postgresql-17-trixie-pkg%d", i)
version := fmt.Sprintf("17.0.%d", iter)
uploadID := fmt.Sprintf("trixie-upload-%d-%d", iter, i)
s.createDebPackage(c, uploadID, packageName, version)
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoTrixie, uploadID), nil)
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package to trixie"))
}
// Add packages to bullseye repo
for i := 0; i < numPackages; i++ {
packageName := fmt.Sprintf("postgresql-17-bullseye-pkg%d", i)
version := fmt.Sprintf("17.0.%d", iter)
uploadID := fmt.Sprintf("bullseye-upload-%d-%d", iter, i)
s.createDebPackage(c, uploadID, packageName, version)
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoBullseye, uploadID), nil)
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package to bullseye"))
}
// Create snapshots from both repos
snapshotTrixie := fmt.Sprintf("%s-snap", repoTrixie)
snapshotBullseye := fmt.Sprintf("%s-snap", repoBullseye)
createSnapshotBody, _ := json.Marshal(gin.H{"Name": snapshotTrixie})
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/snapshots", repoTrixie), createSnapshotBody)
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create trixie snapshot"))
createSnapshotBody, _ = json.Marshal(gin.H{"Name": snapshotBullseye})
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/snapshots", repoBullseye), createSnapshotBody)
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create bullseye snapshot"))
// Publish both snapshots CONCURRENTLY to the SAME prefix
// This mimics production where both are published to "external/postgres-auto"
// Use the SAME prefix across all iterations to trigger the race more aggressively
sharedPrefix := "postgres-auto"
var wg sync.WaitGroup
var trixiePublishCode, bullseyePublishCode int
wg.Add(2)
// Publish or update trixie snapshot
go func() {
defer wg.Done()
var resp *httptest.ResponseRecorder
if iter == 0 {
// First iteration: CREATE
publishBody, _ := json.Marshal(gin.H{
"SourceKind": "snapshot",
"Distribution": "trixie",
"Architectures": []string{"amd64"},
"Sources": []gin.H{
{"Name": snapshotTrixie},
},
"Signing": gin.H{"Skip": true},
"SkipBz2": true,
"ForceOverwrite": true,
"SkipCleanup": false, // Force cleanup to run
})
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/publish/%s", sharedPrefix), publishBody)
} else {
// Subsequent iterations: UPDATE (this is what happens in production)
updateBody, _ := json.Marshal(gin.H{
"Snapshots": []gin.H{
{"Component": "main", "Name": snapshotTrixie},
},
"Signing": gin.H{"Skip": true},
"SkipBz2": true,
"ForceOverwrite": true,
"SkipCleanup": false,
})
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/%s/trixie", sharedPrefix), updateBody)
}
trixiePublishCode = resp.Code
c.Logf("[iter %d] Trixie publish/update completed: %d", iter, resp.Code)
}()
// Publish or update bullseye snapshot
go func() {
defer wg.Done()
var resp *httptest.ResponseRecorder
if iter == 0 {
// First iteration: CREATE
publishBody, _ := json.Marshal(gin.H{
"SourceKind": "snapshot",
"Distribution": "bullseye",
"Architectures": []string{"amd64"},
"Sources": []gin.H{
{"Name": snapshotBullseye},
},
"Signing": gin.H{"Skip": true},
"SkipBz2": true,
"ForceOverwrite": true,
"SkipCleanup": false,
})
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/publish/%s", sharedPrefix), publishBody)
} else {
// Subsequent iterations: UPDATE
updateBody, _ := json.Marshal(gin.H{
"Snapshots": []gin.H{
{"Component": "main", "Name": snapshotBullseye},
},
"Signing": gin.H{"Skip": true},
"SkipBz2": true,
"ForceOverwrite": true,
"SkipCleanup": false,
})
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/%s/bullseye", sharedPrefix), updateBody)
}
bullseyePublishCode = resp.Code
c.Logf("[iter %d] Bullseye publish/update completed: %d", iter, resp.Code)
}()
wg.Wait()
time.Sleep(50 * time.Millisecond)
// Verify publishes succeeded (201 for create, 200 for update)
expectedCode := 201
if iter > 0 {
expectedCode = 200
}
c.Assert(trixiePublishCode, Equals, expectedCode, Commentf("Trixie publish/update should succeed"))
c.Assert(bullseyePublishCode, Equals, expectedCode, Commentf("Bullseye publish/update should succeed"))
// Verify ALL package files exist in the published pool
publishedStorage := s.context.GetPublishedStorage("")
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
missingFiles := []string{}
expectedFiles := []string{}
// Check trixie packages
for i := 0; i < numPackages; i++ {
packageName := fmt.Sprintf("postgresql-17-trixie-pkg%d", i)
version := fmt.Sprintf("17.0.%d", iter)
poolSubdir := string(packageName[0])
expectedPath := filepath.Join(publicPath, sharedPrefix, "pool", "main", poolSubdir, packageName,
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
expectedFiles = append(expectedFiles, expectedPath)
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
missingFiles = append(missingFiles, fmt.Sprintf("TRIXIE: %s", filepath.Base(expectedPath)))
}
}
// Check bullseye packages
for i := 0; i < numPackages; i++ {
packageName := fmt.Sprintf("postgresql-17-bullseye-pkg%d", i)
version := fmt.Sprintf("17.0.%d", iter)
poolSubdir := string(packageName[0])
expectedPath := filepath.Join(publicPath, sharedPrefix, "pool", "main", poolSubdir, packageName,
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
expectedFiles = append(expectedFiles, expectedPath)
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
missingFiles = append(missingFiles, fmt.Sprintf("BULLSEYE: %s", filepath.Base(expectedPath)))
}
}
// BUG: Files from one distribution are deleted by the other's cleanup
if len(missingFiles) > 0 {
c.Logf("★★★ BUG REPRODUCED in iteration %d/%d! ★★★", iter+1, numIterations)
c.Logf("Both publishes to prefix '%s' succeeded, but %d files are MISSING:", sharedPrefix, len(missingFiles))
for i, f := range missingFiles {
c.Logf(" Missing file %d/%d: %s", i+1, len(missingFiles), f)
}
c.Logf("\nThis reproduces the exact production bug where:")
c.Logf(" 1. Mirror updates complete successfully")
c.Logf(" 2. Snapshots are created")
c.Logf(" 3. Both snapshots publish to same prefix (different distributions)")
c.Logf(" 4. Cleanup from one publish DELETES files from the other")
c.Logf(" 5. Result: apt-get returns 404 when downloading packages")
// List what's actually in the pool
poolDir := filepath.Join(publicPath, sharedPrefix, "pool", "main")
if entries, err := os.ReadDir(poolDir); err == nil {
c.Logf("\nActual pool directory contents (%s):", poolDir)
for _, entry := range entries {
c.Logf(" - %s/", entry.Name())
}
}
c.Fatalf("BUG CONFIRMED (iteration %d/%d): %d files missing from shared pool",
iter+1, numIterations, len(missingFiles))
} else {
c.Logf("[iter %d/%d] All %d files present - OK", iter+1, numIterations, len(expectedFiles))
}
}
c.Logf("✓ All %d iterations passed - no files missing", numIterations)
}
+168 -80
View File
@@ -69,26 +69,17 @@ func reposServeInAPIMode(c *gin.Context) {
// @Description Each repo is returned as in “show” API.
// @Tags Repos
// @Produce json
// @Success 200 {array} localRepoResponse
// @Success 200 {array} deb.LocalRepo
// @Router /api/repos [get]
func apiReposList(c *gin.Context) {
result := []localRepoResponse{}
result := []*deb.LocalRepo{}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
err := collection.ForEach(func(r *deb.LocalRepo) error {
err := collection.LoadComplete(r)
if err != nil {
return err
}
result = append(result, newLocalRepoResponse(r))
_ = collection.ForEach(func(r *deb.LocalRepo) error {
result = append(result, r)
return nil
})
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
c.JSON(200, result)
}
@@ -131,46 +122,62 @@ func apiReposCreate(c *gin.Context) {
return
}
repo := deb.NewLocalRepo(b.Name, b.Comment)
repo.DefaultComponent = b.DefaultComponent
repo.DefaultDistribution = b.DefaultDistribution
// Handler: Pre-task validations (shallow)
collectionFactory := context.NewCollectionFactory()
var resources []string
if b.FromSnapshot != "" {
var snapshot *deb.Snapshot
snapshotCollection := collectionFactory.SnapshotCollection()
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
snapshot, err := collectionFactory.SnapshotCollection().ByName(b.FromSnapshot)
if err != nil {
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err))
return
}
resources = append(resources, string(snapshot.Key()))
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to load source snapshot: %s", err))
return
taskName := fmt.Sprintf("Create repository %s", b.Name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
// Task: Create fresh collection and check/create ATOMIC inside task
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.LocalRepoCollection()
// Check duplicate inside lock
if _, err := taskCollection.ByName(b.Name); err == nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil},
fmt.Errorf("local repo with name %s already exists", b.Name)
}
repo.UpdateRefList(snapshot.RefList())
}
// Create repo
repo := deb.NewLocalRepo(b.Name, b.Comment)
repo.DefaultComponent = b.DefaultComponent
repo.DefaultDistribution = b.DefaultDistribution
localRepoCollection := collectionFactory.LocalRepoCollection()
if b.FromSnapshot != "" {
snapshotCollection := taskCollectionFactory.SnapshotCollection()
if _, err := localRepoCollection.ByName(b.Name); err == nil {
AbortWithJSONError(c, http.StatusConflict, fmt.Errorf("local repo with name %s already exists", b.Name))
return
}
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil},
fmt.Errorf("source snapshot not found: %s", err)
}
err := localRepoCollection.Add(repo)
if err != nil {
AbortWithJSONError(c, http.StatusInternalServerError, err)
return
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil},
fmt.Errorf("unable to load source snapshot: %s", err)
}
c.JSON(http.StatusCreated, repo)
repo.UpdateRefList(snapshot.RefList())
}
err := taskCollection.Add(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: repo}, nil
})
}
type reposEditParams struct {
@@ -201,6 +208,8 @@ func apiReposEdit(c *gin.Context) {
return
}
// Load shallowly for 404 check and resource key.
// Mutation and duplicate check happen inside the task for atomicity.
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
@@ -212,31 +221,53 @@ func apiReposEdit(c *gin.Context) {
}
if b.Name != nil && *b.Name != name {
_, err := collection.ByName(*b.Name)
if err == nil {
// already exists
AbortWithJSONError(c, 404, fmt.Errorf("local repo with name %q already exists", *b.Name))
if _, err = collection.ByName(*b.Name); err == nil {
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: local repo %q already exists", *b.Name))
return
}
repo.Name = *b.Name
}
if b.Comment != nil {
repo.Comment = *b.Comment
}
if b.DefaultDistribution != nil {
repo.DefaultDistribution = *b.DefaultDistribution
}
if b.DefaultComponent != nil {
repo.DefaultComponent = *b.DefaultComponent
}
err = collection.Update(repo)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
resources := []string{string(repo.Key())}
taskName := fmt.Sprintf("Edit repository %s", name)
c.JSON(200, repo)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
// Task: Create fresh collection inside task after lock
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.LocalRepoCollection()
// Fresh load after lock acquired
repo, err := taskCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
}
// Check and update ATOMIC (inside lock)
if b.Name != nil && *b.Name != name {
_, err := taskCollection.ByName(*b.Name)
if err == nil {
// already exists
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil},
fmt.Errorf("local repo with name %q already exists", *b.Name)
}
repo.Name = *b.Name
}
if b.Comment != nil {
repo.Comment = *b.Comment
}
if b.DefaultDistribution != nil {
repo.DefaultDistribution = *b.DefaultDistribution
}
if b.DefaultComponent != nil {
repo.DefaultComponent = *b.DefaultComponent
}
err = taskCollection.Update(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: repo}, nil
})
}
// GET /api/repos/:name
@@ -278,10 +309,10 @@ func apiReposDrop(c *gin.Context) {
force := c.Request.URL.Query().Get("force") == "1"
name := c.Params.ByName("name")
// Load shallowly for 404 check, resource key, and task name.
// Full checks (published/snapshots) happen inside the task.
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
publishedCollection := collectionFactory.PublishedRepoCollection()
repo, err := collection.ByName(name)
if err != nil {
@@ -292,19 +323,32 @@ func apiReposDrop(c *gin.Context) {
resources := []string{string(repo.Key())}
taskName := fmt.Sprintf("Delete repo %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
published := publishedCollection.ByLocalRepo(repo)
// Task: Create fresh collections inside task after lock acquired
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.LocalRepoCollection()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
taskPublishedCollection := taskCollectionFactory.PublishedRepoCollection()
// Re-read repo with fresh collection after lock
repo, err := taskCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: %s", err)
}
// Check with fresh collections
published := taskPublishedCollection.ByLocalRepo(repo)
if len(published) > 0 {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published")
}
if !force {
snapshots := snapshotCollection.ByLocalRepoSource(repo)
snapshots := taskSnapshotCollection.ByLocalRepoSource(repo)
if len(snapshots) > 0 {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo has snapshots, use ?force=1 to override")
}
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, collection.Drop(repo)
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, taskCollection.Drop(repo)
})
}
@@ -361,10 +405,13 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
return
}
// Load shallowly for 404 check and resource key.
// Full load and mutations happen inside the task.
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
repo, err := collection.ByName(c.Params.ByName("name"))
name := c.Params.ByName("name")
repo, err := collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
@@ -373,13 +420,23 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
resources := []string{string(repo.Key())}
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.LoadComplete(repo)
// Task: Create fresh factory and collection inside task after lock
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.LocalRepoCollection()
// Fresh load after lock acquired (use captured `name` variable, not gin context)
repo, err := taskCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
}
err = taskCollection.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)
list, err := deb.NewPackageListFromRefList(repo.RefList(), taskCollectionFactory.PackageCollection(), nil)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -388,7 +445,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
for _, ref := range b.PackageRefs {
var p *deb.Package
p, err = collectionFactory.PackageCollection().ByKey([]byte(ref))
p, err = taskCollectionFactory.PackageCollection().ByKey([]byte(ref))
if err != nil {
if err == database.ErrNotFound {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("packages %s: %s", ref, err)
@@ -404,7 +461,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = collectionFactory.LocalRepoCollection().Update(repo)
err = taskCollection.Update(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
@@ -511,6 +568,8 @@ func apiReposPackageFromDir(c *gin.Context) {
return
}
// Load shallowly for 404 check and resource key.
// Full load and mutations happen inside the task.
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
@@ -534,7 +593,17 @@ 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)
// Task: Create fresh factory and collection inside task after lock
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.LocalRepoCollection()
// Fresh load after lock acquired
repo, err := taskCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = taskCollection.LoadComplete(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -555,13 +624,13 @@ func apiReposPackageFromDir(c *gin.Context) {
packageFiles, otherFiles, failedFiles = deb.CollectPackageFiles(sources, reporter)
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
list, err = deb.NewPackageListFromRefList(repo.RefList(), taskCollectionFactory.PackageCollection(), nil)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages: %s", err)
}
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
collectionFactory.PackageCollection(), reporter, nil, collectionFactory.ChecksumCollection)
taskCollectionFactory.PackageCollection(), reporter, nil, taskCollectionFactory.ChecksumCollection)
failedFiles = append(failedFiles, failedFiles2...)
processedFiles = append(processedFiles, otherFiles...)
@@ -571,7 +640,7 @@ func apiReposPackageFromDir(c *gin.Context) {
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = collectionFactory.LocalRepoCollection().Update(repo)
err = taskCollection.Update(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
@@ -650,6 +719,8 @@ func apiReposCopyPackage(c *gin.Context) {
return
}
// Load shallowly for 404 check and resource keys.
// Full load and mutations happen inside the task.
collectionFactory := context.NewCollectionFactory()
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
if err != nil {
@@ -673,12 +744,26 @@ func apiReposCopyPackage(c *gin.Context) {
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)
// Task: Create fresh factory and collections inside task after lock
taskCollectionFactory := context.NewCollectionFactory()
// Fresh load of both repos after lock acquired
dstRepo, err := taskCollectionFactory.LocalRepoCollection().ByName(dstRepoName)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
}
err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
srcRepo, err := taskCollectionFactory.LocalRepoCollection().ByName(srcRepoName)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
}
err = taskCollectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
}
err = taskCollectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
}
@@ -691,12 +776,12 @@ func apiReposCopyPackage(c *gin.Context) {
RemovedLines: []string{},
}
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.PackageCollection(), context.Progress())
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err)
}
srcList, err := deb.NewPackageListFromRefList(srcRefList, collectionFactory.PackageCollection(), context.Progress())
srcList, err := deb.NewPackageListFromRefList(srcRefList, taskCollectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
}
@@ -764,7 +849,7 @@ func apiReposCopyPackage(c *gin.Context) {
} else {
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
err = collectionFactory.LocalRepoCollection().Update(dstRepo)
err = taskCollectionFactory.LocalRepoCollection().Update(dstRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
@@ -867,6 +952,9 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
resources = append(resources, sources...)
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
// Task: Create fresh factory and collection inside task after lock
taskCollectionFactory := context.NewCollectionFactory()
var (
err error
verifier = context.GetVerifier()
@@ -882,8 +970,8 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
changesFiles, failedFiles = deb.CollectChangesFiles(sources, reporter)
_, failedFiles2, err = deb.ImportChangesFiles(
changesFiles, reporter, acceptUnsigned, ignoreSignature, forceReplace, noRemoveFiles, verifier,
repoTemplate, context.Progress(), collectionFactory.LocalRepoCollection(), collectionFactory.PackageCollection(),
context.PackagePool(), collectionFactory.ChecksumCollection, nil, query.Parse)
repoTemplate, context.Progress(), taskCollectionFactory.LocalRepoCollection(), taskCollectionFactory.PackageCollection(),
context.PackagePool(), taskCollectionFactory.ChecksumCollection, nil, query.Parse)
failedFiles = append(failedFiles, failedFiles2...)
if err != nil {
-63
View File
@@ -1,63 +0,0 @@
package api
import (
"bytes"
"encoding/json"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
. "gopkg.in/check.v1"
)
type ReposSuite struct {
APISuite
}
var _ = Suite(&ReposSuite{})
func (s *ReposSuite) TestGetReposIncludesNumPackages(c *C) {
collection := s.context.NewCollectionFactory().LocalRepoCollection()
repo := deb.NewLocalRepo("count-repo-list", "")
repo.UpdateRefList(makePackageRefList(c))
c.Assert(collection.Add(repo), IsNil)
response, err := s.HTTPRequest("GET", "/api/repos", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 200)
var repos []map[string]interface{}
err = json.Unmarshal(response.Body.Bytes(), &repos)
c.Assert(err, IsNil)
found := false
for _, repo := range repos {
if repo["Name"] == "count-repo-list" {
found = true
value, ok := repo["NumPackages"]
c.Assert(ok, Equals, true)
c.Assert(value, Equals, float64(2))
break
}
}
c.Assert(found, Equals, true)
}
func (s *ReposSuite) TestGetReposReturns500OnCorruptRefList(c *C) {
body, err := json.Marshal(gin.H{"Name": "broken-repo-list"})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 201)
collection := s.context.NewCollectionFactory().LocalRepoCollection()
repo, err := collection.ByName("broken-repo-list")
c.Assert(err, IsNil)
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
response, err = s.HTTPRequest("GET", "/api/repos", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 500)
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
}
+26 -29
View File
@@ -11,9 +11,9 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log"
"github.com/aptly-dev/aptly/docs"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
// _ "github.com/aptly-dev/aptly/docs" // import docs
// swaggerFiles "github.com/swaggo/files"
// ginSwagger "github.com/swaggo/gin-swagger"
)
var context *ctx.AptlyContext
@@ -31,21 +31,21 @@ func apiMetricsGet() gin.HandlerFunc {
}
}
func redirectSwagger(c *gin.Context) {
if c.Request.URL.Path == "/docs/index.html" {
c.Redirect(http.StatusMovedPermanently, "/docs.html")
return
}
if c.Request.URL.Path == "/docs/" {
c.Redirect(http.StatusMovedPermanently, "/docs.html")
return
}
if c.Request.URL.Path == "/docs" {
c.Redirect(http.StatusMovedPermanently, "/docs.html")
return
}
c.Next()
}
// func redirectSwagger(c *gin.Context) {
// if c.Request.URL.Path == "/docs/index.html" {
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
// return
// }
// if c.Request.URL.Path == "/docs/" {
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
// return
// }
// if c.Request.URL.Path == "/docs" {
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
// return
// }
// c.Next()
// }
// Router returns prebuilt with routes http.Handler
func Router(c *ctx.AptlyContext) http.Handler {
@@ -69,14 +69,14 @@ func Router(c *ctx.AptlyContext) http.Handler {
router.Use(gin.Recovery(), gin.ErrorLogger())
if c.Config().EnableSwaggerEndpoint {
router.GET("docs.html", func(c *gin.Context) {
c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
})
router.Use(redirectSwagger)
url := ginSwagger.URL("/docs/doc.json")
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
}
// if c.Config().EnableSwaggerEndpoint {
// router.GET("docs.html", func(c *gin.Context) {
// c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
// })
// router.Use(redirectSwagger)
// url := ginSwagger.URL("/docs/doc.json")
// router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
// }
if c.Config().EnableMetricsEndpoint {
MetricsCollectorRegistrar.Register(router)
@@ -164,15 +164,12 @@ func Router(c *ctx.AptlyContext) http.Handler {
api.GET("/mirrors/:name", apiMirrorsShow)
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
api.POST("/mirrors", apiMirrorsCreate)
api.POST("/mirrors/:name", apiMirrorsEdit)
api.PUT("/mirrors/:name", apiMirrorsUpdate)
api.DELETE("/mirrors/:name", apiMirrorsDrop)
}
{
api.GET("/gpg/keys", apiGPGListKeys)
api.POST("/gpg/key", apiGPGAddKey)
api.DELETE("/gpg/key", apiGPGDeleteKey)
}
{
+165 -75
View File
@@ -20,7 +20,7 @@ import (
// @Description Each snapshot is returned as in “show” API.
// @Tags Snapshots
// @Produce json
// @Success 200 {array} snapshotResponse
// @Success 200 {array} deb.Snapshot
// @Router /api/snapshots [get]
func apiSnapshotsList(c *gin.Context) {
SortMethodString := c.Request.URL.Query().Get("sort")
@@ -32,20 +32,11 @@ func apiSnapshotsList(c *gin.Context) {
SortMethodString = "name"
}
result := []snapshotResponse{}
err := collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
err := collection.LoadComplete(snapshot)
if err != nil {
return err
}
result = append(result, newSnapshotResponse(snapshot))
result := []*deb.Snapshot{}
_ = collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
result = append(result, snapshot)
return nil
})
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
c.JSON(200, result)
}
@@ -83,26 +74,33 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
name := c.Params.ByName("name")
repo, err = collection.ByName(name)
repo, err = collectionFactory.RemoteRepoCollection().ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
// including snapshot resource key
resources := []string{string(repo.Key()), "S" + b.Name}
resources := []string{string(repo.Key())}
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err := repo.CheckLock()
taskCollectionFactory := context.NewCollectionFactory()
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
repo, err := taskMirrorCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = repo.CheckLock()
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
}
err = collection.LoadComplete(repo)
err = taskMirrorCollection.LoadComplete(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -116,7 +114,7 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
snapshot.Description = b.Description
}
err = snapshotCollection.Add(snapshot)
err = taskSnapshotCollection.Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
@@ -165,6 +163,7 @@ func apiSnapshotsCreate(c *gin.Context) {
}
}
// Phase 1: Pre-task validation (shallow load for 404 checks only)
collectionFactory := context.NewCollectionFactory()
snapshotCollection := collectionFactory.SnapshotCollection()
var resources []string
@@ -178,37 +177,62 @@ func apiSnapshotsCreate(c *gin.Context) {
return
}
resources = append(resources, string(sources[i].ResourceKey()))
resources = append(resources, string(sources[i].Key()))
}
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
for i := range sources {
err = snapshotCollection.LoadComplete(sources[i])
// Phase 2: Inside task lock - create fresh factory
taskCollectionFactory := context.NewCollectionFactory()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
taskPackageCollection := taskCollectionFactory.PackageCollection()
// Fresh load of all sources after lock acquired
freshSources := make([]*deb.Snapshot, len(b.SourceSnapshots))
for i := range b.SourceSnapshots {
freshSources[i], err = taskSnapshotCollection.ByName(b.SourceSnapshots[i])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// LoadComplete on fresh copy
err = taskSnapshotCollection.LoadComplete(freshSources[i])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
}
list := deb.NewPackageList()
// Merge packages from all source snapshots
var refList *deb.PackageRefList
if len(freshSources) > 0 {
refList = freshSources[0].RefList()
for i := 1; i < len(freshSources); i++ {
refList = refList.Merge(freshSources[i].RefList(), true, false)
}
} else {
refList = deb.NewPackageRefList()
}
// verify package refs and build package list
for _, ref := range b.PackageRefs {
p, err := collectionFactory.PackageCollection().ByKey([]byte(ref))
if err != nil {
if err == database.ErrNotFound {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err)
// Add any explicitly specified package refs on top
if len(b.PackageRefs) > 0 {
list := deb.NewPackageList()
for _, ref := range b.PackageRefs {
p, err := taskPackageCollection.ByKey([]byte(ref))
if err != nil {
if err == database.ErrNotFound {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err)
}
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = list.Add(p)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = list.Add(p)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
refList = refList.Merge(deb.NewPackageRefListFromPackageList(list), true, false)
}
snapshot = deb.NewSnapshotFromRefList(b.Name, sources, deb.NewPackageRefListFromPackageList(list), b.Description)
snapshot = deb.NewSnapshotFromRefList(b.Name, freshSources, refList, b.Description)
err = snapshotCollection.Add(snapshot)
err = taskSnapshotCollection.Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
@@ -249,21 +273,28 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
name := c.Params.ByName("name")
repo, err = collection.ByName(name)
repo, err = collectionFactory.LocalRepoCollection().ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
// including snapshot resource key
resources := []string{string(repo.Key()), "S" + b.Name}
resources := []string{string(repo.Key())}
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err := collection.LoadComplete(repo)
taskCollectionFactory := context.NewCollectionFactory()
taskRepoCollection := taskCollectionFactory.LocalRepoCollection()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
repo, err := taskRepoCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = taskRepoCollection.LoadComplete(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -277,7 +308,7 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
snapshot.Description = b.Description
}
err = snapshotCollection.Add(snapshot)
err = taskSnapshotCollection.Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
@@ -315,6 +346,7 @@ func apiSnapshotsUpdate(c *gin.Context) {
return
}
// Phase 1: Pre-task validation (shallow load for 404 check only)
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
name := c.Params.ByName("name")
@@ -325,14 +357,38 @@ func apiSnapshotsUpdate(c *gin.Context) {
return
}
resources := []string{string(snapshot.ResourceKey()), "S" + b.Name}
taskName := fmt.Sprintf("Update snapshot %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
_, err := collection.ByName(b.Name)
// Pre-task validation of new name if provided (skip if renaming to same name)
if b.Name != "" && b.Name != name {
_, err = collection.ByName(b.Name)
if err == nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name))
return
}
}
resources := []string{string(snapshot.Key())}
taskName := fmt.Sprintf("Update snapshot %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
// Phase 2: Inside task lock - create fresh factory
taskCollectionFactory := context.NewCollectionFactory()
taskCollection := taskCollectionFactory.SnapshotCollection()
// Fresh load after lock acquired
snapshot, err = taskCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// Fresh duplicate check inside lock
if b.Name != "" {
_, err := taskCollection.ByName(b.Name)
if err == nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
}
}
// Update fresh copy
if b.Name != "" {
snapshot.Name = b.Name
}
@@ -341,7 +397,7 @@ func apiSnapshotsUpdate(c *gin.Context) {
snapshot.Description = b.Description
}
err = collectionFactory.SnapshotCollection().Update(snapshot)
err = taskCollection.Update(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -395,9 +451,9 @@ func apiSnapshotsDrop(c *gin.Context) {
name := c.Params.ByName("name")
force := c.Request.URL.Query().Get("force") == "1"
// Phase 1: Pre-task validation (shallow load for 404 check only)
collectionFactory := context.NewCollectionFactory()
snapshotCollection := collectionFactory.SnapshotCollection()
publishedCollection := collectionFactory.PublishedRepoCollection()
snapshot, err := snapshotCollection.ByName(name)
if err != nil {
@@ -405,23 +461,37 @@ func apiSnapshotsDrop(c *gin.Context) {
return
}
resources := []string{string(snapshot.ResourceKey())}
resources := []string{string(snapshot.Key())}
taskName := fmt.Sprintf("Delete snapshot %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
published := publishedCollection.BySnapshot(snapshot)
// Phase 2: Inside task lock - create fresh collections
taskCollectionFactory := context.NewCollectionFactory()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
taskPublishedCollection := taskCollectionFactory.PublishedRepoCollection()
// Fresh load after lock acquired
snapshot, err := taskSnapshotCollection.ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// Fresh checks with current collections
published := taskPublishedCollection.BySnapshot(snapshot)
if len(published) > 0 {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: snapshot is published")
}
if !force {
snapshots := snapshotCollection.BySnapshotSource(snapshot)
// Using fresh collection for dependency check
snapshots := taskSnapshotCollection.BySnapshotSource(snapshot)
if len(snapshots) > 0 {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override")
}
}
err = snapshotCollection.Drop(snapshot)
err = taskSnapshotCollection.Drop(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -576,6 +646,7 @@ func apiSnapshotsMerge(c *gin.Context) {
return
}
// Phase 1: Pre-task validation (shallow load for 404 checks only)
collectionFactory := context.NewCollectionFactory()
snapshotCollection := collectionFactory.SnapshotCollection()
@@ -588,36 +659,47 @@ func apiSnapshotsMerge(c *gin.Context) {
return
}
resources[i] = string(sources[i].ResourceKey())
resources[i] = string(sources[i].Key())
}
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])
// Phase 2: Inside task lock - create fresh factory
taskCollectionFactory := context.NewCollectionFactory()
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
// Fresh load of all sources inside task
freshSources := make([]*deb.Snapshot, len(body.Sources))
for i := range body.Sources {
freshSources[i], err = taskSnapshotCollection.ByName(body.Sources[i])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
result = result.Merge(sources[i].RefList(), overrideMatching, false)
// LoadComplete on fresh copy
err = taskSnapshotCollection.LoadComplete(freshSources[i])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
}
// Merge using fresh sources
result := freshSources[0].RefList()
for i := 1; i < len(freshSources); i++ {
result = result.Merge(freshSources[i].RefList(), overrideMatching, false)
}
if latest {
result.FilterLatestRefs()
}
sourceDescription := make([]string, len(sources))
for i, s := range sources {
sourceDescription := make([]string, len(freshSources))
for i, s := range freshSources {
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
}
snapshot = deb.NewSnapshotFromRefList(name, sources, result,
snapshot = deb.NewSnapshotFromRefList(name, freshSources, result,
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
err = collectionFactory.SnapshotCollection().Add(snapshot)
err = taskCollectionFactory.SnapshotCollection().Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
}
@@ -698,24 +780,32 @@ func apiSnapshotsPull(c *gin.Context) {
return
}
resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())}
resources := []string{string(sourceSnapshot.Key()), string(toSnapshot.Key())}
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)
// Phase 2: Inside task lock - create fresh factory
taskCollectionFactory := context.NewCollectionFactory()
// Fresh load of snapshots after lock acquired
freshToSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(name)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot)
freshSourceSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(body.Source)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = taskCollectionFactory.SnapshotCollection().LoadComplete(freshSourceSnapshot)
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())
toPackageList, err := deb.NewPackageListFromRefList(freshToSnapshot.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
sourcePackageList, err := deb.NewPackageListFromRefList(sourceSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
sourcePackageList, err := deb.NewPackageListFromRefList(freshSourceSnapshot.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
@@ -812,10 +902,10 @@ func apiSnapshotsPull(c *gin.Context) {
}
// Create <destination> snapshot
destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{toSnapshot, sourceSnapshot}, toPackageList,
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", toSnapshot.Name, sourceSnapshot.Name, strings.Join(body.Queries, ", ")))
destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{freshToSnapshot, freshSourceSnapshot}, toPackageList,
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", freshToSnapshot.Name, freshSourceSnapshot.Name, strings.Join(body.Queries, ", ")))
err = collectionFactory.SnapshotCollection().Add(destinationSnapshot)
err = taskCollectionFactory.SnapshotCollection().Add(destinationSnapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
-53
View File
@@ -1,53 +0,0 @@
package api
import (
"encoding/json"
"github.com/aptly-dev/aptly/deb"
. "gopkg.in/check.v1"
)
type SnapshotsSuite struct {
APISuite
}
var _ = Suite(&SnapshotsSuite{})
func (s *SnapshotsSuite) TestGetSnapshotsIncludesNumPackages(c *C) {
collection := s.context.NewCollectionFactory().SnapshotCollection()
snapshot := deb.NewSnapshotFromRefList("count-snapshot-list", nil, makePackageRefList(c), "")
c.Assert(collection.Add(snapshot), IsNil)
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 200)
var snapshots []map[string]interface{}
err = json.Unmarshal(response.Body.Bytes(), &snapshots)
c.Assert(err, IsNil)
found := false
for _, snapshot := range snapshots {
if snapshot["Name"] == "count-snapshot-list" {
found = true
value, ok := snapshot["NumPackages"]
c.Assert(ok, Equals, true)
c.Assert(value, Equals, float64(2))
break
}
}
c.Assert(found, Equals, true)
}
func (s *SnapshotsSuite) TestGetSnapshotsReturns500OnCorruptRefList(c *C) {
collection := s.context.NewCollectionFactory().SnapshotCollection()
snapshot := deb.NewSnapshotFromRefList("broken-snapshot-list", nil, makePackageRefList(c), "")
c.Assert(collection.Add(snapshot), IsNil)
putRawDBValue(c, &s.APISuite, snapshot.RefKey(), []byte("not-msgpack"))
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 500)
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
}
+41 -48
View File
@@ -5,35 +5,28 @@ package azure
import (
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"net/url"
"path/filepath"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
)
func isBlobNotFound(err error) bool {
var respErr *azcore.ResponseError
if errors.As(err, &respErr) {
return respErr.StatusCode == 404 // BlobNotFound
}
return false
storageError, ok := err.(azblob.StorageError)
return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
}
type azContext struct {
client *azblob.Client
container string
container azblob.ContainerURL
prefix string
}
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
cred, err := azblob.NewSharedKeyCredential(accountName, accountKey)
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
if err != nil {
return nil, err
}
@@ -42,14 +35,15 @@ func newAzContext(accountName, accountKey, container, prefix, endpoint string) (
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
}
serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil)
url, err := url.Parse(fmt.Sprintf("%s/%s", endpoint, container))
if err != nil {
return nil, err
}
containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
result := &azContext{
client: serviceClient,
container: container,
container: containerURL,
prefix: prefix,
}
@@ -60,6 +54,10 @@ func (az *azContext) blobPath(path string) string {
return filepath.Join(az.prefix, path)
}
func (az *azContext) blobURL(path string) azblob.BlobURL {
return az.container.NewBlobURL(az.blobPath(path))
}
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
const delimiter = "/"
paths = make([]string, 0, 1024)
@@ -69,33 +67,27 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
prefix += delimiter
}
ctx := context.Background()
maxResults := int32(1)
pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{
Prefix: &prefix,
MaxResults: &maxResults,
Include: azblob.ListBlobsInclude{Metadata: true},
})
// Iterate over each page
for pager.More() {
page, err := pager.NextPage(ctx)
for marker := (azblob.Marker{}); marker.NotDone(); {
listBlob, err := az.container.ListBlobsFlatSegment(
context.Background(), marker, azblob.ListBlobsSegmentOptions{
Prefix: prefix,
MaxResults: 1,
Details: azblob.BlobListingDetails{Metadata: true}})
if err != nil {
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
}
for _, blob := range page.Segment.BlobItems {
if prefix == "" {
paths = append(paths, *blob.Name)
} else {
name := *blob.Name
paths = append(paths, name[len(prefix):])
}
b := *blob
md5 := b.Properties.ContentMD5
md5s = append(md5s, fmt.Sprintf("%x", md5))
marker = listBlob.NextMarker
for _, blob := range listBlob.Segment.BlobItems {
if prefix == "" {
paths = append(paths, blob.Name)
} else {
paths = append(paths, blob.Name[len(prefix):])
}
md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
}
if progress != nil {
time.Sleep(time.Duration(500) * time.Millisecond)
progress.AddBar(1)
@@ -105,27 +97,28 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
return paths, md5s, nil
}
func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error {
uploadOptions := &azblob.UploadFileOptions{
BlockSize: 4 * 1024 * 1024,
Concurrency: 8,
func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
uploadOptions := azblob.UploadStreamToBlockBlobOptions{
BufferSize: 4 * 1024 * 1024,
MaxBuffers: 8,
}
path := az.blobPath(blobName)
if len(sourceMD5) > 0 {
decodedMD5, err := hex.DecodeString(sourceMD5)
if err != nil {
return err
}
uploadOptions.HTTPHeaders = &blob.HTTPHeaders{
BlobContentMD5: decodedMD5,
uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
ContentMD5: decodedMD5,
}
}
var err error
if file, ok := source.(*os.File); ok {
_, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions)
}
_, err := azblob.UploadStreamToBlockBlob(
context.Background(),
source,
blob.ToBlockBlobURL(),
uploadOptions,
)
return err
}
+23 -21
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
"github.com/pkg/errors"
@@ -40,7 +41,10 @@ func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.Checksu
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
}
func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) {
func (pool *PackagePool) ensureChecksums(
poolPath string,
checksumStorage aptly.ChecksumStorage,
) (*utils.ChecksumInfo, error) {
targetChecksums, err := checksumStorage.Get(poolPath)
if err != nil {
return nil, err
@@ -48,7 +52,8 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
if targetChecksums == nil {
// we don't have checksums stored yet for this file
download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil)
blob := pool.az.blobURL(poolPath)
download, err := blob.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
if err != nil {
if isBlobNotFound(err) {
return nil, nil
@@ -58,7 +63,7 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
}
targetChecksums = &utils.ChecksumInfo{}
*targetChecksums, err = utils.ChecksumsForReader(download.Body)
*targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
if err != nil {
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
}
@@ -87,49 +92,45 @@ func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, er
}
func (pool *PackagePool) Size(path string) (int64, error) {
serviceClient := pool.az.client.ServiceClient()
containerClient := serviceClient.NewContainerClient(pool.az.container)
blobClient := containerClient.NewBlobClient(path)
props, err := blobClient.GetProperties(context.TODO(), nil)
blob := pool.az.blobURL(path)
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
}
return *props.ContentLength, nil
return props.ContentLength(), nil
}
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
blob := pool.az.blobURL(path)
temp, err := os.CreateTemp("", "blob-download")
if err != nil {
return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
return nil, errors.Wrap(err, "error creating temporary file for blob download")
}
defer func() { _ = os.Remove(temp.Name()) }()
_, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
if err != nil {
return nil, errors.Wrapf(err, "error downloading blob %s", path)
return nil, errors.Wrapf(err, "error downloading blob at %s", path)
}
return temp, nil
}
func (pool *PackagePool) Remove(path string) (int64, error) {
serviceClient := pool.az.client.ServiceClient()
containerClient := serviceClient.NewContainerClient(pool.az.container)
blobClient := containerClient.NewBlobClient(path)
props, err := blobClient.GetProperties(context.TODO(), nil)
blob := pool.az.blobURL(path)
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
}
_, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil)
_, err = blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
if err != nil {
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
}
return *props.ContentLength, nil
return props.ContentLength(), nil
}
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
@@ -143,6 +144,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
}
path := pool.buildPoolPath(basename, checksums)
blob := pool.az.blobURL(path)
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
if err != nil {
return "", err
@@ -158,7 +160,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
}
defer func() { _ = source.Close() }()
err = pool.az.putFile(path, source, checksums.MD5)
err = pool.az.putFile(blob, source, checksums.MD5)
if err != nil {
return "", err
}
+3 -5
View File
@@ -7,7 +7,7 @@ import (
"path/filepath"
"runtime"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/files"
"github.com/aptly-dev/aptly/utils"
@@ -50,10 +50,8 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
c.Assert(err, IsNil)
publicAccessType := azblob.PublicAccessTypeContainer
_, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
Access: &publicAccessType,
})
cnt := s.pool.az.container
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
c.Assert(err, IsNil)
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
+57 -70
View File
@@ -3,22 +3,19 @@ package azure
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
"github.com/google/uuid"
"github.com/pkg/errors"
)
// PublishedStorage abstract file system with published files (actually hosted on Azure)
type PublishedStorage struct {
// FIXME: unused ???? prefix string
az *azContext
pathCache map[string]map[string]string
}
@@ -67,7 +64,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
}
defer func() { _ = source.Close() }()
err = storage.az.putFile(path, source, sourceMD5)
err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
}
@@ -77,15 +74,14 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
// RemoveDirs removes directory structure under public path
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
path = storage.az.blobPath(path)
filelist, err := storage.Filelist(path)
if err != nil {
return err
}
for _, filename := range filelist {
blob := filepath.Join(path, filename)
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil)
blob := storage.az.blobURL(filepath.Join(path, filename))
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
if err != nil {
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
}
@@ -96,8 +92,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error
// Remove removes single file under public path
func (storage *PublishedStorage) Remove(path string) error {
path = storage.az.blobPath(path)
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil)
blob := storage.az.blobURL(path)
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
}
@@ -116,8 +112,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
relFilePath := filepath.Join(publishedRelPath, fileName)
prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
poolPath := storage.az.blobPath(prefixRelFilePath)
// prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
// FIXME: check how to integrate publishedPrefix:
poolPath := storage.az.blobPath(fileName)
if storage.pathCache == nil {
storage.pathCache = make(map[string]map[string]string)
@@ -160,7 +157,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
}
defer func() { _ = source.Close() }()
err = storage.az.putFile(relFilePath, source, sourceMD5)
err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
if err == nil {
pathCache[relFilePath] = sourceMD5
} else {
@@ -177,60 +174,57 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
}
// Internal copy or move implementation
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error {
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
const leaseDuration = 30
leaseID := uuid.NewString()
serviceClient := storage.az.client.ServiceClient()
containerClient := serviceClient.NewContainerClient(storage.az.container)
srcBlobClient := containerClient.NewBlobClient(src)
blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)})
if err != nil {
return fmt.Errorf("error acquiring lease on source blob %s", src)
dstBlobURL := storage.az.blobURL(dst)
srcBlobURL := storage.az.blobURL(src)
leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
if err != nil || leaseResp.StatusCode() != http.StatusCreated {
return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
}
defer func() { _, _ = srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{}) }()
srcBlobLeaseID := leaseResp.LeaseID()
_, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil)
if err != nil {
return fmt.Errorf("error acquiring lease on source blob %s", src)
}
defer func() {
_, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
}()
dstBlobClient := containerClient.NewBlobClient(dst)
copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
Metadata: metadata,
})
copyResp, err := dstBlobURL.StartCopyFromURL(
context.Background(),
srcBlobURL.URL(),
metadata,
azblob.ModifiedAccessConditions{},
azblob.BlobAccessConditions{},
azblob.DefaultAccessTier,
nil)
if err != nil {
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
}
copyStatus := *copyResp.CopyStatus
copyStatus := copyResp.CopyStatus()
for {
if copyStatus == blob.CopyStatusTypeSuccess {
if copyStatus == azblob.CopyStatusSuccess {
if move {
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{
AccessConditions: &blob.AccessConditions{
LeaseAccessConditions: &blob.LeaseAccessConditions{
LeaseID: &leaseID,
},
},
})
_, err = srcBlobURL.Delete(
context.Background(),
azblob.DeleteSnapshotsOptionNone,
azblob.BlobAccessConditions{
LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
})
return err
}
return nil
} else if copyStatus == blob.CopyStatusTypePending {
} else if copyStatus == azblob.CopyStatusPending {
time.Sleep(1 * time.Second)
getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil)
blobPropsResp, err := dstBlobURL.GetProperties(
context.Background(),
azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
azblob.ClientProvidedKeyOptions{})
if err != nil {
return fmt.Errorf("error getting copy progress %s", dst)
return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
}
copyStatus = *getMetadata.CopyStatus
copyStatus = blobPropsResp.CopyStatus()
_, err = blobLeaseClient.RenewLease(context.Background(), nil)
_, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
if err != nil {
return fmt.Errorf("error renewing source blob lease %s", src)
return fmt.Errorf("error renewing source blob lease %s", srcBlobURL)
}
} else {
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
@@ -245,9 +239,7 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
// SymLink creates a copy of src file and adds link information as meta data
func (storage *PublishedStorage) SymLink(src string, dst string) error {
metadata := make(map[string]*string)
metadata["SymLink"] = &src
return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */)
return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
}
// HardLink using symlink functionality as hard links do not exist
@@ -257,33 +249,28 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
// FileExists returns true if path exists
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
serviceClient := storage.az.client.ServiceClient()
containerClient := serviceClient.NewContainerClient(storage.az.container)
blobClient := containerClient.NewBlobClient(path)
_, err := blobClient.GetProperties(context.Background(), nil)
blob := storage.az.blobURL(path)
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
if isBlobNotFound(err) {
return false, nil
}
return false, fmt.Errorf("error checking if blob %s exists: %v", path, err)
return false, err
} else if resp.StatusCode() == http.StatusOK {
return true, nil
}
return true, nil
return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
}
// ReadLink returns the symbolic link pointed to by path.
// This simply reads text file created with SymLink
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
serviceClient := storage.az.client.ServiceClient()
containerClient := serviceClient.NewContainerClient(storage.az.container)
blobClient := containerClient.NewBlobClient(path)
props, err := blobClient.GetProperties(context.Background(), nil)
blob := storage.az.blobURL(path)
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
return "", fmt.Errorf("failed to get blob properties: %v", err)
return "", err
} else if resp.StatusCode() != http.StatusOK {
return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
}
metadata := props.Metadata
if originalBlob, exists := metadata["original_blob"]; exists {
return *originalBlob, nil
}
return "", fmt.Errorf("error reading link %s: %v", path, err)
return resp.NewMetadata()["SymLink"], nil
}
+23 -26
View File
@@ -1,7 +1,6 @@
package azure
import (
"bytes"
"context"
"crypto/md5"
"crypto/rand"
@@ -9,9 +8,7 @@ import (
"os"
"path/filepath"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/files"
"github.com/aptly-dev/aptly/utils"
. "gopkg.in/check.v1"
@@ -69,10 +66,8 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
c.Assert(err, IsNil)
publicAccessType := azblob.PublicAccessTypeContainer
_, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
Access: &publicAccessType,
})
cnt := s.storage.az.container
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
c.Assert(err, IsNil)
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
@@ -80,39 +75,41 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
}
func (s *PublishedStorageSuite) TearDownTest(c *C) {
_, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
cnt := s.storage.az.container
_, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
blob := s.storage.az.container.NewBlobURL(path)
resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
c.Assert(err, IsNil)
data, err := io.ReadAll(resp.Body)
body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
data, err := io.ReadAll(body)
c.Assert(err, IsNil)
return data
}
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
serviceClient := s.storage.az.client.ServiceClient()
containerClient := serviceClient.NewContainerClient(s.storage.az.container)
blobClient := containerClient.NewBlobClient(path)
_, err := blobClient.GetProperties(context.Background(), nil)
_, err := s.storage.az.container.NewBlobURL(path).GetProperties(
context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
c.Assert(err, NotNil)
storageError, ok := err.(*azcore.ResponseError)
storageError, ok := err.(azblob.StorageError)
c.Assert(ok, Equals, true)
c.Assert(storageError.StatusCode, Equals, 404)
c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
}
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
hash := md5.Sum(data)
uploadOptions := &azblob.UploadStreamOptions{
HTTPHeaders: &blob.HTTPHeaders{
BlobContentMD5: hash[:],
},
}
reader := bytes.NewReader(data)
_, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
_, err := azblob.UploadBufferToBlockBlob(
context.Background(),
data,
s.storage.az.container.NewBlockBlobURL(path),
azblob.UploadToBlockBlobOptions{
BlobHTTPHeaders: azblob.BlobHTTPHeaders{
ContentMD5: hash[:],
},
})
c.Assert(err, IsNil)
}
@@ -333,7 +330,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
// 2nd link from pool, providing wrong path for source file
//
// this test should check that file already exists in Azure and skip upload (which would fail if not skipped)
// this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
s.prefixedStorage.pathCache = nil
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
c.Check(err, IsNil)
-11
View File
@@ -26,7 +26,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
// collect information about references packages...
existingPackageRefs := deb.NewPackageRefList()
referencedAppStreamFiles := []string{}
// used only in verbose mode to report package use source
packageRefSources := map[string][]string{}
@@ -56,10 +55,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
}
}
for _, poolPath := range repo.AppStreamFiles {
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
}
return nil
})
if err != nil {
@@ -123,11 +118,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
return nil
})
}
for _, poolPath := range snapshot.AppStreamFiles {
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
}
return nil
})
if err != nil {
@@ -246,7 +236,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
return err
}
referencedFiles = append(referencedFiles, referencedAppStreamFiles...)
sort.Strings(referencedFiles)
context.Progress().ShutdownBar()
+1 -3
View File
@@ -20,7 +20,6 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
downloadAppStream := context.Flags().Lookup("with-appstream").Value.Get().(bool)
ignoreSignatures := context.Config().GpgDisableVerify
if context.Flags().IsSet("ignore-signatures") {
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
@@ -42,7 +41,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
}
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
downloadSources, downloadUdebs, downloadInstaller, downloadAppStream)
downloadSources, downloadUdebs, downloadInstaller)
if err != nil {
return fmt.Errorf("unable to create mirror: %s", err)
}
@@ -101,7 +100,6 @@ Example:
}
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
-7
View File
@@ -35,8 +35,6 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
repo.Filter = flag.Value.String() // allows file/stdin with @
case "filter-with-deps":
repo.FilterWithDeps = flag.Value.Get().(bool)
case "with-appstream":
repo.DownloadAppStream = flag.Value.Get().(bool)
case "with-installer":
repo.DownloadInstaller = flag.Value.Get().(bool)
case "with-sources":
@@ -55,10 +53,6 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
}
if repo.IsFlat() && repo.DownloadAppStream {
return fmt.Errorf("unable to edit: flat mirrors don't support AppStream (DEP-11) metadata")
}
if repo.Filter != "" {
_, err = query.Parse(repo.Filter)
if err != nil {
@@ -113,7 +107,6 @@ Example:
AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin")
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well")
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
-5
View File
@@ -61,11 +61,6 @@ func aptlyMirrorShowTxt(_ *commander.Command, args []string) error {
downloadUdebs = Yes
}
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
downloadAppStream := No
if repo.DownloadAppStream {
downloadAppStream = Yes
}
fmt.Printf("Download AppStream: %s\n", downloadAppStream)
if repo.Filter != "" {
fmt.Printf("Filter: %s\n", repo.Filter)
filterWithDeps := No
+1 -12
View File
@@ -64,15 +64,6 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to update: %s", err)
}
if repo.DownloadAppStream && !repo.IsFlat() {
context.Progress().Printf("Downloading AppStream metadata...\n")
err = repo.DownloadAppStreamFiles(context.Progress(), context.Downloader(),
context.PackagePool(), collectionFactory.ChecksumCollection(nil), ignoreChecksums)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
}
if repo.Filter != "" {
context.Progress().Printf("Applying filter...\n")
var filterQuery deb.PackageQuery
@@ -96,11 +87,10 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
)
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
latestOnly := context.Flags().Lookup("latest").Value.Get().(bool)
context.Progress().Printf("Building download queue...\n")
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
collectionFactory.ChecksumCollection(nil), skipExistingPackages, latestOnly)
collectionFactory.ChecksumCollection(nil), skipExistingPackages)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
@@ -302,7 +292,6 @@ Example:
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
cmd.Flag.Bool("latest", false, "download only latest version of each package (per architecture)")
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
-2
View File
@@ -51,9 +51,7 @@ Example:
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
cmd.Flag.String("version", "", "version of the release")
return cmd
}
-10
View File
@@ -150,18 +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("signed-by") {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
if duplicate != nil {
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
@@ -255,8 +247,6 @@ Example:
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.String("version", "", "version of the release")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
return cmd
-10
View File
@@ -99,14 +99,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
}
if context.Flags().IsSet("signed-by") {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
@@ -170,8 +162,6 @@ This command would switch published repository (with one component) named ppa/wh
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.String("version", "", "version of the release")
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
-20
View File
@@ -60,26 +60,10 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
}
if context.Flags().IsSet("signed-by") {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("origin") {
published.Origin = context.Flags().Lookup("origin").Value.String()
}
if context.Flags().IsSet("label") {
published.Label = context.Flags().Lookup("label").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
@@ -141,12 +125,8 @@ Example:
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
cmd.Flag.String("origin", "", "overwrite origin name to publish")
cmd.Flag.String("label", "", "overwrite label to publish")
cmd.Flag.String("version", "", "version of the release")
return cmd
}
-3
View File
@@ -185,7 +185,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
$keyring \
"-with-sources=[download source packages in addition to binary packages]:$bool" \
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
"-with-appstream=[download AppStream (DEP-11) metadata]:$bool" \
"(-)2:new mirror name: " ":archive url:_urls" ":distribution:($dists)" "*:components:_values -s ' ' components $components"
;;
list)
@@ -212,7 +211,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
$keyring \
"-max-tries=[max download tries till process fails with download error]:number: " \
"-skip-existing-packages=[do not check file existence for packages listed in the internal database of the mirror]:$bool" \
"-latest=[download only latest version of each package (per architecture)]:$bool" \
"(-)2:mirror name:$mirrors"
;;
rename)
@@ -225,7 +223,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
"-filter-with-deps=[when filtering, include dependencies of matching packages as well]:$bool" \
"-with-sources=[download source packages in addition to binary packages]:$bool" \
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
"-with-appstream=[download AppStream (DEP-11) metadata]:$bool" \
"(-)2:mirror name:$mirrors"
;;
search)
+3 -3
View File
@@ -203,7 +203,7 @@ _aptly()
"create")
if [[ $numargs -eq 0 ]]; then
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-appstream -with-installer -with-sources -with-udebs" -- ${cur}))
COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-installer -with-sources -with-udebs" -- ${cur}))
return 0
fi
fi
@@ -211,7 +211,7 @@ _aptly()
"edit")
if [[ $numargs -eq 0 ]]; then
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-archive-url= -filter= -filter-with-deps -ignore-signatures -keyring= -with-appstream -with-installer -with-sources -with-udebs" -- ${cur}))
COMPREPLY=($(compgen -W "-archive-url= -filter= -filter-with-deps -ignore-signatures -keyring= -with-installer -with-sources -with-udebs" -- ${cur}))
else
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
fi
@@ -263,7 +263,7 @@ _aptly()
"update")
if [[ $numargs -eq 0 ]]; then
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages -latest" -- ${cur}))
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages" -- ${cur}))
else
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
fi
+1
View File
@@ -100,6 +100,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
for _, configLocation := range configLocations {
// FIXME: check if exists, check if readable
err = utils.LoadConfig(configLocation, &utils.Config)
if os.IsPermission(err) || os.IsNotExist(err) {
continue
-3
View File
@@ -26,11 +26,8 @@ var (
"Version",
"Codename",
"Date",
"Valid-Until",
"NotAutomatic",
"ButAutomaticUpgrades",
"Acquire-By-Hash",
"Signed-By",
"Architectures",
"Architecture",
"Components",
+1 -33
View File
@@ -172,39 +172,6 @@ func (l *PackageList) ForEach(handler func(*Package) error) error {
return err
}
// FilterLatest creates a copy of the package list containing only the
// latest version for each package name/architecture pair.
func (l *PackageList) FilterLatest() (*PackageList, error) {
if l == nil {
return nil, fmt.Errorf("package list is nil")
}
filtered := make(map[string]*Package, l.Len())
err := l.ForEach(func(p *Package) error {
key := p.Architecture + "|" + p.Name
if existing, found := filtered[key]; !found || CompareVersions(p.Version, existing.Version) > 0 {
filtered[key] = p
}
return nil
})
if err != nil {
return nil, err
}
result := NewPackageListWithDuplicates(l.duplicatesAllowed, len(filtered))
for _, pkg := range filtered {
if err = result.Add(pkg); err != nil {
return nil, err
}
}
return result, nil
}
// ForEachIndexed calls handler for each package in list in indexed order
func (l *PackageList) ForEachIndexed(handler func(*Package) error) error {
if !l.indexed {
@@ -631,6 +598,7 @@ func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
//
// 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
// FIXME: do not search twice
if result.Search(dep, false, true) != nil {
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
-49
View File
@@ -503,52 +503,3 @@ func (s *PackageListSuite) TestArchitectures(c *C) {
sort.Strings(archs)
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390"})
}
func (s *PackageListSuite) TestFilterLatest(c *C) {
list := NewPackageList()
older := packageStanza.Copy()
older["Version"] = "1.0"
olderPkg := NewPackageFromControlFile(older)
_ = list.Add(olderPkg)
newer := packageStanza.Copy()
newer["Version"] = "2.0"
newerPkg := NewPackageFromControlFile(newer)
_ = list.Add(newerPkg)
shared := packageStanza.Copy()
shared["Architecture"] = ArchitectureAll
shared["Version"] = "3.0"
shared["Package"] = "shared"
sharedPkg := NewPackageFromControlFile(shared)
_ = list.Add(sharedPkg)
filtered, err := list.FilterLatest()
c.Assert(err, IsNil)
c.Assert(filtered.Len(), Equals, 2)
c.Check(filtered.Has(newerPkg), Equals, true)
c.Check(filtered.Has(sharedPkg), Equals, true)
}
func (s *PackageListSuite) TestFilterLatestPreservesDuplicatesFlag(c *C) {
list := NewPackageListWithDuplicates(true, 2)
_ = list.Add(NewPackageFromControlFile(packageStanza.Copy()))
another := packageStanza.Copy()
another["Version"] = "7.41-1"
_ = list.Add(NewPackageFromControlFile(another))
filtered, err := list.FilterLatest()
c.Assert(err, IsNil)
c.Assert(filtered.duplicatesAllowed, Equals, true)
}
func (s *PackageListSuite) TestFilterLatestNil(c *C) {
var list *PackageList
filtered, err := list.FilterLatest()
c.Assert(err, ErrorMatches, "package list is nil")
c.Assert(filtered, IsNil)
}
+1 -1
View File
@@ -59,7 +59,7 @@ func (s *PackageSuite) TestNewUdebFromPara(c *C) {
}
func (s *PackageSuite) TestNewInstallerFromPara(c *C) {
repo, _ := NewRemoteRepo("yandex", "http://example.com/debian", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo, _ := NewRemoteRepo("yandex", "http://example.com/debian", "squeeze", []string{"main"}, []string{}, false, false, false)
downloader := http.NewFakeDownloader()
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/MANIFEST.udebs", "MANIFEST.udebs")
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/udeb.list", "udeb.list")
+57 -60
View File
@@ -55,7 +55,6 @@ type PublishedRepo struct {
Label string
Suite string
Codename string
Version string
// Architectures is a list of all architectures published
Architectures []string
// SourceKind is "local"/"repo"
@@ -83,11 +82,6 @@ type PublishedRepo struct {
// Provide index files per hash also
AcquireByHash bool
// An optional field containing a comma separated list
// of OpenPGP key fingerprints to be used
// for validating the next Release file
SignedBy string
// Support multiple distributions
MultiDist bool
@@ -527,7 +521,6 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
"Origin": p.Origin,
"Suite": p.Suite,
"Codename": p.Codename,
"Version": p.Version,
"NotAutomatic": p.NotAutomatic,
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
"Prefix": p.Prefix,
@@ -537,7 +530,6 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
"Storage": p.Storage,
"SkipContents": p.SkipContents,
"AcquireByHash": p.AcquireByHash,
"SignedBy": p.SignedBy,
"MultiDist": p.MultiDist,
})
}
@@ -612,6 +604,15 @@ func (p *PublishedRepo) Key() []byte {
return []byte("U" + p.StoragePrefix() + ">>" + p.Distribution)
}
// PrefixPoolLockKey returns the task-queue resource key that serialises all
// publish operations sharing the same pool directory under storagePrefix.
// It must be held whenever a non-MultiDist publish may read or clean the
// shared pool, to prevent concurrent cleanup runs from deleting each other's
// files. See docs/Resource-Locking.md for the full key-namespace table.
func PrefixPoolLockKey(storagePrefix string) string {
return "P" + storagePrefix
}
// RefKey is a unique id for package reference list
func (p *PublishedRepo) RefKey(component string) []byte {
return []byte("E" + p.UUID + component)
@@ -1055,38 +1056,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
}
}
// Pass-through AppStream (DEP-11) files from snapshots
for component, item := range p.sourceItems {
if item.snapshot == nil || len(item.snapshot.AppStreamFiles) == 0 {
continue
}
prefix := component + "/"
for relPath, poolPath := range item.snapshot.AppStreamFiles {
if !strings.HasPrefix(relPath, prefix) {
continue
}
withinComponent := strings.TrimPrefix(relPath, prefix)
poolFile, err := packagePool.Open(poolPath)
if err != nil {
return fmt.Errorf("unable to open AppStream file from pool: %v", err)
}
bufWriter, err := indexes.SkelIndex(component, withinComponent).BufWriter()
if err != nil {
_ = poolFile.Close()
return fmt.Errorf("unable to generate AppStream index: %v", err)
}
_, err = bufio.NewReader(poolFile).WriteTo(bufWriter)
_ = poolFile.Close()
if err != nil {
return fmt.Errorf("unable to write AppStream file: %v", err)
}
}
}
udebs := []bool{false}
if hadUdebs {
udebs = append(udebs, true)
@@ -1111,12 +1080,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
if p.AcquireByHash {
release["Acquire-By-Hash"] = "yes"
}
if p.SignedBy != "" {
release["Signed-By"] = p.SignedBy
}
if p.Version != "" {
release["Version"] = p.Version
}
var bufWriter *bufio.Writer
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
@@ -1173,7 +1136,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
release["Label"] = p.GetLabel()
release["Suite"] = p.GetSuite()
release["Codename"] = p.GetCodename()
datetimeformat := "Mon, 2 Jan 2006 15:04:05 MST"
datetimeFormat := "Mon, 2 Jan 2006 15:04:05 MST"
publishDate := time.Now().UTC()
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
@@ -1181,23 +1144,11 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
publishDate = time.Unix(sec, 0).UTC()
}
}
release["Date"] = publishDate.Format(datetimeformat)
release["Date"] = publishDate.Format(datetimeFormat)
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
if p.AcquireByHash {
release["Acquire-By-Hash"] = "yes"
}
if p.SignedBy != "" {
// "If the field is present, a client should only accept future updates
// to the repository that are signed with keys listed in the field.
// The field should be ignored if the Valid-Until field
// is not present or if it is expired."
release["Signed-By"] = p.SignedBy
// Let's use a century as a "forever" value.
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetimeformat)
}
if p.Version != "" {
release["Version"] = p.Version
}
release["Description"] = " Generated by aptly\n"
release["MD5Sum"] = ""
release["SHA1"] = ""
@@ -1589,6 +1540,52 @@ func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix
return referencedFiles, nil
}
// CleanupAfterMultiDistToggle cleans up stale pool files left behind when the
// MultiDist flag is toggled on a published repository.
//
// - false→true: Publish() wrote packages into pool/<distribution>/<component>/
// but the old flat pool/<component>/ files were not removed because
// CleanupPrefixComponentFiles only scans the new MultiDist tree.
// A second pass with MultiDist=false cleans the legacy flat layout by
// reusing the existing orphan-detection logic (the repo is now MultiDist=true
// so it is excluded from the referenced-files scan, making its old pool
// entries appear orphaned).
//
// - true→false: Publish() wrote packages into pool/<component>/ but the old
// per-distribution pool/<distribution>/<component>/ directories were not
// removed. The orphan-detection approach cannot be used here because the
// repo's RefList still contains all packages (they just moved locations).
// Instead we directly remove each pool/<distribution>/<component>/ directory.
// This is safe because per-distribution pool dirs are exclusive to a single
// prefix+distribution combination — no other published repo can share them.
func (collection *PublishedRepoCollection) CleanupAfterMultiDistToggle(publishedStorageProvider aptly.PublishedStorageProvider,
published *PublishedRepo, prevMultiDist bool, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
if prevMultiDist == published.MultiDist {
return nil
}
if !prevMultiDist && published.MultiDist {
// false→true: use orphan-detection via the existing cleanup, but with
// MultiDist temporarily set to false so it scans the flat pool layout.
legacy := *published
legacy.MultiDist = false
return collection.CleanupPrefixComponentFiles(publishedStorageProvider, &legacy, cleanComponents, collectionFactory, progress)
}
// true→false: directly remove the per-distribution pool directories.
publishedStorage := publishedStorageProvider.GetPublishedStorage(published.Storage)
for _, component := range cleanComponents {
poolDir := filepath.Join(published.Prefix, "pool", published.Distribution, component)
if err := publishedStorage.RemoveDirs(poolDir, progress); err != nil {
return err
}
}
// Remove the distribution-level pool dir if it is now empty.
distPoolDir := filepath.Join(published.Prefix, "pool", published.Distribution)
_ = publishedStorage.RemoveDirs(distPoolDir, progress)
return nil
}
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(publishedStorageProvider aptly.PublishedStorageProvider,
published *PublishedRepo, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
+8 -79
View File
@@ -13,7 +13,6 @@ import (
"github.com/aptly-dev/aptly/database"
"github.com/aptly-dev/aptly/database/goleveldb"
"github.com/aptly-dev/aptly/files"
"github.com/aptly-dev/aptly/utils"
"github.com/ugorji/go/codec"
. "gopkg.in/check.v1"
@@ -116,7 +115,7 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
s.reflist = NewPackageRefListFromPackageList(s.list)
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
repo.packageRefs = s.reflist
_ = s.factory.RemoteRepoCollection().Add(repo)
@@ -426,81 +425,6 @@ func (s *PublishedRepoSuite) TestPublish(c *C) {
c.Assert(err, IsNil)
}
func (s *PublishedRepoSuite) TestPublishAppStream(c *C) {
// Components + icons
content1 := []byte("DEP-11 test content for Components-amd64.yml.gz")
tmpFile1 := filepath.Join(c.MkDir(), "Components-amd64.yml.gz")
c.Assert(os.WriteFile(tmpFile1, content1, 0644), IsNil)
checksums1 := utils.ChecksumInfo{Size: int64(len(content1))}
poolPath1, err := s.packagePool.Import(tmpFile1, "Components-amd64.yml.gz", &checksums1, false, s.cs)
c.Assert(err, IsNil)
content2 := []byte("DEP-11 icons tar data")
tmpFile2 := filepath.Join(c.MkDir(), "icons-48x48.tar.gz")
c.Assert(os.WriteFile(tmpFile2, content2, 0644), IsNil)
checksums2 := utils.ChecksumInfo{Size: int64(len(content2))}
poolPath2, err := s.packagePool.Import(tmpFile2, "icons-48x48.tar.gz", &checksums2, false, s.cs)
c.Assert(err, IsNil)
// Include contrib file that should be skipped
contribContent := []byte("DEP-11 contrib content")
tmpFile3 := filepath.Join(c.MkDir(), "Components-contrib.yml.gz")
c.Assert(os.WriteFile(tmpFile3, contribContent, 0644), IsNil)
checksums3 := utils.ChecksumInfo{Size: int64(len(contribContent))}
poolPath3, err := s.packagePool.Import(tmpFile3, "Components-contrib.yml.gz", &checksums3, false, s.cs)
c.Assert(err, IsNil)
s.snapshot.AppStreamFiles = map[string]string{
"main/dep11/Components-amd64.yml.gz": poolPath1,
"main/dep11/icons-48x48.tar.gz": poolPath2,
"contrib/dep11/Components-amd64.yml.gz": poolPath3,
}
err = s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
c.Assert(err, IsNil)
// Both main files should exist
appstreamPath1 := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/dep11/Components-amd64.yml.gz")
c.Check(appstreamPath1, PathExists)
actual1, err := os.ReadFile(appstreamPath1)
c.Assert(err, IsNil)
c.Check(actual1, DeepEquals, content1)
appstreamPath2 := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/dep11/icons-48x48.tar.gz")
c.Check(appstreamPath2, PathExists)
actual2, err := os.ReadFile(appstreamPath2)
c.Assert(err, IsNil)
c.Check(actual2, DeepEquals, content2)
// Contrib file should not appear
contribPath := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/contrib/dep11/Components-amd64.yml.gz")
_, statErr := os.Stat(contribPath)
c.Check(os.IsNotExist(statErr), Equals, true)
// Release file should reference AppStream files
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
c.Assert(err, IsNil)
defer func() { _ = rf.Close() }()
cfr := NewControlFileReader(rf, true, false)
st, err := cfr.ReadStanza()
c.Assert(err, IsNil)
c.Check(st["MD5Sum"], Matches, "(?s).*main/dep11/Components-amd64\\.yml\\.gz.*")
c.Check(st["SHA256"], Matches, "(?s).*main/dep11/Components-amd64\\.yml\\.gz.*")
// Pool open error
s.snapshot.AppStreamFiles = map[string]string{
"main/dep11/Components-amd64.yml.gz": "nonexistent/pool/path",
}
err = s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
c.Assert(err, ErrorMatches, "unable to open AppStream file from pool.*")
}
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
c.Assert(err, IsNil)
@@ -873,7 +797,10 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
_ = s.snapshotCollection.Add(snap3)
// Ensure that adding a second publish point with matching files doesn't give duplicate results.
// When a second publish point references the same package (snap3 is a clone of snap2,
// both containing p3/lonely-strangers), listReferencedFilesByComponent deduplicates by
// package ref so the file appears only once. StrSlicesSubstract handles a single entry
// correctly, so no duplicate is needed for cleanup safety.
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false)
c.Check(err, IsNil)
c.Check(s.collection.Add(repo3), IsNil)
@@ -888,7 +815,9 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
},
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
"main": {
"a/alien-arena/lonely-strangers_7.40-2_i386.deb",
},
})
}
+2 -101
View File
@@ -70,10 +70,6 @@ type RemoteRepo struct {
DownloadUdebs bool
// Should we download installer files?
DownloadInstaller bool
// Should we download AppStream (DEP-11) metadata?
DownloadAppStream bool
// AppStream files: relative path (e.g. "main/dep11/Components-amd64.yml.gz") → pool path
AppStreamFiles map[string]string `codec:"AppStreamFiles" json:"-"`
// Packages for json output
Packages []string `codec:"-" json:",omitempty"`
// "Snapshot" of current list of packages
@@ -86,7 +82,7 @@ type RemoteRepo struct {
// NewRemoteRepo creates new instance of Debian remote repository with specified params
func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string,
architectures []string, downloadSources bool, downloadUdebs bool, downloadInstaller bool, downloadAppStream bool) (*RemoteRepo, error) {
architectures []string, downloadSources bool, downloadUdebs bool, downloadInstaller bool) (*RemoteRepo, error) {
result := &RemoteRepo{
UUID: uuid.NewString(),
Name: name,
@@ -97,7 +93,6 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone
DownloadSources: downloadSources,
DownloadUdebs: downloadUdebs,
DownloadInstaller: downloadInstaller,
DownloadAppStream: downloadAppStream,
}
err := result.prepare()
@@ -116,9 +111,6 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone
if result.DownloadUdebs {
return nil, fmt.Errorf("debian-installer udebs aren't supported for flat repos")
}
if result.DownloadAppStream {
return nil, fmt.Errorf("AppStream (DEP-11) metadata isn't supported for flat repos")
}
result.Components = nil
}
@@ -155,9 +147,6 @@ func (repo *RemoteRepo) String() string {
if repo.DownloadInstaller {
srcFlag += " [installer]"
}
if repo.DownloadAppStream {
srcFlag += " [appstream]"
}
distribution := repo.Distribution
if distribution == "" {
distribution = "./"
@@ -275,82 +264,6 @@ func (repo *RemoteRepo) InstallerPath(component string, architecture string) str
return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture)
}
// AppStreamPaths returns dep11 file paths from ReleaseFiles for a given component
func (repo *RemoteRepo) AppStreamPaths(component string) []string {
prefix := component + "/dep11/"
var paths []string
for path := range repo.ReleaseFiles {
if strings.HasPrefix(path, prefix) {
paths = append(paths, path)
}
}
sort.Strings(paths)
return paths
}
// DownloadAppStreamFiles downloads AppStream (DEP-11) metadata files and imports them into the pool
func (repo *RemoteRepo) DownloadAppStreamFiles(progress aptly.Progress, d aptly.Downloader,
packagePool aptly.PackagePool, checksumStorage aptly.ChecksumStorage, ignoreChecksums bool) error {
repo.AppStreamFiles = make(map[string]string)
for _, component := range repo.Components {
paths := repo.AppStreamPaths(component)
if len(paths) == 0 {
continue
}
for _, relativePath := range paths {
info, ok := repo.ReleaseFiles[relativePath]
if !ok {
continue
}
url := repo.IndexesRootURL().ResolveReference(&url.URL{Path: relativePath}).String()
if progress != nil {
progress.Printf("Downloading AppStream file %s...\n", relativePath)
}
tempDir, err := os.MkdirTemp("", "aptly-appstream-*")
if err != nil {
return fmt.Errorf("unable to create temp dir for AppStream file %s: %s", relativePath, err)
}
tempPath := path.Join(tempDir, path.Base(relativePath))
var expected *utils.ChecksumInfo
if !ignoreChecksums {
expected = &info
}
err = d.DownloadWithChecksum(gocontext.TODO(), url, tempPath, expected, ignoreChecksums)
if err != nil {
_ = os.RemoveAll(tempDir)
// Skip files that are not found (some repos list dep11 files but don't serve them)
if herr, ok := err.(*http.Error); ok && (herr.Code == 404 || herr.Code == 403) {
if progress != nil {
progress.ColoredPrintf("@y[!]@| @!skipping AppStream file %s: not found@|", relativePath)
}
continue
}
return fmt.Errorf("unable to download AppStream file %s: %s", relativePath, err)
}
basename := path.Base(relativePath)
poolPath, err := packagePool.Import(tempPath, basename, &info, true, checksumStorage)
_ = os.RemoveAll(tempDir)
if err != nil {
return fmt.Errorf("unable to import AppStream file %s: %s", relativePath, err)
}
repo.AppStreamFiles[relativePath] = poolPath
}
}
return nil
}
// PackageURL returns URL of package file relative to repository root
// architecture
func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
@@ -699,19 +612,7 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu
}
// BuildDownloadQueue builds queue, discards current PackageList
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages, latestOnly bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
if repo.packageList == nil {
err = fmt.Errorf("package list is empty, please (re)download package indexes")
return
}
if latestOnly {
repo.packageList, err = repo.packageList.FilterLatest()
if err != nil {
return
}
}
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
queue = make([]PackageDownloadTask, 0, repo.packageList.Len())
seen := make(map[string]int, repo.packageList.Len())
+26 -164
View File
@@ -2,7 +2,6 @@ package deb
import (
"errors"
"fmt"
"io"
"os"
"sort"
@@ -91,8 +90,8 @@ type RemoteRepoSuite struct {
var _ = Suite(&RemoteRepoSuite{})
func (s *RemoteRepoSuite) SetUpTest(c *C) {
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false, false, false)
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false, false, false)
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false, false)
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false, false)
s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
s.progress = console.NewProgress(false)
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
@@ -109,7 +108,7 @@ func (s *RemoteRepoSuite) TearDownTest(c *C) {
}
func (s *RemoteRepoSuite) TestInvalidURL(c *C) {
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false, false, false, false)
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false, false, false)
c.Assert(err, ErrorMatches, ".*(hexadecimal escape in host|percent-encoded characters in host|invalid URL escape).*")
}
@@ -118,15 +117,12 @@ func (s *RemoteRepoSuite) TestFlatCreation(c *C) {
c.Check(s.flat.Distribution, Equals, "./")
c.Check(s.flat.Components, IsNil)
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false, false, false)
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false, false)
c.Check(flat2.IsFlat(), Equals, true)
c.Check(flat2.Distribution, Equals, "./binary/")
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false, false, false, false)
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false, false, false)
c.Check(err, ErrorMatches, "components aren't supported for flat repos")
_, err = NewRemoteRepo("fl", "http://some.repo/", "./", []string{}, []string{}, false, false, false, true)
c.Check(err, ErrorMatches, "AppStream \\(DEP-11\\) metadata isn't supported for flat repos")
}
func (s *RemoteRepoSuite) TestString(c *C) {
@@ -139,43 +135,6 @@ func (s *RemoteRepoSuite) TestString(c *C) {
s.flat.DownloadSources = true
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb] [installer]")
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./ [src]")
s.repo.DownloadAppStream = true
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb] [installer] [appstream]")
// AppStream is not supported for flat repos, so no flat test here
}
func (s *RemoteRepoSuite) TestAppStreamPaths(c *C) {
s.repo.ReleaseFiles = nil
c.Check(s.repo.AppStreamPaths("main"), DeepEquals, []string(nil))
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
"main/binary-amd64/Packages": {Size: 100},
"main/dep11/Components-amd64.yml.gz": {Size: 200},
"main/dep11/Components-i386.yml.gz": {Size: 300},
"main/dep11/icons-48x48.tar.gz": {Size: 400},
"contrib/dep11/Components-amd64.yml.gz": {Size: 500},
"main/source/Sources": {Size: 600},
}
paths := s.repo.AppStreamPaths("main")
c.Check(paths, DeepEquals, []string{
"main/dep11/Components-amd64.yml.gz",
"main/dep11/Components-i386.yml.gz",
"main/dep11/icons-48x48.tar.gz",
})
paths = s.repo.AppStreamPaths("contrib")
c.Check(paths, DeepEquals, []string{
"contrib/dep11/Components-amd64.yml.gz",
})
paths = s.repo.AppStreamPaths("non-free")
c.Check(paths, DeepEquals, []string(nil))
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{}
c.Check(s.repo.AppStreamPaths("main"), DeepEquals, []string(nil))
}
func (s *RemoteRepoSuite) TestNumPackages(c *C) {
@@ -277,13 +236,13 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) {
}
func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) {
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false, false, false)
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false, false)
err := s.repo.Fetch(s.downloader, nil, true)
c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*")
}
func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) {
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false, false, false)
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false, false)
err := s.repo.Fetch(s.downloader, nil, true)
c.Assert(err, ErrorMatches, "component xyz not available in repo.*")
}
@@ -322,7 +281,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(3))
c.Check(queue, HasLen, 1)
@@ -349,7 +308,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(0))
c.Check(queue, HasLen, 0)
@@ -370,7 +329,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(3))
c.Check(queue, HasLen, 1)
@@ -397,7 +356,7 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(3)+int64(len(exampleInstallerManifestFile)))
c.Check(queue, HasLen, 2)
@@ -423,35 +382,6 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) {
c.Check(pkg.Name, Equals, "installer")
}
func (s *RemoteRepoSuite) TestBuildDownloadQueueLatestOnly(c *C) {
s.repo.Architectures = []string{"i386"}
err := s.repo.Fetch(s.downloader, nil, true)
c.Assert(err, IsNil)
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404})
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.Error{Code: 404})
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false)
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
stanza := packageStanza.Copy()
stanza["Package"] = "amanda-client"
stanza["Version"] = "1:3.4.0-1"
stanza["Filename"] = "pool/main/a/amanda/amanda-client_3.4.0-1_i386.deb"
newest := NewPackageFromControlFile(stanza)
_ = s.repo.packageList.Add(newest)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, true)
c.Assert(err, IsNil)
c.Check(queue, HasLen, 1)
c.Check(queue[0].File.DownloadURL(), Equals, "pool/main/a/amanda/amanda-client_3.4.0-1_i386.deb")
c.Check(size, Equals, int64(187518))
}
func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
s.repo.Architectures = []string{"i386"}
s.repo.DownloadSources = true
@@ -470,7 +400,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(15))
c.Check(queue, HasLen, 4)
@@ -514,7 +444,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(0))
c.Check(queue, HasLen, 0)
@@ -539,7 +469,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
c.Assert(err, IsNil)
c.Assert(s.downloader.Empty(), Equals, true)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(15))
c.Check(queue, HasLen, 4)
@@ -563,7 +493,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
c.Assert(err, IsNil)
c.Assert(downloader.Empty(), Equals, true)
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(3))
c.Check(queue, HasLen, 1)
@@ -591,7 +521,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
c.Assert(err, IsNil)
c.Assert(downloader.Empty(), Equals, true)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(0))
c.Check(queue, HasLen, 0)
@@ -613,7 +543,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
c.Assert(err, IsNil)
c.Assert(downloader.Empty(), Equals, true)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(3))
c.Check(queue, HasLen, 1)
@@ -644,7 +574,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
c.Assert(err, IsNil)
c.Assert(downloader.Empty(), Equals, true)
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(15))
c.Check(queue, HasLen, 4)
@@ -690,7 +620,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
c.Assert(err, IsNil)
c.Assert(downloader.Empty(), Equals, true)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(0))
c.Check(queue, HasLen, 0)
@@ -716,7 +646,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
c.Assert(err, IsNil)
c.Assert(downloader.Empty(), Equals, true)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(15))
c.Check(queue, HasLen, 4)
@@ -725,74 +655,6 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
c.Assert(s.flat.packageRefs, NotNil)
}
func (s *RemoteRepoSuite) TestDownloadAppStreamFiles(c *C) {
// No dep11 entries
s.repo.Components = []string{"main"}
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
"main/binary-amd64/Packages": {Size: 100},
"main/source/Sources": {Size: 200},
}
err := s.repo.DownloadAppStreamFiles(s.progress, s.downloader, s.packagePool, s.cs, false)
c.Assert(err, IsNil)
c.Check(s.repo.AppStreamFiles, HasLen, 0)
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
"main/dep11/Components-amd64.yml.gz": {Size: 16},
"main/dep11/icons-48x48.tar.gz": {Size: 16},
"main/binary-amd64/Packages": {Size: 100},
}
downloader := http.NewFakeDownloader()
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", "dep11-components")
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/icons-48x48.tar.gz", "dep11-icons-data")
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, false)
c.Assert(err, IsNil)
c.Check(s.repo.AppStreamFiles, HasLen, 2)
c.Check(s.repo.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Not(Equals), "")
c.Check(s.repo.AppStreamFiles["main/dep11/icons-48x48.tar.gz"], Not(Equals), "")
// 404 skipped
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
"main/dep11/Components-amd64.yml.gz": {Size: 16},
"main/dep11/icons-48x48.tar.gz": {Size: 15},
}
downloader = http.NewFakeDownloader()
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", "dep11-components")
downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/icons-48x48.tar.gz", &http.Error{Code: 404, URL: "http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/icons-48x48.tar.gz"})
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, false)
c.Assert(err, IsNil)
c.Check(s.repo.AppStreamFiles, HasLen, 1)
c.Check(s.repo.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Not(Equals), "")
// Generic download error propagated
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
"main/dep11/Components-amd64.yml.gz": {Size: 18},
}
downloader = http.NewFakeDownloader()
downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", fmt.Errorf("connection refused"))
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, false)
c.Assert(err, ErrorMatches, "unable to download AppStream file.*connection refused")
// Bypass checksum validation
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
"main/dep11/Components-amd64.yml.gz": {Size: 999, MD5: "bad", SHA256: "bad"},
}
downloader = http.NewFakeDownloader()
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", "dep11-components")
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, true)
c.Assert(err, IsNil)
c.Check(s.repo.AppStreamFiles, HasLen, 1)
c.Check(s.repo.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Not(Equals), "")
}
type RemoteRepoCollectionSuite struct {
PackageListMixinSuite
db database.Storage
@@ -815,7 +677,7 @@ func (s *RemoteRepoCollectionSuite) TestAddByName(c *C) {
_, err := s.collection.ByName("yandex")
c.Assert(err, ErrorMatches, "*.not found")
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
c.Assert(s.collection.Add(repo), IsNil)
c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists")
@@ -833,7 +695,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
_, err := s.collection.ByUUID("some-uuid")
c.Assert(err, ErrorMatches, "*.not found")
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
c.Assert(s.collection.Add(repo), IsNil)
r, err := s.collection.ByUUID(repo.UUID)
@@ -847,7 +709,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
}
func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
c.Assert(s.collection.Update(repo), IsNil)
collection := NewRemoteRepoCollection(s.db)
@@ -868,7 +730,7 @@ func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
}
func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
_ = s.collection.Add(repo)
count := 0
@@ -890,10 +752,10 @@ func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
}
func (s *RemoteRepoCollectionSuite) TestDrop(c *C) {
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
_ = s.collection.Add(repo1)
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false, false, false, false)
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false, false, false)
_ = s.collection.Add(repo2)
r1, _ := s.collection.ByUUID(repo1.UUID)
+7 -31
View File
@@ -40,9 +40,6 @@ type Snapshot struct {
NotAutomatic string
ButAutomaticUpgrades string
// AppStream files: relative path → pool path (pass-through from mirror)
AppStreamFiles map[string]string `json:",omitempty"`
packageRefs *PackageRefList
}
@@ -62,7 +59,6 @@ func NewSnapshotFromRepository(name string, repo *RemoteRepo) (*Snapshot, error)
Origin: repo.Meta["Origin"],
NotAutomatic: repo.Meta["NotAutomatic"],
ButAutomaticUpgrades: repo.Meta["ButAutomaticUpgrades"],
AppStreamFiles: repo.AppStreamFiles,
packageRefs: repo.packageRefs,
}, nil
}
@@ -98,28 +94,14 @@ func NewSnapshotFromRefList(name string, sources []*Snapshot, list *PackageRefLi
sourceUUIDs[i] = sources[i].UUID
}
// Merge AppStreamFiles from all source snapshots
var mergedAppStream map[string]string
for _, source := range sources {
if len(source.AppStreamFiles) > 0 {
if mergedAppStream == nil {
mergedAppStream = make(map[string]string)
}
for k, v := range source.AppStreamFiles {
mergedAppStream[k] = v
}
}
}
return &Snapshot{
UUID: uuid.NewString(),
Name: name,
CreatedAt: time.Now(),
SourceKind: "snapshot",
SourceIDs: sourceUUIDs,
Description: description,
AppStreamFiles: mergedAppStream,
packageRefs: list,
UUID: uuid.NewString(),
Name: name,
CreatedAt: time.Now(),
SourceKind: "snapshot",
SourceIDs: sourceUUIDs,
Description: description,
packageRefs: list,
}
}
@@ -143,12 +125,6 @@ func (s *Snapshot) Key() []byte {
return []byte("S" + s.UUID)
}
// ResourceKey is a unique identifier of the resource
// this snapshot uses. Instead of uuid it uses name
// which needs to be unique as well.
func (s *Snapshot) ResourceKey() []byte {
return []byte("S" + s.Name)
}
// RefKey is a unique id for package reference list
func (s *Snapshot) RefKey() []byte {
+4 -60
View File
@@ -19,7 +19,7 @@ var _ = Suite(&SnapshotSuite{})
func (s *SnapshotSuite) SetUpTest(c *C) {
s.SetUpPackages()
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
s.repo.packageRefs = s.reflist
}
@@ -101,62 +101,6 @@ func (s *SnapshotSuite) TestEncodeDecode(c *C) {
c.Assert(snapshot2.packageRefs, IsNil)
}
func (s *SnapshotSuite) TestSnapshotFromRepositoryAppStream(c *C) {
s.repo.AppStreamFiles = map[string]string{
"main/dep11/Components-amd64.yml.gz": "ab/cd/Components-amd64.yml.gz",
"main/dep11/icons-48x48.tar.gz": "ef/gh/icons-48x48.tar.gz",
}
snapshot, err := NewSnapshotFromRepository("snap-as", s.repo)
c.Assert(err, IsNil)
c.Check(snapshot.AppStreamFiles, DeepEquals, s.repo.AppStreamFiles)
s.repo.AppStreamFiles = nil
snapshot2, err := NewSnapshotFromRepository("snap-no-as", s.repo)
c.Assert(err, IsNil)
c.Check(snapshot2.AppStreamFiles, IsNil)
}
func (s *SnapshotSuite) TestSnapshotFromRefListAppStreamMerge(c *C) {
snap1, _ := NewSnapshotFromRepository("snap1", s.repo)
snap1.AppStreamFiles = map[string]string{
"main/dep11/Components-amd64.yml.gz": "aa/bb/Components-amd64.yml.gz",
"main/dep11/icons-48x48.tar.gz": "cc/dd/icons-48x48.tar.gz",
}
snap2, _ := NewSnapshotFromRepository("snap2", s.repo)
snap2.AppStreamFiles = map[string]string{
"contrib/dep11/Components-amd64.yml.gz": "ee/ff/Components-amd64.yml.gz",
"main/dep11/Components-amd64.yml.gz": "xx/yy/Components-amd64.yml.gz",
}
merged := NewSnapshotFromRefList("merged", []*Snapshot{snap1, snap2}, s.reflist, "Merged")
c.Check(len(merged.AppStreamFiles), Equals, 3)
c.Check(merged.AppStreamFiles["main/dep11/icons-48x48.tar.gz"], Equals, "cc/dd/icons-48x48.tar.gz")
c.Check(merged.AppStreamFiles["contrib/dep11/Components-amd64.yml.gz"], Equals, "ee/ff/Components-amd64.yml.gz")
c.Check(merged.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Equals, "xx/yy/Components-amd64.yml.gz")
snap3, _ := NewSnapshotFromRepository("snap3", s.repo)
snap3.AppStreamFiles = nil
snap4, _ := NewSnapshotFromRepository("snap4", s.repo)
snap4.AppStreamFiles = nil
merged2 := NewSnapshotFromRefList("merged2", []*Snapshot{snap3, snap4}, s.reflist, "Merged2")
c.Check(merged2.AppStreamFiles, IsNil)
}
func (s *SnapshotSuite) TestEncodeDecodeAppStream(c *C) {
snapshot, _ := NewSnapshotFromRepository("snap-as-enc", s.repo)
snapshot.AppStreamFiles = map[string]string{
"main/dep11/Components-amd64.yml.gz": "ab/cd/Components-amd64.yml.gz",
"main/dep11/icons-48x48.tar.gz": "ef/gh/icons-48x48.tar.gz",
}
decoded := &Snapshot{}
c.Assert(decoded.Decode(snapshot.Encode()), IsNil)
c.Check(decoded.Name, Equals, snapshot.Name)
c.Check(decoded.AppStreamFiles, DeepEquals, snapshot.AppStreamFiles)
}
type SnapshotCollectionSuite struct {
PackageListMixinSuite
db database.Storage
@@ -174,11 +118,11 @@ func (s *SnapshotCollectionSuite) SetUpTest(c *C) {
s.collection = NewSnapshotCollection(s.db)
s.SetUpPackages()
s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
s.repo1.packageRefs = s.reflist
s.snapshot1, _ = NewSnapshotFromRepository("snap1", s.repo1)
s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false, false)
s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false)
s.repo2.packageRefs = s.reflist
s.snapshot2, _ = NewSnapshotFromRepository("snap2", s.repo2)
@@ -279,7 +223,7 @@ func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
c.Check(s.collection.ByRemoteRepoSource(s.repo1), DeepEquals, []*Snapshot{s.snapshot1})
c.Check(s.collection.ByRemoteRepoSource(s.repo2), DeepEquals, []*Snapshot{s.snapshot2})
repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false, false)
repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false)
c.Check(s.collection.ByRemoteRepoSource(repo3), DeepEquals, []*Snapshot(nil))
}
Vendored
+33
View File
@@ -0,0 +1,33 @@
aptly (1.6.0+ds1-1) unstable; urgency=medium
- aptly-api: configuration file is now /etc/aptly.conf, and `rootDir`
defaults to `~/.aptly`
- aptly-api: default port is 8080, as declared in
`/etc/default/aptly-api`
- aptly: swagger support is disabled, but will be re-enabled after the
corresponding golang packages make it to unstable
- aptly: default format is yaml, but the old format is still supported
for now
-- Sebastien Delafond <seb@debian.org> Sun, 29 Dec 2024 08:46:07 +0100
aptly (1.3.0+ds1-2.2) unstable; urgency=medium
This version tries to fix the database backwards compatibility,
so you don't need to rebuild the database if you upgrade from
aptly <= 1.3.0-6.
However some fields are missing, like created time of a snapshot.
-- Shengjing Zhu <zhsj@debian.org> Sat, 13 Apr 2019 23:26:39 +0800
aptly (1.3.0+ds1-2) unstable; urgency=medium
* The database created by aptly <= 1.3.0-6 is not compatible
with greater versions. Users must create a new database.
The `aptly db recover` will not fix the issue.
-- Alexandre Viau <aviau@debian.org> Fri, 26 Oct 2018 13:20:53 -0400
+2 -1
View File
@@ -83,8 +83,9 @@ serve_in_api_mode: false
# Enable metrics for Prometheus client
enable_metrics_endpoint: false
# Not implemented in this version.
# Enable API documentation on /docs
enable_swagger_endpoint: false
#enable_swagger_endpoint: false
# OBSOLETE: use via url param ?_async=true
async_api: false
+514 -43
View File
@@ -1,51 +1,522 @@
aptly (1.6.2) stable; urgency=medium
aptly (1.6.2-3) unstable; urgency=medium
* doc: add swagger doc for /api/gpg/key (https://github.com/aptly-dev/aptly/pull/1456)
* bash-completion: include global options in aptly command completions (https://github.com/aptly-dev/aptly/pull/1452)
* Bump golang.org/x/net from 0.33.0 to 0.38.0 (https://github.com/aptly-dev/aptly/pull/1443)
* Bump golang.org/x/crypto from 0.31.0 to 0.35.0 (https://github.com/aptly-dev/aptly/pull/1441)
* Remove corrupt package references in `db recover` (https://github.com/aptly-dev/aptly/pull/1445)
* Fix upload of unchanged packages in S3 (https://github.com/aptly-dev/aptly/pull/1440)
* use go 1.24 (https://github.com/aptly-dev/aptly/pull/1439)
[ Sébastien Delafond ]
* tests: disable t04_mirror/create/CreateMirror18Test (Closes: #1135740)
* tests: disable t12_api/gpg/GPGAPITestAddKey (Closes: #1135672)
* d/control: bump-up Standards-Version
-- André Roth <neolynx@gmail.com> Mon, 09 Jun 2025 13:45:15 +0200
-- Sebastien Delafond <seb@debian.org> Tue, 05 May 2026 18:14:44 +0200
aptly (1.6.1) stable; urgency=medium
aptly (1.6.2-2) unstable; urgency=medium
* update golang-github-syndtr-goleveldb-dev dependency (v1.0.1-0.20220721030215-126854af5e6d) to fix segfault on arm64
(bug in golang-github-golang-snappy-dev)
* allow snapshotting empty mirrors again (regression)
* debian compliance: add postrm (note: `apt purge aptly-api` will remove all data in ~aptly-api/)
* update other dependencies (x/net 0.33.0, gin-gonic/gin 1.9.1)
[ Sébastien Delafond ]
* Remove Built-Using
* Mark patches as "Forwarded: not-needed"
-- André Roth <neolynx@gmail.com> Sat, 15 Feb 2025 13:03:16 +0100
-- Sebastien Delafond <seb@debian.org> Fri, 21 Nov 2025 15:46:51 +0100
aptly (1.6.0) stable; urgency=medium
aptly (1.6.2-1) unstable; urgency=medium
* support reading filters from file or stdin
* fix mirroring source packages
* support yaml config per default
* provide swagger API documentation
* provide API for querying aptly storage usage
* provide snapshot pull via API
* support creating repos from snapshots
* fix mirroring flat remote repos
* support skeleton files for publishing
* use new azure sdk
* support updating the components of a published repo
* support publishing multiple distributions (-multi-dist)
* support etcd database
* allow slash (/) in distribution names
* support for storing the "local" pool on Azure
* provide copy package API
* fix publish concurrency and improve performance
* improved mirroring
* fix download throttling
* fix resuming package downloads
* fix ignoring signatures
* fix packages dependency resolution (Virtual Packages, version numbers in Provides)
* improved S3 support and performance
* fix race condition with goleveldb
* use go 1.22
[ Sébastien Delafond ]
* d/watch: v5
* Bump up Standards-Version
* Remove +ds suffix
* Add Static-Built-Using
* New upstream version 1.6.2
-- André Roth <neolynx@gmail.com> Tue, 24 Dec 2024 17:44:35 +0100
-- Sebastien Delafond <seb@debian.org> Wed, 24 Sep 2025 06:19:54 +0200
aptly (1.6.1+ds1-3) unstable; urgency=medium
[ Sébastien Delafond ]
* tests: declare needs-internet for system-test
* tests: disable unit test TestVerifyClearsigned & system test
CreateMirror31Test (Closes: #1108828)
-- Sebastien Delafond <seb@debian.org> Tue, 08 Jul 2025 14:12:52 +0200
aptly (1.6.1+ds1-2) unstable; urgency=medium
[ Sébastien Delafond ]
* Do not re-publish unchanged files to S3 every single time (Closes: #1104299)
-- Sebastien Delafond <seb@debian.org> Mon, 28 Apr 2025 15:38:43 +0200
aptly (1.6.1+ds1-1) unstable; urgency=medium
[ Sébastien Delafond ]
* New upstream version 1.6.1
-- Sebastien Delafond <seb@debian.org> Mon, 24 Feb 2025 09:04:35 +0100
aptly (1.6.0+ds1-8) unstable; urgency=medium
[ Sébastien Delafond ]
* tests: disable riscv64-flaky EditRepo4Test
-- Sebastien Delafond <seb@debian.org> Fri, 21 Feb 2025 06:54:18 +0100
aptly (1.6.0+ds1-7) unstable; urgency=medium
[ Sébastien Delafond ]
* tests: clean way to disable single tests, disable s390-flaky CreateMirror35Test
-- Sebastien Delafond <seb@debian.org> Thu, 20 Feb 2025 07:26:41 +0100
aptly (1.6.0+ds1-6) unstable; urgency=medium
[ Sébastien Delafond ]
* tests: disable system-test's etcd tests as the corresponding fixture is also arch-specific
-- Sebastien Delafond <seb@debian.org> Wed, 19 Feb 2025 14:02:19 +0100
aptly (1.6.0+ds1-5) unstable; urgency=medium
[ Sébastien Delafond ]
* tests: do not use upstream's etcd installer
-- Sebastien Delafond <seb@debian.org> Wed, 19 Feb 2025 07:20:44 +0100
aptly (1.6.0+ds1-4) unstable; urgency=medium
[ Sébastien Delafond ]
* postrm: remove aptly-api user and home directory on purge
* tests: use extensive coverage from make's test and system-test
-- Sebastien Delafond <seb@debian.org> Tue, 18 Feb 2025 10:36:13 +0100
aptly (1.6.0+ds1-3) unstable; urgency=medium
[ Sébastien Delafond ]
* aptly.conf: fix s3 example
-- Sebastien Delafond <seb@debian.org> Mon, 13 Jan 2025 14:51:29 +0100
aptly (1.6.0+ds1-2) unstable; urgency=medium
[ Sébastien Delafond ]
* Document disabled swagger in default config file
* Rediff patches: swagger references in man page
-- Sebastien Delafond <seb@debian.org> Mon, 30 Dec 2024 11:11:07 +0100
aptly (1.6.0+ds1-1) unstable; urgency=medium
[ Sébastien Delafond ]
* Official 1.6 release
-- Sebastien Delafond <seb@debian.org> Fri, 27 Dec 2024 14:23:29 +0100
aptly (1.6.0+ds1~beta3-1) experimental; urgency=medium
[ Sébastien Delafond ]
* Latest upstream version
-- Sebastien Delafond <seb@debian.org> Wed, 11 Dec 2024 18:16:19 +0100
aptly (1.6.0+ds1~beta2-1) experimental; urgency=medium
[ Sébastien Delafond ]
* Latest upstream version
[ André Roth ]
* set systemd service file limit to 32768
-- Sebastien Delafond <seb@debian.org> Thu, 21 Nov 2024 16:08:05 +0100
aptly (1.6.0+ds1~beta1-1) experimental; urgency=medium
[ Sébastien Delafond ]
* Latest upstream version
* Disable "use new azure-sdk"
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 18:52:06 +0100
aptly (1.6.0+ds1~alpha5-1) experimental; urgency=medium
* Sort out dependencies on gpg*
* Reduce diff with upstream some more
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 15:59:23 +0100
aptly (1.6.0+ds1~alpha4-1) experimental; urgency=medium
[ Sébastien Delafond ]
* Remove build-dep on git
* Adjust systemd unit for aptly-api
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 14:51:49 +0100
aptly (1.6.0+ds1~alpha3-1) experimental; urgency=medium
[ Sébastien Delafond ]
* Latest upstream version
* Remove useless maintainer scripts
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 07:50:06 +0100
aptly (1.6.0+ds1~alpha2-1) experimental; urgency=medium
[ Sébastien Delafond ]
* Latest upstream version
-- Sebastien Delafond <seb@debian.org> Sat, 16 Nov 2024 14:42:10 +0100
aptly (1.6.0+ds1~alpha1-2) experimental; urgency=medium
[ Sébastien Delafond ]
* Add zsh completion
* aptly-api: revert arch:all, so mv_conffile does the right thing
-- Sebastien Delafond <seb@debian.org> Wed, 16 Oct 2024 09:10:05 +0200
aptly (1.6.0+ds1~alpha1-1) experimental; urgency=medium
[ Sébastien Delafond ]
* d/patches: remove old patch, and disable swagger support
* d/control: bump up Standards-Version
* Adjust packaging for 1.6.0~alpha1
-- Sebastien Delafond <seb@debian.org> Tue, 15 Oct 2024 14:40:57 +0200
aptly (1.5.0+ds1-2) unstable; urgency=medium
[ Shengjing Zhu ]
* Replace golang-gopkg-cheggaaa-pb.v1-dev with
golang-github-cheggaaa-pb-dev (Closes: #1050900)
-- Sebastien Delafond <seb@debian.org> Mon, 04 Sep 2023 08:49:36 +0200
aptly (1.5.0+ds1-1) unstable; urgency=medium
* Team upload.
* New upstream release (Closes: #1022721), including fix for "Order of
fields in Packages/Sources is unpredictable" (Closes: #907121).
-- Roland Mas <lolando@debian.org> Tue, 31 Jan 2023 14:47:04 +0100
aptly (1.4.0+ds1-7) unstable; urgency=medium
* Team upload.
* Add support for zstd compression (Closes: #1010465)
-- Anton Gladky <gladk@debian.org> Tue, 17 May 2022 22:42:29 +0200
aptly (1.4.0+ds1-6) unstable; urgency=medium
* Conflict on gpgv1 (Closes: #990821)
-- Sebastien Delafond <seb@debian.org> Thu, 04 Nov 2021 10:24:53 +0100
aptly (1.4.0+ds1-5) unstable; urgency=medium
* Conflict on gnupg1 (Closes: #990821)
-- Sebastien Delafond <seb@debian.org> Thu, 14 Oct 2021 18:43:04 +0200
aptly (1.4.0+ds1-4) unstable; urgency=medium
* Install correct bash completion snippet (Closes: #984979)
-- Sebastien Delafond <seb@debian.org> Thu, 11 Mar 2021 15:20:57 +0100
aptly (1.4.0+ds1-3) unstable; urgency=medium
* Fix s3 etag issue (Closes: #983877)
* Bump-up d/watch version
* Bump-up Standards-Version
-- Sebastien Delafond <seb@debian.org> Wed, 03 Mar 2021 10:50:51 +0100
aptly (1.4.0+ds1-2) unstable; urgency=medium
* Use pipeline from salsa-ci-team
* Allow reprotest failure
* Pass version from d/rules (Closes: #968585)
-- Sebastien Delafond <seb@debian.org> Fri, 21 Aug 2020 10:13:44 +0200
aptly (1.4.0+ds1-1) unstable; urgency=medium
* New upstream version 1.4.0+ds1
* Rediff patches
* Depend on gnupg 2
-- Sebastien Delafond <seb@debian.org> Sun, 22 Dec 2019 15:16:25 +0100
aptly (1.3.0+ds1-4) unstable; urgency=medium
[ Debian Janitor ]
* Rename obsolete path debian/tests/control.autodep8 to debian/tests/control.
* Use secure URI in Homepage field.
* Bump debhelper from old 11 to 12.
* Set debhelper-compat version in Build-Depends.
[ Sébastien Delafond ]
* Bump up Standards-Version
-- Sebastien Delafond <seb@debian.org> Sun, 22 Dec 2019 14:10:19 +0100
aptly (1.3.0+ds1-3) unstable; urgency=medium
* Build-Depend on golang-golang-x-tools-dev instead of golang-go.tools (Closes: #945884)
* Lintian fix
-- Sebastien Delafond <seb@debian.org> Sat, 21 Dec 2019 10:29:09 +0100
aptly (1.3.0+ds1-2.3) unstable; urgency=medium
* Non-maintainer upload.
* Remove myself from uploaders.
-- Alexandre Viau <aviau@debian.org> Sun, 15 Sep 2019 19:27:47 -0400
aptly (1.3.0+ds1-2.2) unstable; urgency=medium
* Non-maintainer upload.
* Add patch to fix DB backwards compatibility (Closes: #911924)
* Fix struct field tag typo
* Update debian/NEWS about DB compatibility
-- Shengjing Zhu <zhsj@debian.org> Tue, 16 Apr 2019 00:18:23 +0800
aptly (1.3.0+ds1-2.1) unstable; urgency=medium
[ Shengjing Zhu ]
* Non-maintainer upload.
* Add patch to fix UUID struct field not encoded in msgpack (Closes: #923866)
[ Tobias Frost ]
* Prepare upload.
-- Tobias Frost <tobi@debian.org> Fri, 05 Apr 2019 17:19:14 +0200
aptly (1.3.0+ds1-2) unstable; urgency=medium
* Add NEWS to warn about database compatibility.
-- Alexandre Viau <aviau@debian.org> Fri, 26 Oct 2018 13:22:38 -0400
aptly (1.3.0+ds1-1) unstable; urgency=medium
[ Ondřej Nový ]
* d/changelog: Remove trailing whitespaces
[ Alexandre Viau ]
* d/watch: Append +ds suffix.
* d/copyright: ignore vendor/*. (Closes: #902128)
* d/copyright: remove vendor/* sections.
* d/copyright: MIT -> Expat.
* d/control: add vendor/* build dependencies.
* Patch: Use Debian's uuid package.
* Patch: Use Debian's lzma package.
* d/rules: remove trailing whitespace.
-- Alexandre Viau <aviau@debian.org> Mon, 15 Oct 2018 11:54:03 -0400
aptly (1.3.0-6) unstable; urgency=medium
* Combine autodep8 and autopkgtest.
-- Alexandre Viau <aviau@debian.org> Fri, 29 Jun 2018 18:11:41 -0400
aptly (1.3.0-5) unstable; urgency=medium
* Fix syntax-error-in-dep5-copyright.
* Fix unnecessary-testsuite-autopkgtest-field.
* Fix broken autopkgtest.
* Depend on gpgv1.
-- Alexandre Viau <aviau@debian.org> Tue, 26 Jun 2018 23:01:36 -0400
aptly (1.3.0-4) unstable; urgency=medium
* Document #902128 in debian/copyright
* Point debian/watch to new git repo on GitHub
* Add simple autopkgtest
* Add debian/.gitlab-ci.yml
* Depend on gnupg1 (Closes: #902419)
-- Sebastien Delafond <seb@debian.org> Tue, 26 Jun 2018 14:24:34 +0200
aptly (1.3.0-3) unstable; urgency=medium
* Create aptly-api package. (Closes: #902032)
-- Alexandre Viau <aviau@debian.org> Fri, 22 Jun 2018 13:51:50 -0400
aptly (1.3.0-2) unstable; urgency=medium
* Team upload, many thanks to Alexandre Viau for this work
* Switch to dh-golang. (Closes: #902038)
* Fix vcs-field-not-canonical.
* Fix insecure-copyright-format-uri.
-- Sebastien Delafond <seb@debian.org> Fri, 22 Jun 2018 13:53:11 +0200
aptly (1.3.0-1) unstable; urgency=medium
* Fix copyright
* New upstream version 1.3.0
-- Sebastien Delafond <seb@debian.org> Thu, 21 Jun 2018 15:14:57 +0200
aptly (1.2.0-4) unstable; urgency=medium
* Enable Built-Using in debian/control (thanks M. Staperberg)
-- Sebastien Delafond <seb@debian.org> Tue, 06 Mar 2018 10:10:42 +0100
aptly (1.2.0-3) unstable; urgency=medium
* Switch to DEP 14
* Update Vcs-* to point to salsa.d.o
-- Sebastien Delafond <seb@debian.org> Fri, 16 Feb 2018 17:02:25 +0100
aptly (1.2.0-2) unstable; urgency=medium
* Team upload.
* Build-Depend on golang-any, not golang
* Set XS-Go-Import-Path
-- Michael Stapelberg <stapelberg@debian.org> Sat, 10 Feb 2018 18:41:37 +0100
aptly (1.2.0-1) unstable; urgency=medium
* New upstream version 1.2.0
-- Sebastien Delafond <seb@debian.org> Wed, 03 Jan 2018 13:43:30 +0100
aptly (1.1.1-2) unstable; urgency=medium
* Support both uncompressed control.tar, and xz-compressed
control.tar.xz. Thanks to Boyuan Yang for the patch (Closes: 879718)
-- Sebastien Delafond <seb@debian.org> Thu, 02 Nov 2017 13:23:29 +0100
aptly (1.1.1-1) unstable; urgency=medium
* New upstream version 1.1.1
* Use bash-completion snippet from upstream
* Bump up Standards-Version
* Update debian/copyright
-- Sebastien Delafond <seb@debian.org> Thu, 02 Nov 2017 09:03:23 +0100
aptly (1.0.1-1) unstable; urgency=medium
* Imported Upstream version 1.0.1
* Update debian/copyright
* Adapt debian/rules
* Update Vcs-Git
* Bump-up Standards-Version
-- Sebastien Delafond <seb@debian.org> Tue, 18 Jul 2017 11:53:29 +0200
aptly (0.9.7-1) unstable; urgency=medium
* Imported new upstream version 0.9.7
* Add new licenses and copyrights info
-- Sebastien Delafond <seb@debian.org> Tue, 24 May 2016 09:17:08 +0200
aptly (0.9.6-1) unstable; urgency=medium
* Import new upstream version 0.9.6
* Add dependency on xz-utils
* Licenses and copyrights
-- Sebastien Delafond <seb@debian.org> Wed, 10 Feb 2016 18:28:51 +0100
aptly (0.9.5-2) unstable; urgency=medium
* Remove empty component in GOPATH (Closes: #793838)
[ Sebastien Delafond ]
-- Sebastien Delafond <seb@debian.org> Sun, 16 Aug 2015 18:42:22 +0200
aptly (0.9.5-1) unstable; urgency=medium
* Imported Upstream version 0.9.5
* Up-to-date bash completion
-- Sebastien Delafond <seb@debian.org> Fri, 15 May 2015 10:46:51 +0200
aptly (0.9.1-1) unstable; urgency=medium
* Imported Upstream version 0.9.1
* Document licenses and copyrights for new dependencies
* Suggest graphviz
* Proper name for bash-completion file
* Bump-up Standards-Version
-- Sebastien Delafond <seb@debian.org> Sat, 02 May 2015 12:57:16 +0200
aptly (0.8-3) unstable; urgency=medium
* Correct Vcs-Browser entry (Closes: #764622)
* Correct dependency from "gpg" to "gnupg" (Closes: #764619)
-- Sebastien Delafond <seb@debian.org> Thu, 09 Oct 2014 18:41:38 +0200
aptly (0.8-2) unstable; urgency=medium
* Add missing dependencies on bzip2, gpg and gpgv
-- Sebastien Delafond <seb@debian.org> Thu, 09 Oct 2014 11:15:21 +0200
aptly (0.8-1) unstable; urgency=medium
* Imported Upstream version 0.8
* Document new copyrights and licenses
* Add bash completion snippet
* Reference full paths in debian/copyright, and remove unused
references, so lintian does not complain
-- Sebastien Delafond <seb@debian.org> Sat, 04 Oct 2014 17:46:24 +0200
aptly (0.7.1-1) unstable; urgency=medium
* Imported Upstream version 0.7.1
* Add copyright information for new libraries
-- Sebastien Delafond <seb@debian.org> Wed, 10 Sep 2014 10:03:27 +0200
aptly (0.5-5) unstable; urgency=medium
* Turn off verbose mode
* Correct Vcs-* information
* gocov is licensed MIT, not BSD-3
* Add missing MIT license text
-- Sebastien Delafond <seb@debian.org> Tue, 09 Sep 2014 09:20:40 +0200
aptly (0.5-4) unstable; urgency=medium
* Collect licenses by going over files one by one
* Use packaged golang-go.tools
-- Sebastien Delafond <seb@debian.org> Thu, 10 Jul 2014 00:49:09 +0200
aptly (0.5-3) unstable; urgency=low
* Licensing:
- goleveldb is BSD-2, not BSD-3
- _vendor/src/code.google.com/p/gographviz/scanner/scanner is BSD-3,
copyright 2009 The Go Authors
-- Sebastien Delafond <seb@debian.org> Wed, 28 May 2014 10:04:01 +0200
aptly (0.5-2) unstable; urgency=low
* Go interpreter not needed at runtime
-- Sebastien Delafond <seb@debian.org> Fri, 02 May 2014 12:04:02 +0200
aptly (0.5-1) unstable; urgency=low
* Initial release (Closes: #746343)
-- Sebastien Delafond <seb@debian.org> Tue, 29 Apr 2014 15:47:31 +0200
+7 -16
View File
@@ -1,7 +1,7 @@
Source: aptly
Section: utils
Priority: optional
Maintainer: André Roth <neolynx@gmail.com>
Maintainer: Sebastien Delafond <seb@debian.org>
Build-Depends: bash-completion,
debhelper-compat (= 13),
dh-golang,
@@ -77,20 +77,18 @@ Build-Depends: bash-completion,
golang-go.uber-zap-dev,
golang-etcd-server-dev (>= 3.5.15-7),
golang-gopkg-yaml.v3-dev,
git
Standards-Version: 4.7.0
Standards-Version: 4.7.4
Homepage: https://www.aptly.info
Vcs-Git: https://github.com/aptly-dev/aptly.git
Vcs-Browser: https://github.com/aptly-dev/aptly
Vcs-Git: https://salsa.debian.org/debian/aptly.git
Vcs-Browser: https://salsa.debian.org/debian/aptly
XS-Go-Import-Path: github.com/aptly-dev/aptly
Testsuite: autopkgtest-pkg-go
Package: aptly
Architecture: any
Depends: ${misc:Depends}, ${shlibs:Depends}, bzip2, xz-utils, gpgv, gpg
Suggests: graphviz
Static-Built-Using: ${misc:Static-Built-Using}
Conflicts: gnupg1, gpgv1
Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using}
Suggests: graphviz
Description: Swiss army knife for Debian repository management - main package
It offers several features making it easy to manage Debian package
repositories:
@@ -107,7 +105,7 @@ Description: Swiss army knife for Debian repository management - main package
This is the main package, it contains the aptly command-line utility.
Package: aptly-api
Architecture: any
Architecture: all
Depends: ${misc:Depends}, aptly
Description: Swiss army knife for Debian repository management - API
It offers several features making it easy to manage Debian package
@@ -123,10 +121,3 @@ Description: Swiss army knife for Debian repository management - API
- merge two or more snapshots into one
.
This package contains the aptly-api service.
Package: aptly-dbg
Architecture: any
Depends: ${misc:Depends}
Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using}
Description: Debian repository management tool (debug files)
Debug symbols for aptly
+3
View File
@@ -0,0 +1,3 @@
[DEFAULT]
debian-branch = debian/master
upstream-branch = upstream/latest
+1
View File
@@ -0,0 +1 @@
usr/bin/files
+123
View File
@@ -0,0 +1,123 @@
From: =?utf-8?q?Andr=C3=A9_Roth?= <neolynx@gmail.com>
Date: Tue, 15 Oct 2024 12:09:33 +0200
Subject: Disable swagger
This can be enabled once the following modules make it into Debian:
- github.com/swaggo/files v1.0.1
- github.com/swaggo/gin-swagger v1.6.0
- github.com/swaggo/swag v1.16.3
Forwarded: not-needed
---
api/router.go | 22 +++++++++++-----------
docs/index.go | 10 ----------
docs/index.go.disabled | 10 ++++++++++
man/aptly.1 | 3 ++-
man/aptly.1.ronn.tmpl | 3 ++-
5 files changed, 25 insertions(+), 23 deletions(-)
delete mode 100644 docs/index.go
create mode 100644 docs/index.go.disabled
diff --git a/api/router.go b/api/router.go
index 3cd7d42..cf53cd2 100644
--- a/api/router.go
+++ b/api/router.go
@@ -11,9 +11,9 @@
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log"
- "github.com/aptly-dev/aptly/docs"
- swaggerFiles "github.com/swaggo/files"
- ginSwagger "github.com/swaggo/gin-swagger"
+ // _ "github.com/aptly-dev/aptly/docs" // import docs
+ // swaggerFiles "github.com/swaggo/files"
+ // ginSwagger "github.com/swaggo/gin-swagger"
)
var context *ctx.AptlyContext
@@ -63,14 +63,14 @@ func Router(c *ctx.AptlyContext) http.Handler {
router.Use(gin.Recovery(), gin.ErrorLogger())
- if c.Config().EnableSwaggerEndpoint {
- router.GET("docs.html", func(c *gin.Context) {
- c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
- })
- router.Use(redirectSwagger)
- url := ginSwagger.URL("/docs/doc.json")
- router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
- }
+ // if c.Config().EnableSwaggerEndpoint {
+ // router.GET("docs.html", func(c *gin.Context) {
+ // c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
+ // })
+ // router.Use(redirectSwagger)
+ // url := ginSwagger.URL("/docs/doc.json")
+ // router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
+ // }
if c.Config().EnableMetricsEndpoint {
MetricsCollectorRegistrar.Register(router)
diff --git a/docs/index.go b/docs/index.go
deleted file mode 100644
index ca4c914..0000000
--- a/docs/index.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package docs
-
-import (
- _ "embed" // embed html below
-
- _ "github.com/swaggo/swag" // make sure swag is in go.mod
-)
-
-//go:embed docs.html
-var DocsHTML []byte
diff --git a/docs/index.go.disabled b/docs/index.go.disabled
new file mode 100644
index 0000000..ca4c914
--- /dev/null
+++ b/docs/index.go.disabled
@@ -0,0 +1,10 @@
+package docs
+
+import (
+ _ "embed" // embed html below
+
+ _ "github.com/swaggo/swag" // make sure swag is in go.mod
+)
+
+//go:embed docs.html
+var DocsHTML []byte
diff --git a/man/aptly.1 b/man/aptly.1
index bd6ad22..11ed990 100644
--- a/man/aptly.1
+++ b/man/aptly.1
@@ -111,8 +111,9 @@ The legacy json configuration is still supported (and also supports comments):
// Enable metrics for Prometheus client
"enableMetricsEndpoint": false,
+ // Not implemented in this version\.
// Enable API documentation on /docs
- "enableSwaggerEndpoint": false,
+ //"enableSwaggerEndpoint": false,
// OBSOLETE: use via url param ?_async=true
"AsyncAPI": false,
diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl
index 203cc7f..ed2c87c 100644
--- a/man/aptly.1.ronn.tmpl
+++ b/man/aptly.1.ronn.tmpl
@@ -100,8 +100,9 @@ The legacy json configuration is still supported (and also supports comments):
// Enable metrics for Prometheus client
"enableMetricsEndpoint": false,
+ // Not implemented in this version.
// Enable API documentation on /docs
- "enableSwaggerEndpoint": false,
+ //"enableSwaggerEndpoint": false,
// OBSOLETE: use via url param ?_async=true
"AsyncAPI": false,
+880
View File
@@ -0,0 +1,880 @@
From: =?utf-8?q?Andr=C3=A9_Roth?= <neolynx@gmail.com>
Date: Sun, 17 Nov 2024 17:58:04 +0100
Subject: Disable new azure-sdk
This reverts commit e2cbd637b82a153a6756f2af0519e8fe769ee9ab.
We can enable this once the golang-github-azure-azure-sdk-for-go-dev
packages are upgraded in Debian:
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
Forwarded: not-needed
---
azure/azure.go | 89 +++++++++++++++-----------------
azure/package_pool.go | 45 ++++++++--------
azure/package_pool_test.go | 8 ++-
azure/public.go | 125 +++++++++++++++++++++------------------------
azure/public_test.go | 49 +++++++++---------
context/context.go | 1 +
deb/list.go | 1 +
go.mod | 8 +--
go.sum | 52 +++++++++++--------
9 files changed, 185 insertions(+), 193 deletions(-)
diff --git a/azure/azure.go b/azure/azure.go
index 3f12678..b313f90 100644
--- a/azure/azure.go
+++ b/azure/azure.go
@@ -5,35 +5,28 @@
import (
"context"
"encoding/hex"
- "errors"
"fmt"
"io"
- "os"
+ "net/url"
"path/filepath"
"time"
- "github.com/Azure/azure-sdk-for-go/sdk/azcore"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
+ "github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
)
func isBlobNotFound(err error) bool {
- var respErr *azcore.ResponseError
- if errors.As(err, &respErr) {
- return respErr.StatusCode == 404 // BlobNotFound
- }
- return false
+ storageError, ok := err.(azblob.StorageError)
+ return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
}
type azContext struct {
- client *azblob.Client
- container string
+ container azblob.ContainerURL
prefix string
}
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
- cred, err := azblob.NewSharedKeyCredential(accountName, accountKey)
+ credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
if err != nil {
return nil, err
}
@@ -42,14 +35,15 @@ func newAzContext(accountName, accountKey, container, prefix, endpoint string) (
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
}
- serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil)
+ url, err := url.Parse(fmt.Sprintf("%s/%s", endpoint, container))
if err != nil {
return nil, err
}
+ containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
+
result := &azContext{
- client: serviceClient,
- container: container,
+ container: containerURL,
prefix: prefix,
}
@@ -60,6 +54,10 @@ func (az *azContext) blobPath(path string) string {
return filepath.Join(az.prefix, path)
}
+func (az *azContext) blobURL(path string) azblob.BlobURL {
+ return az.container.NewBlobURL(az.blobPath(path))
+}
+
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
const delimiter = "/"
paths = make([]string, 0, 1024)
@@ -69,33 +67,27 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
prefix += delimiter
}
- ctx := context.Background()
- maxResults := int32(1)
- pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{
- Prefix: &prefix,
- MaxResults: &maxResults,
- Include: azblob.ListBlobsInclude{Metadata: true},
- })
-
- // Iterate over each page
- for pager.More() {
- page, err := pager.NextPage(ctx)
+ for marker := (azblob.Marker{}); marker.NotDone(); {
+ listBlob, err := az.container.ListBlobsFlatSegment(
+ context.Background(), marker, azblob.ListBlobsSegmentOptions{
+ Prefix: prefix,
+ MaxResults: 1,
+ Details: azblob.BlobListingDetails{Metadata: true}})
if err != nil {
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
}
- for _, blob := range page.Segment.BlobItems {
- if prefix == "" {
- paths = append(paths, *blob.Name)
- } else {
- name := *blob.Name
- paths = append(paths, name[len(prefix):])
- }
- b := *blob
- md5 := b.Properties.ContentMD5
- md5s = append(md5s, fmt.Sprintf("%x", md5))
+ marker = listBlob.NextMarker
+ for _, blob := range listBlob.Segment.BlobItems {
+ if prefix == "" {
+ paths = append(paths, blob.Name)
+ } else {
+ paths = append(paths, blob.Name[len(prefix):])
+ }
+ md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
}
+
if progress != nil {
time.Sleep(time.Duration(500) * time.Millisecond)
progress.AddBar(1)
@@ -105,27 +97,28 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
return paths, md5s, nil
}
-func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error {
- uploadOptions := &azblob.UploadFileOptions{
- BlockSize: 4 * 1024 * 1024,
- Concurrency: 8,
+func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
+ uploadOptions := azblob.UploadStreamToBlockBlobOptions{
+ BufferSize: 4 * 1024 * 1024,
+ MaxBuffers: 8,
}
- path := az.blobPath(blobName)
if len(sourceMD5) > 0 {
decodedMD5, err := hex.DecodeString(sourceMD5)
if err != nil {
return err
}
- uploadOptions.HTTPHeaders = &blob.HTTPHeaders{
- BlobContentMD5: decodedMD5,
+ uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
+ ContentMD5: decodedMD5,
}
}
- var err error
- if file, ok := source.(*os.File); ok {
- _, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions)
- }
+ _, err := azblob.UploadStreamToBlockBlob(
+ context.Background(),
+ source,
+ blob.ToBlockBlobURL(),
+ uploadOptions,
+ )
return err
}
diff --git a/azure/package_pool.go b/azure/package_pool.go
index 97be8e6..6d7af1a 100644
--- a/azure/package_pool.go
+++ b/azure/package_pool.go
@@ -5,6 +5,7 @@
"os"
"path/filepath"
+ "github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
"github.com/pkg/errors"
@@ -40,7 +41,10 @@ func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.Checksu
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
}
-func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) {
+func (pool *PackagePool) ensureChecksums(
+ poolPath string,
+ checksumStorage aptly.ChecksumStorage,
+) (*utils.ChecksumInfo, error) {
targetChecksums, err := checksumStorage.Get(poolPath)
if err != nil {
return nil, err
@@ -48,7 +52,8 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
if targetChecksums == nil {
// we don't have checksums stored yet for this file
- download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil)
+ blob := pool.az.blobURL(poolPath)
+ download, err := blob.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
if err != nil {
if isBlobNotFound(err) {
return nil, nil
@@ -58,7 +63,7 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
}
targetChecksums = &utils.ChecksumInfo{}
- *targetChecksums, err = utils.ChecksumsForReader(download.Body)
+ *targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
if err != nil {
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
}
@@ -87,49 +92,46 @@ func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, er
}
func (pool *PackagePool) Size(path string) (int64, error) {
- serviceClient := pool.az.client.ServiceClient()
- containerClient := serviceClient.NewContainerClient(pool.az.container)
- blobClient := containerClient.NewBlobClient(path)
-
- props, err := blobClient.GetProperties(context.TODO(), nil)
+ blob := pool.az.blobURL(path)
+ props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
}
- return *props.ContentLength, nil
+ return props.ContentLength(), nil
}
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
+ blob := pool.az.blobURL(path)
+
temp, err := os.CreateTemp("", "blob-download")
if err != nil {
- return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
+ return nil, errors.Wrap(err, "error creating temporary file for blob download")
}
+
defer func () { _ = os.Remove(temp.Name()) }()
- _, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
+ err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
if err != nil {
- return nil, errors.Wrapf(err, "error downloading blob %s", path)
+ return nil, errors.Wrapf(err, "error downloading blob at %s", path)
}
return temp, nil
}
func (pool *PackagePool) Remove(path string) (int64, error) {
- serviceClient := pool.az.client.ServiceClient()
- containerClient := serviceClient.NewContainerClient(pool.az.container)
- blobClient := containerClient.NewBlobClient(path)
-
- props, err := blobClient.GetProperties(context.TODO(), nil)
+ blob := pool.az.blobURL(path)
+ props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
- return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
+ return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
}
- _, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil)
+ _, err = blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
if err != nil {
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
}
- return *props.ContentLength, nil
+ return props.ContentLength(), nil
}
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
@@ -143,6 +145,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
}
path := pool.buildPoolPath(basename, checksums)
+ blob := pool.az.blobURL(path)
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
if err != nil {
return "", err
@@ -158,7 +161,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
}
defer func() { _ = source.Close() }()
- err = pool.az.putFile(path, source, checksums.MD5)
+ err = pool.az.putFile(blob, source, checksums.MD5)
if err != nil {
return "", err
}
diff --git a/azure/package_pool_test.go b/azure/package_pool_test.go
index ef562cb..6b1341d 100644
--- a/azure/package_pool_test.go
+++ b/azure/package_pool_test.go
@@ -7,7 +7,7 @@
"path/filepath"
"runtime"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
+ "github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/files"
"github.com/aptly-dev/aptly/utils"
@@ -50,10 +50,8 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
c.Assert(err, IsNil)
- publicAccessType := azblob.PublicAccessTypeContainer
- _, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
- Access: &publicAccessType,
- })
+ cnt := s.pool.az.container
+ _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
c.Assert(err, IsNil)
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
diff --git a/azure/public.go b/azure/public.go
index 6775e14..efd2e7a 100644
--- a/azure/public.go
+++ b/azure/public.go
@@ -3,22 +3,21 @@
import (
"context"
"fmt"
+ "net/http"
"os"
"path/filepath"
"time"
- "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease"
+ "github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
- "github.com/google/uuid"
"github.com/pkg/errors"
)
// PublishedStorage abstract file system with published files (actually hosted on Azure)
type PublishedStorage struct {
// FIXME: unused ???? prefix string
+ container azblob.ContainerURL
az *azContext
pathCache map[string]map[string]string
}
@@ -67,7 +66,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
}
defer func() { _ = source.Close() }()
- err = storage.az.putFile(path, source, sourceMD5)
+ err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
}
@@ -77,15 +76,14 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
// RemoveDirs removes directory structure under public path
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
- path = storage.az.blobPath(path)
filelist, err := storage.Filelist(path)
if err != nil {
return err
}
for _, filename := range filelist {
- blob := filepath.Join(path, filename)
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil)
+ blob := storage.az.blobURL(filepath.Join(path, filename))
+ _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
if err != nil {
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
}
@@ -96,8 +94,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error
// Remove removes single file under public path
func (storage *PublishedStorage) Remove(path string) error {
- path = storage.az.blobPath(path)
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil)
+ blob := storage.az.blobURL(path)
+ _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
}
@@ -116,8 +114,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
relFilePath := filepath.Join(publishedRelPath, fileName)
- prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
- poolPath := storage.az.blobPath(prefixRelFilePath)
+ // prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
+ // FIXME: check how to integrate publishedPrefix:
+ poolPath := storage.az.blobPath(fileName)
if storage.pathCache == nil {
storage.pathCache = make(map[string]map[string]string)
@@ -160,7 +159,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
}
defer func() { _ = source.Close() }()
- err = storage.az.putFile(relFilePath, source, sourceMD5)
+ err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
if err == nil {
pathCache[relFilePath] = sourceMD5
} else {
@@ -177,60 +176,59 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
}
// Internal copy or move implementation
-func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error {
+func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
const leaseDuration = 30
- leaseID := uuid.NewString()
- serviceClient := storage.az.client.ServiceClient()
- containerClient := serviceClient.NewContainerClient(storage.az.container)
- srcBlobClient := containerClient.NewBlobClient(src)
- blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)})
- if err != nil {
- return fmt.Errorf("error acquiring lease on source blob %s", src)
- }
-
- _, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil)
- if err != nil {
- return fmt.Errorf("error acquiring lease on source blob %s", src)
+ dstBlobURL := storage.az.blobURL(dst)
+ srcBlobURL := storage.az.blobURL(src)
+ leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
+ if err != nil || leaseResp.StatusCode() != http.StatusCreated {
+ return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
}
defer func() {
- _, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
+ _, _ = srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{})
}()
+ srcBlobLeaseID := leaseResp.LeaseID()
- dstBlobClient := containerClient.NewBlobClient(dst)
- copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
- Metadata: metadata,
- })
-
+ copyResp, err := dstBlobURL.StartCopyFromURL(
+ context.Background(),
+ srcBlobURL.URL(),
+ metadata,
+ azblob.ModifiedAccessConditions{},
+ azblob.BlobAccessConditions{},
+ azblob.DefaultAccessTier,
+ nil)
if err != nil {
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
}
- copyStatus := *copyResp.CopyStatus
+ copyStatus := copyResp.CopyStatus()
for {
- if copyStatus == blob.CopyStatusTypeSuccess {
+ if copyStatus == azblob.CopyStatusSuccess {
if move {
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{
- AccessConditions: &blob.AccessConditions{
- LeaseAccessConditions: &blob.LeaseAccessConditions{
- LeaseID: &leaseID,
- },
- },
- })
+ _, err = srcBlobURL.Delete(
+ context.Background(),
+ azblob.DeleteSnapshotsOptionNone,
+ azblob.BlobAccessConditions{
+ LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
+ })
return err
}
return nil
- } else if copyStatus == blob.CopyStatusTypePending {
+ } else if copyStatus == azblob.CopyStatusPending {
time.Sleep(1 * time.Second)
- getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil)
+ blobPropsResp, err := dstBlobURL.GetProperties(
+ context.Background(),
+ azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
+ azblob.ClientProvidedKeyOptions{})
if err != nil {
- return fmt.Errorf("error getting copy progress %s", dst)
+ return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
}
- copyStatus = *getMetadata.CopyStatus
+ copyStatus = blobPropsResp.CopyStatus()
- _, err = blobLeaseClient.RenewLease(context.Background(), nil)
+ _, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
if err != nil {
- return fmt.Errorf("error renewing source blob lease %s", src)
+ return fmt.Errorf("error renewing source blob lease %s", srcBlobURL)
}
} else {
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
@@ -245,9 +243,7 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
// SymLink creates a copy of src file and adds link information as meta data
func (storage *PublishedStorage) SymLink(src string, dst string) error {
- metadata := make(map[string]*string)
- metadata["SymLink"] = &src
- return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */)
+ return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
}
// HardLink using symlink functionality as hard links do not exist
@@ -257,33 +253,28 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
// FileExists returns true if path exists
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
- serviceClient := storage.az.client.ServiceClient()
- containerClient := serviceClient.NewContainerClient(storage.az.container)
- blobClient := containerClient.NewBlobClient(path)
- _, err := blobClient.GetProperties(context.Background(), nil)
+ blob := storage.az.blobURL(path)
+ resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
if isBlobNotFound(err) {
return false, nil
}
- return false, fmt.Errorf("error checking if blob %s exists: %v", path, err)
+ return false, err
+ } else if resp.StatusCode() == http.StatusOK {
+ return true, nil
}
- return true, nil
+ return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
}
// ReadLink returns the symbolic link pointed to by path.
// This simply reads text file created with SymLink
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
- serviceClient := storage.az.client.ServiceClient()
- containerClient := serviceClient.NewContainerClient(storage.az.container)
- blobClient := containerClient.NewBlobClient(path)
- props, err := blobClient.GetProperties(context.Background(), nil)
+ blob := storage.az.blobURL(path)
+ resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
- return "", fmt.Errorf("failed to get blob properties: %v", err)
+ return "", err
+ } else if resp.StatusCode() != http.StatusOK {
+ return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
}
-
- metadata := props.Metadata
- if originalBlob, exists := metadata["original_blob"]; exists {
- return *originalBlob, nil
- }
- return "", fmt.Errorf("error reading link %s: %v", path, err)
+ return resp.NewMetadata()["SymLink"], nil
}
diff --git a/azure/public_test.go b/azure/public_test.go
index 5c912c5..f58ad51 100644
--- a/azure/public_test.go
+++ b/azure/public_test.go
@@ -7,11 +7,8 @@
"io"
"os"
"path/filepath"
- "bytes"
- "github.com/Azure/azure-sdk-for-go/sdk/azcore"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
+ "github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/files"
"github.com/aptly-dev/aptly/utils"
. "gopkg.in/check.v1"
@@ -69,10 +66,8 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
c.Assert(err, IsNil)
- publicAccessType := azblob.PublicAccessTypeContainer
- _, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
- Access: &publicAccessType,
- })
+ cnt := s.storage.az.container
+ _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
c.Assert(err, IsNil)
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
@@ -80,39 +75,41 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
}
func (s *PublishedStorageSuite) TearDownTest(c *C) {
- _, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
+ cnt := s.storage.az.container
+ _, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
- resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
+ blob := s.storage.az.container.NewBlobURL(path)
+ resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
c.Assert(err, IsNil)
- data, err := io.ReadAll(resp.Body)
+ body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
+ data, err := io.ReadAll(body)
c.Assert(err, IsNil)
return data
}
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
- serviceClient := s.storage.az.client.ServiceClient()
- containerClient := serviceClient.NewContainerClient(s.storage.az.container)
- blobClient := containerClient.NewBlobClient(path)
- _, err := blobClient.GetProperties(context.Background(), nil)
+ _, err := s.storage.az.container.NewBlobURL(path).GetProperties(
+ context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
c.Assert(err, NotNil)
-
- storageError, ok := err.(*azcore.ResponseError)
+ storageError, ok := err.(azblob.StorageError)
c.Assert(ok, Equals, true)
- c.Assert(storageError.StatusCode, Equals, 404)
+ c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
}
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
hash := md5.Sum(data)
- uploadOptions := &azblob.UploadStreamOptions{
- HTTPHeaders: &blob.HTTPHeaders{
- BlobContentMD5: hash[:],
- },
- }
- reader := bytes.NewReader(data)
- _, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
+ _, err := azblob.UploadBufferToBlockBlob(
+ context.Background(),
+ data,
+ s.storage.az.container.NewBlockBlobURL(path),
+ azblob.UploadToBlockBlobOptions{
+ BlobHTTPHeaders: azblob.BlobHTTPHeaders{
+ ContentMD5: hash[:],
+ },
+ })
c.Assert(err, IsNil)
}
@@ -333,7 +330,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
// 2nd link from pool, providing wrong path for source file
//
- // this test should check that file already exists in Azure and skip upload (which would fail if not skipped)
+ // this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
s.prefixedStorage.pathCache = nil
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
c.Check(err, IsNil)
diff --git a/context/context.go b/context/context.go
index 0ffc3f7..503cad2 100644
--- a/context/context.go
+++ b/context/context.go
@@ -100,6 +100,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
for _, configLocation := range configLocations {
+ // FIXME: check if exists, check if readable
err = utils.LoadConfig(configLocation, &utils.Config)
if os.IsPermission(err) || os.IsNotExist(err) {
continue
diff --git a/deb/list.go b/deb/list.go
index 25a2d28..9eda528 100644
--- a/deb/list.go
+++ b/deb/list.go
@@ -598,6 +598,7 @@ func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
//
// 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
+ // FIXME: do not search twice
if result.Search(dep, false, true) != nil {
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
diff --git a/go.mod b/go.mod
index 53c5e78..d7f145a 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.24
require (
github.com/AlekSi/pointer v1.1.0
+ github.com/Azure/azure-storage-blob-go v0.15.0
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55
github.com/awalterschulze/gographviz v2.0.1+incompatible
github.com/cavaliergopher/grab/v3 v3.0.1
@@ -41,7 +42,7 @@ require (
)
require (
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
+ github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -87,6 +88,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
+ github.com/mattn/go-ieproxy v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -96,7 +98,7 @@ require (
github.com/prometheus/common v0.59.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
- github.com/rogpeppe/go-internal v1.12.0 // indirect
+ github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
@@ -115,8 +117,6 @@ require (
)
require (
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/ProtonMail/go-crypto v1.0.0
github.com/aws/aws-sdk-go-v2 v1.32.5
github.com/aws/aws-sdk-go-v2/config v1.28.5
diff --git a/go.sum b/go.sum
index 502f4b2..453a288 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,20 @@
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
+github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
+github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -91,6 +94,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
@@ -127,8 +132,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
-github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -149,7 +152,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
@@ -197,6 +201,9 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
+github.com/mattn/go-ieproxy v0.0.9 h1:RvVbLiMv/Hbjf1gRaC2AQyzwbdVhdId7D2vPnXIml4k=
+github.com/mattn/go-ieproxy v0.0.9/go.mod h1:eF30/rfdQUO9EnzNIZQr0r9HiLMlZNCpJkHbmMuOAE0=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -235,8 +242,6 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -254,8 +259,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
@@ -319,6 +324,8 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
@@ -333,14 +340,14 @@ golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -362,6 +369,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -0,0 +1,35 @@
From: =?utf-8?q?S=C3=A9bastien_Delafond?= <seb@debian.org>
Date: Mon, 17 Feb 2025 10:11:55 +0100
Subject: tests: no upstream's etcd install as it's arch-specific,
and no swagger-related or modules tasks
Forwarded: not-needed
---
Makefile | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/Makefile b/Makefile
index ffe2e8a..91f96a8 100644
--- a/Makefile
+++ b/Makefile
@@ -89,17 +89,12 @@ install:
# go install -v
@out=`mktemp`; if ! go install -v > $$out 2>&1; then cat $$out; rm -f $$out; echo "\nBuild failed\n"; exit 1; else rm -f $$out; fi
-test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify which tests to run)
- @echo "\e[33m\e[1mStarting etcd ...\e[0m"
- @mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 &
+test: ## Run unit tests
@echo "\e[33m\e[1mRunning go test ...\e[0m"
- faketime "$(TEST_FAKETIME)" go test -v ./... -gocheck.v=true -check.f "$(TEST)" -coverprofile=unit.out; echo $$? > .unit-test.ret
- @echo "\e[33m\e[1mStopping etcd ...\e[0m"
- @pid=`cat /tmp/etcd.pid`; kill $$pid
- @rm -f /tmp/aptly-etcd-data/etcd.log
+ go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
@ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret
-system-test: prepare swagger etcd-install ## Run system tests
+system-test: ## Run system tests
# build coverage binary
go test -v -coverpkg="./..." -c -tags testruncli
# Download fixture-db, fixture-pool, etcd.db
@@ -0,0 +1,40 @@
From: =?utf-8?q?S=C3=A9bastien_Delafond?= <seb@debian.org>
Date: Wed, 24 Sep 2025 07:23:24 +0200
Subject: Revert "system-tests: abort on failure"
We'd rather have the test suite always run completely, and report
every failed test at the end.
Forwarded: not-needed
---
system/run.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/system/run.py b/system/run.py
index 4e73fb2..599afe5 100755
--- a/system/run.py
+++ b/system/run.py
@@ -50,7 +50,6 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
if not coverage_dir:
coverage_dir = mkdtemp(suffix="aptly-coverage")
- failed = False
for test in tests:
orig_stdout = sys.stdout
orig_stderr = sys.stderr
@@ -158,15 +157,8 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
t.shutdown()
- if failed:
- break
- if failed:
- break
-
sys.stdout = orig_stdout
sys.stderr = orig_stderr
- if failed:
- break
if lastBase is not None:
lastBase.shutdown_class()
+4
View File
@@ -0,0 +1,4 @@
0001-disable-swagger.patch
0002-disable-new-azure-sdk.patch
0003-tests-no-upstream-s-etcd-install-as-it-s-arch-specif.patch
0004-Revert-system-tests-abort-on-failure.patch
+4 -34
View File
@@ -2,47 +2,17 @@
include /usr/share/dpkg/pkg-info.mk
export GOPATH=$(shell pwd)/.go
export DEB_BUILD_OPTIONS=crossbuildcanrunhostbinaries
export GOARCH := $(shell if [ $(DEB_TARGET_ARCH) = "i386" ]; then echo "386"; elif [ $(DEB_TARGET_ARCH) = "armhf" ]; then echo "arm"; else echo $(DEB_TARGET_ARCH); fi)
export CGO_ENABLED=1
ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE))
export CC=$(DEB_HOST_GNU_TYPE)-gcc
endif
%:
dh $@ --buildsystem=golang --with=golang,bash-completion
override_dh_auto_clean:
rm -rf build/
rm -rf obj-$(DEB_TARGET_GNU_TYPE)/
dh_auto_clean
override_dh_auto_test:
# run during autopkgtests
override_dh_auto_install:
dh_auto_install -- --no-source
override_dh_strip:
dh_strip --dbg-package=aptly-dbg
override_dh_golang: # fails on non native debian build
# override_dh_makeshlibs: # fails with cross compiling on non native debian build
override_dh_dwz: # somehow dwz works only with certain newer debhelper versions
dhver=`dpkg-query -f '$${Version}' -W debhelper`; (dpkg --compare-versions "$$dhver" lt 13 || test "$$dhver" = "13.3.4" || test "$$dhver" = "13.6ubuntu1") || dh_dwz
override_dh_shlibdeps:
ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE))
LD_LIBRARY_PATH=/usr/$(DEB_HOST_GNU_TYPE)/lib:$$LD_LIBRARY_PATH dh_shlibdeps
else
dh_shlibdeps
endif
override_dh_auto_build:
echo $(DEB_VERSION) > VERSION
go build -buildmode=pie -o usr/bin/aptly
echo $(DEB_VERSION) > obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/VERSION
mkdir -p obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian
cp debian/aptly.conf obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian/
dh_auto_build
+1 -1
View File
@@ -1 +1 @@
3.0 (git)
3.0 (quilt)
Vendored Executable
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# run upstream's unit tests with Debian-supplied dependencies
# FIXME: right now this fails hard because many prerequisites are only
# handled in `make test`
set -e
set -u
set -x
BUILD_DIR="${PWD}/_build"
DH_OPTIONS="-O--buildsystem=golang -O--builddirectory=$BUILD_DIR"
APTLY_DIR="${BUILD_DIR}/src/github.com/aptly-dev/aptly"
dpkg-source --before-build .
dh_update_autotools_config $DH_OPTIONS
dh_autoreconf $DH_OPTIONS
dh_auto_configure $DH_OPTIONS
dpkg-parsechangelog --show-field Version | perl -pe 's/\n//' >| ${APTLY_DIR}/VERSION
find . -name VERSION
mkdir -p ${APTLY_DIR}/debian
cp debian/aptly.conf ${APTLY_DIR}/debian/
dh_auto_build $DH_OPTIONS
export PATH=${BUILD_DIR}/bin:$PATH
dh_auto_test $DH_OPTIONS --no-parallel
+6 -3
View File
@@ -1,4 +1,7 @@
# This file is an addition to the autodep8 tests.
Test-Command: HOME=/tmp aptly repo create autopkgtest
Tests: unit-test
Depends: @, @builddeps@, ca-certificates, curl, git, gpg, gpg-agent
Restrictions: allow-stderr
Tests: system-test
Depends: @, @builddeps@, ca-certificates, curl, dirmngr, git, gpg, gpg-agent, gpgconf, graphviz, procps, python3, python3-requests-unixsocket, python3-termcolor, python3-swiftclient, python3-boto3, python3-azure-storage, python3-etcd3, python3-plyvel, sudo, zip
Restrictions: allow-stderr, needs-internet
Vendored Executable
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
set -eux
TMP_DIR="$(mktemp -d)"
APTLY_SRC_DIR="${TMP_DIR}/src/github.com/aptly-dev/aptly"
# apply patches
dpkg-source --before-build .
# copy source to GOPATH-compatible dir
mkdir -p $(dirname $APTLY_SRC_DIR)
cp -a . $APTLY_SRC_DIR
# not in a git tree, so we need to generate the version ourselves
dpkg-parsechangelog --show-field Version | perl -pe 's/\n//' > ${APTLY_SRC_DIR}/VERSION
# use only apt-supplied go libraries
export GOPATH="${TMP_DIR}:/usr/share/gocode"
export GO111MODULE=off
Vendored Executable
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
# run upstream's integration tests
set -eux
. debian/tests/setup
## env
TESTS_DIR="${APTLY_SRC_DIR}/system"
## functions
disable_test() {
local file=${1}.py
local name=$2
local reason=$3
echo "${name}.skipTest = 'Debian autopkgtest: $reason'" >> ${TESTS_DIR}/${file}
}
## main
export USER=root # for t07/RootDirInaccessible
disable_test t01_version/version VersionTest "version"
disable_test t02_config/config CreateConfigTest "different conf"
disable_test t04_mirror/create CreateMirror31Test "public key not found"
disable_test t04_mirror/create CreateMirror35Test "flaky on s390"
disable_test t07_serve/serve Serve1Test "minor html diff"
disable_test t09_repo/edit EditRepo4Test "flaky on riscv64"
disable_test t10_task/run RunTask1Test "version"
disable_test t12_api/docs TaskAPITestSwaggerDocs "no recent swag"
disable_test t12_api/gpg GPGAPITestAddKey "flaky on s390"
disable_test t12_api/unix_socket UnixSocketAPITest "type mismatch"
disable_test t12_api/version VersionAPITest "type mismatch"
disable_test t14_graph/graph CreateGraphTest "no viewer"
disable_test t14_graph/graph CreateGraphOutputTest "no viewer"
# etcd fixture is entirely arch-specific
rm -fr ${TESTS_DIR}/t13_etcd
make -C $APTLY_SRC_DIR system-test
Vendored Executable
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# run upstream's unit tests with their full etcd fixtures, etc
set -eux
. debian/tests/setup
# FIXME: errors with non-constant format string in call to
# github.com/aptly-dev/aptly/s3.fatalError
rm ${APTLY_SRC_DIR}/s3/server_test.go
rm ${APTLY_SRC_DIR}/s3/public_test.go
# upstream's etcd fixture is arch-specific
rm ${APTLY_SRC_DIR}/database/etcddb/database_test.go
# TestVerifyClearsigned fails because of an extra signature
perl -i -pe 's/(TestVerifyClearsigned)/No$1/' ${APTLY_SRC_DIR}/pgp/verify_test.go
make -C $APTLY_SRC_DIR test
+6 -5
View File
@@ -1,5 +1,6 @@
version=4
opts=\
repacksuffix=+ds1,\
dversionmangle=s/\+ds\d*$// \
https://github.com/aptly-dev/aptly/tags .*/v(\d[\d\.]*)\.tar\.gz debian uupdate
Version: 5
Template: Github
Owner: aptly-dev
Project: aptly
Dversion-Mangle: s/\+ds\d*$//
Repacksuffix: +ds1
-209
View File
@@ -1,209 +0,0 @@
# GPG Keys Management
GPG keys are used by aptly to verify the authenticity of remote repository Release files when creating mirrors. This document describes the API endpoints for managing GPG keys in the aptly keyring.
## Overview
Aptly uses GNU Privacy Guard (GPG) to verify signed repository metadata. You must add the repository's GPG public key to aptly's keyring before creating mirrors that verify signatures.
Keys are stored in the aptly keyring (default: `trustedkeys.gpg`). You can have multiple keyrings and specify which one to use via the `Keyring` parameter.
## API Endpoints
### List GPG Keys
**GET /api/gpg/keys**
Lists all public GPG keys currently installed in the aptly keyring.
**Parameters:**
- `keyring` (query, optional): Keyring file to list keys from. Default: `trustedkeys.gpg`
**Response:**
```json
{
"Keys": [
{
"KeyID": "8B48AD6246925553",
"Fingerprint": "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0",
"Validity": "f",
"UserIDs": ["John Doe <john@example.com>"],
"CreatedAt": "1611864000"
}
]
}
```
**Status Codes:**
- `200 OK`: Keys successfully retrieved
- `400 Bad Request`: GPG execution failed or invalid parameters
**Example:**
```bash
curl http://localhost:8080/api/gpg/keys
curl "http://localhost:8080/api/gpg/keys?keyring=custom.gpg"
```
---
### Add GPG Key
**POST /api/gpg/key**
Adds a GPG public key to the aptly keyring. Keys can be added in two ways:
1. Provide the ASCII-armored key directly
2. Provide a key server and key ID(s) to download from
**Request Body:**
```json
{
"Keyring": "trustedkeys.gpg",
"GpgKeyArmor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
"Keyserver": "hkp://keyserver.ubuntu.com:80",
"GpgKeyID": "8B48AD6246925553"
}
```
**Parameters:**
- `Keyring` (optional): Keyring file to add keys to. Default: `trustedkeys.gpg`
- `GpgKeyArmor` (optional): ASCII-armored GPG public key
- `Keyserver` (optional): Keyserver URL (e.g., `hkp://keyserver.ubuntu.com:80`)
- `GpgKeyID` (optional): Space-separated key IDs to download from keyserver
**Status Codes:**
- `200 OK`: Key successfully added
- `400 Bad Request`: Invalid parameters or GPG execution failed
**Example - From ASCII Key:**
```bash
curl -X POST http://localhost:8080/api/gpg/key \
-H "Content-Type: application/json" \
-d '{
"GpgKeyArmor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2\n...\n-----END PGP PUBLIC KEY BLOCK-----"
}'
```
**Example - From Keyserver:**
```bash
curl -X POST http://localhost:8080/api/gpg/key \
-H "Content-Type: application/json" \
-d '{
"Keyserver": "hkp://keyserver.ubuntu.com:80",
"GpgKeyID": "8B48AD6246925553 A1B2C3D4E5F67890"
}'
```
---
### Delete GPG Key
**DELETE /api/gpg/key**
Removes a GPG key from the aptly keyring.
**Request Body:**
```json
{
"Keyring": "trustedkeys.gpg",
"GpgKeyID": "8B48AD6246925553"
}
```
**Parameters:**
- `Keyring` (optional): Keyring file to delete from. Default: `trustedkeys.gpg`
- `GpgKeyID` (required): Key ID or fingerprint to delete
**Status Codes:**
- `200 OK`: Key successfully deleted
- `400 Bad Request`: Invalid parameters or GPG execution failed
**Example:**
```bash
curl -X DELETE http://localhost:8080/api/gpg/key \
-H "Content-Type: application/json" \
-d '{
"GpgKeyID": "8B48AD6246925553"
}'
```
---
## Use Cases
### 1. Verify Downloaded Repository Metadata
Before creating a mirror from a signed repository, add the repository's GPG key:
```bash
# Add the key from a keyserver
curl -X POST http://localhost:8080/api/gpg/key \
-H "Content-Type: application/json" \
-d '{
"Keyserver": "hkp://keyserver.ubuntu.com:80",
"GpgKeyID": "EB9B46B91F2D3B7E"
}'
# Now create a mirror with signature verification
# (signature verification configured in mirror settings)
```
### 2. Manage Multiple Keyrings
Aptly supports using different keyrings for different purposes. For example, one for Debian repositories and another for custom internal repositories:
```bash
# Add key to Debian keyring
curl -X POST http://localhost:8080/api/gpg/key \
-H "Content-Type: application/json" \
-d '{
"Keyring": "debian-keys.gpg",
"Keyserver": "hkp://keyserver.ubuntu.com:80",
"GpgKeyID": "EB9B46B91F2D3B7E"
}'
# List keys in Debian keyring
curl "http://localhost:8080/api/gpg/keys?keyring=debian-keys.gpg"
```
### 3. Remove Compromised Keys
If a GPG key is compromised, remove it from the keyring immediately:
```bash
curl -X DELETE http://localhost:8080/api/gpg/key \
-H "Content-Type: application/json" \
-d '{
"GpgKeyID": "COMPROMISED_KEY_ID"
}'
```
---
## Key Validity Values
Keys retrieved from `GET /api/gpg/keys` have a `Validity` field with the following possible values:
- `u` — Unknown validity
- `f` — Full trust
- `m` — Marginal trust
- `n` — Never trust
- `-` — Trust not set
The trust level is typically managed in your GPG configuration and does not affect aptly's ability to verify signatures.
---
## Troubleshooting
**"failed to list keys"**
- Check that the keyring file exists and is readable
- Verify GPG is installed and configured
**"unable to delete key: no public key"**
- The key might not exist in the keyring
- Verify the key ID is correct by listing keys first
**"invalid request body"**
- Ensure the JSON is properly formatted
- For POST requests, provide either `GpgKeyArmor` or (`Keyserver` + `GpgKeyID`)
- For DELETE requests, `GpgKeyID` is required
-283
View File
@@ -1,283 +0,0 @@
package files
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
. "gopkg.in/check.v1"
)
type LinkFromPoolConcurrencySuite struct {
root string
poolDir string
storage *PublishedStorage
pool *PackagePool
cs aptly.ChecksumStorage
testFile string
testContent []byte
testChecksums utils.ChecksumInfo
srcPoolPath string
}
var _ = Suite(&LinkFromPoolConcurrencySuite{})
func (s *LinkFromPoolConcurrencySuite) SetUpTest(c *C) {
s.root = c.MkDir()
s.poolDir = filepath.Join(s.root, "pool")
publishDir := filepath.Join(s.root, "public")
// Create package pool and published storage
s.pool = NewPackagePool(s.poolDir, true)
s.storage = NewPublishedStorage(publishDir, "copy", "checksum")
s.cs = NewMockChecksumStorage()
// Create test file content
s.testContent = []byte("test package content for concurrency testing")
s.testFile = filepath.Join(s.root, "test-package.deb")
err := os.WriteFile(s.testFile, s.testContent, 0644)
c.Assert(err, IsNil)
// Calculate checksums
md5sum, err := utils.MD5ChecksumForFile(s.testFile)
c.Assert(err, IsNil)
s.testChecksums = utils.ChecksumInfo{
Size: int64(len(s.testContent)),
MD5: md5sum,
}
// Import the test file into the pool
s.srcPoolPath, err = s.pool.Import(s.testFile, "test-package.deb", &s.testChecksums, false, s.cs)
c.Assert(err, IsNil)
}
func (s *LinkFromPoolConcurrencySuite) TestLinkFromPoolConcurrency(c *C) {
// Test concurrent LinkFromPool operations to ensure no race conditions
concurrency := 5000
iterations := 10
for iter := 0; iter < iterations; iter++ {
c.Logf("Iteration %d: Testing concurrent LinkFromPool with %d goroutines", iter+1, concurrency)
destPath := fmt.Sprintf("main/t/test%d", iter)
var wg sync.WaitGroup
errors := make(chan error, concurrency)
successes := make(chan struct{}, concurrency)
start := time.Now()
// Launch concurrent LinkFromPool operations
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Use force=true to test the most vulnerable code path (remove-then-create)
err := s.storage.LinkFromPool(
"", // publishedPrefix
destPath, // publishedRelPath
"test-package.deb", // fileName
s.pool, // sourcePool
s.srcPoolPath, // sourcePath
s.testChecksums, // sourceChecksums
true, // force - this triggers vulnerable remove-then-create pattern
)
if err != nil {
errors <- fmt.Errorf("goroutine %d failed: %v", id, err)
} else {
successes <- struct{}{}
}
}(i)
}
// Wait for completion
wg.Wait()
duration := time.Since(start)
close(errors)
close(successes)
// Count results
errorCount := 0
successCount := 0
var firstError error
for err := range errors {
errorCount++
if firstError == nil {
firstError = err
}
c.Logf("Race condition error: %v", err)
}
for range successes {
successCount++
}
c.Logf("Results: %d successes, %d errors, took %v", successCount, errorCount, duration)
// Assert no race conditions occurred
if errorCount > 0 {
c.Fatalf("Race condition detected in iteration %d! "+
"Errors: %d out of %d operations (%.1f%% failure rate). "+
"First error: %v. "+
"This indicates the fix is not working properly.",
iter+1, errorCount, concurrency,
float64(errorCount)/float64(concurrency)*100, firstError)
}
// Verify the final file exists and has correct content
finalFile := filepath.Join(s.storage.rootPath, destPath, "test-package.deb")
_, err := os.Stat(finalFile)
c.Assert(err, IsNil, Commentf("Final file should exist after concurrent operations"))
content, err := os.ReadFile(finalFile)
c.Assert(err, IsNil, Commentf("Should be able to read final file"))
c.Assert(content, DeepEquals, s.testContent, Commentf("File content should be intact after concurrent operations"))
c.Logf("✓ Iteration %d: No race conditions detected", iter+1)
}
c.Logf("SUCCESS: Handled %d total concurrent operations across %d iterations with no race conditions",
concurrency*iterations, iterations)
}
func (s *LinkFromPoolConcurrencySuite) TestLinkFromPoolConcurrencyDifferentFiles(c *C) {
// Test concurrent operations on different files to ensure no blocking
concurrency := 10
var wg sync.WaitGroup
errors := make(chan error, concurrency)
start := time.Now()
// Launch concurrent operations on different destination files
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
destPath := fmt.Sprintf("main/t/test-file-%d", id)
err := s.storage.LinkFromPool(
"", // publishedPrefix
destPath, // publishedRelPath
"test-package.deb", // fileName
s.pool, // sourcePool
s.srcPoolPath, // sourcePath
s.testChecksums, // sourceChecksums
false, // force
)
if err != nil {
errors <- fmt.Errorf("goroutine %d failed: %v", id, err)
}
}(i)
}
// Wait for completion
wg.Wait()
duration := time.Since(start)
close(errors)
// Count errors
errorCount := 0
for err := range errors {
errorCount++
c.Logf("Error: %v", err)
}
c.Assert(errorCount, Equals, 0, Commentf("No errors should occur when linking to different files"))
c.Logf("SUCCESS: %d concurrent operations on different files completed in %v", concurrency, duration)
// Verify all files were created correctly
for i := 0; i < concurrency; i++ {
finalFile := filepath.Join(s.storage.rootPath, fmt.Sprintf("main/t/test-file-%d", i), "test-package.deb")
_, err := os.Stat(finalFile)
c.Assert(err, IsNil, Commentf("File %d should exist", i))
content, err := os.ReadFile(finalFile)
c.Assert(err, IsNil, Commentf("Should be able to read file %d", i))
c.Assert(content, DeepEquals, s.testContent, Commentf("File %d content should be correct", i))
}
}
func (s *LinkFromPoolConcurrencySuite) TestLinkFromPoolWithoutForceNoConcurrencyIssues(c *C) {
// Test that when force=false, concurrent operations fail gracefully without corruption
concurrency := 20
destPath := "main/t/single-dest"
var wg sync.WaitGroup
errors := make(chan error, concurrency)
successes := make(chan struct{}, concurrency)
// First, create the file so subsequent operations will conflict
err := s.storage.LinkFromPool("", destPath, "test-package.deb", s.pool, s.srcPoolPath, s.testChecksums, false)
c.Assert(err, IsNil)
start := time.Now()
// Launch concurrent operations that should mostly fail
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
err := s.storage.LinkFromPool(
"", // publishedPrefix
destPath, // publishedRelPath
"test-package.deb", // fileName
s.pool, // sourcePool
s.srcPoolPath, // sourcePath
s.testChecksums, // sourceChecksums
false, // force=false - should fail if file exists and is same
)
if err != nil {
errors <- err
} else {
successes <- struct{}{}
}
}(i)
}
// Wait for completion
wg.Wait()
duration := time.Since(start)
close(errors)
close(successes)
errorCount := 0
successCount := 0
for range errors {
errorCount++
}
for range successes {
successCount++
}
c.Logf("Results with force=false: %d successes, %d errors, took %v", successCount, errorCount, duration)
// With force=false and identical files, operations should succeed (file already exists with same content)
// No race conditions should cause crashes or corruption
c.Assert(errorCount, Equals, 0, Commentf("With identical files and force=false, operations should succeed"))
// Verify the file still exists and has correct content
finalFile := filepath.Join(s.storage.rootPath, destPath, "test-package.deb")
content, err := os.ReadFile(finalFile)
c.Assert(err, IsNil)
c.Assert(content, DeepEquals, s.testContent, Commentf("File should not be corrupted by concurrent access"))
}
+7 -32
View File
@@ -26,26 +26,6 @@ type PublishedStorage struct {
verifyMethod uint
}
// Global mutex map to prevent concurrent access to the same destinationPath in LinkFromPool
var (
fileLockMutex sync.Mutex
fileLocks = make(map[string]*sync.Mutex)
)
// getFileLock returns a mutex for a specific file path to prevent concurrent modifications
func getFileLock(filePath string) *sync.Mutex {
fileLockMutex.Lock()
defer fileLockMutex.Unlock()
if mutex, exists := fileLocks[filePath]; exists {
return mutex
}
mutex := &sync.Mutex{}
fileLocks[filePath] = mutex
return mutex
}
// Check interfaces
var (
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
@@ -172,11 +152,6 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
poolPath := filepath.Join(storage.rootPath, publishedPrefix, publishedRelPath, filepath.Dir(fileName))
destinationPath := filepath.Join(poolPath, baseName)
// Acquire file-specific lock to prevent concurrent access to the same file
fileLock := getFileLock(destinationPath)
fileLock.Lock()
defer fileLock.Unlock()
var localSourcePool aptly.LocalPackagePool
if storage.linkMethod != LinkMethodCopy {
pp, ok := sourcePool.(aptly.LocalPackagePool)
@@ -194,7 +169,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
var dstStat os.FileInfo
dstStat, err = os.Stat(destinationPath)
dstStat, err = os.Stat(filepath.Join(poolPath, baseName))
if err == nil {
// already exists, check source file
@@ -213,7 +188,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
} else {
// if source and destination have the same checksums, no need to copy
var dstMD5 string
dstMD5, err = utils.MD5ChecksumForFile(destinationPath)
dstMD5, err = utils.MD5ChecksumForFile(filepath.Join(poolPath, baseName))
if err != nil {
return err
@@ -244,11 +219,11 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
// source and destination have different inodes, if !forced, this is fatal error
if !force {
return fmt.Errorf("error linking file to %s: file already exists and is different", destinationPath)
return fmt.Errorf("error linking file to %s: file already exists and is different", filepath.Join(poolPath, baseName))
}
// forced, so remove destination
err = os.Remove(destinationPath)
err = os.Remove(filepath.Join(poolPath, baseName))
if err != nil {
return err
}
@@ -263,7 +238,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
}
var dst *os.File
dst, err = os.Create(destinationPath)
dst, err = os.Create(filepath.Join(poolPath, baseName))
if err != nil {
_ = r.Close()
return err
@@ -291,9 +266,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
err = dst.Close()
} else if storage.linkMethod == LinkMethodSymLink {
err = localSourcePool.Symlink(sourcePath, destinationPath)
err = localSourcePool.Symlink(sourcePath, filepath.Join(poolPath, baseName))
} else {
err = localSourcePool.Link(sourcePath, destinationPath)
err = localSourcePool.Link(sourcePath, filepath.Join(poolPath, baseName))
}
return err
-10
View File
@@ -632,16 +632,6 @@ func (s *DiskFullNoRootSuite) TestLinkFromPoolCopySyncErrorIsReturned(c *C) {
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
}
func (s *DiskFullNoRootSuite) TestGetFileLockReusesMutex(c *C) {
a := getFileLock(filepath.Join(s.root, "a"))
b := getFileLock(filepath.Join(s.root, "a"))
c.Check(a == b, Equals, true)
c1 := getFileLock(filepath.Join(s.root, "c1"))
c2 := getFileLock(filepath.Join(s.root, "c2"))
c.Check(c1 == c2, Equals, false)
}
func (s *DiskFullNoRootSuite) TestPutFileFailsIfDestinationDirMissing(c *C) {
storage := NewPublishedStorage(s.root, "", "")
+16 -18
View File
@@ -2,6 +2,8 @@ module github.com/aptly-dev/aptly
go 1.24.0
toolchain go1.24.4
require (
github.com/AlekSi/pointer v1.1.0
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55
@@ -32,17 +34,16 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
github.com/ugorji/go/codec v1.2.11
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/sys v0.39.0
golang.org/x/term v0.38.0
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sys v0.38.0
golang.org/x/term v0.37.0
golang.org/x/time v0.5.0
google.golang.org/protobuf v1.36.10 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)
require (
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -88,6 +89,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-ieproxy v0.0.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -104,20 +106,19 @@ require (
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.39.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/grpc v1.64.1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/Azure/azure-storage-blob-go v0.15.0
github.com/ProtonMail/go-crypto v1.4.0
github.com/aws/aws-sdk-go-v2 v1.41.5
github.com/aws/aws-sdk-go-v2/config v1.28.5
@@ -125,10 +126,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
github.com/aws/smithy-go v1.24.2
github.com/google/uuid v1.6.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
go.etcd.io/etcd/client/v3 v3.5.15
golang.org/x/oauth2 v0.34.0
gopkg.in/yaml.v3 v3.0.1
)
+52 -82
View File
@@ -1,19 +1,20 @@
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -91,22 +92,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -131,8 +128,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -150,10 +145,11 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
@@ -201,6 +197,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -239,8 +237,6 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -288,10 +284,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
@@ -304,25 +296,12 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
@@ -335,44 +314,41 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -388,24 +364,21 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -413,22 +386,19 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -437,8 +407,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-1
View File
@@ -44,7 +44,6 @@ func NewDownloader(downLimit int64, maxTries int, progress aptly.Progress) aptly
transport.DisableCompression = true
initTransport(&transport)
transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{})
transport.RegisterProtocol("ar+https", NewGCPRoundTripper(&transport))
downloader := &downloaderImpl{
progress: progress,
-64
View File
@@ -1,64 +0,0 @@
package http
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
// gcpRoundTripper wraps http.RoundTripper to add Google Cloud authentication.
// It delays GCP authentication initialization until the first actual request is made.
// This avoids unnecessary credential loading when ar+https protocol is not actually used.
//
// It uses Application Default Credentials (ADC) which checks:
// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
// 2. gcloud auth application-default credentials
// 3. GCE/GKE metadata server
// See https://cloud.google.com/docs/authentication/application-default-credentials for usage details.
type gcpRoundTripper struct {
base http.RoundTripper
initOnce sync.Once
tokenSrc oauth2.TokenSource
initErr error
}
func (t *gcpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Lazy initialization: only initialize GCP credentials on first request
t.initOnce.Do(func() {
creds, err := google.FindDefaultCredentials(context.Background(),
"https://www.googleapis.com/auth/cloud-platform")
if err != nil {
t.initErr = fmt.Errorf("failed to find default credentials: %w", err)
return
}
t.tokenSrc = creds.TokenSource
})
reqCopy := req.Clone(req.Context())
reqCopy.URL.Scheme = strings.TrimPrefix(reqCopy.URL.Scheme, "ar+")
// Fall back to base transport if GCP auth initialization failed
if t.initErr != nil {
return t.base.RoundTrip(reqCopy)
}
token, err := t.tokenSrc.Token()
if err != nil {
return nil, fmt.Errorf("failed to get OAuth2 token: %w", err)
}
token.SetAuthHeader(reqCopy)
return t.base.RoundTrip(reqCopy)
}
// NewGCPRoundTripper creates a new RoundTripper that handles GCP authentication for ar+https protocol.
func NewGCPRoundTripper(base http.RoundTripper) http.RoundTripper {
return &gcpRoundTripper{
base: base,
}
}
-110
View File
@@ -1,110 +0,0 @@
package http
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"golang.org/x/oauth2"
)
func TestGCPAuthTransport_RoundTrip(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" {
t.Error("Expected Authorization header, got none")
}
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
transport := NewGCPRoundTripper(http.DefaultTransport)
if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
t.Skip("Skipping test: GOOGLE_APPLICATION_CREDENTIALS not set")
}
client := &http.Client{Transport: transport}
resp, err := client.Get(ts.URL)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
}
func TestGCPAuthTransport_RoundTrip_with_dummy_tokenSource(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth != "Bearer dummy-token" {
t.Errorf("Expected Authorization header 'Bearer dummy-token', got '%s'", auth)
}
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
// Use a dummy token source for testing
transport := &gcpRoundTripper{
base: http.DefaultTransport,
tokenSrc: oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: "dummy-token",
}),
}
transport.initOnce.Do(func() {}) // Mark as initialized for testing
client := &http.Client{Transport: transport}
resp, err := client.Get(ts.URL)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer func(){ _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
}
func TestGCPAuthTransport_RoundTrip_with_InvalidCredentials(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer ts.Close()
// Create a temporary invalid credentials file
tmpFile, err := os.CreateTemp("", "invalid_credentials.json")
if err != nil {
t.Fatalf("Failed to create temp file: %s", err)
}
defer func() { _ = os.Remove(tmpFile.Name()) }()
if _, err := tmpFile.WriteString(`{"invalid": "data"}`); err != nil {
t.Fatalf("Failed to write to temp file: %s", err)
}
_ = tmpFile.Close()
defaultEnv := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
_ = os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", tmpFile.Name())
defer func() { _ = os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", defaultEnv) }()
transport := &gcpRoundTripper{
base: http.DefaultTransport,
}
client := &http.Client{Transport: transport}
resp, err := client.Get(ts.URL)
if err != nil {
t.Fatalf("Failed to make request: %s", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusForbidden {
t.Errorf("Expected status 403, got %d", resp.StatusCode)
}
if transport.initErr == nil {
t.Error("Expected init error due to invalid credentials, got none")
}
}
+2 -41
View File
@@ -111,8 +111,9 @@ The legacy json configuration is still supported (and also supports comments):
// Enable metrics for Prometheus client
"enableMetricsEndpoint": false,
// Not implemented in this version\.
// Enable API documentation on /docs
"enableSwaggerEndpoint": false,
//"enableSwaggerEndpoint": false,
// OBSOLETE: use via url param ?_async=true
"AsyncAPI": false,
@@ -615,10 +616,6 @@ gpg keyring to use when verifying Release file (could be specified multiple time
max download tries till process fails with download error
.
.TP
\-\fBwith\-appstream\fR
download AppStream (DEP\-11) metadata
.
.TP
\-\fBwith\-installer\fR
download additional not packaged installer files
.
@@ -742,10 +739,6 @@ max download tries till process fails with download error
\-\fBskip\-existing\-packages\fR
do not check file existence for packages listed in the internal database of the mirror
.
.TP
\-\fBlatest\fR
download only latest version of each package (per architecture)
.
.SH "RENAMES MIRROR"
\fBaptly\fR \fBmirror\fR \fBrename\fR \fIold\-name\fR \fInew\-name\fR
.
@@ -794,10 +787,6 @@ disable verification of Release file signatures
gpg keyring to use when verifying Release file (could be specified multiple times)
.
.TP
\-\fBwith\-appstream\fR
download AppStream (DEP\-11) metadata
.
.TP
\-\fBwith\-installer\fR
download additional not packaged installer files
.
@@ -1573,14 +1562,6 @@ $ aptly publish repo testing
Options:
.
.TP
\-\fBsigned\-by\fR
set value for Signed-By field
.
.TP
\-\fBversion\fR
version of the release
.
.TP
\-\fBacquire\-by\-hash\fR
provide index files by hash
.
@@ -1722,14 +1703,6 @@ $ aptly publish snapshot wheezy\-main
Options:
.
.TP
\-\fBsigned\-by\fR
set value for Signed-By field
.
.TP
\-\fBversion\fR
version of the release
.
.TP
\-\fBacquire\-by\-hash\fR
provide index files by hash
.
@@ -2089,10 +2062,6 @@ This command would switch published repository (with one component) named ppa/wh
Options:
.
.TP
\-\fBsigned\-by\fR
set value for Signed-By field
.
.TP
\-\fBbatch\fR
run GPG with detached tty
.
@@ -2199,14 +2168,6 @@ $ aptly publish update wheezy ppa
Options:
.
.TP
\-\fBsigned\-by\fR
set value for Signed-By field
.
.TP
\-\fBversion\fR
version of the release
.
.TP
\-\fBbatch\fR
run GPG with detached tty
.
+2 -1
View File
@@ -100,8 +100,9 @@ The legacy json configuration is still supported (and also supports comments):
// Enable metrics for Prometheus client
"enableMetricsEndpoint": false,
// Not implemented in this version.
// Enable API documentation on /docs
"enableSwaggerEndpoint": false,
//"enableSwaggerEndpoint": false,
// OBSOLETE: use via url param ?_async=true
"AsyncAPI": false,
-7
View File
@@ -9,7 +9,6 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
@@ -90,12 +89,6 @@ func (g *GpgSigner) gpgArgs() []string {
}
}
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
if _, err := strconv.ParseInt(epoch, 10, 64); err == nil {
args = append(args, "--faked-system-time", epoch)
}
}
return args
}
-9
View File
@@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"syscall"
"time"
@@ -75,14 +74,6 @@ func (g *GoSigner) Init() error {
},
}
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
if sec, err := strconv.ParseInt(epoch, 10, 64); err == nil {
t := time.Unix(sec, 0).UTC()
g.signerConfig.Time = func() time.Time { return t }
g.signerConfig.NonDeterministicSignaturesViaNotation = packet.BoolPointer(false)
}
}
if g.passphraseFile != "" {
passF, err := os.Open(g.passphraseFile)
if err != nil {
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -83,8 +83,9 @@ serve_in_api_mode: false
# Enable metrics for Prometheus client
enable_metrics_endpoint: false
# Not implemented in this version.
# Enable API documentation on /docs
enable_swagger_endpoint: false
#enable_swagger_endpoint: false
# OBSOLETE: use via url param ?_async=true
async_api: false
@@ -29,7 +29,6 @@ Options:
-ignore-signatures: disable verification of Release file signatures
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
-max-tries=1: max download tries till process fails with download error
-with-appstream: download AppStream (DEP-11) metadata
-with-installer: download additional not packaged installer files
-with-sources: download source packages in addition to binary packages
-with-udebs: download .udeb packages (Debian installer support)
-1
View File
@@ -20,7 +20,6 @@ Options:
-ignore-signatures: disable verification of Release file signatures
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
-max-tries=1: max download tries till process fails with download error
-with-appstream: download AppStream (DEP-11) metadata
-with-installer: download additional not packaged installer files
-with-sources: download source packages in addition to binary packages
-with-udebs: download .udeb packages (Debian installer support)
-1
View File
@@ -21,7 +21,6 @@ Options:
-ignore-signatures: disable verification of Release file signatures
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
-max-tries=1: max download tries till process fails with download error
-with-appstream: download AppStream (DEP-11) metadata
-with-installer: download additional not packaged installer files
-with-sources: download source packages in addition to binary packages
-with-udebs: download .udeb packages (Debian installer support)
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components:
Architectures: all, amd64, i386
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
Architectures: i386
Download Sources: yes
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main
Architectures: amd64, armel, i386, powerpc
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main
Architectures: i386
Download Sources: yes
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components:
Architectures: all
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main
Architectures: amd64, arm64, armel, armhf, i386
Download Sources: no
Download .udebs: no
Download AppStream: no
Filter: nginx | Priority (required)
Filter With Deps: no
Last update: never
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
Architectures: i386
Download Sources: no
Download .udebs: yes
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: openmanage/740
Architectures: amd64, i386
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components:
Architectures: amd64
Download Sources: no
Download .udebs: no
Download AppStream: no
Last update: never
Information from release file:
@@ -5,7 +5,6 @@ Components:
Architectures: amd64
Download Sources: no
Download .udebs: no
Download AppStream: no
Filter: cuda-12-6 (= 12.6.2-1)
Filter With Deps: yes
Last update: never
@@ -5,7 +5,6 @@ Components: main
Architectures: amd64, arm64, armel, armhf, i386
Download Sources: no
Download .udebs: no
Download AppStream: no
Filter: nginx | Priority (required)
Filter With Deps: no
Last update: never
@@ -5,7 +5,6 @@ Components: main
Architectures: amd64, arm64, armel, armhf, i386
Download Sources: no
Download .udebs: no
Download AppStream: no
Filter: nginx | Priority (required)
Filter With Deps: no
Last update: never
@@ -1,4 +0,0 @@
Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release
Mirror [mirror38]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch [appstream] successfully added.
You can run 'aptly mirror update mirror38' to download repository contents.

Some files were not shown because too many files have changed in this diff Show More