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
109 changed files with 5489 additions and 993 deletions
+5
View File
@@ -4,6 +4,10 @@ Fixes #
All new code should be covered with tests, documentation should be updated. CI should pass.
Also, to speed up things, if you could kindly "Allow edits and access to secrets by maintainers" in the
PR settings, as this allows us to rebase the PR on master, fix conflicts, run coverage and help with
implementing code and tests.
## Description of the Change
<!--
@@ -14,6 +18,7 @@ Why this change is important?
## Checklist
- [ ] allow Maintainers to edit PR (rebase, run coverage, help with tests, ...)
- [ ] unit-test added (if change is algorithm)
- [ ] functional test added/updated (if change is functional)
- [ ] man page updated (if applicable)
+74 -26
View File
@@ -1,3 +1,4 @@
---
name: CI
on:
@@ -10,15 +11,38 @@ on:
defaults:
run:
# see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
shell: bash --noprofile --norc -eo pipefail {0}
env:
DEBIAN_FRONTEND: noninteractive
jobs:
unit-test:
name: "Unit Tests"
runs-on: ubuntu-22.04
continue-on-error: false
timeout-minutes: 30
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
# fetch the whole repo for `git describe` to work
fetch-depth: 0
- name: "Docker Image"
run: |
make docker-image
- name: "Unit Tests"
run: |
make docker-unit-test
mkdir -p out/coverage
mv unit.out out/coverage/
- uses: actions/upload-artifact@v4
with:
name: unit-tests-coverage
path: out/
test:
name: "Test (Ubuntu 22.04)"
name: "System Test"
runs-on: ubuntu-22.04
continue-on-error: false
timeout-minutes: 30
@@ -63,21 +87,10 @@ jobs:
with:
directory: ${{ runner.temp }}
- name: "Run Unit Tests"
env:
RUN_LONG_TESTS: 'yes'
AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1"
AZURE_STORAGE_ACCOUNT: "devstoreaccount1"
AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
sudo mkdir -p /srv ; sudo chown runner /srv
COVERAGE_DIR=${{ runner.temp }} make test
- name: "Run Benchmark"
run: |
COVERAGE_DIR=${{ runner.temp }} make bench
mkdir -p out/coverage
COVERAGE_DIR=$PWD/out/coverage make bench
- name: "Run System Tests"
env:
@@ -89,30 +102,63 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
sudo mkdir -p /srv ; sudo chown runner /srv
COVERAGE_DIR=${{ runner.temp }} make system-test
mkdir -p out/coverage
COVERAGE_DIR=$PWD/out/coverage make system-test
- uses: actions/upload-artifact@v4
with:
name: system-tests-coverage
path: out/
coverage:
name: "Upload Coverage"
runs-on: ubuntu-22.04
continue-on-error: false
timeout-minutes: 30
needs:
- unit-test
- test
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
- name: "Download Unit Test Coverage"
uses: actions/download-artifact@v4
with:
name: unit-tests-coverage
- name: "Download System Test Coverage"
uses: actions/download-artifact@v4
with:
name: system-tests-coverage
- name: "Merge Code Coverage"
run: |
go install github.com/wadey/gocovmerge@v0.0.0-20160331181800-b5bfa59ec0ad
~/go/bin/gocovmerge unit.out ${{ runner.temp }}/*.out > coverage.txt
# go install github.com/wadey/gocovmerge@v0.0.0-20160331181800-b5bfa59ec0ad
# ~/go/bin/gocovmerge coverage/*.out > coverage.txt
awk 'FNR==1 && NR!=1 {next} {print}' coverage/*.out > coverage.txt
- name: "Upload Code Coverage"
uses: codecov/codecov-action@v2
if: github.actor != 'dependabot[bot]'
uses: codecov/codecov-action@v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.txt
fail_ci_if_error: true
ci-debian-build:
name: "Build"
needs: test
needs:
- coverage
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
name: ["Debian 13/testing", "Debian 12/bookworm", "Debian 11/bullseye", "Debian 10/buster", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
name: ["Debian 13/trixie", "Debian 12/bookworm", "Debian 11/bullseye", "Ubuntu 26.04", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
arch: ["amd64", "i386" , "arm64" , "armhf"]
include:
- name: "Debian 13/testing"
- name: "Debian 13/trixie"
suite: trixie
image: debian:trixie-slim
- name: "Debian 12/bookworm"
@@ -121,9 +167,9 @@ jobs:
- name: "Debian 11/bullseye"
suite: bullseye
image: debian:bullseye-slim
- name: "Debian 10/buster"
suite: buster
image: debian:buster-slim
- name: "Ubuntu 26.04"
suite: resolute
image: ubuntu:26.04
- name: "Ubuntu 24.04"
suite: noble
image: ubuntu:24.04
@@ -135,6 +181,7 @@ jobs:
image: ubuntu:20.04
container:
image: ${{ matrix.image }}
options: --user root
env:
APT_LISTCHANGES_FRONTEND: none
DEBIAN_FRONTEND: noninteractive
@@ -226,7 +273,8 @@ jobs:
ci-binary-build:
name: "Build"
needs: test
needs:
- coverage
runs-on: ubuntu-latest
strategy:
matrix:
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- name: Install and initialize swagger
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -q --markdownFiles docs
swag init -q --propertyStrategy pascalcase --markdownFiles docs
shell: sh
- name: golangci-lint
+7
View File
@@ -69,3 +69,10 @@ List of contributors, in chronological order:
* Leigh London (https://github.com/leighlondon)
* Gordian Schoenherr (https://github.com/schoenherrg)
* Silke Hofstra (https://github.com/silkeh)
* Itay Porezky (https://github.com/itayporezky)
* JupiterRider (https://github.com/JupiterRider)
* Tobias Assarsson (https://github.com/daedaluz)
* Yaksh Bariya (https://github.com/thunder-coding)
* Brian Witt (https://github.com/bwitt)
* Ales Bregar (https://github.com/abregar)
* Tim Foerster (https://github.com/tonobo)
+3 -3
View File
@@ -16,7 +16,7 @@ Please report unacceptable behavior on [https://github.com/aptly-dev/aptly/discu
### List of Repositories
* [aptly-dev/aptly](https://github.com/aptly-dev/aptly) - aptly source code, functional tests, man page
* [apty-dev/aptly-dev.github.io](https://github.com/aptly-dev/aptly-dev.github.io) - aptly website (https://www.aptly.info/)
* [aptly-dev/aptly-dev.github.io](https://github.com/aptly-dev/aptly-dev.github.io) - aptly website (https://www.aptly.info/)
* [aptly-dev/aptly-fixture-db](https://github.com/aptly-dev/aptly-fixture-db) & [aptly-dev/aptly-fixture-pool](https://github.com/aptly-dev/aptly-fixture-pool) provide
fixtures for aptly functional tests
@@ -130,14 +130,14 @@ aptly version: 1.5.0+189+g0fc90dff
In order to run aptly unit tests, enter the following:
```
make docker-unit-tests
make docker-unit-test
```
#### Running system tests
In order to run aptly system tests, enter the following:
```
make docker-system-tests
make docker-system-test
```
#### Running golangci-lint
+30 -15
View File
@@ -7,9 +7,23 @@ COVERAGE_DIR?=$(shell mktemp -d)
GOOS=$(shell go env GOHOSTOS)
GOARCH=$(shell go env GOHOSTARCH)
export PODMAN_USERNS = keep-id
DOCKER_RUN = docker run --security-opt label=disable --user 0:0 --rm -v ${PWD}:/work/src
# Setting TZ for certificates
export TZ=UTC
# Unit Tests and some sysmte tests rely on expired certificates, turn back the time
export TEST_FAKETIME := 2025-01-02 03:04:05
# run with 'COVERAGE_SKIP=1' to skip coverage checks during system tests
ifeq ($(COVERAGE_SKIP),1)
COVERAGE_ARG_BUILD :=
COVERAGE_ARG_TEST := --coverage-skip
else
COVERAGE_ARG_BUILD := -coverpkg="./..."
COVERAGE_ARG_TEST := --coverage-dir $(COVERAGE_DIR)
endif
# export CAPUTRE=1 for regenrating test gold files
ifeq ($(CAPTURE),1)
CAPTURE_ARG := --capture
@@ -61,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 --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
@@ -101,13 +115,13 @@ test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify
system-test: prepare swagger etcd-install ## Run system tests
# build coverage binary
go test -v -coverpkg="./..." -c -tags testruncli
go test -v $(COVERAGE_ARG_BUILD) -c -tags testruncli
# Download fixture-db, fixture-pool, etcd.db
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz)
# Run system tests
PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE_ARG) $(TEST)
PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long $(COVERAGE_ARG_TEST) $(CAPTURE_ARG) $(TEST)
bench:
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
@@ -117,7 +131,8 @@ serve: prepare swagger-install ## Run development server (auto recompiling)
test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3
cp debian/aptly.conf ~/.aptly.conf
sed -i /enable_swagger_endpoint/s/false/true/ ~/.aptly.conf
PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs --generalInfo docs/swagger.conf' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142
sed -i /enable_metrics_endpoint/s/false/true/ ~/.aptly.conf
PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --propertyStrategy pascalcase --markdownFiles docs --generalInfo docs/swagger.conf' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142
dpkg: prepare swagger ## Build debian packages
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
@@ -171,16 +186,16 @@ docker-image-no-cache: ## Build aptly-dev docker image (no cache)
@docker build --no-cache -f system/Dockerfile . -t aptly-dev
docker-build: ## Build aptly in docker container
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper build
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper build
docker-shell: ## Run aptly and other commands in docker container
@docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper || true
@$(DOCKER_RUN) -it -p 3142:3142 aptly-dev /work/src/system/docker-wrapper || true
docker-deb: ## Build debian packages in docker container
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64
docker-unit-test: ## Run unit tests in docker container (add TEST=regex to specify which tests to run)
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \
$(DOCKER_RUN) -t --tmpfs /smallfs:rw,size=1m aptly-dev /work/src/system/docker-wrapper \
azurite-start \
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
@@ -189,27 +204,27 @@ docker-unit-test: ## Run unit tests in docker container (add TEST=regex to spec
azurite-stop
docker-system-test: ## Run system tests in docker container (add TEST=t04_mirror or TEST=UpdateMirror26Test to run only specific tests)
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper \
azurite-start \
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \
AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \
system-test TEST=$(TEST) \
system-test TEST=$(TEST) CAPTURE=$(CAPTURE) COVERAGE_SKIP=$(COVERAGE_SKIP) \
azurite-stop
docker-serve: ## Run development server (auto recompiling) on http://localhost:3142
@docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper serve || true
@$(DOCKER_RUN) -it -p 3142:3142 -v /tmp/cache-go-aptly:/var/lib/aptly/.cache/go-build aptly-dev /work/src/system/docker-wrapper serve || true
docker-lint: ## Run golangci-lint in docker container
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper lint
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper lint
docker-binaries: ## Build binary releases (FreeBSD, macOS, Linux generic) in docker container
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper binaries
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper binaries
docker-man: ## Create man page in docker container
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper man
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper man
mem.png: mem.dat mem.gp
gnuplot mem.gp
+4 -6
View File
@@ -63,7 +63,7 @@ Define Release APT sources in ``/etc/apt/sources.list.d/aptly.list``::
deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/release DIST main
Where DIST is one of: ``buster``, ``bullseye``, ``bookworm``, ``focal``, ``jammy``, ``noble``
Where DIST is one of: ``bullseye``, ``bookworm``, ``trixie``, ``focal``, ``jammy``, ``noble``
Install aptly packages::
@@ -80,7 +80,7 @@ Define CI APT sources in ``/etc/apt/sources.list.d/aptly-ci.list``::
deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/ci DIST main
Where DIST is one of: ``buster``, ``bullseye``, ``bookworm``, ``focal``, ``jammy``, ``noble``
Where DIST is one of: ``bullseye``, ``bookworm``, ``trixie``, ``focal``, ``jammy``, ``noble``
Note: same gpg key is used as for the Upstream Debian Packages.
@@ -111,10 +111,8 @@ With configuration management systems:
- `Chef cookbook <https://github.com/hw-cookbooks/aptly>`_ by Aaron Baer
(Heavy Water Operations, LLC)
- `Puppet module <https://github.com/alphagov/puppet-aptly>`_ by
Government Digital Services
- `Puppet module <https://github.com/tubemogul/puppet-aptly>`_ by
TubeMogul
- `Puppet module <https://github.com/voxpupuli/puppet-aptly>`_ by
Vox Pupuli
- `SaltStack Formula <https://github.com/saltstack-formulas/aptly-formula>`_ by
Forrest Alvarez and Brian Jackson
- `Ansible role <https://github.com/aioue/ansible-role-aptly>`_ by Tom Paine
+1
View File
@@ -13,5 +13,6 @@ git push origin v$version master
- run swagger locally (`make docker-serve`)
- copy generated docs/swagger.json to https://github.com/aptly-dev/www.aptly.info/tree/master/static/swagger/aptly_1.x.y.json
- add new version to select tag in content/doc/api/swagger.md line 48
- update version in content/download.md
- push commit to master
- create release announcement on https://github.com/aptly-dev/aptly/discussions
+2 -2
View File
@@ -70,7 +70,7 @@ func apiReady(isReady *atomic.Value) func(*gin.Context) {
return
}
status := aptlyStatus{Status: "Aptly is ready"}
status := aptlyStatus{Status: "Aptly is ready"}
c.JSON(200, status)
}
}
@@ -178,7 +178,7 @@ func truthy(value interface{}) bool {
if value == nil {
return false
}
switch v := value.(type) {
switch v := value.(type) {
case string:
switch strings.ToLower(v) {
case "n", "no", "f", "false", "0", "off":
+41 -2
View File
@@ -13,6 +13,10 @@ import (
"github.com/saracen/walker"
)
// syncFile is a seam to allow tests to force fsync failures (e.g. ENOSPC).
// In production it calls (*os.File).Sync().
var syncFile = func(f *os.File) error { return f.Sync() }
func verifyPath(path string) bool {
path = filepath.Clean(path)
for _, part := range strings.Split(path, string(filepath.Separator)) {
@@ -114,34 +118,69 @@ func apiFilesUpload(c *gin.Context) {
}
stored := []string{}
openFiles := []*os.File{}
// Write all files first
for _, files := range c.Request.MultipartForm.File {
for _, file := range files {
src, err := file.Open()
if err != nil {
// Close any files we've opened
for _, f := range openFiles {
_ = f.Close()
}
AbortWithJSONError(c, 500, err)
return
}
defer func() { _ = src.Close() }()
destPath := filepath.Join(path, filepath.Base(file.Filename))
dst, err := os.Create(destPath)
if err != nil {
_ = src.Close()
// Close any files we've opened
for _, f := range openFiles {
_ = f.Close()
}
AbortWithJSONError(c, 500, err)
return
}
defer func() { _ = dst.Close() }()
_, err = io.Copy(dst, src)
_ = src.Close()
if err != nil {
_ = dst.Close()
// Close any files we've opened
for _, f := range openFiles {
_ = f.Close()
}
AbortWithJSONError(c, 500, err)
return
}
// Keep file open for batch sync
openFiles = append(openFiles, dst)
stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename)))
}
}
// Sync all files at once to catch ENOSPC errors
for i, dst := range openFiles {
err := syncFile(dst)
if err != nil {
// Close all files
for _, f := range openFiles {
_ = f.Close()
}
AbortWithJSONError(c, 500, fmt.Errorf("error syncing file %s: %s", stored[i], err))
return
}
}
// Close all files
for _, dst := range openFiles {
_ = dst.Close()
}
apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc()
c.JSON(200, stored)
}
+476
View File
@@ -0,0 +1,476 @@
package api
import (
"bytes"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/aptly-dev/aptly/aptly"
ctx "github.com/aptly-dev/aptly/context"
"github.com/gin-gonic/gin"
"github.com/smira/flag"
. "gopkg.in/check.v1"
)
type FilesUploadDiskFullSuite struct {
aptlyContext *ctx.AptlyContext
flags *flag.FlagSet
configFile *os.File
router http.Handler
}
var _ = Suite(&FilesUploadDiskFullSuite{})
func (s *FilesUploadDiskFullSuite) SetUpTest(c *C) {
aptly.Version = "testVersion"
file, err := os.CreateTemp("", "aptly")
c.Assert(err, IsNil)
s.configFile = file
jsonString, err := json.Marshal(gin.H{
"architectures": []string{},
"rootDir": c.MkDir(),
})
c.Assert(err, IsNil)
_, err = file.Write(jsonString)
c.Assert(err, IsNil)
_ = file.Close()
flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
flags.Bool("no-lock", false, "dummy")
flags.Int("db-open-attempts", 3, "dummy")
flags.String("config", s.configFile.Name(), "dummy")
flags.String("architectures", "", "dummy")
s.flags = flags
aptlyContext, err := ctx.NewContext(s.flags)
c.Assert(err, IsNil)
s.aptlyContext = aptlyContext
s.router = Router(aptlyContext)
context = aptlyContext
}
func (s *FilesUploadDiskFullSuite) TearDownTest(c *C) {
if s.configFile != nil {
_ = os.Remove(s.configFile.Name())
}
if s.aptlyContext != nil {
s.aptlyContext.Shutdown()
}
}
func (s *FilesUploadDiskFullSuite) TestUploadSuccessWithSync(c *C) {
testContent := []byte("test file content for upload")
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "testfile.txt")
c.Assert(err, IsNil)
_, err = part.Write(testContent)
c.Assert(err, IsNil)
err = writer.Close()
c.Assert(err, IsNil)
req, err := http.NewRequest("POST", "/api/files/testdir", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
uploadedFile := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "testdir", "testfile.txt")
content, err := os.ReadFile(uploadedFile)
c.Assert(err, IsNil)
c.Check(content, DeepEquals, testContent)
}
func (s *FilesUploadDiskFullSuite) TestUploadVerifiesFileIntegrity(c *C) {
testContent := bytes.Repeat([]byte("A"), 10000)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "largefile.bin")
c.Assert(err, IsNil)
_, err = io.Copy(part, bytes.NewReader(testContent))
c.Assert(err, IsNil)
err = writer.Close()
c.Assert(err, IsNil)
req, err := http.NewRequest("POST", "/api/files/testdir2", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
uploadedFile := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "testdir2", "largefile.bin")
content, err := os.ReadFile(uploadedFile)
c.Assert(err, IsNil)
c.Check(len(content), Equals, len(testContent))
c.Check(content, DeepEquals, testContent)
}
func (s *FilesUploadDiskFullSuite) TestUploadMultipleFilesWithBatchSync(c *C) {
testFiles := map[string][]byte{
"file1.txt": []byte("content of file 1"),
"file2.txt": bytes.Repeat([]byte("B"), 5000),
"file3.deb": []byte("debian package content"),
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
for filename, content := range testFiles {
part, err := writer.CreateFormFile("file", filename)
c.Assert(err, IsNil)
_, err = part.Write(content)
c.Assert(err, IsNil)
}
err := writer.Close()
c.Assert(err, IsNil)
req, err := http.NewRequest("POST", "/api/files/multitest", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
uploadDir := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "multitest")
for filename, expectedContent := range testFiles {
uploadedFile := filepath.Join(uploadDir, filename)
content, err := os.ReadFile(uploadedFile)
c.Assert(err, IsNil, Commentf("Failed to read %s", filename))
c.Check(content, DeepEquals, expectedContent, Commentf("Content mismatch for %s", filename))
}
}
func (s *FilesUploadDiskFullSuite) TestUploadReturnsErrorOnSyncFailure(c *C) {
oldSyncFile := syncFile
syncFile = func(f *os.File) error {
if filepath.Base(f.Name()) == "syncfail.txt" {
return syscall.ENOSPC
}
return nil
}
defer func() { syncFile = oldSyncFile }()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part1, err := writer.CreateFormFile("file", "ok.txt")
c.Assert(err, IsNil)
_, err = part1.Write([]byte("ok"))
c.Assert(err, IsNil)
part2, err := writer.CreateFormFile("file", "syncfail.txt")
c.Assert(err, IsNil)
_, err = part2.Write([]byte("will fail on sync"))
c.Assert(err, IsNil)
err = writer.Close()
c.Assert(err, IsNil)
req, err := http.NewRequest("POST", "/api/files/syncfaildir", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
c.Check(bytes.Contains(w.Body.Bytes(), []byte("error syncing file")), Equals, true)
}
func (s *FilesUploadDiskFullSuite) TestVerifyPath(c *C) {
c.Check(verifyPath("a/b/c"), Equals, true)
c.Check(verifyPath("../x"), Equals, false)
c.Check(verifyPath("./x"), Equals, true)
c.Check(verifyPath(".."), Equals, false)
c.Check(verifyPath("."), Equals, false)
}
func (s *FilesUploadDiskFullSuite) TestListDirsEmptyWhenUploadMissing(c *C) {
_ = os.RemoveAll(s.aptlyContext.UploadPath())
req, err := http.NewRequest("GET", "/api/files", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
c.Check(strings.TrimSpace(w.Body.String()), Equals, "[]")
}
func (s *FilesUploadDiskFullSuite) TestListDirsReturnsDirectories(c *C) {
uploadRoot := s.aptlyContext.UploadPath()
c.Assert(os.MkdirAll(filepath.Join(uploadRoot, "d1"), 0777), IsNil)
c.Assert(os.MkdirAll(filepath.Join(uploadRoot, "d2"), 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(uploadRoot, "rootfile"), []byte("x"), 0644), IsNil)
req, err := http.NewRequest("GET", "/api/files", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
body := w.Body.String()
c.Check(strings.Contains(body, "d1"), Equals, true)
c.Check(strings.Contains(body, "d2"), Equals, true)
}
func (s *FilesUploadDiskFullSuite) TestListFilesNotFound(c *C) {
req, err := http.NewRequest("GET", "/api/files/does-not-exist", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 404)
}
func (s *FilesUploadDiskFullSuite) TestListFilesReturnsFiles(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "dir")
c.Assert(os.MkdirAll(base, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
c.Assert(os.WriteFile(filepath.Join(base, "b.txt"), []byte("b"), 0644), IsNil)
req, err := http.NewRequest("GET", "/api/files/dir", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
body := w.Body.String()
c.Check(strings.Contains(body, "a.txt"), Equals, true)
c.Check(strings.Contains(body, "b.txt"), Equals, true)
}
func (s *FilesUploadDiskFullSuite) TestDeleteDirRemovesDirectory(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "todel")
c.Assert(os.MkdirAll(base, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
req, err := http.NewRequest("DELETE", "/api/files/todel", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
_, statErr := os.Stat(base)
c.Check(os.IsNotExist(statErr), Equals, true)
}
func (s *FilesUploadDiskFullSuite) TestDeleteFileRemovesFile(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "todel2")
c.Assert(os.MkdirAll(base, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
req, err := http.NewRequest("DELETE", "/api/files/todel2/a.txt", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
_, statErr := os.Stat(filepath.Join(base, "a.txt"))
c.Check(os.IsNotExist(statErr), Equals, true)
}
func (s *FilesUploadDiskFullSuite) TestDeleteFileNotFoundStillOk(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "todel3")
c.Assert(os.MkdirAll(base, 0777), IsNil)
req, err := http.NewRequest("DELETE", "/api/files/todel3/nope.txt", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
}
func (s *FilesUploadDiskFullSuite) TestRejectsInvalidDir(c *C) {
req, err := http.NewRequest("DELETE", "/api/files/..", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 400)
}
func (s *FilesUploadDiskFullSuite) TestRejectsInvalidFileName(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "dirx")
c.Assert(os.MkdirAll(base, 0777), IsNil)
req, err := http.NewRequest("DELETE", "/api/files/dirx/..", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 400)
}
func (s *FilesUploadDiskFullSuite) TestListDirsEmptyIfUploadPathIsNotDir(c *C) {
_ = os.RemoveAll(s.aptlyContext.UploadPath())
c.Assert(os.WriteFile(s.aptlyContext.UploadPath(), []byte("not a dir"), 0644), IsNil)
req, err := http.NewRequest("GET", "/api/files", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 200)
c.Check(strings.TrimSpace(w.Body.String()), Equals, "[]")
}
func (s *FilesUploadDiskFullSuite) TestListFilesReturns500OnPermissionError(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "noperms")
c.Assert(os.MkdirAll(base, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
c.Assert(os.Chmod(base, 0), IsNil)
defer func() { _ = os.Chmod(base, 0777) }()
req, err := http.NewRequest("GET", "/api/files/noperms", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
}
func (s *FilesUploadDiskFullSuite) TestDeleteFileReturns500OnNonNotExistError(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "dirisfile")
c.Assert(os.MkdirAll(base, 0777), IsNil)
subdir := filepath.Join(base, "subdir")
c.Assert(os.MkdirAll(subdir, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(subdir, "x"), []byte("x"), 0644), IsNil)
req, err := http.NewRequest("DELETE", "/api/files/dirisfile/subdir", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
}
func (s *FilesUploadDiskFullSuite) TestUploadBadMultipartReturns400(c *C) {
req, err := http.NewRequest("POST", "/api/files/badmultipart", bytes.NewBufferString("not multipart"))
c.Assert(err, IsNil)
req.Header.Set("Content-Type", "multipart/form-data; boundary=missing")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 400)
}
func (s *FilesUploadDiskFullSuite) TestUploadRejectsInvalidDir(c *C) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "a.txt")
c.Assert(err, IsNil)
_, err = part.Write([]byte("x"))
c.Assert(err, IsNil)
c.Assert(writer.Close(), IsNil)
req, err := http.NewRequest("POST", "/api/files/..", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 400)
}
func (s *FilesUploadDiskFullSuite) TestUploadReturns500IfUploadRootIsNotDir(c *C) {
_ = os.RemoveAll(s.aptlyContext.UploadPath())
c.Assert(os.WriteFile(s.aptlyContext.UploadPath(), []byte("not a dir"), 0644), IsNil)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "a.txt")
c.Assert(err, IsNil)
_, err = part.Write([]byte("x"))
c.Assert(err, IsNil)
c.Assert(writer.Close(), IsNil)
req, err := http.NewRequest("POST", "/api/files/testdir", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
}
func (s *FilesUploadDiskFullSuite) TestUploadReturns500OnFileOpenFailure(c *C) {
// Pre-populate MultipartForm to inject a FileHeader that fails on Open().
form := &multipart.Form{
File: map[string][]*multipart.FileHeader{
"file": {{Filename: "broken.bin"}},
},
}
req, err := http.NewRequest("POST", "/api/files/openfaildir", nil)
c.Assert(err, IsNil)
req.MultipartForm = form
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
}
func (s *FilesUploadDiskFullSuite) TestUploadReturns500OnCreateFailure(c *C) {
base := filepath.Join(s.aptlyContext.UploadPath(), "readonly")
c.Assert(os.MkdirAll(base, 0777), IsNil)
c.Assert(os.Chmod(base, 0555), IsNil)
defer func() { _ = os.Chmod(base, 0777) }()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "a.txt")
c.Assert(err, IsNil)
_, err = part.Write([]byte("x"))
c.Assert(err, IsNil)
c.Assert(writer.Close(), IsNil)
req, err := http.NewRequest("POST", "/api/files/readonly", body)
c.Assert(err, IsNil)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
}
func (s *FilesUploadDiskFullSuite) TestDeleteDirReturns500OnRemoveFailure(c *C) {
parent := s.aptlyContext.UploadPath()
base := filepath.Join(parent, "cantremove")
c.Assert(os.MkdirAll(base, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
c.Assert(os.Chmod(parent, 0555), IsNil)
defer func() { _ = os.Chmod(parent, 0777) }()
req, err := http.NewRequest("DELETE", "/api/files/cantremove", nil)
c.Assert(err, IsNil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
c.Assert(w.Code, Equals, 500)
}
+1 -1
View File
@@ -28,7 +28,7 @@ type gpgAddKeyParams struct {
// @Summary Add GPG Keys
// @Description **Adds GPG keys to aptly keyring**
// @Description
// @Description Add GPG public keys for veryfing remote repositories for mirroring.
// @Description Add GPG public keys for verifying remote repositories for mirroring.
// @Description
// @Description Keys can be added in two ways:
// @Description * By providing the ASCII armord key in `GpgKeyArmor` (leave Keyserver and GpgKeyID empty)
+47 -62
View File
@@ -175,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 {
@@ -187,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)
}
@@ -333,26 +346,8 @@ func apiMirrorsPackages(c *gin.Context) {
type mirrorUpdateParams struct {
// Change mirror name to `Name`
Name string ` json:"Name" example:"mirror1"`
// Url of the archive to mirror
ArchiveURL string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
// Package query that is applied to mirror packages
Filter string ` json:"Filter" example:"xserver-xorg"`
// Limit mirror to those architectures, if not specified aptly would fetch all architectures
Architectures []string ` json:"Architectures" example:"amd64"`
// Components to mirror, if not specified aptly would fetch all components
Components []string ` json:"Components" example:"main"`
// Gpg keyring(s) for verifing Release file
// Gpg keyring(s) for verifying Release file
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
// Set "true" to include dependencies of matching packages when filtering
FilterWithDeps bool ` json:"FilterWithDeps"`
// Set "true" to mirror source packages
DownloadSources bool ` json:"DownloadSources"`
// Set "true" to mirror udeb files
DownloadUdebs bool ` json:"DownloadUdebs"`
// Set "true" to skip checking if the given components are in the Release file
SkipComponentCheck bool ` json:"SkipComponentCheck"`
// Set "true" to skip checking if the given architectures are in the Release file
SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"`
// Set "true" to ignore checksum errors
IgnoreChecksums bool ` json:"IgnoreChecksums"`
// Set "true" to skip the verification of Release file signatures
@@ -387,21 +382,14 @@ 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
}
b.Name = remote.Name
b.DownloadUdebs = remote.DownloadUdebs
b.DownloadSources = remote.DownloadSources
b.SkipComponentCheck = remote.SkipComponentCheck
b.SkipArchitectureCheck = remote.SkipArchitectureCheck
b.FilterWithDeps = remote.FilterWithDeps
b.Filter = remote.Filter
b.Architectures = remote.Architectures
b.Components = remote.Components
b.IgnoreSignatures = context.Config().GpgDisableVerify
log.Info().Msgf("%s: Starting mirror update", b.Name)
@@ -410,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 {
@@ -418,27 +407,6 @@ func apiMirrorsUpdate(c *gin.Context) {
}
}
if b.DownloadUdebs != remote.DownloadUdebs {
if remote.IsFlat() && b.DownloadUdebs {
AbortWithJSONError(c, 400, fmt.Errorf("unable to update: flat mirrors don't support udebs"))
return
}
}
if b.ArchiveURL != "" {
remote.SetArchiveRoot(b.ArchiveURL)
}
remote.Name = b.Name
remote.DownloadUdebs = b.DownloadUdebs
remote.DownloadSources = b.DownloadSources
remote.SkipComponentCheck = b.SkipComponentCheck
remote.SkipArchitectureCheck = b.SkipArchitectureCheck
remote.FilterWithDeps = b.FilterWithDeps
remote.Filter = b.Filter
remote.Architectures = b.Architectures
remote.Components = b.Components
verifier, err := getVerifier(b.Keyrings)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
@@ -447,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)
}
@@ -461,7 +446,7 @@ func apiMirrorsUpdate(c *gin.Context) {
}
}
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, b.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)
}
@@ -480,8 +465,8 @@ func apiMirrorsUpdate(c *gin.Context) {
}
}
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
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)
}
@@ -491,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)
}
@@ -600,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)
@@ -653,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)
}
+339 -181
View File
@@ -16,8 +16,8 @@ import (
type signingParams struct {
// Don't sign published repository
Skip bool ` json:"Skip" example:"false"`
// GPG key ID to use when signing the release, if not specified default key is used
GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"`
// GPG key ID(s) to use when signing the release, separated by comma, and if not specified, default configured key(s) are used
GpgKey string ` json:"GpgKey" example:"KEY_ID_a, KEY_ID_b"`
// GPG keyring to use (instead of default)
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
@@ -41,7 +41,21 @@ func getSigner(options *signingParams) (pgp.Signer, error) {
}
signer := context.GetSigner()
signer.SetKey(options.GpgKey)
var multiGpgKeys []string
// REST params have priority over config
if options.GpgKey != "" {
for _, p := range strings.Split(options.GpgKey, ",") {
if t := strings.TrimSpace(p); t != "" {
multiGpgKeys = append(multiGpgKeys, t)
}
}
} else if len(context.Config().GpgKeys) > 0 {
multiGpgKeys = context.Config().GpgKeys
}
for _, gpgKey := range multiGpgKeys {
signer.SetKey(gpgKey)
}
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
@@ -110,7 +124,7 @@ func apiPublishList(c *gin.Context) {
// @Description See also: `aptly publish show`
// @Tags Publish
// @Produce json
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambigious in URLs"
// @Param prefix path string true "publishing prefix, use `:.` instead of `.` because it is ambiguous in URLs"
// @Param distribution path string true "distribution name"
// @Success 200 {object} deb.PublishedRepo
// @Failure 404 {object} Error "Published repository not found"
@@ -146,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,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`
@@ -249,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 {
@@ -280,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,
}
@@ -296,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)
@@ -309,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 {
@@ -341,18 +358,18 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
published.AcquireByHash = *b.AcquireByHash
}
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)
}
@@ -393,7 +410,6 @@ type publishedRepoUpdateSwitchParams struct {
// @Description
// @Description See also: `aptly publish update` / `aptly publish switch`
// @Tags Publish
// @Produce json
// @Param prefix path string true "publishing prefix"
// @Param distribution path string true "distribution name"
// @Param _async query bool false "Run in background and return task object"
@@ -425,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 {
@@ -432,48 +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.MultiDist != nil {
published.MultiDist = *b.MultiDist
}
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
@@ -485,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)
}
@@ -503,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
@@ -551,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)
}
@@ -590,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)
}
@@ -708,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)
}
@@ -773,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)
}
@@ -826,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)
}
@@ -907,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)
}
@@ -997,48 +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.MultiDist != nil {
published.MultiDist = *b.MultiDist
}
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)
}
@@ -1046,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)
}
+176 -77
View File
@@ -24,7 +24,7 @@ import (
// @Tags Repos
// @Produce html
// @Success 200 {object} string "HTML"
// @Router /api/repos [get]
// @Router /repos [get]
func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -49,7 +49,7 @@ func reposListInAPIMode(localRepos map[string]utils.FileSystemPublishRoot) gin.H
// @Param pkgPath path string true "Package Path" allowReserved=true
// @Produce json
// @Success 200 ""
// @Router /api/{storage}/{pkgPath} [get]
// @Router /repos/{storage}/{pkgPath} [get]
func reposServeInAPIMode(c *gin.Context) {
pkgpath := c.Param("pkgPath")
@@ -93,7 +93,7 @@ type repoCreateParams struct {
DefaultDistribution string ` json:"DefaultDistribution" example:"stable"`
// Default component when publishing from this local repo
DefaultComponent string ` json:"DefaultComponent" example:"main"`
// Snapshot name to create repoitory from (optional)
// Snapshot name to create repository from (optional)
FromSnapshot string ` json:"FromSnapshot" example:""`
}
@@ -122,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 {
@@ -171,7 +187,7 @@ type reposEditParams struct {
Comment *string ` json:"Comment" example:"example repo"`
// Change Default Distribution for publishing
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
// Change Devault Component for publishing
// Change Default Component for publishing
DefaultComponent *string ` json:"DefaultComponent" example:""`
}
@@ -192,41 +208,66 @@ 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()
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
}
if b.Name != nil {
_, err := collection.ByName(*b.Name)
if err == nil {
// already exists
AbortWithJSONError(c, 404, err)
if b.Name != nil && *b.Name != 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
@@ -268,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 {
@@ -282,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)
})
}
@@ -351,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
@@ -363,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
}
@@ -378,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)
@@ -394,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)
}
@@ -410,6 +477,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
// @Description API verifies that packages actually exist in aptly database and checks constraint that conflicting packages cant be part of the same local repository.
// @Tags Repos
// @Param name path string true "Repository name"
// @Consume json
// @Param request body reposPackagesAddDeleteParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
@@ -455,7 +523,7 @@ func apiReposPackagesDelete(c *gin.Context) {
// @Tags Repos
// @Param name path string true "Repository name"
// @Param dir path string true "Directory of packages"
// @Param file path string false "Filename (optional)"
// @Param file path string true "Filename"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {string} string "OK"
@@ -500,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()
@@ -523,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
}
@@ -544,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...)
@@ -560,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)
}
@@ -613,11 +693,11 @@ type reposCopyPackageParams struct {
// @Summary Copy Package
// @Description Copies a package from a source to destination repository
// @Tags Repos
// @Produce json
// @Param name path string true "Destination repo"
// @Param src path string true "Source repo"
// @Param file path string true "File/packages to copy"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} task.ProcessReturnValue "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
@@ -639,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 {
@@ -662,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)
}
@@ -680,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)
}
@@ -753,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)
}
@@ -856,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()
@@ -871,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 {
@@ -901,10 +1000,10 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", "))
}
ret := reposIncludePackageFromDirResponse{
ret := reposIncludePackageFromDirResponse{
Report: reporter,
FailedFiles: failedFiles,
}
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: ret}, nil
})
}
+32 -26
View File
@@ -11,13 +11,19 @@ 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
// @Summary Get Metrics
// @Description **Get Prometheus Metrics**
// @Tags Status
// @Produce text/plain
// @Success 200 {string} string Metrics
// @Router /api/metrics [get]
func apiMetricsGet() gin.HandlerFunc {
return func(c *gin.Context) {
countPackagesByRepos()
@@ -25,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 {
@@ -63,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)
+162 -64
View File
@@ -74,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
}
@@ -107,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
}
@@ -156,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
@@ -169,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
}
@@ -217,10 +250,9 @@ type snapshotsCreateFromRepositoryParams struct {
// @Summary Snapshot Repository
// @Description **Create a snapshot of a repository by name**
// @Tags Snapshots
// @Param name path string true "Repository name"
// @Consume json
// @Param request body snapshotsCreateFromRepositoryParams true "Parameters"
// @Param name path string true "Name of the snapshot"
// @Param name path string true "Repository name"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 201 {object} deb.Snapshot "Created snapshot object"
@@ -241,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
}
@@ -269,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
}
@@ -307,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")
@@ -317,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
}
@@ -333,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
}
@@ -387,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 {
@@ -397,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
}
@@ -568,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()
@@ -580,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)
}
@@ -690,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
}
@@ -804,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
}
+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
}
+24 -22
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()) }()
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
@@ -7,11 +7,8 @@ import (
"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)
+33 -1
View File
@@ -1,6 +1,8 @@
package cmd
import (
"strings"
"github.com/aptly-dev/aptly/pgp"
"github.com/smira/commander"
"github.com/smira/flag"
@@ -12,7 +14,20 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
}
signer := context.GetSigner()
signer.SetKey(flags.Lookup("gpg-key").Value.String())
var gpgKeys []string
// CLI args have priority over config
cliKeys := flags.Lookup("gpg-key").Value.Get().([]string)
if len(cliKeys) > 0 {
gpgKeys = cliKeys
} else if len(context.Config().GpgKeys) > 0 {
gpgKeys = context.Config().GpgKeys
}
for _, gpgKey := range gpgKeys {
signer.SetKey(gpgKey)
}
signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String())
signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
signer.SetBatch(flags.Lookup("batch").Value.Get().(bool))
@@ -26,6 +41,23 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
}
type gpgKeyFlag struct {
gpgKeys []string
}
func (k *gpgKeyFlag) Set(value string) error {
k.gpgKeys = append(k.gpgKeys, value)
return nil
}
func (k *gpgKeyFlag) Get() interface{} {
return k.gpgKeys
}
func (k *gpgKeyFlag) String() string {
return strings.Join(k.gpgKeys, ",")
}
func makeCmdPublish() *commander.Command {
return &commander.Command{
UsageLine: "publish",
+1 -1
View File
@@ -34,7 +34,7 @@ Example:
}
cmd.Flag.String("distribution", "", "distribution name to publish")
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -230,7 +230,7 @@ Example:
}
cmd.Flag.String("distribution", "", "distribution name to publish")
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -151,7 +151,7 @@ This command would switch published repository (with one component) named ppa/wh
`,
Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
}
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -115,7 +115,7 @@ Example:
`,
Flag: *flag.NewFlagSet("aptly-publish-update", flag.ExitOnError),
}
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -11,7 +11,7 @@ func Test(t *testing.T) {
TestingT(t)
}
type ProgressSuite struct {}
type ProgressSuite struct{}
var _ = Suite(&ProgressSuite{})
+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
+2 -3
View File
@@ -14,7 +14,7 @@ func Test(t *testing.T) {
}
type EtcDDBSuite struct {
db database.Storage
db database.Storage
}
var _ = Suite(&EtcDDBSuite{})
@@ -133,7 +133,7 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
v, err := s.db.Get(key)
c.Assert(err, IsNil)
c.Check(v, DeepEquals, value)
err = transaction.Delete(key)
err = transaction.Delete(key)
c.Assert(err, IsNil)
_, err = transaction.Get(key2)
@@ -156,4 +156,3 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) {
_, err = transaction.Get(key)
c.Assert(err, NotNil)
}
+1
View File
@@ -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))
+2 -1
View File
@@ -168,6 +168,8 @@ func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
// LoadComplete loads additional information for local repo
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
repo.packageRefs = &PackageRefList{}
encoded, err := collection.db.Get(repo.RefKey())
if err == database.ErrNotFound {
return nil
@@ -176,7 +178,6 @@ func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
return err
}
repo.packageRefs = &PackageRefList{}
return repo.packageRefs.Decode(encoded)
}
+12
View File
@@ -133,6 +133,18 @@ func (s *LocalRepoCollectionSuite) TestByUUID(c *C) {
c.Assert(r.String(), Equals, repo.String())
}
func (s *LocalRepoCollectionSuite) TestLoadCompleteNoRefKey(c *C) {
repo := NewLocalRepo("local1", "Comment 1")
c.Assert(s.collection.Update(repo), IsNil)
r, err := s.collection.ByName("local1")
c.Assert(err, IsNil)
c.Assert(s.collection.LoadComplete(r), IsNil)
c.Assert(r.packageRefs, NotNil)
c.Assert(r.NumPackages(), Equals, 0)
}
func (s *LocalRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
repo := NewLocalRepo("local1", "Comment 1")
c.Assert(s.collection.Update(repo), IsNil)
+6 -1
View File
@@ -28,6 +28,11 @@ func ParsePPA(ppaURL string, config *utils.ConfigStructure) (url string, distrib
}
}
baseurl := config.PpaBaseURL
if baseurl == "" {
baseurl = "http://ppa.launchpad.net"
}
codename := config.PpaCodename
if codename == "" {
codename, err = getCodename()
@@ -39,7 +44,7 @@ func ParsePPA(ppaURL string, config *utils.ConfigStructure) (url string, distrib
distribution = codename
components = []string{"main"}
url = fmt.Sprintf("http://ppa.launchpad.net/%s/%s/%s", matches[1], matches[2], distributorID)
url = fmt.Sprintf("%s/%s/%s/%s", baseurl, matches[1], matches[2], distributorID)
return
}
+65 -1
View File
@@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@@ -603,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)
@@ -1126,7 +1136,15 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
release["Label"] = p.GetLabel()
release["Suite"] = p.GetSuite()
release["Codename"] = p.GetCodename()
release["Date"] = time.Now().UTC().Format("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 != "" {
if sec, err := strconv.ParseInt(epoch, 10, 64); err == nil {
publishDate = time.Unix(sec, 0).UTC()
}
}
release["Date"] = publishDate.Format(datetimeFormat)
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
if p.AcquireByHash {
release["Acquire-By-Hash"] = "yes"
@@ -1522,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 {
+48 -2
View File
@@ -433,6 +433,47 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"), PathExists)
}
func (s *PublishedRepoSuite) TestPublishSourceDateEpoch(c *C) {
// Test with SOURCE_DATE_EPOCH set
_ = os.Setenv("SOURCE_DATE_EPOCH", "1234567890")
defer func() { _ = os.Unsetenv("SOURCE_DATE_EPOCH") }()
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
c.Assert(err, IsNil)
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)
// Expected date for Unix timestamp 1234567890: Fri, 13 Feb 2009 23:31:30 UTC
c.Check(st["Date"], Equals, "Fri, 13 Feb 2009 23:31:30 UTC")
}
func (s *PublishedRepoSuite) TestPublishSourceDateEpochInvalid(c *C) {
// Test with invalid SOURCE_DATE_EPOCH (should fallback to current time)
_ = os.Setenv("SOURCE_DATE_EPOCH", "invalid")
defer func() { _ = os.Unsetenv("SOURCE_DATE_EPOCH") }()
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
c.Assert(err, IsNil)
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"))
c.Assert(err, IsNil)
defer func() { _ = rf.Close() }()
cfr := NewControlFileReader(rf, true, false)
st, err := cfr.ReadStanza()
c.Assert(err, IsNil)
// Should have a valid Date field (not empty, not the fixed date from SOURCE_DATE_EPOCH)
c.Check(st["Date"], Not(Equals), "")
c.Check(st["Date"], Not(Equals), "Fri, 13 Feb 2009 23:31:30 UTC")
}
func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) {
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
c.Assert(err, IsNil)
@@ -756,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)
@@ -771,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",
},
})
}
+3
View File
@@ -79,6 +79,9 @@ func (l *PackageRefList) Decode(input []byte) error {
// ForEach calls handler for each package ref in list
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
if l == nil {
return nil
}
var err error
for _, p := range l.Refs {
err = handler(p)
+12 -1
View File
@@ -65,7 +65,7 @@ func (s *PackageRefListSuite) TestNewPackageListFromRefList(c *C) {
list, err := NewPackageListFromRefList(reflist, coll, nil)
c.Assert(err, IsNil)
c.Check(list.Len(), Equals, 4)
c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
list, err = NewPackageListFromRefList(nil, coll, nil)
c.Assert(err, IsNil)
@@ -130,6 +130,17 @@ func (s *PackageRefListSuite) TestPackageRefListForeach(c *C) {
c.Check(err, Equals, e)
}
func (s *PackageRefListSuite) TestForEachNilList(c *C) {
var l *PackageRefList
called := false
err := l.ForEach(func([]byte) error {
called = true
return nil
})
c.Assert(err, IsNil)
c.Assert(called, Equals, false)
}
func (s *PackageRefListSuite) TestHas(c *C) {
_ = s.list.Add(s.p1)
_ = s.list.Add(s.p3)
+1 -1
View File
@@ -574,7 +574,7 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
if progress != nil {
progress.ColoredPrintf("@y[!]@| @!skipping package %s: duplicate in packages index@|", p)
}
} else if err != nil {
} else {
return err
}
}
-6
View File
@@ -125,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 {
+1 -2
View File
@@ -31,8 +31,7 @@ func BenchmarkSnapshotCollectionForEach(b *testing.B) {
for i := 0; i < b.N; i++ {
collection = NewSnapshotCollection(db)
_ = collection.ForEach(func(s *Snapshot) error {
_ = collection.ForEach(func(s *Snapshot) error {
return nil
})
}
+7 -7
View File
@@ -30,16 +30,16 @@ func CompareVersions(ver1, ver2 string) int {
// parseVersions breaks down full version to components (possibly empty)
func parseVersion(ver string) (epoch, upstream, debian string) {
i := strings.LastIndex(ver, "-")
if i != -1 {
debian, ver = ver[i+1:], ver[:i]
}
i = strings.Index(ver, ":")
i := strings.Index(ver, ":")
if i != -1 {
epoch, ver = ver[:i], ver[i+1:]
}
i = strings.Index(ver, "-")
if i != -1 {
debian, ver = ver[i+1:], ver[:i]
}
upstream = ver
return
@@ -50,7 +50,7 @@ func compareLexicographic(s1, s2 string) int {
i := 0
l1, l2 := len(s1), len(s2)
for !(i == l1 && i == l2) { // break if s1 equal to s2
for !(i == l1 && i == l2) { // break if s1 equal to s2
if i == l2 {
// s1 is longer than s2
+3 -2
View File
@@ -20,10 +20,10 @@ func (s *VersionSuite) TestParseVersion(c *C) {
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", "1"})
e, u, d = parseVersion("1.3-pre4-1")
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3-pre4", "1"})
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3", "pre4-1"})
e, u, d = parseVersion("4:1.3-pre4-1")
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3-pre4", "1"})
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3", "pre4-1"})
}
func (s *VersionSuite) TestCompareLexicographic(c *C) {
@@ -100,6 +100,7 @@ func (s *VersionSuite) TestCompareVersions(c *C) {
c.Check(CompareVersions("1.0-133-avc", "1.0"), Equals, 1)
c.Check(CompareVersions("5.2.0.3", "5.2.0.283"), Equals, -1)
c.Check(CompareVersions("4.3.5a", "4.3.5-rc3-1"), Equals, 1)
}
func (s *VersionSuite) TestParseDependency(c *C) {
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
+5 -1
View File
@@ -70,6 +70,9 @@ ppa_distributor_id: ubuntu
# Codename for short PPA url expansion
ppa_codename: ""
# PPA Base URL (default: launchpad)
# # ppa_baseurl: http://ppa.launchpad.net
# Aptly Server
###############
@@ -80,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
+1 -1
View File
@@ -2,7 +2,7 @@
<div>
In order to add debian package files to a local repository, files are first uploaded to a temporary directory.
Then the directory (or a specific file within) is added to a repository. After adding to a repositorty, the directory resp. files are removed bt default.
Then the directory (or a specific file within) is added to a repository. After adding to a repository, the directory resp. files are removed bt default.
All uploaded files are stored under `<rootDir>/upload/<tempdir>` directory.
+1 -1
View File
@@ -1,5 +1,5 @@
# Search Package Collection
<div>
Perform operations on the whole collection of packages in apty database.
Perform operations on the whole collection of packages in aptly database.
</div>
+21 -2
View File
@@ -11,11 +11,30 @@ Repositories can be published to local directories, Amazon S3 buckets, Azure or
GPG key is required to sign any published repository. The key pari should be generated before publishing.
Publiс part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
Public part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
* Multiple signing keys can be defined in aptly.conf using the gpgKeys array:
```
"gpgKeys": [
"KEY_ID_x",
"KEY_ID_y"
]
```
* It is also possible to pass multiple keys via the CLI using the repeatable `--gpg-key` flag:
```
aptly publish repo my-repo --gpg-key=KEY_ID_a --gpg-key=KEY_ID_b
```
* When using the REST API, the `gpgKey` parameter supports a comma-separated list of key IDs:
```
"gpgKey": "KEY_ID_a,KEY_ID_b"
```
* If `--gpg-key` is specified on the command line, or `gpgKey` is provided via the REST API, it takes precedence over any gpgKeys configuration in aptly.conf.
* With multi-key support, aptly will sign all Release files (both clearsigned and detached signatures) with each provided key, ensuring a smooth key rotation process while maintaining compatibility for existing clients.
#### Parameters
Publish APIs use following convention to identify published repositories: `/api/publish/:prefix/:distribution`. `:distribution` is distribution name, while `:prefix` is `[<storage>:]<prefix>` (storage is optional, it defaults to empty string), if publishing prefix contains slashes `/`, they should be replaced with underscores (`_`) and underscores
should be replaced with double underscore (`__`). To specify root `:prefix`, use `:.`, as `.` is ambigious in URLs.
should be replaced with double underscore (`__`). To specify root `:prefix`, use `:.`, as `.` is ambiguous in URLs.
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
# Manage Local Repositories
<div>
A local repository is a collection of versionned packages (usually custom packages created internally).
A local repository is a collection of versioned packages (usually custom packages created internally).
Packages can be added, removed, moved or copied between repos.
+1 -1
View File
@@ -241,7 +241,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
return "", err
}
defer func() {
_ = source.Close()
_ = source.Close()
}()
sourceInfo, err := source.Stat()
+23 -1
View File
@@ -15,6 +15,10 @@ import (
"github.com/saracen/walker"
)
// syncFile is a seam to allow tests to force fsync failures (e.g. ENOSPC).
// In production it calls (*os.File).Sync().
var syncFile = func(f *os.File) error { return f.Sync() }
// PublishedStorage abstract file system with public dirs (published repos)
type PublishedStorage struct {
rootPath string
@@ -99,7 +103,17 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
}()
_, err = io.Copy(f, source)
return err
if err != nil {
return err
}
// Sync to ensure all data is written to disk and catch ENOSPC errors
err = syncFile(f)
if err != nil {
return fmt.Errorf("error syncing file %s: %s", path, err)
}
return nil
}
// Remove removes single file under public path
@@ -136,6 +150,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
baseName := filepath.Base(fileName)
poolPath := filepath.Join(storage.rootPath, publishedPrefix, publishedRelPath, filepath.Dir(fileName))
destinationPath := filepath.Join(poolPath, baseName)
var localSourcePool aptly.LocalPackagePool
if storage.linkMethod != LinkMethodCopy {
@@ -242,6 +257,13 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
return err
}
// Sync to ensure all data is written to disk and catch ENOSPC errors
err = syncFile(dst)
if err != nil {
_ = dst.Close()
return fmt.Errorf("error syncing file %s: %s", destinationPath, err)
}
err = dst.Close()
} else if storage.linkMethod == LinkMethodSymLink {
err = localSourcePool.Symlink(sourcePath, filepath.Join(poolPath, baseName))
+376
View File
@@ -1,8 +1,14 @@
package files
import (
"bytes"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"github.com/aptly-dev/aptly/aptly"
@@ -11,6 +17,77 @@ import (
. "gopkg.in/check.v1"
)
type fakeProgress struct{ bytes.Buffer }
func (p *fakeProgress) Start() {}
func (p *fakeProgress) Shutdown() {}
func (p *fakeProgress) Flush() {}
func (p *fakeProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {
}
func (p *fakeProgress) ShutdownBar() {}
func (p *fakeProgress) AddBar(count int) {}
func (p *fakeProgress) SetBar(count int) {}
func (p *fakeProgress) Printf(msg string, a ...interface{}) {
}
func (p *fakeProgress) ColoredPrintf(msg string, a ...interface{}) {
}
func (p *fakeProgress) PrintfStdErr(msg string, a ...interface{}) {
}
type fakeRSC struct {
*bytes.Reader
closeErr error
}
func (r *fakeRSC) Close() error { return r.closeErr }
type fakePool struct {
sizeErr error
openFn func(string) (aptly.ReadSeekerCloser, error)
}
type fakeLocalPool struct {
fakePool
statErr error
}
func (p *fakeLocalPool) Stat(path string) (os.FileInfo, error) { return nil, p.statErr }
func (p *fakeLocalPool) GenerateTempPath(filename string) (string, error) {
return "", nil
}
func (p *fakeLocalPool) Link(path, dstPath string) error { return nil }
func (p *fakeLocalPool) Symlink(path, dstPath string) error { return nil }
func (p *fakeLocalPool) FullPath(path string) string { return path }
func (p *fakePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) {
return "", false, nil
}
func (p *fakePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) {
return "", nil
}
func (p *fakePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) {
return "", nil
}
func (p *fakePool) Size(path string) (int64, error) {
if p.sizeErr != nil {
return 0, p.sizeErr
}
return int64(len(path)), nil
}
func (p *fakePool) Open(path string) (aptly.ReadSeekerCloser, error) {
if p.openFn != nil {
return p.openFn(path)
}
return nil, io.EOF
}
func (p *fakePool) FilepathList(progress aptly.Progress) ([]string, error) { return nil, nil }
func (p *fakePool) Remove(path string) (int64, error) { return 0, nil }
type PublishedStorageSuite struct {
root string
storage *PublishedStorage
@@ -69,6 +146,14 @@ func (s *PublishedStorageSuite) TestPutFile(c *C) {
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) TestPutFileReturnsErrorIfSourceMissing(c *C) {
err := s.storage.MkDir("ppa/dists/squeeze/")
c.Assert(err, IsNil)
err = s.storage.PutFile("ppa/dists/squeeze/Release", filepath.Join(s.root, "no-such-file"))
c.Assert(err, NotNil)
}
func (s *PublishedStorageSuite) TestFilelist(c *C) {
err := s.storage.MkDir("ppa/pool/main/a/ab/")
c.Assert(err, IsNil)
@@ -134,6 +219,11 @@ func (s *PublishedStorageSuite) TestSymLink(c *C) {
c.Assert(linkTarget, Equals, "ppa/dists/squeeze/Release")
}
func (s *PublishedStorageSuite) TestReadLinkReturnsErrorOnMissingPath(c *C) {
_, err := s.storage.ReadLink("does/not/exist")
c.Assert(err, NotNil)
}
func (s *PublishedStorageSuite) TestHardLink(c *C) {
err := s.storage.MkDir("ppa/dists/squeeze/")
c.Assert(err, IsNil)
@@ -163,6 +253,18 @@ func (s *PublishedStorageSuite) TestRemoveDirs(c *C) {
c.Assert(os.IsNotExist(err), Equals, true)
}
func (s *PublishedStorageSuite) TestRemoveDirsWithProgress(c *C) {
err := s.storage.MkDir("ppa/dists/squeeze/")
c.Assert(err, IsNil)
err = s.storage.PutFile("ppa/dists/squeeze/Release", "/dev/null")
c.Assert(err, IsNil)
p := &fakeProgress{}
err = s.storage.RemoveDirs("ppa/dists/", p)
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) TestRemove(c *C) {
err := s.storage.MkDir("ppa/dists/squeeze/")
c.Assert(err, IsNil)
@@ -337,3 +439,277 @@ func (s *PublishedStorageSuite) TestRootRemove(c *C) {
dirStorage := NewPublishedStorage(pwd, "", "")
c.Assert(func() { _ = dirStorage.RemoveDirs("", nil) }, PanicMatches, "trying to remove the root directory")
}
// DiskFullSuite uses a loopback mount; requires Linux + root.
type DiskFullSuite struct {
root string
}
var _ = Suite(&DiskFullSuite{})
func (s *DiskFullSuite) SetUpTest(c *C) {
if runtime.GOOS != "linux" {
c.Skip("disk full tests only run on Linux")
}
s.root = c.MkDir()
}
func (s *DiskFullSuite) TestPutFileOutOfSpace(c *C) {
mountPoint := "/smallfs"
if os.Geteuid() == 0 {
mountPoint = filepath.Join(s.root, "smallfs")
err := os.MkdirAll(mountPoint, 0777)
c.Assert(err, IsNil)
fsImage := filepath.Join(s.root, "small.img")
cmd := exec.Command("dd", "if=/dev/zero", "of="+fsImage, "bs=1M", "count=1")
err = cmd.Run()
c.Assert(err, IsNil)
cmd = exec.Command("mkfs.ext4", "-F", fsImage)
err = cmd.Run()
c.Assert(err, IsNil)
cmd = exec.Command("mount", "-o", "loop", fsImage, mountPoint)
err = cmd.Run()
c.Assert(err, IsNil)
defer func() {
_ = exec.Command("umount", mountPoint).Run()
}()
}
storage := NewPublishedStorage(mountPoint, "", "")
largeFile := filepath.Join(s.root, "largefile")
cmd := exec.Command("dd", "if=/dev/zero", "of="+largeFile, "bs=1M", "count=2")
err := cmd.Run()
c.Assert(err, IsNil)
err = storage.PutFile("testfile", largeFile)
c.Assert(err, NotNil)
c.Check(strings.Contains(err.Error(), "no space left on device") ||
strings.Contains(err.Error(), "sync"), Equals, true,
Commentf("Expected disk full error, got: %v", err))
}
func (s *DiskFullSuite) TestLinkFromPoolCopyOutOfSpace(c *C) {
mountPoint := "/smallfs"
if os.Geteuid() == 0 {
mountPoint = filepath.Join(s.root, "smallfs")
err := os.MkdirAll(mountPoint, 0777)
c.Assert(err, IsNil)
fsImage := filepath.Join(s.root, "small.img")
cmd := exec.Command("dd", "if=/dev/zero", "of="+fsImage, "bs=1M", "count=1")
err = cmd.Run()
c.Assert(err, IsNil)
cmd = exec.Command("mkfs.ext4", "-F", fsImage)
err = cmd.Run()
c.Assert(err, IsNil)
cmd = exec.Command("mount", "-o", "loop", fsImage, mountPoint)
err = cmd.Run()
c.Assert(err, IsNil)
defer func() {
_ = exec.Command("umount", mountPoint).Run()
}()
}
storage := NewPublishedStorage(mountPoint, "copy", "")
poolPath := filepath.Join(s.root, "pool")
pool := NewPackagePool(poolPath, false)
cs := NewMockChecksumStorage()
largeFile := filepath.Join(s.root, "package.deb")
cmd := exec.Command("dd", "if=/dev/zero", "of="+largeFile, "bs=1M", "count=2")
err := cmd.Run()
c.Assert(err, IsNil)
sourceChecksum, err := utils.ChecksumsForFile(largeFile)
c.Assert(err, IsNil)
srcPoolPath, err := pool.Import(largeFile, "package.deb",
&utils.ChecksumInfo{MD5: "d41d8cd98f00b204e9800998ecf8427e"}, false, cs)
c.Assert(err, IsNil)
err = storage.LinkFromPool("", "pool/main/p/package", "package.deb",
pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, NotNil)
c.Check(strings.Contains(err.Error(), "no space left on device") ||
strings.Contains(err.Error(), "sync"), Equals, true,
Commentf("Expected disk full error, got: %v", err))
}
type DiskFullNoRootSuite struct {
root string
}
var _ = Suite(&DiskFullNoRootSuite{})
func (s *DiskFullNoRootSuite) SetUpTest(c *C) {
s.root = c.MkDir()
}
func (s *DiskFullNoRootSuite) TestSyncIsCalled(c *C) {
storage := NewPublishedStorage(s.root, "", "")
sourceFile := filepath.Join(s.root, "source.txt")
err := os.WriteFile(sourceFile, []byte("test content"), 0644)
c.Assert(err, IsNil)
err = storage.PutFile("dest.txt", sourceFile)
c.Assert(err, IsNil)
content, err := os.ReadFile(filepath.Join(s.root, "dest.txt"))
c.Assert(err, IsNil)
c.Check(string(content), Equals, "test content")
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopySyncIsCalled(c *C) {
storage := NewPublishedStorage(s.root, "copy", "")
poolPath := filepath.Join(s.root, "pool")
pool := NewPackagePool(poolPath, false)
cs := NewMockChecksumStorage()
pkgFile := filepath.Join(s.root, "package.deb")
err := os.WriteFile(pkgFile, []byte("package content"), 0644)
c.Assert(err, IsNil)
sourceChecksum, err := utils.ChecksumsForFile(pkgFile)
c.Assert(err, IsNil)
srcPoolPath, err := pool.Import(pkgFile, "package.deb",
&utils.ChecksumInfo{MD5: "d41d8cd98f00b204e9800998ecf8427e"}, false, cs)
c.Assert(err, IsNil)
err = storage.LinkFromPool("", "pool/main/p/package", "package.deb",
pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, IsNil)
destPath := filepath.Join(s.root, "pool/main/p/package/package.deb")
content, err := os.ReadFile(destPath)
c.Assert(err, IsNil)
c.Check(string(content), Equals, "package content")
}
func (s *DiskFullNoRootSuite) TestPutFileSyncErrorIsReturned(c *C) {
storage := NewPublishedStorage(s.root, "", "")
sourceFile := filepath.Join(s.root, "source-syncfail.txt")
err := os.WriteFile(sourceFile, []byte("test content"), 0644)
c.Assert(err, IsNil)
oldSyncFile := syncFile
syncFile = func(_ *os.File) error { return syscall.ENOSPC }
defer func() { syncFile = oldSyncFile }()
err = storage.PutFile("dest-syncfail.txt", sourceFile)
c.Assert(err, NotNil)
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopySyncErrorIsReturned(c *C) {
storage := NewPublishedStorage(s.root, "copy", "")
poolPath := filepath.Join(s.root, "pool")
pool := NewPackagePool(poolPath, false)
cs := NewMockChecksumStorage()
pkgFile := filepath.Join(s.root, "package-syncfail.deb")
err := os.WriteFile(pkgFile, []byte("package content"), 0644)
c.Assert(err, IsNil)
sourceChecksum, err := utils.ChecksumsForFile(pkgFile)
c.Assert(err, IsNil)
srcPoolPath, err := pool.Import(pkgFile, "package-syncfail.deb",
&utils.ChecksumInfo{MD5: "d41d8cd98f00b204e9800998ecf8427e"}, false, cs)
c.Assert(err, IsNil)
oldSyncFile := syncFile
syncFile = func(_ *os.File) error { return syscall.ENOSPC }
defer func() { syncFile = oldSyncFile }()
err = storage.LinkFromPool("", "pool/main/p/package", "package-syncfail.deb",
pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, NotNil)
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
}
func (s *DiskFullNoRootSuite) TestPutFileFailsIfDestinationDirMissing(c *C) {
storage := NewPublishedStorage(s.root, "", "")
sourceFile := filepath.Join(s.root, "src.txt")
err := os.WriteFile(sourceFile, []byte("x"), 0644)
c.Assert(err, IsNil)
err = storage.PutFile("missingdir/dest.txt", sourceFile)
c.Assert(err, NotNil)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolRejectsNonLocalPoolForHardlink(c *C) {
storage := NewPublishedStorage(s.root, "", "")
pool := &fakePool{}
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
c.Assert(err, NotNil)
c.Check(strings.Contains(err.Error(), "cannot link"), Equals, true)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyReturnsErrorIfOpenFails(c *C) {
storage := NewPublishedStorage(s.root, "copy", "")
pool := &fakePool{openFn: func(string) (aptly.ReadSeekerCloser, error) { return nil, io.ErrUnexpectedEOF }}
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
c.Assert(err, NotNil)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyReturnsErrorIfReaderCloseFails(c *C) {
storage := NewPublishedStorage(s.root, "copy", "")
pool := &fakePool{openFn: func(string) (aptly.ReadSeekerCloser, error) {
return &fakeRSC{Reader: bytes.NewReader([]byte("data")), closeErr: io.ErrClosedPipe}, nil
}}
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
c.Assert(err, NotNil)
c.Check(err, Equals, io.ErrClosedPipe)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyReturnsErrorIfSizeFailsWhenDestExists(c *C) {
storage := NewPublishedStorage(s.root, "copy", "size")
pool := &fakePool{sizeErr: io.ErrUnexpectedEOF, openFn: func(string) (aptly.ReadSeekerCloser, error) {
return &fakeRSC{Reader: bytes.NewReader([]byte("data")), closeErr: nil}, nil
}}
destDir := filepath.Join(s.root, "pool/main/p/pkg")
c.Assert(os.MkdirAll(destDir, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(destDir, "x.deb"), []byte("old"), 0644), IsNil)
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
c.Assert(err, NotNil)
c.Check(err, Equals, io.ErrUnexpectedEOF)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolCopyChecksumReturnsErrorIfDstMD5Fails(c *C) {
storage := NewPublishedStorage(s.root, "copy", "")
pool := &fakePool{openFn: func(string) (aptly.ReadSeekerCloser, error) {
return &fakeRSC{Reader: bytes.NewReader([]byte("data")), closeErr: nil}, nil
}}
// Make destinationPath a directory so MD5ChecksumForFile fails.
destDir := filepath.Join(s.root, "pool/main/p/pkg")
c.Assert(os.MkdirAll(destDir, 0777), IsNil)
c.Assert(os.MkdirAll(filepath.Join(destDir, "x.deb"), 0777), IsNil)
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
c.Assert(err, NotNil)
}
func (s *DiskFullNoRootSuite) TestLinkFromPoolHardlinkReturnsErrorIfStatFailsWhenDestExists(c *C) {
storage := NewPublishedStorage(c.MkDir(), "hardlink", "")
pool := &fakeLocalPool{statErr: errors.New("stat failed")}
destDir := filepath.Join(storage.rootPath, "pool", "main", "p", "pkg")
c.Assert(os.MkdirAll(destDir, 0777), IsNil)
c.Assert(os.WriteFile(filepath.Join(destDir, "x.deb"), []byte("x"), 0644), IsNil)
err := storage.LinkFromPool("", "pool/main/p/pkg", "x.deb", pool, "x", utils.ChecksumInfo{MD5: "x"}, false)
c.Assert(err, NotNil)
}
+26 -26
View File
@@ -1,6 +1,8 @@
module github.com/aptly-dev/aptly
go 1.24
go 1.24.0
toolchain go1.24.4
require (
github.com/AlekSi/pointer v1.1.0
@@ -32,29 +34,29 @@ 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.36.0 // indirect
golang.org/x/sys v0.31.0
golang.org/x/term v0.30.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.34.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)
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
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
@@ -62,7 +64,7 @@ require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cloudflare/circl v1.4.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/fatih/color v1.17.0 // indirect
@@ -87,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
@@ -103,10 +106,10 @@ 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.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.30.0 // 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
@@ -115,17 +118,14 @@ 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/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
github.com/aws/aws-sdk-go-v2/credentials v1.17.46
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1
github.com/aws/smithy-go v1.22.1
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
gopkg.in/yaml.v3 v3.0.1
+68 -92
View File
@@ -1,68 +1,70 @@
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=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/awalterschulze/gographviz v2.0.1+incompatible h1:XIECBRq9VPEQqkQL5pw2OtjCAdrtIgFKoJU8eT98AS8=
github.com/awalterschulze/gographviz v2.0.1+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo=
github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w=
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c=
github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg=
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@@ -78,9 +80,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -91,14 +92,14 @@ 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=
@@ -127,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,6 +149,7 @@ 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=
@@ -197,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=
@@ -235,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=
@@ -284,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=
@@ -300,7 +296,6 @@ 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=
@@ -319,49 +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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
@@ -377,30 +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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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=
@@ -408,10 +386,8 @@ 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
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=
+1 -1
View File
@@ -240,7 +240,7 @@ func (downloader *downloaderImpl) download(req *http.Request, url, destination s
}
if resp.Body != nil {
defer func() {
_ = resp.Body.Close()
_ = resp.Body.Close()
}()
}
+1
View File
@@ -1,3 +1,4 @@
//go:build !go1.7
// +build !go1.7
package http
+11 -11
View File
@@ -49,9 +49,9 @@ func (d *GrabDownloader) Download(ctx context.Context, url string, destination s
func (d *GrabDownloader) DownloadWithChecksum(ctx context.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error {
maxTries := d.maxTries
// FIXME: const delayMax = time.Duration(5 * time.Minute)
// FIXME: const delayMax = time.Duration(5 * time.Minute)
delay := time.Duration(1 * time.Second)
// FIXME: const delayMultiplier = 2
// FIXME: const delayMultiplier = 2
err := fmt.Errorf("no tries available")
for maxTries > 0 {
err = d.download(ctx, url, destination, expected, ignoreMismatch)
@@ -133,17 +133,17 @@ func (d *GrabDownloader) download(_ context.Context, url string, destination str
resp := d.client.Do(req)
<-resp.Done
<-resp.Done
// download is complete
// Loop:
// for {
// select {
// case <-resp.Done:
// // download is complete
// break Loop
// }
// }
// Loop:
// for {
// select {
// case <-resp.Done:
// // download is complete
// break Loop
// }
// }
err = resp.Err()
if err != nil && err == grab.ErrBadChecksum && ignoreMismatch {
fmt.Printf("Ignoring checksum mismatch for %s\n", url)
+5 -1
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,
@@ -2452,6 +2453,9 @@ show yaml config
.SH "ENVIRONMENT"
If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\.
.
.P
If environment variable \fBSOURCE_DATE_EPOCH\fR is set to a Unix timestamp, \fBaptly\fR would use that timestamp for the \fBDate\fR and \fBValid\-Until\fR fields in the \fBRelease\fR file when publishing\. This enables reproducible builds as specified by \fIhttps://reproducible\-builds\.org/specs/source\-date\-epoch/\fR\.
.
.SH "RETURN VALUES"
\fBaptly\fR exists with:
.
+7 -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,
@@ -533,6 +534,11 @@ For example, default aptly display format could be presented with the following
If environment variable `HTTP_PROXY` is set `aptly` would use its value
to proxy all HTTP requests.
If environment variable `SOURCE_DATE_EPOCH` is set to a Unix timestamp,
`aptly` would use that timestamp for the `Date` and `Valid-Until` fields
in the `Release` file when publishing. This enables reproducible builds
as specified by https://reproducible-builds.org/specs/source-date-epoch/.
## RETURN VALUES
`aptly` exists with:
+11 -4
View File
@@ -22,7 +22,7 @@ var (
type GpgSigner struct {
gpg string
version GPGVersion
keyRef string
keyRefs []string
keyring, secretKeyring string
passphrase, passphraseFile string
batch bool
@@ -35,7 +35,14 @@ func (g *GpgSigner) SetBatch(batch bool) {
// SetKey sets key ID to use when signing files
func (g *GpgSigner) SetKey(keyRef string) {
g.keyRef = keyRef
keyRef = strings.TrimSpace(keyRef)
if keyRef != "" {
if g.keyRefs == nil {
g.keyRefs = []string{keyRef}
} else {
g.keyRefs = append(g.keyRefs, keyRef)
}
}
}
// SetKeyRing allows to set custom keyring and secretkeyring
@@ -57,8 +64,8 @@ func (g *GpgSigner) gpgArgs() []string {
args = append(args, "--secret-keyring", g.secretKeyring)
}
if g.keyRef != "" {
args = append(args, "-u", g.keyRef)
for _, k := range g.keyRefs {
args = append(args, "-u", k)
}
if g.passphrase != "" || g.passphraseFile != "" {
+1 -1
View File
@@ -346,7 +346,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
storage.pathCache = make(map[string]string, len(paths))
for i := range paths {
storage.pathCache[filepath.Join("pool", paths[i])] = md5s[i]
storage.pathCache[filepath.Join(publishedPrefix, "pool", paths[i])] = md5s[i]
}
}
+7 -6
View File
@@ -112,9 +112,11 @@ func NewServer(config *Config) (*Server, error) {
buckets: make(map[string]*bucket),
config: config,
}
go func() { _ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
srv.serveHTTP(w, req)
})) }()
go func() {
_ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
srv.serveHTTP(w, req)
}))
}()
return srv, nil
}
@@ -527,14 +529,13 @@ func (bucketResource) post(a *action) interface{} {
// and dashes (-). You can use uppercase letters for buckets only in the
// US Standard region.
//
// Must start with a number or letter
// # Must start with a number or letter
//
// Must be between 3 and 255 characters long
// # Must be between 3 and 255 characters long
//
// There's one extra rule (Must not be formatted as an IP address (e.g., 192.168.5.4)
// but the real S3 server does not seem to check that rule, so we will not
// check it either.
//
func validBucketName(name string) bool {
if len(name) < 3 || len(name) > 255 {
return false
+1
View File
@@ -34,6 +34,7 @@ class APITest(BaseTest):
"linkMethod": "symlink"
}
},
"enableMetricsEndpoint": True,
"enableSwaggerEndpoint": True
}
+1 -1
View File
@@ -15,4 +15,4 @@ else
fi
cd /work/src
sudo -u aptly PATH=$PATH:/work/src/build GOPATH=/work/src/.go $cmd
sudo -u aptly PATH=$PATH:/work/src/build GOPATH=/work/src/.go GOCACHE=/work/src/.go/cache $cmd
+29
View File
@@ -0,0 +1,29 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGiBFL7pY8RBAC5uHg/9AuGJ7EF7RYty89IDLeqvlPe710eDQpJ+itsOaA/5rr3
IV1LMlqHpM2rkZkAPpARwjrga2ByJ1ww77Zq2uPqJIO2LZYWTLXic9Zity2OVu3Z
XwtdsqagIMfT5dAgNmhe5lL7qgGUwYcFFa52s7U4qO0z2FfwHW1IQrnMpwCg5RQh
Uqs5iUKdDtoeQjX5mWgQhjEEAI1zfXUvvcOrRsDlGNKYZigZiWC6J46jeR8Nnf9C
WwhXS2fzQaJyDq9DorkvPZgWUAaLLCdfGETqLzDKajynhS1+OnfFQNzvkvEPRBSb
C5k+GOF2E1E9rGXb31+1XZTcdIprp4/F3RNLLWNUwfgPLWJx9NzHTYqgBStecHkC
ySZRA/9PNFAbeJZ27HNuzoGnAa0piZDLeAAHsM1V6cosMh7U1IZqjZcrMC9YXNxH
2D90PvoBvpufCMRzL/fOVPT1JzQGYoKIX17Nmzvdq/a4YyLWRODjvWXd94bae2Xd
Vy03DYhfp8VOVJW6HuAX9JN6MKXSNxaibgOPjU822Hxd1iCIQ7QtQXB0bHkgVGVz
dGVyIChkb24ndCB1c2UgaXQpIDx0ZXN0QGFwdGx5LmluZm8+iGIEExECACIFAlL7
pY8CGyMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECHbuJwW2z5t2sQAoNn+
0cADZa66HZNY2qJi44Oq4hjaAJsHzj9JKAHEpdix5N7b6QvaZQZYhrkBDQRS+6WP
EAQA9BX+kbIM6VJYoyY9vUHXfAF4E2y2M7vl9knZ+jMPfMbI7dE3gRJQb3mngST5
7eZWawo1DNE6h3LbHsB4mpro9XLUXUMBgXRsOq4D5E0ygvDZ/tJhy0AwFiTOXKEs
/erzmbF7j/TWh4LVHXFI9DrnN0+EeF/mQC/wzX7WGCKe70cAAwUEAMr7959zUYNp
E3v4IquIJpD22bT/FiyQjFG8yGy36c+7mOP3VWi0lz5yFqqeR9NDFuLDSwOEi0nB
zXNmimLy+hIwMaHjbQLjLODmy/T9wKCgeAmK1ygT6YBGJJflThZ05M80T5hBtRA9
z2eoTn0wbi6MLmD/rbEt+lUPfSA4V0t2iEkEGBECAAkFAlL7pY8CGwwACgkQIdu4
nBbbPm05hgCgvYatZXRbEdZ91jJCQi1KI7lJ5Y8AnjvrHU0g84mE45QZFegZzzQo
9relmDMEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqz
Y8yWdcj3HUy0L0FwdGx5IFNlY29uZGFyeSBTaWduaW5nIEtleSA8YXB0bHlAZXhh
bXBsZS5jb20+iJYEExYKAD4WIQSu4W3wGDVPZ/5fXHK79OGUNOkeTgUCZ3YCRgIb
AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC79OGUNOkeTid/AP9A
kIMn2qI5TqZgzrnPt7SN16VvpMppPb2H0m0P6knQKQD8DHcLcrqAl2cjcEuntv75
gOnEvmPDAO6S1rc8UgcWdQQ=
=XPoo
-----END PGP PUBLIC KEY BLOCK-----
+11
View File
@@ -0,0 +1,11 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
lFgEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqzY8yW
dcj3HUwAAP9lsZgE1YQfaS9xfVOSi3f91lbq13+U9FPdwxfiET0+bBFrtC9BcHRs
eSBTZWNvbmRhcnkgU2lnbmluZyBLZXkgPGFwdGx5QGV4YW1wbGUuY29tPoiWBBMW
CgA+FiEEruFt8Bg1T2f+X1xyu/ThlDTpHk4FAmd2AkYCGwMFCQPCZwAFCwkIBwIG
FQoJCAsCBBYCAwECHgECF4AACgkQu/ThlDTpHk4nfwD/QJCDJ9qiOU6mYM65z7e0
jdelb6TKaT29h9JtD+pJ0CkA/Ax3C3K6gJdnI3BLp7b++YDpxL5jwwDukta3PFIH
FnUE
=IXTY
-----END PGP PRIVATE KEY BLOCK-----
+1 -1
View File
@@ -20,7 +20,7 @@ func main() {
if err != nil {
log.Fatalf("Error opening DB %q: %s", dbPath, err)
}
defer db.Close()
defer func() { _ = db.Close() }()
keys := db.KeysByPrefix([]byte(prefix))
if len(keys) == 0 {
+8 -3
View File
@@ -272,6 +272,9 @@ class BaseTest(object):
self.run_cmd([
self.gpgFinder.gpg2, "--import",
os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly.sec"], expected_code=None)
self.run_cmd([
self.gpgFinder.gpg2, "--import",
os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly3.sec"], expected_code=None)
if self.fixtureGpg:
self.run_cmd([self.gpgFinder.gpg, "--no-default-keyring", "--trust-model", "always", "--batch", "--keyring", "aptlytest.gpg", "--import"] +
@@ -310,7 +313,9 @@ class BaseTest(object):
if command[0] == "aptly":
aptly_testing_bin = Path(__file__).parent / ".." / "aptly.test"
command = [str(aptly_testing_bin), f"-test.coverprofile={Path(self.coverage_dir) / self.__class__.__name__}-{uuid4()}.out", *command[1:]]
command = [str(aptly_testing_bin), *command[1:]]
if self.coverage_dir is not None:
command.insert(1, f"-test.coverprofile={Path(self.coverage_dir) / self.__class__.__name__}-{uuid4()}.out")
if self.faketime:
command = ["faketime", os.environ.get("TEST_FAKETIME", "2025-01-02 03:04:05")] + command
@@ -337,7 +342,7 @@ class BaseTest(object):
if is_aptly_command:
# remove the last two rows as go tests always print PASS/FAIL and coverage in those
# two lines. This would otherwise fail the tests as they would not match gold
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*\ncoverage: .*", raw_output)
matches = re.findall(r"((.|\n)*)EXIT: (\d)\n.*(?:\ncoverage: .*|$)", raw_output)
if not matches:
raise Exception("no matches found in command output '%s'" % raw_output)
@@ -517,7 +522,7 @@ class BaseTest(object):
if gold != output:
diff = "".join(difflib.unified_diff(
[l + "\n" for l in gold.split("\n")], [l + "\n" for l in output.split("\n")]))
raise Exception("content doesn't match:\n" + diff + "\n\nOutput:\n" + orig + "\n")
raise Exception(f"content doesn't match:\n{diff}\n\nOutput:\n{orig}\n")
check = check_output
+1 -1
View File
@@ -1,6 +1,6 @@
azure-storage-blob
boto
requests==2.28.2
requests==2.33.0
requests-unixsocket
python-swiftclient
flake8
+6 -3
View File
@@ -36,7 +36,7 @@ def natural_key(string_):
return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]
def run(include_long_tests=False, capture_results=False, tests=None, filters=None, coverage_dir=None):
def run(include_long_tests=False, capture_results=False, tests=None, filters=None, coverage_dir=None, coverage_skip=False):
"""
Run system test.
"""
@@ -47,7 +47,7 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
fails = []
numTests = numFailed = numSkipped = 0
lastBase = None
if not coverage_dir:
if not coverage_dir and not coverage_skip:
coverage_dir = mkdtemp(suffix="aptly-coverage")
failed = False
@@ -213,6 +213,7 @@ if __name__ == "__main__":
include_long_tests = False
capture_results = False
coverage_dir = None
coverage_skip = False
tests = None
args = sys.argv[1:]
@@ -224,6 +225,8 @@ if __name__ == "__main__":
elif args[0] == "--coverage-dir":
coverage_dir = args[1]
args = args[1:]
elif args[0] == "--coverage-skip":
coverage_skip = True
args = args[1:]
@@ -236,4 +239,4 @@ if __name__ == "__main__":
else:
filters.append(arg)
run(include_long_tests, capture_results, tests, filters, coverage_dir)
run(include_long_tests, capture_results, tests, filters, coverage_dir, coverage_skip)
+2
View File
@@ -12,6 +12,7 @@
"dependencyVerboseResolve": false,
"ppaDistributorID": "ubuntu",
"ppaCodename": "",
"ppaBaseURL": "http://ppa.launchpad.net",
"serveInAPIMode": true,
"enableMetricsEndpoint": true,
"enableSwaggerEndpoint": false,
@@ -29,6 +30,7 @@
"gpgProvider": "gpg",
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgKeys": [],
"skipContentsPublishing": false,
"skipBz2Publishing": false,
"FileSystemPublishEndpoints": {},
@@ -11,6 +11,7 @@ dep_follow_source: false
dep_verboseresolve: false
ppa_distributor_id: ubuntu
ppa_codename: ""
ppa_baseurl: http://ppa.launchpad.net
serve_in_api_mode: true
enable_metrics_endpoint: true
enable_swagger_endpoint: false
@@ -27,6 +28,7 @@ download_sourcepackages: false
gpg_provider: gpg
gpg_disable_sign: false
gpg_disable_verify: false
gpg_keys: []
skip_contents_publishing: false
skip_bz2_publishing: false
filesystem_publish_endpoints: {}
+5 -1
View File
@@ -70,6 +70,9 @@ ppa_distributor_id: ubuntu
# Codename for short PPA url expansion
ppa_codename: ""
# PPA Base URL (default: launchpad)
# # ppa_baseurl: http://ppa.launchpad.net
# Aptly Server
###############
@@ -80,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
+2 -2
View File
@@ -1,4 +1,4 @@
Downloading: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease
Downloading: http://repo.aptly.info/system-tests/ppa/gladky-anton/gnuplot/ubuntu/dists/maverick/InRelease
gpgv: Signature made Sun Jul 28 07:57:01 2024 UTC
gpgv: using RSA key 5BFCD481D86D5824470E469F9000B1C3A01F726C
gpgv: Good signature from "Launchpad PPA for Anton Gladky"
@@ -6,5 +6,5 @@ gpgv: Signature made Sun Jul 28 07:57:01 2024 UTC
gpgv: using RSA key 02219381E9161C78A46CB2BFA5279A973B1F56C0
gpgv: Good signature from "Launchpad sim"
Mirror [mirror18]: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/ maverick successfully added.
Mirror [mirror18]: http://repo.aptly.info/system-tests/ppa/gladky-anton/gnuplot/ubuntu/ maverick successfully added.
You can run 'aptly mirror update mirror18' to download repository contents.
@@ -1,5 +1,5 @@
Name: mirror18
Archive Root URL: http://ppa.launchpad.net/gladky-anton/gnuplot/ubuntu/
Archive Root URL: http://repo.aptly.info/system-tests/ppa/gladky-anton/gnuplot/ubuntu/
Distribution: maverick
Components: main
Architectures: amd64, armel, i386, powerpc
+1
View File
@@ -221,6 +221,7 @@ class CreateMirror18Test(BaseTest):
"max-tries": 1,
"ppaDistributorID": "ubuntu",
"ppaCodename": "maverick",
"ppaBaseURL": "http://repo.aptly.info/system-tests/ppa",
}
fixtureCmds = [
+12
View File
@@ -0,0 +1,12 @@
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
Local repo local-repo has been successfully published.
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
Now you can add following line to apt sources:
deb http://your-server/ maverick main
deb-src http://your-server/ maverick main
Don't forget to add your GPG key to apt with apt-key.
You can also use `aptly serve` to publish your repositories over HTTP quickly.
@@ -0,0 +1,12 @@
Origin: . maverick
Label: . maverick
Suite: maverick
Codename: maverick
Date: Fri, 13 Feb 2009 23:31:30 UTC
Architectures: i386
Components: main
Description: Generated by aptly
MD5Sum:
SHA1:
SHA256:
SHA512:
+35
View File
@@ -9,6 +9,10 @@ def strip_processor(output):
return "\n".join([l for l in output.split("\n") if not l.startswith(' ') and not l.startswith('Date:')])
def strip_processor_keep_date(output):
return "\n".join([l for l in output.split("\n") if not l.startswith(' ')])
class PublishRepo1Test(BaseTest):
"""
publish repo: default
@@ -951,3 +955,34 @@ class PublishRepo34Test(BaseTest):
if 'main/dep11/README' not in pathsSeen:
raise Exception("README file not included in release file")
class PublishRepo36Test(BaseTest):
"""
publish repo: SOURCE_DATE_EPOCH produces byte-identical output
"""
fixtureCmds = [
"aptly repo create local-repo",
"aptly repo add local-repo ${files}",
]
runCmd = "aptly publish repo -skip-signing -distribution=maverick local-repo"
gold_processor = BaseTest.expand_environ
environmentOverride = {"SOURCE_DATE_EPOCH": "1234567890"}
def check(self):
super(PublishRepo36Test, self).check()
# verify Release file includes the expected date from SOURCE_DATE_EPOCH
self.check_file_contents(
'public/dists/maverick/Release', 'release', match_prepare=strip_processor_keep_date)
# save Release file from first publish
first_release = self.read_file('public/dists/maverick/Release')
# drop and republish with same SOURCE_DATE_EPOCH
self.run_cmd("aptly publish drop maverick")
self.run_cmd("aptly publish repo -skip-signing -distribution=maverick local-repo")
# verify byte-identical output
second_release = self.read_file('public/dists/maverick/Release')
self.check_equal(first_release, second_release)
@@ -0,0 +1,14 @@
gpg: Signature made Mon Jan 26 10:18:32 2026 UTC
gpg: using DSA key C5ACD2179B5231DFE842EE6121DBB89C16DB3E6D
gpg: checking the trustdb
gpg: no ultimately trusted keys found
gpg: Good signature from "Aptly Tester (don't use it) <test@aptly.info>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: C5AC D217 9B52 31DF E842 EE61 21DB B89C 16DB 3E6D
gpg: Signature made Mon Jan 26 10:18:32 2026 UTC
gpg: using EDDSA key AEE16DF018354F67FE5F5C72BBF4E19434E91E4E
gpg: Good signature from "Aptly Secondary Signing Key <aptly@example.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: AEE1 6DF0 1835 4F67 FE5F 5C72 BBF4 E194 34E9 1E4E
-17
View File
@@ -1,17 +0,0 @@
from api_lib import APITest
class TaskAPITestSwaggerDocs(APITest):
"""
GET /docs
"""
def check(self):
resp = self.get("/docs/doc.json")
self.check_equal(resp.status_code, 200)
resp = self.get("/docs/", allow_redirects=False)
self.check_equal(resp.status_code, 301)
resp = self.get("/docs/index.html")
self.check_equal(resp.status_code, 200)

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