Compare commits

...

2115 Commits

Author SHA1 Message Date
André Roth 89177bbd77 debian: fix build 2026-05-17 10:56:15 +02:00
André Roth c0a0a44066 go: mod tidy 2026-05-17 10:56:15 +02:00
André Roth b1dfa154c3 debian: embed yaml config 2026-05-17 10:56:15 +02:00
André Roth d834c7210d Revert "use new azure-sdk"
This reverts commit e2cbd637b8.

# Conflicts:
#	azure/public.go
#	go.sum

# Conflicts:
#	go.mod
#	go.sum
2026-05-17 10:56:15 +02:00
André Roth 8f6d9e5d7c Revert "go1.24: fix lint, unit and system tests"
This reverts commit f7057a9517.

# Conflicts:
#	api/api.go
#	api/files.go
#	api/repos.go
#	database/etcddb/database_test.go
#	deb/remote_test.go
#	deb/snapshot_bench_test.go
#	deb/version.go
#	files/package_pool.go
#	files/public_test.go
#	http/download.go
#	http/grab.go
#	s3/server_test.go
2026-05-17 10:56:15 +02:00
André Roth 337a95f8a4 Revert "ran "gofmt -s -w ." to format the code"
This reverts commit b49a631e0b.

# Conflicts:
#	utils/config_test.go
2026-05-17 10:56:15 +02:00
André Roth fe8e99115f disable swagger 2026-05-17 10:56:15 +02:00
André Roth c7d89a7910 debian native build 2026-05-17 10:56:15 +02:00
André Roth 8dc61cf362 ci: use correct ubuntu 26.04 codename 2026-05-17 10:16:01 +02:00
André Roth 4a9ddbdc34 Merge pull request #1565 from muresan/fix/aptly-crash-db-recover
Crash in aptly db recover
2026-05-15 16:51:51 +02:00
André Roth c316ea9b73 Merge pull request #1567 from aptly-dev/fix/doc-typos
docs: fix typos
2026-05-15 16:49:14 +02:00
André Roth d027a251ba Merge pull request #1571 from aptly-dev/feature/ubu26.04
ci: build for ubuntu 26.04
2026-05-15 16:48:55 +02:00
André Roth 16b6348710 ci: build for ubuntu 26.04 2026-05-15 00:05:35 +02:00
Catalin Muresan 1c1abe6b10 Added tests to please codeconv 2026-05-14 23:33:27 +02:00
Catalin Muresan c4bfbe52ca Fix crash in aptly db recover 2026-05-14 23:33:27 +02:00
André Roth c723fea807 docs: fix typos 2026-05-04 11:35:55 +02:00
André Roth 0d31298f37 Merge pull request #1568 from aptly-dev/fix/launchpad-test-dependency
Fix/launchpad test dependency
2026-05-04 11:30:56 +02:00
André Roth bba6bd7db5 system tests: do not depend on launchpad.net 2026-05-04 11:05:04 +02:00
André Roth faeaad0378 config: allow setting PPA Base URL 2026-05-04 11:05:04 +02:00
André Roth a20eb6866a document prometheus API
* enable in dev and test env
* fix api/repos doc
2026-04-26 23:56:05 +02:00
André Roth 809ab47042 Merge pull request #1559 from linuxdataflow/feat/pls/api-package-count
feat(api): add NumPackages to mirrors/repos/snapshots list responses
2026-04-26 18:39:24 +02:00
André Roth 0b84009b4a tests: add new arguments 2026-04-26 18:37:36 +02:00
Pierig Le Saux 92d7561d49 test(api): add coverage for NumPackages list handlers and error paths 2026-04-26 18:37:36 +02:00
Pierig Le Saux e908531bef feat(api): add NumPackages to mirrors/repos/snapshots list responses
add API response wrappers with NumPackages derived from RefList length; keep show endpoint payloads unchanged for backward compatibility; add API tests for list endpoint NumPackages; update swagger response schemas for list endpoints
2026-04-26 18:37:36 +02:00
André Roth f8620d10b2 Merge pull request #1558 from linuxdataflow/feat/pls/gpg-list-and-delete
list and delete gpg keys
2026-04-26 18:36:30 +02:00
Pierig Le Saux 8be72b48a1 update 2026-04-26 17:44:25 +02:00
Pierig Le Saux 5655480e00 add codecoverage 2026-04-26 17:44:25 +02:00
Pierig Le Saux 3c8defa304 update 2026-04-26 17:44:25 +02:00
Pierig Le Saux 1ed50697ec fix: delete is interactive 2026-04-26 17:44:25 +02:00
Pierig Le Saux 3b432d42b5 documentation 2026-04-26 17:44:25 +02:00
Pierig Le Saux 89e3bdfa07 delete a gpg key 2026-04-26 17:44:25 +02:00
André Roth f8d2d3cb8d fix lint errors 2026-04-26 17:41:12 +02:00
André Roth 01004e19c0 Merge pull request #1546 from aptly-dev/dependabot/go_modules/google.golang.org/grpc-1.79.3
build(deps): bump google.golang.org/grpc from 1.64.1 to 1.79.3
2026-04-26 17:11:45 +02:00
dependabot[bot] 92bb28149c build(deps): bump google.golang.org/grpc from 1.64.1 to 1.79.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.1 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.1...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-26 14:43:41 +00:00
André Roth 652210acfa Merge pull request #1554 from aptly-dev/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/service/s3-1.97.3
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.67.1 to 1.97.3
2026-04-26 16:38:23 +02:00
André Roth 45f3da256b Merge pull request #1543 from PhilipCramer/feat/appstream-mirror-support
Add appstream support
2026-04-26 15:36:07 +02:00
dependabot[bot] 3c5e83366a 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-04-26 13:12:54 +00:00
Philip Cramer a7a4bb7001 test: improve test coverage for AppStream feature 2026-04-26 15:04:38 +02:00
Philip Cramer 2f7f726d4c fix: reject AppStream flag for flat repos instead of silently skipping 2026-04-26 15:04:38 +02:00
Philip Cramer 43d7284657 docs: update man page and AUTHORS for AppStream support 2026-04-26 15:04:37 +02:00
Philip Cramer 02423af931 fix: prevent db cleanup from deleting AppStream pool files 2026-04-26 15:04:17 +02:00
Philip Cramer 2a228625e2 test: add system test for AppStream publish pass-through 2026-04-26 15:04:17 +02:00
Philip Cramer 16e0302f30 test: update snapshot golden files for AppStream field 2026-04-26 15:04:17 +02:00
Philip Cramer 6ecbc9ba90 test: add system tests for AppStream mirror create, edit, and update 2026-04-26 15:04:17 +02:00
Philip Cramer 7276b9621f feat: add --with-appstream to bash/zsh shell completions 2026-04-26 15:04:17 +02:00
Philip Cramer fb7734b5b0 test: add unit tests for AppStream pass-through feature 2026-04-26 15:04:17 +02:00
Philip Cramer 29c37293b9 feat: wire AppStream support through CLI, API, and publish 2026-04-26 15:04:17 +02:00
Philip Cramer f25ba2e6b0 feat: propagate AppStreamFiles through snapshots 2026-04-26 15:04:17 +02:00
Philip Cramer 6a5b9ddacf feat: add AppStream (DEP-11) download support to RemoteRepo 2026-04-26 15:04:17 +02:00
André Roth 48355f65ed Merge pull request #1542 from tonobo/reproducible-publish
Add SOURCE_DATE_EPOCH support for GPG signers
2026-04-26 15:01:27 +02:00
Tim Foerster d616977904 Add SOURCE_DATE_EPOCH support for GPG signers
Both the external GPG signer (--faked-system-time) and internal Go
OpenPGP signer (signerConfig.Time) now honor SOURCE_DATE_EPOCH,
producing reproducible signatures alongside the plain Release file dates.

Adds system tests for both signer backends verifying byte-identical
Release, Release.gpg and InRelease across repeated publishes.

The signer tests (PublishRepo3[78]Test) are using an ed25519 key because
ed25519 signatures are deterministic by design. The Go openpgp library
uses a random nonce for DSA/ECDSA (see signature.go Sign calls using
config.Random() link below) so those signatures vary across runs
even with a fixed timestamp, making byte-identical verification impossible.

In addition to 49f342878a
Ref: https://github.com/aptly-dev/aptly/pull/1537
Ref: https://github.com/ProtonMail/go-crypto/blob/v1.4.0/openpgp/packet/signature.go#L945-L979
2026-04-26 14:32:23 +02:00
André Roth 3c068febde Merge pull request #1541 from aptly-dev/dependabot/go_modules/github.com/cloudflare/circl-1.6.3
build(deps): bump github.com/cloudflare/circl from 1.6.1 to 1.6.3
2026-04-26 14:31:08 +02:00
dependabot[bot] 76adbe49e0 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-04-26 12:00:02 +00:00
André Roth f6221a2413 Merge pull request #1535 from lecafard/push-qxtqtunqqqnu
Add edit mirror API endpoint
2026-04-26 13:57:49 +02:00
André Roth 4f46cb04f5 Merge pull request #1561 from russelltg/docs
fix docs for Serve in API mode
2026-04-25 20:07:40 +02:00
Russell Greene 66e814c086 fix docs for Serve in API mode 2026-04-13 10:59:47 -06:00
tom b3f5d96490 Add edit mirror API endpoint 2026-04-12 22:26:58 +02:00
André Roth 144265122a Merge pull request #1533 from xzhang1/add-version
`Release` file: support `Version` field
2026-04-12 20:09:39 +02:00
André Roth 4f95d75c37 Merge pull request #1547 from aptly-dev/dependabot/pip/system/requests-2.33.0
build(deps): bump requests from 2.32.4 to 2.33.0 in /system
2026-04-12 20:06:48 +02:00
dependabot[bot] 8db1d2e7f1 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-04-12 17:26:36 +00:00
André Roth 4088a811cd ci: do not upload coverage for dependabot 2026-04-12 18:55:54 +02:00
André Roth 2ac4c75fad ci: fix coverage 2026-03-28 15:17:47 +01:00
Zhang Xiao e2ebcbb02a Release file: support Version field
https://wiki.debian.org/DebianRepository/Format#Version

The Version field, if specified, shall be the version of the release.
On the other hand, if not set or set to an empty value, the Version
field will not be included in the Release file.

Signed-off-by: Zhang Xiao <xiao.zhang@windriver.com>
2026-03-03 07:38:44 +00:00
André Roth 9defe70190 Merge pull request #1537 from tonobo/reproducible-publish
Add SOURCE_DATE_EPOCH support for reproducible builds
2026-02-28 12:46:57 +01:00
André Roth 23943d47e9 Merge pull request #1531 from xzhang1/fix-testcase
fix `t06_publish/PublishSnapshot43Test_gold` gold error
2026-02-28 11:44:32 +01:00
Tim Foerster 49f342878a 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-02-20 07:24:52 +01:00
Zhang Xiao 1f29c65a95 fix t06_publish/PublishSnapshot43Test_gold gold error
Signed-off-by: Zhang Xiao <xiao.zhang@windriver.com>
2026-02-03 07:12:04 +00:00
André Roth a65f79eb79 Merge pull request #1479 from abregar/issues-309-691
Add config key for 'gpgKeys' and allow multiple keyRefs when signing with gpg, fixing Issues #309 and #691
2026-01-26 11:58:03 +01:00
André Roth c6a9f82358 multi sign: add test 2026-01-26 11:26:40 +01:00
Ales Bregar 1702537979 clearer REST api docs, put whitespace to docs to show that keyId strings are trimmed 2026-01-24 10:55:15 +01:00
Ales Bregar 12604b9379 updating REST api with multiple gpg keys support, due backwards compatibility introducing CSV under same key (gpg-key) 2026-01-24 10:55:15 +01:00
Ales Bregar 9b523e6bd5 review fix 2026-01-24 10:55:15 +01:00
Ales Bregar aa030e3f36 system test t12_api sends empty keyRef string, making gpg fail 2026-01-24 10:55:15 +01:00
Ales Bregar 8e739524b0 system test unexpected string fix (would be helpful, but not changing the test just for this) 2026-01-24 10:55:15 +01:00
Ales Bregar 4ba4c0cba6 system test configuration fix 2026-01-24 10:55:15 +01:00
Ales Bregar 48d02918c1 documentation updated 2026-01-24 10:55:15 +01:00
Ales Bregar d672bfa317 white space revert to minimize change 2026-01-24 10:55:15 +01:00
Ales Bregar 9a90038dd2 - #309 adding gpgKeys config key, accepting array of keyRef, cli args has precedence
- #691 adding handling of multiple keyRefs when signing with gpg
2026-01-24 10:55:15 +01:00
André Roth 7d23196f76 Merge pull request #1529 from aptly-dev/fix/deadlocks
tasklist: fix deadlocks
2026-01-20 21:28:00 +01:00
André Roth 2c6812934e tasklist: fix deadlocks
* lock correct resources
* unlock list before queueing
2026-01-18 19:31:26 +01:00
André Roth 60f3eb151b Merge pull request #1484 from xzhang1/update_label
Support updating label and origin domain of publish
2026-01-17 11:44:32 +01:00
André Roth 0db9797c4e add test 2026-01-16 14:50:09 +01:00
Zhang Xiao a2ffffedc1 Support updating label and origin domain of publish
Signed-off-by: Zhang Xiao <xiao.zhang@windriver.com>
2026-01-16 14:50:09 +01:00
André Roth 19b98c62c1 Merge pull request #1504 from bwitt/error_outofspace
Return error on out of space
2026-01-16 14:49:20 +01:00
André Roth 06fea598e1 ci: fail on failed coverage upload 2026-01-16 13:15:43 +01:00
André Roth 9a6f06d23e unit-test: use /smallfs when non-root 2026-01-11 21:19:24 +01:00
André Roth 67f6a0e458 ci: provide 1MB /smallfs to docker 2026-01-11 21:19:24 +01:00
André Roth a57e2aecd7 ci: run unit tests in docker
- run separate unit-test job
- build docker
- allow make docker-unit-tests in ci
2026-01-11 21:19:13 +01:00
Brian Witt 1e7c15b69b error on out of space 2026-01-11 14:26:56 +01:00
André Roth a70f572efd docs: update PR tempalte 2026-01-11 14:02:24 +01:00
André Roth 8c183b45c6 Merge pull request #1524 from jay7x/patch-1
Update Puppet module references in README
2026-01-11 13:06:46 +01:00
Yury Bushmelev a8f7f58dab Update Puppet module references in README
Removed outdated Puppet module references and added an actively
maintained one.
2026-01-07 13:52:12 +08:00
André Roth 31fe26de5e Merge pull request #1461 from aptly-dev/dependabot/go_modules/github.com/cloudflare/circl-1.6.1
build(deps): bump github.com/cloudflare/circl from 1.4.0 to 1.6.1
2026-01-04 18:14:13 +01:00
André Roth eb1b770dc2 Merge pull request #1506 from aptly-dev/dependabot/go_modules/golang.org/x/crypto-0.45.0
build(deps): bump golang.org/x/crypto from 0.36.0 to 0.45.0
2026-01-04 17:56:40 +01:00
dependabot[bot] abb2ad635f 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-01-04 16:30:54 +00:00
dependabot[bot] a75df0a697 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-01-04 16:29:56 +00:00
André Roth ea797f8ebe Merge pull request #1518 from LebedevRI/inrelease-signedby
`InRelease` file: support `Signed-By` field
2026-01-04 12:46:35 +01:00
Roman Lebedev a4cc9211d6 InRelease file: support Signed-By field
https://wiki.debian.org/DebianRepository/Format#Signed-By says:
> **Signed-By**
> An optional field containing a comma separated list of
> OpenPGP key fingerprints to be used for validating
> the next Release file. The fingerprints must consist
> only of hex digits and may not contain spaces.
> The fingerprint specifies either the key the Release file
> must be signed with or the key the signature key must be
> a subkey of. The later match can be disabled by appending
> an exclamation mark to the fingerprint.
>
> If the field is present, a client should only accept future updates
> to the repository that are signed with keys listed in the field.
> The field should be ignored if the Valid-Until field is not present
> or if it is expired.

For both the CLI tools and JSON, the field is taken as a string verbatim.

When specified, we must also provide `Valid-Until` field,
and i'm not sure there is an 'infinity' value for it,
so 100 years will have to do?

Fixes https://github.com/aptly-dev/aptly/issues/1497
2025-12-30 06:06:48 +03:00
André Roth 836d9f3b8b Merge pull request #1509 from thunder-coding/dpkg-compliant-version-compare
make version comparision more similar to that of dpkg
2025-12-26 16:56:11 +01:00
André Roth 61650e5b3b Merge branch 'master' into dpkg-compliant-version-compare 2025-12-26 16:55:52 +01:00
André Roth 4e457aa570 Merge pull request #1513 from gaby/add-latest-flag-to-makecmdmirrorupdate
feat: Support for only downloading the latest version of packages
2025-12-26 16:26:43 +01:00
André Roth fe70da9c08 Merge branch 'master' into add-latest-flag-to-makecmdmirrorupdate 2025-12-26 16:26:01 +01:00
André Roth 4b57e65658 Merge pull request #1505 from atotto/feature/mirror-from-google-artifact-registry
feat(http): add Google Artifact Registry authentication for ar+https scheme
2025-12-26 16:13:01 +01:00
Ato Araki bcd81eeae4 update AUTHORS 2025-12-17 12:57:17 +09:00
Ato Araki af483d1165 feat(http): add GCP authentication for ar+https scheme 2025-12-17 12:57:17 +09:00
André Roth 6b8651fda2 Merge pull request #1510 from LeiCraft/fix-swagger-pascalcase
Fix swagger property casing
2025-12-15 10:23:41 +01:00
Juan Calderon-Perez de699aebe5 Update AUTHORS list 2025-12-11 09:42:15 -05:00
Juan Calderon-Perez 0021cf876b Harden latest-only filtering 2025-12-11 07:20:37 -05:00
Linus Fischer 32b601bde6 Fix swagger property casing 2025-12-02 11:03:28 +00:00
Yaksh Bariya b464e7f80b give myself some credit as well
Cause I'm nice :)
2025-12-01 00:33:04 +05:30
Yaksh Bariya e0f282aca9 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
2025-12-01 00:28:37 +05:30
André Roth ba65daf6cb Merge pull request #1480 from alguimodd/s3-reupload-fix
feat(s3): add publishedPrefix to pathCache to avoid reupload of files
2025-11-11 19:00:31 +01:00
Christoph Fiehe b8455f6de9 Merge branch 'master' into s3-reupload-fix 2025-11-10 20:58:43 +01:00
André Roth 132c923f25 Merge pull request #1493 from Daedaluz/fix/update-repo-api
Editing repo via api 404
2025-11-08 13:10:48 +01:00
André Roth b6d83a4f61 Merge pull request #1492 from refi64/docker-test-fixes
Various fixes for system-test and docker integration
2025-10-19 20:33:34 +02:00
André Roth 4526d6d831 Merge pull request #1498 from chesseed/fix/swagger-errors
Fix Swagger errors
2025-10-19 20:30:42 +02:00
chesseed 02d2ba255c fix comment 2025-10-09 21:33:20 +02:00
chesseed d94792dd65 fix swagger errors 2025-10-09 21:26:30 +02:00
Tobias Assarsson 66eb75f492 fix repo edit api. 2025-09-24 16:40:23 +02:00
Ryan Gonzalez 33a2f70d07 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.
2025-09-23 16:25:46 -05:00
Ryan Gonzalez 10f942c8e0 system-test: Forward CAPTURE to docker
The code was only forwarding TEST, but CAPTURE is useful too.
2025-09-23 16:25:46 -05:00
Ryan Gonzalez 568a9ce4d5 docker: Preserve the go build cache
Otherwise, every `make docker-...` invocation will need to rebuild
everything from scratch.
2025-09-23 16:25:46 -05:00
Ryan Gonzalez ddf415a359 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.
2025-09-23 16:25:46 -05:00
Ryan Gonzalez 29ac9c1919 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.
2025-09-23 16:25:46 -05:00
André Roth d3bed7830c Merge pull request #1482 from JupiterRider/tautological_condition
remove useless nil check
2025-08-30 19:22:52 +02:00
JupiterRider c2d5f47643 Merge branch 'master' into tautological_condition 2025-08-30 18:51:27 +02:00
André Roth 731e92c8e4 Merge pull request #1483 from JupiterRider/gofmt
format the Go code with gofmt
2025-08-30 18:04:29 +02:00
André Roth 94a600c0c1 README: remove buster 2025-08-29 10:35:02 +02:00
André Roth e1d8ae8a35 Merge pull request #1472 from Yye847/master
Update README.rst
2025-08-29 10:34:33 +02:00
André Roth d3b7186dea Merge pull request #1481 from agustinhenze/master
Add mutex on LinkFromPool to fix #1449
2025-08-29 09:55:15 +02:00
Agustin Henze 3608c137a0 Add mutex on LinkFromPool to fix #1449
This fixes the race condition that happens when you call publish
concurrently. It adds a valuable test that reproduces the error almost
deterministically, it's hard to say always but I have run this in loop
100 times and it reproduces the error consistently without the patch and
after the patch it works consistently.
2025-08-28 10:40:53 +02:00
JupiterRider 15a3efe758 add JupiterRider to AUTHORS file 2025-08-20 19:48:29 +02:00
JupiterRider 4b73ae462f remove tautological (unnecessary) nil condition 2025-08-20 19:45:53 +02:00
JupiterRider b49a631e0b ran "gofmt -s -w ." to format the code 2025-08-20 19:41:26 +02:00
Alejandro Guijarro Monerris 12b6b04055 chore: add name to AUTHORS 2025-08-13 16:38:44 +02:00
Alejandro Guijarro Monerris a1f659bea0 feat(s3): add publishedPrefix to pathCache to avoid reupload of files 2025-08-13 16:35:34 +02:00
André Roth 8ca4cb8dcb ci: remove EOL debian/buster 2025-08-12 14:31:44 +02:00
André Roth 8ce8f250d5 update Releasing.md 2025-08-12 14:00:57 +02:00
Yye847 3672f6f92f Update README.rst
add trixie in list of available dists also in CLI part of README
2025-07-25 12:29:59 +02:00
Yye847 888a6b2caa Update README.rst
add trixie in list of available dists
2025-07-25 12:23:04 +02:00
André Roth 231039e86c Merge pull request #1466 from itayporezky/itay-api
Fix non related API params from mirror update
2025-07-20 23:32:41 +02:00
Itay Porezky dc884e6052 Removing non related actions from mirror update 2025-06-29 21:55:27 +03:00
André Roth 4675589cf6 Merge pull request #1460 from aptly-dev/dependabot/pip/system/requests-2.32.4
build(deps): bump requests from 2.28.2 to 2.32.4 in /system
2025-06-21 15:40:30 +02:00
dependabot[bot] 32f03bfd62 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>
2025-06-10 03:23:26 +00:00
André Roth d1bfd29dfd Merge pull request #1458 from aptly-dev/release/1.6.2
Release 1.6.2
2025-06-09 18:12:35 +02:00
André Roth 27ec594606 update releasing.md 2025-06-09 14:38:53 +02:00
André Roth f652a522fd update changelog for 1.6.2 2025-06-09 14:38:53 +02:00
André Roth a794e87490 Merge pull request #1456 from aptly-dev/doc/gpg-api
doc: add swagger doc for /api/gpg/key
tests: use faketime for expired keys/signatures
2025-06-09 13:40:54 +02:00
André Roth 5b04d4fbe1 system-tests: abort on failure 2025-06-09 13:17:54 +02:00
André Roth 1566e193f6 system-test: enable faketime optionally per test 2025-06-09 13:17:54 +02:00
André Roth 601c8e9d52 tests: use faketime to prevent expired signing keys 2025-06-08 20:05:49 +02:00
André Roth 8e5707dbcc unit-tests: allow running individual tests 2025-06-08 15:00:16 +02:00
André Roth ad4d0c7b96 doc: add swagger doc for /api/gpg/key
- cleanup swagger validation errors
2025-06-08 14:24:27 +02:00
André Roth a11e004943 Merge pull request #1452 from boxjan/master
bash-completion: include global options in aptly command completions
2025-05-25 22:54:45 +02:00
boxjan f605d86a4e bash-completion: include global options in aptly command completions 2025-05-06 10:11:46 +00:00
André Roth f8bde63081 Merge pull request #1443 from aptly-dev/dependabot/go_modules/golang.org/x/net-0.38.0
Bump golang.org/x/net from 0.33.0 to 0.38.0
2025-05-01 12:17:14 +02:00
dependabot[bot] 887ce71005 Bump golang.org/x/net from 0.33.0 to 0.38.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 09:14:39 +00:00
André Roth 87233ceafe Merge pull request #1441 from aptly-dev/dependabot/go_modules/golang.org/x/crypto-0.35.0
Bump golang.org/x/crypto from 0.31.0 to 0.35.0
2025-05-01 11:13:30 +02:00
André Roth 27c15680e8 Merge pull request #1445 from silkeh/fix-db-references
Remove corrupt package references in `db recover`
2025-05-01 10:27:42 +02:00
dependabot[bot] cb72e2d70f Bump golang.org/x/crypto from 0.31.0 to 0.35.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.31.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.31.0...v0.35.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 08:15:38 +00:00
André Roth 2cafbc8484 Merge pull request #1439 from aptly-dev/feature/go-1.24
go: use version 1.24
2025-05-01 10:14:27 +02:00
Silke Hofstra 6dbb28b2b8 Add myself to authors 2025-04-30 12:21:34 +02:00
Silke Hofstra d8a4a28259 Remove corrupt package references in db recover
When aptly crashes it is possible to get a corrupt database with a dangling key reference.
This results in an error with 'key not found', eg:

    ERROR: unable to load package Pall example-package 1.2.3 778cf6f877bf6e2d: key not found

This change makes `db recover` fix this situation by removing the dangling references.
2025-04-30 12:21:34 +02:00
André Roth 9a217171c8 go: mod tidy 2025-04-26 13:35:49 +02:00
André Roth c67cafcf94 Makefile: allow no cache docker build 2025-04-26 13:31:16 +02:00
André Roth f7057a9517 go1.24: fix lint, unit and system tests
- development env: base on debian trixie with go1.24
- lint: run with default config
- fix lint errors
- fix unit tests
- fix system test
2025-04-26 13:29:50 +02:00
André Roth ae5379d84a go: use version 1.24 2025-04-25 14:20:13 +02:00
André Roth c05068c2e8 Merge pull request #1440 from aptly-dev/bugfix/issue-1435-fix-s3-upload-unchanged-package
Fix upload of unchanged packages in S3 on source update of published repository
2025-04-25 13:21:10 +02:00
André Roth 22bc2f9d0f system-tests: improve sorted compare
sort both aptly output and gold file. output original output for
debugging on failure.

* Makefile: enable CAPTURE=1 env variable for capturing gold files
* docker-system-test: use AWS env vars for S3 tests
* fix system tests timing issue with order of gpg logs in publish tests
2025-04-25 00:51:59 +02:00
André Roth c07bf2b108 s3: add debug logs for commands
* initialize zerolog for commands
* Change default log format: remote colors and timestamp
2025-04-24 12:13:38 +02:00
André Roth e447fc0f1e ci: keep CI artifacts for 7 days 2025-04-21 12:01:39 +02:00
André Roth e062df68c5 go1.23: update golangci-lint version
and fix warnings.
2025-04-20 20:32:55 +02:00
André Roth 664a5cd675 go1.23: fix system test 2025-04-20 11:57:42 +02:00
André Roth 9ef217b351 ci: use go 1.23 compatible with gocovmerge 2025-04-20 11:38:33 +02:00
Christoph Fiehe 67bd15487d Fixes Issue#1435.
Signed-off-by: Christoph Fiehe <christoph.fiehe@eurodata.de>
2025-04-14 13:39:45 +02:00
André Roth ab18da351d ci: add release notes
and update Releasing.md
2025-02-15 22:25:56 +01:00
André Roth 1abb735bfa Merge pull request #1430 from aptly-dev/release/1.6.1
Release/1.6.1
2025-02-15 19:10:42 +01:00
André Roth 9397d8ab36 add releasing doc 2025-02-15 16:23:53 +01:00
André Roth 82300d6944 update changelog 2025-02-15 16:17:37 +01:00
André Roth cf3841e35c Merge pull request #1425 from aptly-dev/fix/debian-compliance
postrm: remove aptly-api user and home directory on purge
2025-01-24 00:49:15 +01:00
Sébastien Delafond 1a0bffdc51 postrm: remove aptly-api user and home directory on purge 2025-01-22 21:48:02 +01:00
André Roth 666b5c9700 Merge pull request #1422 from aptly-dev/fix/empty-mirror-snapshot
Allow snapshotting empty mirrors
2025-01-13 12:36:01 +01:00
André Roth 2eabc6045f go mod tidy 2025-01-12 00:05:00 +01:00
André Roth cc32e79f2a Merge pull request #1423 from mikelolasagasti/google-uuid
Switch to google/uuid module
2025-01-11 23:56:23 +01:00
Mikel Olasagasti Uranga 7074fc8856 Switch to google/uuid module
Current used github.com/pborman/uuid hasn't seen any updates in years.

Signed-off-by: Mikel Olasagasti Uranga <mikel@olasagasti.info>
2025-01-11 23:18:50 +01:00
André Roth a7d85e5905 Merge pull request #1187 from aptly-dev/dependabot/go_modules/github.com/gin-gonic/gin-1.9.1
Bump github.com/gin-gonic/gin from 1.7.7 to 1.9.1
2025-01-11 22:15:59 +01:00
André Roth cad4233d0d Bump github.com/gin-gonic/gin from 1.7.7 to 1.9.1
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.7.7 to 1.9.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.7.7...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
# Conflicts:
#	go.mod
#	go.sum
2025-01-11 21:48:14 +01:00
André Roth 9b9894c07d update README 2025-01-11 21:33:40 +01:00
André Roth 8546cf31ce add test: snapshot empty mirror 2025-01-11 20:00:42 +01:00
André Roth aa0830ff0c Revert "fix empty mirror check"
This reverts commit 09a44ba409.
2025-01-11 19:17:28 +01:00
dependabot[bot] 4076941bd7 Bump golang.org/x/net from 0.28.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.28.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.28.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-11 15:58:10 +01:00
André Roth 4170c9e995 update README 2025-01-11 15:58:10 +01:00
André Roth a862192bc4 ci: more relaxed aptly upload 2025-01-11 15:58:10 +01:00
André Roth 5a18428666 aptly.conf: fix s3 example 2025-01-11 15:25:53 +01:00
August Feng 0b5a627c84 update goleveldb dependency 2025-01-11 14:35:28 +01:00
André Roth 65820cdf7a update man page 2024-12-24 19:02:38 +01:00
André Roth 2c3a107e00 update changelog 2024-12-24 18:57:40 +01:00
André Roth e028db585f fix man page 2024-12-21 22:32:50 +01:00
André Roth d523ca8186 update Makefile PHONY 2024-12-21 22:13:26 +01:00
André Roth f008f245dc update man page 2024-12-21 21:35:06 +01:00
André Roth f2f3196368 fix AUTHORS for man page
only US ASCII seems to be supported
2024-12-21 21:34:46 +01:00
Karol Swiderski 29eccc9226 improve doc
add instructions for macos users
2024-12-21 21:29:26 +01:00
André Roth 9abbd74a9f improve doc
do not set default value for FromSnapshot when creating a repo
2024-12-21 20:23:52 +01:00
André Roth 846fe5e08a update changelog 2024-12-21 19:41:59 +01:00
André Roth da29961052 Revert "debian: do not conflict with gnupg1"
This reverts commit 2f540a8026.
2024-12-21 18:55:49 +01:00
André Roth e5b8315859 Merge pull request #1411 from schoenherrg/feature/filter-using-file
Feature: Support Reading Filter Expressions from a File
2024-12-21 18:54:44 +01:00
André Roth c6bb5f76f7 cmd filter: add comment and cleanup 2024-12-21 11:37:15 +01:00
André Roth fea7acb56e Merge pull request #1407 from aptly-dev/dependabot/go_modules/golang.org/x/crypto-0.31.0
Bump golang.org/x/crypto from 0.26.0 to 0.31.0
2024-12-20 11:29:07 +01:00
Gordian Schoenherr 50d3676847 Update man page 2024-12-20 12:55:56 +09:00
Gordian Schoenherr 8830354027 Extend system tests for @file filter syntax 2024-12-20 10:59:29 +09:00
Gordian Schoenherr 2467674fca Update system tests 2024-12-19 16:05:21 +09:00
Gordian Schoenherr 9691b0f518 Refactor query reading from file, update docs
Add support for @file syntax in more places.
2024-12-19 15:02:10 +09:00
Christof Warlich 005114839a Generalize to read filter from file or stdin. 2024-12-13 11:24:54 +09:00
Christof Warlich a5d322252a Allow reading package query for -filter option from a file. 2024-12-13 11:24:47 +09:00
dependabot[bot] b49630d6fc Bump golang.org/x/crypto from 0.26.0 to 0.31.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.26.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.26.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-12 00:24:55 +00:00
André Roth 93650efddb Merge pull request #1404 from schoenherrg/fix/with-sources-ignored
Fix `-with-sources` not fetching differently named source packages
2024-12-11 13:01:30 +01:00
André Roth d87327835e Merge pull request #1401 from aptly-dev/feature/yaml-config
Feature/yaml config
2024-12-11 12:38:47 +01:00
André Roth 0d90ff96b9 debian: add build dependency for yaml 2024-12-11 12:02:52 +01:00
André Roth b14595cb2d cleanup makefile 2024-12-11 12:02:52 +01:00
André Roth e50a5e175f update documentation and man page 2024-12-11 12:02:52 +01:00
André Roth a7d6782176 add test and improve config error messages 2024-12-11 12:02:52 +01:00
André Roth eb6dd4d69e swagger: fix docker serve 2024-12-11 12:02:52 +01:00
André Roth a15d8a3f7b add test 2024-12-11 12:02:52 +01:00
André Roth 22cfa4c8c7 add yaml example 2024-12-11 12:02:52 +01:00
André Roth 4e566b4692 fix tests and lint 2024-12-11 12:02:52 +01:00
André Roth 9d0c7b5ade make yaml config default 2024-12-11 12:02:52 +01:00
André Roth 87a36633e9 group and order config items
and fix lint
2024-12-11 12:02:52 +01:00
André Roth 0e0189f0eb use yaml config file 2024-12-11 12:02:52 +01:00
André Roth a880a88fc0 yaml config 2024-12-11 12:02:52 +01:00
André Roth 465312b8a0 Merge pull request #1359 from aptly-dev/improve/documentation
Improve/documentation
2024-12-11 12:02:13 +01:00
André Roth e319f3cd14 update doc
make descrptions consistent
2024-12-11 11:19:46 +01:00
André Roth e92afd8f78 fix unit tests on arm
and fix etcd data dir
2024-12-11 10:40:44 +01:00
André Roth 280563caa8 unit-tests: allow running as user 2024-12-11 10:40:44 +01:00
André Roth 3d8968eff3 swagger: install native swag 2024-12-11 10:40:44 +01:00
André Roth 1301847b7e update CONTRIBUTING 2024-12-11 10:40:44 +01:00
André Roth 09c56342d2 fix json 2024-12-11 10:40:44 +01:00
André Roth ea80f6d49c write commented json default config 2024-12-11 10:40:44 +01:00
André Roth 622072bd50 document aptly.conf 2024-12-11 10:40:44 +01:00
André Roth 1f469e23b5 fix optional params 2024-12-11 10:40:44 +01:00
André Roth d8b9777b40 swagger: document params 2024-12-11 10:40:44 +01:00
André Roth e5e3c49ace swagger: document async 2024-12-11 10:40:44 +01:00
André Roth c6e0a06b14 swagger: cleanup 2024-12-11 10:40:44 +01:00
André Roth 75e5f95277 task-dummy: remove internal testing API 2024-12-11 10:40:44 +01:00
André Roth a59fc6b8e8 swwagger: cleanup 2024-12-11 10:40:44 +01:00
André Roth 4ff3c894fa swagger: cleanup Snapshots 2024-12-11 10:40:44 +01:00
André Roth abfad37640 swagger: cleanup files doc 2024-12-11 10:40:44 +01:00
André Roth 63b8cc9ad9 dos: improve api info 2024-12-11 10:40:44 +01:00
André Roth a69c00a5bc swagger: improve layout
and fix lint
2024-12-11 10:40:44 +01:00
André Roth 4f229a5bcf update doc 2024-12-11 10:40:44 +01:00
André Roth 83f7c869f0 doc: improve cmd usage arguments 2024-12-11 10:40:44 +01:00
André Roth 397362bb1a fix swagger build 2024-12-11 10:40:44 +01:00
iofq d5571c41c7 Update files api docs 2024-12-11 10:40:44 +01:00
iofq 39921809ee Update db api docs 2024-12-11 10:40:44 +01:00
iofq 68fe2bc852 Update gpg, graph api docs 2024-12-11 10:40:44 +01:00
iofq 398fec13b0 Update packages api docs 2024-12-11 10:40:44 +01:00
iofq 9fc7ebdac2 Update repos, task, snapshot api docs 2024-12-11 10:40:44 +01:00
André Roth 74bc3f5db3 update go.mod
and make sure make lint has the VERSION generated
2024-12-11 10:40:44 +01:00
André Roth a5f8ce2503 doc: import chapters from aptly.info 2024-12-11 10:40:44 +01:00
André Roth 2171c05ef8 fix lint 2024-12-11 10:40:44 +01:00
André Roth 8f8de4bd29 update 2024-12-11 10:40:44 +01:00
André Roth c055611914 update go.mod 2024-12-11 10:40:44 +01:00
André Roth 9b8f6b1d56 fix conflict 2024-12-11 10:40:43 +01:00
André Roth 096fa47c6d update doc 2024-12-11 10:40:43 +01:00
André Roth e677a2e84a go mod tidy 2024-12-11 10:40:43 +01:00
André Roth 69a1e2561d docs: improve swagger
- use markdown files in swagger
- automate version, use swager.conf template
- embed swagger ui index.html as docs.html
2024-12-11 10:40:43 +01:00
André Roth 2df82e87b7 document aptly.conf 2024-12-11 10:40:43 +01:00
André Roth cc1fc7ccfe allow comments in config file 2024-12-11 10:40:43 +01:00
André Roth ba86851d07 add api documentation stubs 2024-12-11 10:40:43 +01:00
André Roth f9ae9b323a Merge pull request #1214 from aptly-dev/fix/1213-aptly-graph-removed-before-exiting
Fix: Graph deleted before aptly exits
2024-12-11 10:11:49 +01:00
André Roth 5e91b10c8c improve test to check for source pkgs with different name 2024-12-11 05:33:38 +01:00
Gordian Schoenherr 568345c396 Add name to AUTHORS 2024-12-10 12:12:23 +09:00
Gordian Schoenherr 8c3fe8dabb Fix failing system test
The fix of the -with-filter flag causes the following previously
missing source files to be downloaded, so I updated the test file.

```
rkward_0.7.5-1~bullseyecran.0.debian.tar.xz
rkward_0.7.5-1~bullseyecran.0.dsc
rkward_0.7.5.orig.tar.gz
rpy2_3.5.12-1~bullseyecran.0.debian.tar.xz
rpy2_3.5.12-1~bullseyecran.0.dsc
rpy2_3.5.12.orig.tar.gz
```
2024-12-10 11:52:55 +09:00
Gordian Schoenherr ef6815222c Add unit tests for filtering with source packages 2024-12-09 13:17:41 +09:00
Gordian Schoenherr 0c76677b16 Fix -with-sources not downloading differently named sources
Such as e.g. downloading 'glibc' when the sources for 'libc6'
are requested.
2024-12-09 13:17:41 +09:00
Gordian Schoenherr 3b785e4165 Refactor Filter options into a struct
It was already a lot of options for one method and I am going to add
another one in the next commit.
2024-12-09 13:17:41 +09:00
André Roth 320307f504 graph: do not remove tempfile when opening in viewer 2024-12-04 17:30:27 +01:00
André Roth 88ef8efba5 Merge pull request #1396 from aptly-dev/fix/debianization
Fix/debianization
2024-12-04 09:07:26 +01:00
André Roth 3fad19650d Merge pull request #1400 from cfiehe/fix/null_pointer_when_dropping_published_repo
Fix: Null pointer when dropping a multi-dist published repo
2024-12-02 15:47:30 +01:00
Christoph Fiehe 7d9f020ae8 Fix null pointer when dropping a multi dist published repo.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-12-02 15:09:46 +01:00
André Roth 2f540a8026 debian: do not conflict with gnupg1 2024-12-01 10:54:54 +01:00
André Roth b2d05828a5 set systemd service file limit to 32768 2024-12-01 10:54:54 +01:00
André Roth b7e91f0994 Merge pull request #1395 from leighlondon/patching-aws-sdk-go-v2
Bulk patch aws/aws-sdk-go-v2 dependencies
2024-11-28 13:57:06 +01:00
Leigh London 247f5e7c60 update AUTHORS 2024-11-19 15:06:58 +11:00
Leigh London 36121e5643 Bulk patch aws/aws-sdk-go-v2 dependencies 2024-11-19 14:59:26 +11:00
André Roth 763b810ca8 Merge pull request #1392 from aptly-dev/fix/azure-sdk-migration
Migrate to  azure-sdk-for-go
2024-11-17 18:13:02 +01:00
André Roth d80b905945 run azure tests in docker 2024-11-17 17:44:31 +01:00
André Roth e2cbd637b8 use new azure-sdk 2024-11-17 17:43:20 +01:00
André Roth d96ef0f178 Merge pull request #1393 from aptly-dev/improve/debianization
Improve/debianization
2024-11-17 17:38:47 +01:00
André Roth de181bef0f Merge pull request #1186 from aptly-dev/feature/384-generate-checksums-for-component-files
Feature/384-generate-checksums-for-component-files
2024-11-17 17:33:41 +01:00
André Roth 14e4f3dad8 include more official debianization 2024-11-17 15:44:27 +01:00
André Roth d2b9adf6f2 Add a test to confirm that skel files added with the same path as a repository file do not override the repository file.
Co-authored-by: iofq <cjriddz@protonmail.com>
2024-11-17 14:17:14 +01:00
André Roth 036422399a add -no-lock flag to service 2024-11-17 14:10:31 +01:00
André Roth 53c2f8b778 debian: add lintian
and fix/improve cross building. build now with PIE and RELRO
2024-11-17 14:10:30 +01:00
André Roth 6050051e04 adapt to official debian aptly packaging 2024-11-17 14:10:30 +01:00
André Roth 8c2ea639fd debian: use package versions from bookworm-backports 2024-11-17 14:10:30 +01:00
André Roth 5ddb718eab support ~ in rootDir 2024-11-17 14:09:37 +01:00
André Roth 9ca9569714 fix build and golangci-lint 2024-11-17 14:09:37 +01:00
Mauro Regli 1357d246d8 rename addon files to skel files 2024-11-17 14:09:37 +01:00
Mauro Regli 03f189b62c add first test of addon files 2024-11-17 14:09:37 +01:00
Mauro Regli 8d8f4714c3 add name to AUTHORS list 2024-11-17 14:09:37 +01:00
Mauro Regli c75c2c7594 pass down addonpath from api and cmd context 2024-11-17 14:09:37 +01:00
Mauro Regli 17186b0c73 add GetAddonPaths to publish file 2024-11-17 14:09:37 +01:00
Mauro Regli 2aac7baf52 add AddonIndex to index_files
I had to remove "signable: false" (line 399), since that property
doesn't exist.
2024-11-17 14:09:37 +01:00
Mauro Regli bc090e1dce add GetAddonDir to context 2024-11-17 14:09:37 +01:00
André Roth 31ccce1343 Merge pull request #1388 from aptly-dev/fix/flat-mirror-filtering
do not set empty mirror architectures for flat mirrors
2024-11-16 21:40:27 +01:00
André Roth 147955c682 Merge pull request #1390 from iofq/master
Make HTTP server wait for tasks before shutdown
2024-11-10 15:38:47 +01:00
iofq 840b76228a add shutdown context unit test 2024-11-09 15:34:35 -06:00
iofq 8436001d5b Make HTTP server wait for tasks before shutdown 2024-11-08 14:06:23 -06:00
André Roth c86b888b0a add tests 2024-11-08 19:00:18 +01:00
André Roth 0936922172 only allow mirrors with architectures set 2024-11-08 17:07:37 +01:00
André Roth 62a0a1a560 log error 2024-11-08 17:07:37 +01:00
André Roth 596f59d3c4 fix tests 2024-11-08 17:07:37 +01:00
André Roth e642847a82 log filtering error 2024-11-08 17:07:37 +01:00
André Roth 26c14e218a fix lint 2024-11-08 17:07:37 +01:00
André Roth 26c775ccfd fix test
flat repos may have architecture which is needed for filtering dependencies
2024-11-08 17:07:37 +01:00
André Roth d6284148f9 set Architectures from flat mirror
note: 'Architecture' is not official, but used by nvidia mirrors for no debian arch 'x86_64'. shold this be supported ?
2024-11-08 17:07:37 +01:00
André Roth 4c58266a87 do not set empty mirror architectures for flat mirrors 2024-11-08 17:07:37 +01:00
André Roth 2f06b0690c Merge pull request #1387 from 5hir0kur0/fix/providesDependency-error
package.go: Fix bug in providesDependency
2024-11-08 16:22:48 +01:00
André Roth 19d213d748 fix tests 2024-11-08 15:55:01 +01:00
5hir0kur0 c8fca7953c package.go: Fix bug in providesDependency
Use package version if `Provides:` entry does not specify a version.
2024-11-08 15:55:01 +01:00
André Roth 55d4d98353 Merge pull request #1389 from aptly-dev/remove-lfs
Remove lfs
2024-11-08 15:52:32 +01:00
André Roth dd4f90e4c2 Revert "use git-lfs for test files"
This reverts commit bf4b660568.
2024-11-08 15:23:31 +01:00
André Roth 6647f6d0e0 Merge pull request #1386 from aptly-dev/fix/add-provided-package
Fix/add provided package
2024-11-08 10:58:30 +01:00
André Roth 9d05949d19 update doc 2024-11-08 10:20:25 +01:00
André Roth 9da58b1ea2 ci: lfs checkout 2024-11-07 17:14:56 +01:00
André Roth 38485a5b1e add test 2024-11-07 17:07:37 +01:00
André Roth bf4b660568 use git-lfs for test files 2024-11-07 17:07:37 +01:00
André Roth c028d5e8cb docker-server: also watch cmd/ directory 2024-11-04 17:02:54 +01:00
André Roth eafec74c29 allow to exclude provided packages from list.Search 2024-11-04 17:02:54 +01:00
André Roth 74364544c2 Merge pull request #1366 from cfiehe/feature/allow_component_management
Allow adding, removing and replacing of published repository components
2024-11-01 20:45:23 +01:00
André Roth a4c53689ca docker-wrapper: ignore root user
some systems (MacOS) might have root permissions on the volume directories.
2024-11-01 20:18:05 +01:00
André Roth 0ceff44421 improve log 2024-11-01 20:01:45 +01:00
André Roth f79423a4ee update swagger documentation 2024-11-01 17:48:03 +01:00
Christoph Fiehe c9309c926c Command to replace the whole staged source list added.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-11-01 17:48:03 +01:00
André Roth ee3124cfc6 update bash completion 2024-11-01 17:48:03 +01:00
André Roth eb94211053 fix race conditions 2024-11-01 17:48:03 +01:00
André Roth bd01cd4033 update swagger documentation 2024-11-01 17:48:03 +01:00
Christoph Fiehe 21013a8317 Command descriptions fixed.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-11-01 17:48:03 +01:00
Christoph Fiehe 451de79666 Improve consistency between API and Swagger docs.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-11-01 17:48:03 +01:00
André Roth 755fdfaca2 update swagger documentation
- add default values
-  set default values
2024-11-01 17:48:03 +01:00
André Roth f4057850b9 fix compile and lint errors 2024-11-01 17:47:50 +01:00
André Roth a56f52ff18 update man pages 2024-10-22 16:58:15 +02:00
André Roth 4d6688d68e sanitize archs 2024-10-22 16:58:15 +02:00
Christoph Fiehe 7a7ff1142c Minor code and documentation changes.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe 8cceed12f7 Fix tests.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe f8f28e9554 Fixing tests and fix cleanup.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe ac5ecf946d Cleanup improved and code redundant code removed.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe d87d8bac92 Fix test cases.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe 9dffe791ad Restoring original test sequence
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe 3057aed571 Test cases added.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe 14c29ff912 Fixing tests.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
Christoph Fiehe 73cdf5417b Use POST instead of PUT for source creation.
Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
André Roth fa0d2860f0 fix multidist in publish 2024-10-22 16:58:15 +02:00
André Roth dcbb2a06a5 fix build 2024-10-22 16:58:15 +02:00
Christoph Fiehe bd64232eb6 Allow management of components
This commit allows to add, remove and update components of published repositories without the need to recreate them.

Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-10-22 16:58:15 +02:00
André Roth 767bc6bd0b Merge pull request #1380 from aptly-dev/fix/concurrent-api
Fix race condition with async API operations
2024-10-22 16:56:12 +02:00
André Roth 8ddb81eb5c Merge pull request #1368 from aptly-dev/fix/repo-add-errmsg
repo add: improve error message
2024-10-22 16:39:45 +02:00
André Roth f16a68f59c fix race condition with repo add files
Do all relevant database reading/modifying inside `maybeRunTaskInBackground`.

Notably, `LoadComplete` will load the reflist of a repo. if this is done outside of a background operation,
the data might be outdated when the background tasks runs.
2024-10-22 15:12:25 +02:00
André Roth 0e6f9c38fb ci: add packages to aptly repo with async 2024-10-22 14:38:02 +02:00
André Roth 037da55de1 Merge pull request #1375 from aol-nnov/api-create-repo-from-snapshot
Update create repo API to support snapshots
2024-10-22 11:45:47 +02:00
André Roth 0666f8784f repo from snapshot: add negative test 2024-10-22 11:13:31 +02:00
André Roth 01f16d35c2 swagger: make json params uppercase and add default values 2024-10-22 11:02:59 +02:00
Андрей Лухнов f8e0a8d880 Update create repo API to support snapshots
To achieve feature parity with cli, it is now possible
to create repos from snapshots
2024-10-22 07:53:43 +03:00
André Roth ae0fa20aa6 Merge pull request #1370 from aptly-dev/fix/path-traversal
fix path traversal
2024-10-11 15:15:30 +02:00
André Roth cefc09a41b more sanitize 2024-10-11 14:11:09 +02:00
André Roth 7742980426 use specific go version
As of Go 1.21, toolchain versions must use the 1.N.P syntax.
2024-10-11 12:56:08 +02:00
André Roth 57639c4adf Sanitize path api params
- fix path traversal complains by CodeQL
2024-10-11 12:56:08 +02:00
André Roth 75ca51b23b improve error message 2024-10-10 12:03:13 +02:00
André Roth ce2966e547 Merge pull request #1364 from aptly-dev/feature/persist-multidist
Feature/persist multidist
2024-10-09 11:55:48 +02:00
André Roth 861260198a publish: persist multidist flag 2024-10-08 22:28:12 +02:00
André Roth 3e7bec5604 Merge pull request #1363 from aptly-dev/improve/dev-ci
Improve/dev ci
2024-10-08 22:22:34 +02:00
André Roth 14a343a0d7 system tests: support fitureCmds and allow dirmgr to startup
- fixes a race condition, where dirmgr does not seem to  be ready
- imports secret key for signing if gpg2 is used
2024-10-08 15:38:42 +02:00
André Roth 704af8f2f0 docker: use bash shell for aptly user 2024-10-08 02:14:30 +02:00
André Roth ac2dd1dfd3 go mod tidy 2024-10-08 01:22:10 +02:00
André Roth df18133179 fix system test
fixture commands are actually executing ./aptly-test, do the same here
2024-10-08 01:22:10 +02:00
André Roth be6d06a653 ci: delete aptly tasks after publish 2024-10-08 01:22:10 +02:00
André Roth a998755245 debian: fix dependencies
- docker: install more tools
2024-10-08 01:22:10 +02:00
André Roth 98c82a3684 improve Makefile
- simplify Makefile
- improve devserver
- improve make clean
2024-10-08 01:22:10 +02:00
André Roth 4658bec08b Merge pull request #1358 from aptly-dev/improve/grab-downloader
improve and test grab downloader
2024-10-05 18:29:08 +02:00
André Roth 33047c2c55 cleanup gpg keys
- move gpg files to one place
- with gpg2, the secretkey parameter is ignored. aptly can also ignore it
2024-10-04 18:46:40 +02:00
André Roth b2b7f11d17 ci: remove pip and virtualenv
- separate unit tests, benchmark, system tests, flake8
2024-10-04 15:56:57 +02:00
André Roth f0ad0f9496 improve and test grab downloader 2024-10-04 13:37:56 +02:00
André Roth d6a156b181 Merge pull request #1162 from aptly-dev/feature/176-snapshot-pull-api
Snapshot Pull API
2024-10-03 23:07:27 +02:00
André Roth 880f487093 Merge pull request #1352 from aptly-dev/feature/storage-api
Feature/storage api
2024-10-03 23:02:19 +02:00
André Roth bce54d5878 mirror api: update documentation 2024-10-03 22:39:03 +02:00
André Roth dbc336f921 system-tests: show execution time 2024-10-03 21:53:43 +02:00
André Roth 71085969f5 Makefile: more colors 2024-10-03 19:17:27 +02:00
André Roth c35cd783cf swagger: improve doc 2024-10-03 17:46:32 +02:00
André Roth 38ea720fc5 snapshot merge: use proper REST api
- this breaks the existing api, which is only available in CI builds
- improve swagger doc
2024-10-03 17:34:29 +02:00
André Roth 06b2b920da make REST api more restful 2024-10-03 14:51:45 +02:00
André Roth a3078fa93e improve make clean 2024-10-03 14:25:46 +02:00
André Roth 0bc45c822d swagger: document /api/snapshots/pull 2024-10-03 14:25:46 +02:00
Mauro Regli af5b04b24f Feature: Add Pull Snapshot API 2024-10-03 14:25:46 +02:00
André Roth 678d0c61f1 github: move upload-artifacts script to pipeline directory 2024-10-03 01:24:10 +02:00
André Roth 37ee41af67 system-test: enable test not depending on FTP 2024-10-03 01:15:56 +02:00
André Roth 8b8eb57555 update etcd go mod
to be compatible with debian version
2024-10-03 01:15:56 +02:00
André Roth d6efe8636e cleanup FTP system tests
as we cannot test FTP with S3 backend easily, test is disabled and removed from api tests
2024-10-03 01:15:56 +02:00
André Roth caf55bb8e0 ci: cleanup step names 2024-10-03 01:15:56 +02:00
André Roth cc4798472f fix test depending on gpg1 keys 2024-10-02 21:29:28 +02:00
André Roth e3a95d5c4e make swagger quiet 2024-10-02 21:29:25 +02:00
André Roth 997ffe9c31 system-tests: install swag only if needed 2024-10-02 21:29:12 +02:00
André Roth c6c607a406 improve make docker-shell 2024-10-02 19:13:00 +02:00
André Roth c25693b009 storage: add tests 2024-10-02 19:12:05 +02:00
André Roth fca153cc8b docker-deb: install and init swagger 2024-10-02 18:48:48 +02:00
André Roth 06cbd29d0d add storage API 2024-10-02 18:48:48 +02:00
André Roth aff7b0db50 Merge pull request #1348 from aptly-dev/improve/debian-build
Improve/debian build
2024-10-01 20:11:00 +02:00
André Roth f7ff964085 debian: fix lintian complaints
- remove dulplicate bash completion
- fix lintian complaint about bash completion
-  add built-using
2024-10-01 13:59:42 +02:00
André Roth 94dc4f2365 debian: update dependencies
all go dependencies are available in debian/testing (trixie) and aptly builds.
2024-10-01 13:59:42 +02:00
André Roth 09954b2c73 build-deb: build only host architecture 2024-10-01 13:59:42 +02:00
André Roth 87fc8c16af Merge pull request #1351 from aptly-dev/feature/swagger
Feature/swagger
2024-10-01 13:07:40 +02:00
André Roth 75530810b5 ci: update actions/checkout module 2024-10-01 01:36:09 +02:00
André Roth bdabfa1071 go mod tidy 2024-10-01 01:32:56 +02:00
André Roth 26a9219f7d swagger: add test 2024-10-01 01:32:56 +02:00
André Roth 5b92336668 add development server 2024-10-01 01:07:09 +02:00
André Roth fb538333fa add swagger documentation 2024-10-01 01:07:09 +02:00
André Roth 1d1bd41bb8 add swagger support
- install swaggo
- add swagger config option
2024-10-01 01:07:09 +02:00
dependabot[bot] 96e60ae540 build(deps): bump google.golang.org/grpc from 1.64.0 to 1.64.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.64.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.64.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 16:41:06 +02:00
Christoph Fiehe 4195ad90bc Allow to add a new component to a published repo
This commit modifies the behavior of the publish switch method in the way, that also new components can be added to an already published repository. It is no longer necessary to drop and recreate the whole publish.

Signed-off-by: Christoph Fiehe <c.fiehe@eurodata.de>
2024-09-24 15:43:27 +02:00
André Roth eaa363eb82 ci: allow to force ci build
this should not build release if pipeline triggered on master and master also has a version tag. avoid building same version twice and uploading to ci and release repos.
2024-09-24 10:14:39 +02:00
André Roth 795870bca5 ci: skip deb upload if no creds 2024-09-24 10:14:39 +02:00
André Roth 7e73165409 ci: use tag/branch for release/ci building 2024-09-24 10:14:39 +02:00
André Roth 2d6d371292 update man page 2024-09-24 10:14:39 +02:00
André Roth d9168ed723 ci: log aptly version 2024-09-24 10:14:39 +02:00
André Roth 57fc7f7098 fix github-upload 2024-09-24 10:14:39 +02:00
André Roth 20c81d7f9a ci: allow pull requests
disable tests if env secrets are empty

- detect emtpy aws token
- upload: check for aptly creds
2024-09-24 10:14:39 +02:00
André Roth 67d04ca878 debian: move config file to aptly-api 2024-09-24 10:14:39 +02:00
André Roth f5dda668e3 ci: do not upload ci on release builds 2024-09-24 10:14:39 +02:00
André Roth 769150dec6 system tests: enable S3 tests 2024-09-24 10:14:39 +02:00
André Roth 02d080955b ci: move scripts to makefile 2024-09-24 10:14:39 +02:00
André Roth 04739a41fa ci: do not unzip articact upload 2024-09-24 10:14:39 +02:00
André Roth 9771747916 improve CI workflow
- use debian version consistently
- if the commit is not on a git tag, append ci version
- avoid double zipping
- cleanup binary builds
- replace `make release` with `make binaries`
- add missing files to archives
- use matrix build for deb packages
- allow building release version
- keep directory inside zip archives
- accept releases only on master
  only tags on master will trigger a release
- cleanup namings
- keep path in zip
- add retention period for pipeline artifacts
2024-09-24 10:14:39 +02:00
André Roth 497196886a system-tests: download compressed etcd.db 2024-09-24 10:14:39 +02:00
iofq 056df39a3c move release script to Makefile 2024-09-24 10:14:39 +02:00
iofq 5e86a0b9e6 use multiarch CI build for release 2024-09-24 10:14:39 +02:00
André Roth ba2c86361d remove debug 2024-09-24 10:14:39 +02:00
André Roth b92ca5d6e5 use unique and incremental CI version 2024-09-24 10:14:39 +02:00
André Roth fe2c623638 debian: fix config migration 2024-09-24 10:14:39 +02:00
André Roth 0e9f047c37 ci upload: handle errors 2024-09-24 10:14:39 +02:00
André Roth 582201ab7c build for Debian trixie (testing) 2024-09-24 10:14:39 +02:00
André Roth 88f4101866 update or downgrade go modules to match debian versions
- use go 1.22 (as available also in bookworm-backports)
- do not install go mods in docker
2024-09-24 10:14:39 +02:00
André Roth 373a157163 release for ubu24.04 2024-09-24 10:14:39 +02:00
André Roth 0178093f6c catch config file errors 2024-09-24 10:14:39 +02:00
André Roth 8e8cf90a71 ci: use async aptly publish 2024-09-24 10:14:39 +02:00
André Roth a22dc9be1b do not hardcode go version 2024-09-24 10:14:39 +02:00
André Roth 2306993b7b ci: improve aptly repo layout
use the new -multi-dist option to combine all distributions into one
publish point:

deb http://repo.aptly.info/ci bookworm main

or:

deb http://repo.aptly.info/release bookworm main

for the following distributions: buster, bullseye, bookworm, focal, jammy
2024-09-24 10:14:39 +02:00
André Roth c248dc1803 github CI: use dpkg-buildpackage for building debian packages
- use go 1.19
- Makefile: improve unit test output
- cleanup: remove travis
2024-09-24 10:14:39 +02:00
André Roth 53f96c98ad CI: use go 1.22 for merging code code 2024-09-24 10:14:39 +02:00
André Roth 98b1ed07d1 docker: improve dev env
- abort docker scripts on error
- generate version in system tests
- build debian packages in docker
- add make clean target
- fix lint
2024-09-24 10:14:39 +02:00
André Roth b342af0d96 system tests: fix gpgv warning when verifying signatures
gpgv: can't allocate lock for '/home/runner/.gnupg/aptlytest.gpg'

this forced running local system tessts in /home/runner, as it is
in the gitgub actions.
2024-09-24 10:14:39 +02:00
André Roth 52faf78324 use /usr/local/etc/aptly.conf config file
fixes #1108
2024-09-24 10:14:39 +02:00
Andre Roth 8fd48e9fa9 add default aptly config files
config rfiles are read in the following order:

1) ~/.aptly.conf
2) /usr/local/etc/aptly.conf
3) /etc/aptly.conf
2024-09-24 10:14:39 +02:00
André Roth 32a3943821 support ~ in rootDir as home directory 2024-09-24 10:14:39 +02:00
André Roth f7f220aa18 debianize
- fix make version on debian
- update gitignore
- add aptly-api and service
- install zsh completion
- add debug package
- move aptly data from orig deb package
- do not add shell for service user
- use 8080 as default port
2024-09-24 10:14:39 +02:00
iofq 372ce3c4bc Avoid nil panic when downloadSpeedLimit is set in api mode 2024-08-16 10:04:46 +02:00
André Roth 95915480a0 update tests
the fixed handling of provided packages results in snapshots no longer missing provided packages,
and also provided packages being added to repos.
2024-08-11 12:35:46 +02:00
5hir0kur0 d2332e6452 Log a warning for errors in MatchesDependency 2024-08-11 12:35:46 +02:00
André Roth 1428f54a02 make compatible with go 1.19 2024-08-11 12:35:46 +02:00
André Roth feb87c0f19 Revert "Remove errors.Join usage for go1.19 compatibility"
This reverts commit 1339e35dd785fff114549e027d81cbe47a882e27.
2024-08-11 12:35:46 +02:00
5hir0kur0 934fa0598b Remove errors.Join usage for go1.19 compatibility 2024-08-11 12:35:46 +02:00
5hir0kur0 6d6761e234 Add unit tests for Provides entries with version 2024-08-11 12:35:46 +02:00
5hir0kur0 ab18d4835b Support version relation in Provides entries 2024-08-11 12:35:46 +02:00
iofq ff8a02959c fix throttled downloader 2024-08-11 10:20:37 +02:00
André Roth 37a9fbe530 api: fix OOM with sync tasks
since sync API calls also use tasks internally, this lead to out of memory due to aptly never removing them.
2024-08-03 14:36:04 +02:00
André Roth 735d7a4d61 docker: reduce build size 2024-08-03 00:14:26 +02:00
André Roth 40eb4b4751 docker-lint: use go 1.19 compatible golangci-lint version
- use same user in docker container
- use GOPATH in source dir to prevent downloading all dependencies on each run
- add make clean
2024-08-03 00:14:26 +02:00
André Roth 0a6e8e3c9e update code of conduct to use github discussions 2024-08-03 00:14:26 +02:00
André Roth 556d7fa4b8 apply PR feedback 2024-08-03 00:14:26 +02:00
André Roth 5718f3f2f5 improve Makefile help 2024-08-03 00:14:26 +02:00
André Roth 0251fddae4 improve Makefile documentation 2024-08-03 00:14:26 +02:00
André Roth 0215925608 add graphviz to enable system tests
aptly uses dot in the aptly graph command and API. if it is not available, the test is skipped...
2024-08-03 00:14:26 +02:00
André Roth 696b78f207 docker: update dev env and documentation 2024-08-03 00:14:26 +02:00
André Roth 674f4f784b s3: use new Endpoint API
lint: s3/public.go#L136
SA1019: config.WithEndpointResolverWithOptions is deprecated: The global endpoint resolution interface is deprecated. See deprecation docs on [WithEndpointResolver]. (staticcheck)
lint: s3/public.go#L137
SA1019: aws.Endpoint is deprecated: This structure was used with the global [EndpointResolver] interface, which has been deprecated in favor of service-specific endpoint resolution. See the deprecation docs on that interface for more information.  (staticcheck)
lint: s3/public.go#L138
SA1019: aws.Endpoint is deprecated: This structure was used with the global [EndpointResolver] interface, which has been deprecated in favor of service-specific endpoint resolution. See the deprecation docs on that interface for more information.  (staticcheck)
2024-08-03 00:14:26 +02:00
André Roth 83a05a1900 golangci-lint: download and build before lint 2024-08-03 00:14:26 +02:00
André Roth 48a0bca35e allow s3 test in docker
read AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY from ./aws.creds when running:

  make docker-system-tests
2024-08-03 00:14:26 +02:00
dependabot[bot] a7690c375e Bump google.golang.org/grpc from 1.38.0 to 1.56.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.38.0 to 1.56.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.38.0...v1.56.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 22:15:17 +02:00
André Roth a0bd32a39c system tests: fix expired ppa mirror key 2024-07-31 22:16:00 +02:00
André Roth 0b3dd2709b apply PR feedback 2024-07-31 22:16:00 +02:00
André Roth ff1557afee update go.mod 2024-07-31 22:16:00 +02:00
André Roth 67771795ca etcd: implement transactions
- use temporary db for lookups in transactions
- use batch implementation to commit transaction
2024-07-31 22:16:00 +02:00
André Roth 7a01c9c62d etcd: implement batch operations
- cache the operations internally in a list
- Write() applies the list to etcd
2024-07-31 22:16:00 +02:00
André Roth 9768ecef22 etcd: implement temporary db support
- temporary db support is implemented with a unique key prefix
- prevent closing etcd connection when closing temporary db
2024-07-31 22:16:00 +02:00
André Roth 640c202ee5 etcd: implement separate system tests
- add t13_etcd test directory
- etcd will be started for the unit tests and each system test
- etcd will load fixture DB export if requested by the test
- existing tests are reused for etcd testing
2024-07-31 22:16:00 +02:00
André Roth f10acb3df8 etcd: fix db config and test
fix unit test for adapted config file
2024-07-31 22:16:00 +02:00
André Roth 5b74f82edb etcd: fix int overflow
goxc fails with:

Error: database/etcddb/database.go:17:25: cannot use 2048 * 1024 * 1024 (untyped int constant 2147483648) as int value in struct literal (overflows)
2024-07-31 22:16:00 +02:00
hudeng 59bf4501e8 feat: Use databaseBackend config repace databaseEtcd
databaseBackend config contains type and url sub config, It can facilitate the expansion of other types of databases in the future.
2024-07-31 22:16:00 +02:00
hudeng f29449db14 feat: Add system test for etcd 2024-07-31 22:16:00 +02:00
hudeng 78172d11d7 feat: Add etcd database support
improve concurrent access and high availability of aptly with the help of the characteristics of etcd
2024-07-31 22:16:00 +02:00
NeroBurner f42ff697d4 CONTRIBUTING: Python3 is supportet
Explicitly state that Python3 is supported and required.

Since aptly `v1.5.1` (with commit 035d5314b0)
the tests are ported to Python3.

With a687df2f4f the system
tests are run with `python3` per default.

With f4a152ab22 (after `v1.5.0` aptly tag)
the tests run against Python 3.11.
2024-07-25 10:10:05 +02:00
André Roth 57e2c5c670 api tests: use check_task_fail 2024-07-24 21:19:47 +02:00
André Roth 8b41ec48c8 use consistent golangci-lint version 2024-07-24 21:19:47 +02:00
André Roth 49ff832f94 reenable lost tests 2024-07-24 21:19:47 +02:00
André Roth 09a44ba409 fix empty mirror check 2024-07-24 21:19:47 +02:00
André Roth deae90485a fix DirIsAccessible
perms 0000 need to be checked explicitly
2024-07-24 21:19:47 +02:00
André Roth 4a0bdcbb64 improve system tests
- log import errors for test modules
- log output only on test failure
- improve docker system test container
- use go 1.19 in docker system tests
- download go dependencies in docker container
- system tests: color failues output
- imrpove test result output
- do not install golangci-lint in system tests
2024-07-24 21:19:47 +02:00
André Roth 9f1860dff7 fix unit test 2024-07-24 21:19:47 +02:00
André Roth fe25414b45 api: repo copy handle package not found
and add tests for error proper handling.
2024-07-24 21:19:47 +02:00
André Roth 49184c9163 fix apiReposCopyPackage getting corrupt file name
it seems c.Params.ByName("file") should not be used
inside maybeRunTaskInBackground, as the content may be corrupted sometimes.
2024-07-24 21:19:47 +02:00
André Roth 440c3debdc improve api tests and error output
show only relevant aptly logs if a test fails.
for async tasks, show task output, as it contains the error message.
2024-07-24 21:19:47 +02:00
André Roth 8029305d32 dependencies: remove duplicates / missing deps from test 2024-07-11 18:25:49 +02:00
5hir0kur0 02bdb7c76a Deduplicate missing dependency list 2024-07-11 18:25:49 +02:00
5hir0kur0 8d537b4e3e Fix bug in dependency resolution 2024-07-11 18:25:49 +02:00
Sylvain Nieuwlandt 11401ca472 [api/copy] create system tests for new copy api endpoint 2024-07-10 16:43:03 +02:00
Valentin BRICE 66429bff45 [api/repos] Add copy API 2024-07-10 16:43:03 +02:00
Sylvain Nieuwlandt 8114786179 Declare the Copy API 2024-07-10 16:43:03 +02:00
André Roth d8c1e432c6 add test 2024-07-03 18:08:58 +02:00
André Roth 3a286ae07f fix unit tests 2024-07-03 18:08:58 +02:00
André Roth a93ccd4100 fix tests 2024-07-03 18:08:58 +02:00
André Roth c1f7e5fe96 handle GpgDisableVerify and ignore-signatures consistently
and be less verbose
2024-07-03 18:08:58 +02:00
André Roth d16110068c allow not signed mirrors without InRelease file 2024-07-03 18:08:58 +02:00
André Roth 4661913265 add system test for maximumVersion filter 2024-06-24 17:44:40 +02:00
hudeng ecc88e7a40 feat: repo and snapshots packages filter api add 'maximumVersion' query parameter support
example: `curl http://localhost:8080/api/repos/test/packages\?maximumVersion\=1`

Change-Id: Ie9ffd36146bf017bbb353737f32360f7b73d6b0a
2024-06-24 17:44:40 +02:00
André Roth 5cf8c54cb2 fix test 2024-06-20 23:40:46 +02:00
André Roth b758033ccb fix compilation 2024-06-20 23:40:46 +02:00
Kevin Martin 13f4bb441d Check if S3 bucket is encrypted by default.
Adds check to see if the S3 bucket is encrypted by default. If so this
uses the existing workaround for object etags not matching file MD5s.
2024-06-20 23:40:46 +02:00
Kevin Martin 1af09069f7 Check both MD5 locations for S3 KMS support.
If the S3 bucket used to house a repo has KMS encryption enabled then
the etag of an object may not match the MD5 of the file. This may
cause an incorrect error to be reported stating the file already
exists and is different.

A mechanism exists to work around this issue by using the MD5 stored
in object metadata. This check doesn't always cover the case where KMS
is enabled as the fallback is only used if the etag is not 32
characters long.

This commit changes the fallback mechanism so that it is used in any
case where the object's etag does not match the source MD5. This will
incur a performance penalty of an extra head request for each object
with a mismatch.
2024-06-20 23:40:46 +02:00
Ryan Gonzalez b5bf2cbcda Fix functional tests' '--capture' on Python 3
None of the commands' output is ever treated as binary, so we can just
always decode it as text.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
André Roth fb3436b23d fix golangci-lint errors 2024-06-17 11:51:18 +02:00
André Roth 1a3cfea348 replace io/ioutil
fixes golangci-lint errors
2024-06-17 11:51:18 +02:00
André Roth c33141d49b fix golangci-lint and compilation errors 2024-06-17 11:51:18 +02:00
Ryan Gonzalez f9325fbc91 Add support for Azure package pools
This adds support for storing packages directly on Azure, with no truly
"local" (on-disk) repo used. The existing Azure PublishedStorage
implementation was refactored to move the shared code to a separate
context struct, which can then be re-used by the new PackagePool. In
addition, the files package's mockChecksumStorage was made public so
that it could be used in the Azure PackagePool tests as well.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 810df17009 Clean up temporary files when mirroring
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 19255debb9 Reduce required usage of LocalPackagePool
Several sections of the code *required* a LocalPackagePool, but they
could still perform their operations with a standard PackagePool.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 1ebd37f9ad Move Stat() into LocalPackagePool
The contents of `os.Stat` are rather fitted towards local package pools,
but the method is in the generic PackagePool interface. This moves it to
LocalPackagePool, and the use case of simply finding a file's size is
delegated to a new, more generic PackagePool.Size() method.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 8e37813129 Add support for custom package pool locations
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 91574b53d9 Add functional tests for Azure publishing
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez b8e0aba3cf Document Azure configuration
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 0eccaf2b60 Change Azure endpoint syntax to require a full URL
Before, a "partial" URL (either "localhost:port" or an endpoint URL
*without* the account name as the subdomain) would be specified, and the
full one would automatically be inferred. Although this is somewhat
nice, it means that the endpoint string doesn't match the official Azure
syntax:

https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string

This also raises issues for the creation of functional tests for Azure,
as the code to determine the endpoint string needs to be duplicated
there as well.

Instead, it's just easiest to follow Azure's own standard, and then
sidestep the need for any custom logic in the functional tests.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
Ryan Gonzalez 859edb10cd Fix S3 tests on Python 3
read_path() can read in binary, which the S3 tests don't support (simply
because they don't need it)...but it needs to be able to take the `mode`
argument anyway.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-06-17 11:51:18 +02:00
André Roth f0bf519d36 cleanup 2024-06-15 19:18:14 +02:00
André Roth d8cfafccc9 system tests: improve log output 2024-06-15 19:18:14 +02:00
André Roth 34c1c1f83a system-tests: make docker abortable and use colors 2024-06-15 19:18:14 +02:00
André Roth 3e1485faf5 queue sync calls 2024-06-15 19:18:14 +02:00
André Roth efc5573b8e Makefile: run unit tests in docker 2024-06-15 19:18:14 +02:00
André Roth 45035802be implement task queue waiting for resources 2024-06-15 19:18:14 +02:00
André Roth 2d97ba2bbd fix flake8 error 2024-06-15 19:18:14 +02:00
André Roth 05fed16f6d fix golangci-lint error 2024-06-15 19:18:14 +02:00
Ramón N.Rodriguez 9b1f4272c0 AUTHORS: claiming the fame and glory 2024-06-15 19:18:14 +02:00
Ramón N.Rodriguez 1987220f1e api: publish: block on concurrent calls
This commit blocks concurrent calls to RunTaskInBackground which is
intended to fix the quirky behaviour where concurrent PUT calls to
api/publish/<prefix>/<distribution> would immedietly reuturn an error.

The solution proposed in this commit is not elegant and probaly has
unintended side-effects. The intention of this commit is to highlight
the area that actually needs to be addressed.
Ideally this patch is amended or dropped entierly in favor of a better
fixup.
2024-06-15 19:18:14 +02:00
Ramón N.Rodriguez 47696a3303 api:publish: test concurrency
This commit introduces a test which runs concurrent publishes (which
could be parallell with multiproccessing, python is fun).
The test purposly fails (at the point in history that this patch is
written) in order to make it as easy as possible to verify later patches,
which hopefully addresses concurrency problems.

The same behaviour can easily be tested outside of the system tests with
the following (or similar) shell

$ aptly serve -listen=:8080 -no-lock
$ aptly repo create create -distributions=testing local-repo
$ atply publish repo -architectures=amd64 local-repo
$ apt download aptly
$ aptly repo add local-repo ./aptly*.deb
$ for _ in $(seq 10); do curl -X PUT 127.0.0.1:8080/api/publish//testing

In the local testing perfomed (on a dual core vm) the first 1-4 jobs
would typically succeed and the rest would error out.
2024-06-15 19:18:14 +02:00
André Roth 787f954833 mirror: do not download already downloaded packages
this change imports downloaded packages into the pool immediately after download.
in case mirroring is aborted and later resumed, already downloaded packages will not be downloaded anymore.
2024-06-15 16:15:23 +02:00
André Roth e9bdb983c8 tasks: improve log level 2024-06-15 16:15:23 +02:00
Noa Resare b4cd86aa14 Introduce option multi-dist to the publish commands
This change makes it possible to publish multiple distributions
with packages named the same but with different content by changing
structure of the generated pool hierarchy. The option not enabled
by default as this changes the structure of the output which could
break the expectations of other tools.
2024-06-15 11:27:26 +02:00
Cal Jurgella 4bd26f5977 Enable SkipArchitectureCheck and IgnoreSignatures in mirror API 2024-06-14 14:30:29 +02:00
André Roth 57ff7c69cd mirror api: add test for SkipArchitectureCheck and SkipComponentCheck 2024-06-14 14:30:29 +02:00
André Roth c843709eab add github sponsors 2024-06-12 13:01:40 +02:00
Jens Reinsberger 4bc2180eed fix failing build on hosts with wildcard dns
on hosts which have wildcard dns domains in their local domain search
list, builds failed because "nosuch.host" could actually be resolved.

Since ".host" isn't a recommended TLD by RFC2606, we use ".invalid" now.
And since this is not enough to fix the problem, we use now absoulte
domain names (having a '.' at the end)
2024-05-15 16:42:37 +02:00
Paul Cacheux 5353890b24 use tagged version of ProtonMail/go-crypto in go.mod 2024-05-15 16:41:35 +02:00
Ryan Gonzalez 79975bf2b6 Fix reflist diffs failing to compact when one of the inputs ends
The previous reflist logic would early-exit the loop body if one of the
lists was empty, but that skips the compacting logic entirely.

Instead of doing the early-exit, we can leave a list's ref as nil when
the list end is reached and then flip the comparison result, which will
essentially treat it as being greater than all others. This should
preserve the general behavior without omitting the compaction.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-04-24 17:36:36 +02:00
Ryan Gonzalez 8d09c202db Skip loading reflists when listing published repos
The output doesn't actually depend on the reflists, and loading them for
every published repo starts to take substantial time and memory.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-04-24 17:35:44 +02:00
André Roth 27013c0b2b apply go mod tidy 2024-04-24 16:46:16 +02:00
André Roth 756c499314 fix go mod tidy and use go 1.19
let's be compatible with debian/bookworm
2024-04-24 16:46:16 +02:00
Ryan Gonzalez 6c6d1b18ba Use zero-copy decoding for reflists
Reflists are basically stored as arrays of strings, which are quite
space-efficient in MessagePack. Thus, using zero-copy decoding results
in nice performance and memory savings, because the overhead of separate
allocations ends up far exceeding the overhead of the original slice.

With the included benchmark run for 20s with -benchmem, the runtime,
memory usage, and allocations go from ~740us/op, ~192KiB/op, and 4100
allocs/op to ~240us/op, ~97KiB/op, and 13 allocs/op, respectively.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-04-24 16:46:16 +02:00
Ryan Gonzalez 8cb1236a8c Improve publish cleanup perf when sources share most of their packages
The cleanup phase needs to list out all the files in each component in
order to determine what's still in use. When there's a large number of
sources (e.g. from having many snapshots), the time spent just loading
the package information becomes substantial. However, in many cases,
most of the packages being loaded are actually shared across the
sources; if you're taking frequent snapshots, for instance, most of the
packages in each snapshot will be the same as other snapshots. In these
cases, re-reading the packages repeatedly is just a waste of time.

To improve this, we maintain a list of refs that we know were processed
for each component. When listing the refs from a source, only the ones
that have not yet been processed will be examined. Some tests were also
added specifically to check listing the files in a component.

With this change, listing the files in components on a copy of our
production database went from >10 minutes to ~10 seconds, and the newly
added benchmark went from ~300ms to ~43ms.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-04-24 16:46:16 +02:00
Ryan Gonzalez 5636a9990b Improve performance of simple reflist merges
When merging reflists with ignoreConflicting set to true and
overrideMatching set to false, the individual ref components are never
examined, but the refs are still split anyway. Avoiding the split when
we never use the components brings a massive speedup: on my system, the
included benchmark goes from ~1500 us/it to ~180 us/it.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-04-24 16:46:16 +02:00
Ryan Gonzalez 8ab8398c50 Use github.com/saracen/walker for file walk operations
In some local tests w/ a slowed down filesystem, this massively cut down
on the time to clean up a repository by ~3x, bringing a total 'publish
update' time from ~16s to ~13s.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2024-04-24 16:46:16 +02:00
André Roth 53c4a567c0 README: add new gitter url 2024-04-21 13:22:06 +02:00
dependabot[bot] ff8f79f883 Bump golang.org/x/net from 0.17.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-20 22:51:50 +02:00
André Roth 06be6f23a6 Revert "Bump requests from 2.28.2 to 2.31.0 in /system"
This reverts commit 24e62b58bc.
2024-04-17 22:08:27 +02:00
dependabot[bot] 24e62b58bc Bump requests from 2.28.2 to 2.31.0 in /system
Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.31.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.28.2...v2.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-17 21:35:16 +02:00
André Roth b37a3341e8 trigger pipeline 2024-04-17 18:39:45 +02:00
André Roth eee6ccc311 trigger pipeline 2024-04-17 18:19:29 +02:00
André Roth f233a21787 github CI: nightly builds for multiple distributions
Since the pipeline changed to use ucuntu22.04 runners, the nighty debian packages do not work on debian buster anymore.
    This change updates the pipeline to build for Ubuntu 20.04 and 22.04 as well as for
    Debian 10, 11 and 12.

    The distribution specific apt sources are as follows:

      "deb http://repo.aptly.info/nightly-bookworm bookworm main"

    (replace bookworm with focal, buster, bullseye. Install aptly repo key with: curl -fsS https://www.aptly.info/pubkey.txt | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/aptly.gpg)

    The builds on focal will also release to the legacy nightly apt repo: https://github.com/aptly-dev/aptly/actions/runs/8723898496/job/23933824692#step:7:24

    This only affects nightly builds, for now.
    Pipeline example: [](https://github.com/aptly-dev/aptly/actions/runs/8723898496)
2024-04-17 17:48:37 +02:00
dependabot[bot] 940538e39b Bump google.golang.org/protobuf from 1.31.0 to 1.33.0
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 23:34:38 +02:00
André Roth 3a29e08ff2 fix typo 2024-04-11 19:40:25 +02:00
André Roth 2a2e35c096 add test for publishing distribution with slash (/) 2024-04-11 19:37:51 +02:00
Ariel D'Alessandro 287a947c62 Revert "Don't allow '/' in distribution name, auto-replace '/' with '-' while guessing. #110"
This reverts commit 1daa076d65.

Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>
2024-04-11 19:37:51 +02:00
André Roth 32595e7cb7 openpgp: export full key for internal gpg 2024-04-11 10:15:02 +02:00
André Roth 9deb031c44 fix system tests
- use s3 mirror instead of internet download
- reduce download verbosity
- do not use venv in docker-system-tests
- be more verbose on test output
- do not run golangci-lint in system-tests
2024-04-11 10:15:02 +02:00
André Roth 6be4f5e8d0 gpg api: allow self signed and use default gpg version 2024-04-03 10:16:56 +02:00
André Roth b5b0a52cbe s3 api: get publish root list 2024-04-03 10:14:01 +02:00
André Roth a0af6a25aa fix lint complaints 2024-04-03 10:13:24 +02:00
Robert LeBlanc c2ee086487 Fix the installer path for Ubuntu Focal
Ubuntu has started depreciating the Debian installer in focal
and moved the installer images to a different path. In versions
after focal, they are completly removed. This basically gives
us more time to figure out how to use the new system.
2024-04-03 10:13:24 +02:00
xinhangzhou 43a0ceb350 chore: remove repetitive words
Signed-off-by: xinhangzhou <shuangcui@aliyun.com>
2024-04-03 10:12:06 +02:00
André Roth 50eaf6c0bb update docker / makefile 2024-03-06 12:46:44 +01:00
André Roth e564b7c150 cleanup Makefile 2024-03-06 08:08:47 +01:00
André Roth 943e76ae8b golangci-lint: add new github workflow 2024-03-06 08:08:42 +01:00
André Roth 72a7780054 fix golint complaints 2024-03-06 06:21:36 +01:00
dependabot[bot] 1001ca92c8 Bump golang.org/x/crypto from 0.14.0 to 0.17.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 21:19:55 +01:00
dependabot[bot] d060ad7200 Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.3 to 1.3.7.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.3...v1.3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 20:50:27 +01:00
Ludovico Cavedon eeb5bd79d0 s3: fix test 2024-02-06 20:49:35 +01:00
Ludovico Cavedon fad660450c Cache bucket content by prefix
When a publishing uses a publish prefix, instead of listing the contents
of the whole bucket under the storage prefix, only list the contents of
the bucket under the storage prefix and publish prefix, and cache it by
publish prefix.
This speeds up publish operations under a prefix.
2024-02-06 20:49:35 +01:00
André Roth 01893a492f s3: call s3.ListFiles only on publish path in LinkFromPool
instead of caching the whole s3 bucket, cache only the pool path. this
requires an additional parameter, and since this is an interface, all
implementations need to follow. might help in other backends too.

closes #1181
2024-02-06 20:49:35 +01:00
André Roth e61a4dd53c fix golangci-lint errors 2024-02-06 20:49:35 +01:00
André Roth 183e6ec436 fix indentation 2024-02-06 20:49:35 +01:00
André Roth ebd5aa5fe9 s3: respect default ACLs 2024-02-06 20:49:35 +01:00
André Roth 1b6e5e5b3b s3: clear / invalidate pathCache for repeated operations 2024-02-06 20:49:35 +01:00
André Roth 7b7ebc5711 s3: fix FileExists not working in some go versions 2024-02-06 20:49:35 +01:00
Nic Waller 92e16c81df Update AUTHORS
as per contributor instructions
2024-02-06 20:49:35 +01:00
Nic Waller 5c1fd4dd2c clean pathCache 2024-02-06 20:49:35 +01:00
André Roth d7cc9b89d1 system tests: use repository mirrors on S3 for reproducibility
no direct internet download from apt repositories,
which over time will change or cease to exist.

also migrate to gpg2 on newer ubuntu.
2024-02-05 13:04:45 +01:00
André Roth a69aa7c533 system-tests: add Dockerfile 2024-02-05 13:04:45 +01:00
André Roth a71186bcc3 gitlab: use ubuntu22.04 runners and gpg2 2024-02-05 13:04:45 +01:00
Paul Cacheux aeef41bf70 add support for EdDSA keys in pubkeyAlgorithmName 2023-11-23 11:40:58 +01:00
Paul Cacheux 99dbe31d20 fix t09_repo/IncludeRepo21Test_gold gold error 2023-11-23 11:40:58 +01:00
Paul Cacheux 5ca3a97bd3 add name to authors 2023-11-23 11:40:58 +01:00
Paul Cacheux cfcab13c2a replace golang.org/x/crypto/openpgp with github.com/ProtonMail/go-crypto/openpgp 2023-11-23 11:40:58 +01:00
dependabot[bot] f1649a647b Bump golang.org/x/net from 0.15.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 19:32:49 +00:00
Ryan Gonzalez 11deb9425b Shut down cleanly when 'api serve' is interrupted
This will properly close the db and, more particularly, flush out any
profile files being written. Otherwise, they can end up empty or
truncated.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2023-10-24 15:35:41 +02:00
Sylvain Baubeau 3aaf0a8c44 Switch to aws-sdk-go-v2 2023-10-24 15:30:52 +02:00
Ryan Gonzalez 322e5c1587 Add myself to authors
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2023-10-03 08:31:39 +02:00
Ryan Gonzalez ed45c44931 Fix the test output regex on Go 1.20
1.20 changes the output format of coverage checks slightly to include
a package name on each line, followed by `coverage:`, but the current
regex assumes that the line *starts* with `coverage:`.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2023-10-03 08:31:39 +02:00
Ryan Gonzalez 889fcc2158 Improve test output regex for better perf
The current regex runs in exponential time, which massively impacts the
runtime of the test suite, taking several seconds (~4s on my system)
just to perform a single match. By replacing the mix of re.findall + the
initial capture group with re.search + some string slicing, the time
spent matching the regex becomes nearly instant, e.g.:

    $ make system-test TESTS='Config*'

goes from taking ~10s to ~1.5s.

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
2023-10-03 08:31:39 +02:00
Mauro Regli f155ed3ba9 Set Golangci-lint timeout to 5m 2023-09-21 11:25:18 +02:00
Mauro Regli 0d20c59a98 Fix: Change expected output for malformed input that changed in Go 2023-09-21 11:25:18 +02:00
Mauro Regli ae61706a34 Fix: Implement golangci-lint suggestions 2023-09-21 11:25:18 +02:00
Mauro Regli f4a152ab22 Update CI to only use 1.21 and python 3.11 2023-09-21 11:25:18 +02:00
Mauro Regli 972bf6b3cf Update Golang to 1.21 and dependencies 2023-09-21 11:25:18 +02:00
Mauro Regli 18203c614d Fix: Pipeline failing because of outdated Repo
Updated the repo key, repo links in tests (jessie-cran35 -> bullseye-cran40) and the expected test output.

Fixes: #1218
2023-09-14 10:35:00 +02:00
Mauro Regli 40c242f9d1 Fix: Remove Batch from API options, set to true by default, add comments
Fixes: #1106
2023-09-14 10:34:20 +02:00
Mauro Regli ee4c83e323 Fix: Pipeline failing because of nvidia repo change 2023-09-13 09:04:51 +02:00
Crawax 214e9075ad Fix returncode when deleting a mirror with snapshot
When trying to delete a mirror that has snapshot and not providing the
force option, the API should not return a `500
StatusInternalServerError`.
A `403 StatusForbidden` is more appropriate when the condition is
expected by the server.
2023-08-18 14:20:23 +02:00
guoguangwu 847fd90e36 chore: unnecessary use of fmt.Sprintf 2023-07-14 11:35:08 +02:00
Benj Fassbind ecc055180c Fix publishing race condition
A race condition for publishing packages and
mirrors at the same time was introduced in
commit 77d7c38.

The problem is that when opening a leveldb transaction
and performing another 'put' to the db
the system freezes.
2023-05-31 15:48:42 +02:00
Alexander Zubarev 1501a4e531 Add strike to AUTHORS 2023-05-26 17:20:16 +02:00
Alexander Zubarev 8f53e01749 Show storage of publish on graph 2023-05-26 17:20:16 +02:00
Sjoerd Simons 1df8cff842 Update go-xz to 0.1.0
Older versions go-xz didn't wait for child processes meaning for exery
unpack action a defunct xz would stick around. This got fixed in 0.1.0

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2023-05-19 19:49:54 +02:00
Mauro Regli 76744ead86 Fix Release failing 'Cannot find goxc' 2023-05-15 11:15:48 +02:00
Mauro Regli f6a7030654 Try to fix UnixSocketAPITest by upgrading dependencies
Updated urllib, requests and requests_unixsocket
2023-05-15 11:15:48 +02:00
Mauro Regli 7c8dd7362d Fix: Missing newline makes tests fail 2023-05-15 11:15:48 +02:00
Mauro Regli 0ae9884836 Fix: Tests with jenkins repo not finding public key. 2023-05-15 11:15:48 +02:00
Mauro Regli 95ca6fb376 Fix: Replace security.debian.org with archive 2023-05-15 11:15:48 +02:00
Mauro Regli c9b1782d62 Fix CreateMirror9Test by removing Acquire-By-Hash 2023-05-15 11:15:48 +02:00
Mauro Regli d1102e2e9c Fix: Pipeline dependency on deb.debian.org, replace with archive
This should fix some tests, as a lot of them are dependent on
deb.debian.org which no longer supports Debian 9 "Stretch".
Instead we use archive.debian.org which will continue to contain
"Stretch" packages for a long time.
2023-05-15 11:15:48 +02:00
Markus Muellner 9c6f896666 add endpoint for listing repos while serving in api mode and add more metrics 2023-03-22 17:22:54 +01:00
Markus Muellner 0fdba29d51 make serving published repos in api mode configurable 2023-03-22 17:22:54 +01:00
Markus Muellner f74217ed9c implement system tests for serving api and published repos simultaneously 2023-03-22 17:22:54 +01:00
Андрей Лухнов e25ade8af3 Serve api and published repos simultaneously
refs #1017 #975
2023-03-22 17:22:54 +01:00
Markus Muellner bece12ad4d update golangci-lint to v1.51.2 2023-03-22 17:22:54 +01:00
Mauro Regli 77e02bf7a3 Feature: Add Merge Snapshot API
Is part of Issue #176
2023-03-14 08:38:55 +01:00
Mauro Regli 90932cdac5 Improvement: Remove Magic Numbers in Tests with Tasks
Replaced 2 with TASK_SUCCEEDED, 3 with TASK_FAILED.

fixes: #1158
2023-03-13 13:17:17 +01:00
Mauro Regli 5b5307cc15 Fix CodeCov Config has two targets and thresholds
fixes: #1160
2023-03-13 08:20:18 +01:00
Mauro Regli aaa622288c Fix: Make CodeCov Pipeline more lenient
The Pipeline will only fail if the code coverage has fallen more than 2
Percent.

fixes: #1154
2023-03-07 17:05:16 +01:00
Mauro Regli dbf1ac7867 Fix: Drop Publish returned wrong status code if not found
Deleting a publish that does not exist now results in a status code 404
instead of 500.

Fixes: #1006
2023-03-07 13:46:57 +01:00
Mauro Regli c187b0d52c Fix: Switch gin mode depending on aptly.EnableDebug
If aptly.EnableDebug is active, we use Debug, otherwise we use
gin.ReleaseMode to remove the annoying nuding messages when running the
api.

fixes: #1103
2023-03-07 13:04:12 +01:00
Markus Muellner 8e62195eb5 implement structured logging 2023-02-20 13:42:50 +01:00
dependabot[bot] 0c749922c9 Bump github.com/aws/aws-sdk-go from 1.33.0 to 1.34.0
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.33.0 to 1.34.0.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Changelog](https://github.com/aws/aws-sdk-go/blob/v1.34.0/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.33.0...v1.34.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 13:07:27 +01:00
Markus Muellner ecc41f0c0f replace AbortWithError calls by custom function that sets the content type correctly 2023-01-23 10:42:57 +01:00
dependabot[bot] 81582bffd2 Bump github.com/aws/aws-sdk-go from 1.25.0 to 1.33.0
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.25.0 to 1.33.0.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Changelog](https://github.com/aws/aws-sdk-go/blob/v1.33.0/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.25.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-10 16:36:03 +01:00
Samuel Bachmann ced5ac7876 return the snapshot in apiSnapshotsCreate
In v1.4.0 it [returned the snapshot](https://github.com/aptly-dev/aptly/blob/v1.4.0/api/snapshot.go#L168), but this was removed (by accident) in v1.5.0. This adds it back.
2022-12-22 15:17:12 +01:00
Markus Muellner 2020ca9971 add ready and healthy probe endpoints 2022-12-12 13:39:07 +01:00
Markus Muellner 352f4e8772 update golangci-lint and replace deprecated calls to io/ioutil 2022-12-12 10:21:39 +01:00
Benj Fassbind 71fd730598 Return an empty array if no tasks are available
All other api endpoints also send empty arrays instead of nil.
Closes #1123
2022-11-17 10:44:35 +01:00
boxjan e90ac6767f Update AUTHORS 2022-09-09 09:02:52 +02:00
boxjan 268c39ea8c add forceVirtualHostedStyle for stores which only support virtual hosted style 2022-09-09 09:02:52 +02:00
Josh Bayfield b3d9055059 Fix system tests for custom codenames 2022-08-29 15:54:29 +02:00
Steven Stone 904265120b Fix PublishSnapshot39Test_release_i386 system test 2022-08-29 15:54:29 +02:00
Steven Stone 47930a4214 Fix system test 2022-08-29 15:54:29 +02:00
Steven Stone a59cad6f20 Enable the ability to pass in a custom codename
While testing out Aptly, the `apt-get` client complains with the following error, since the `codename` was switched from the InRelease files that are baked out by Aptly:

```
E: Repository 'http://debianrepo.example.com/bionic testing InRelease' changed its 'Codename' value from '' to 'testing'
```
2022-08-29 15:54:29 +02:00
Sjoerd Simons 393d1a6888 api: Allow querying the packages endpoint
The ".../packages" endpoints for mirror, local repos and snapshots all
share the same syntax for querying. However the "/api/packages" endpoint
doesn't match this. Adjust that to allow for a bit more consistency and
allow querying the full package database.

The current endpoint functionality "/packages/:name" is kept intact and
can be used the same as now

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2022-08-29 10:28:44 +02:00
Benj Fassbind 42cfee2c09 Fix mirror test 2022-08-16 09:04:16 +02:00
Benj Fassbind afdc10b919 Fix golangci-lint 2022-08-16 09:04:16 +02:00
Benj Fassbind af899149c7 Fix wrong nil check for SkipBz2 2022-08-16 09:04:16 +02:00
Adam Bambuch abf8abb59b upgrade go-xz go module 2022-08-04 10:48:20 +02:00
Benj Fassbind f0a85b2b6e Fix release build 2022-07-13 08:33:48 +02:00
Benj Fassbind 515e5532c8 Fix temp dir on ci 2022-07-13 08:33:48 +02:00
Benj Fassbind ff3bf4b180 Improve error messages 2022-07-13 08:33:48 +02:00
Benj Fassbind 1d4e6183be Capture coverage of integration tests
To capture the coverage also for the integration tests,
a test only executing the cmd.Run function is used.

The test always exits with code 0 and prints the
real exit code to stdout. Otherwise no coverage
report is generated.

Those changes enable a more accurate coverage report
for future contributions.
2022-07-13 08:33:48 +02:00
Benj Fassbind 69d473ea6f Fix failing mirror test
Add the https redirect to the gold ouptut of the test
as this was changed for the jenkins debian repos
and the tests were failing after this change.
2022-07-13 08:33:48 +02:00
Benj Fassbind bfc86d3b30 Test copyfile 2022-07-13 08:33:48 +02:00
Benj Fassbind 3ce27743ae Test utils 2022-07-13 08:33:48 +02:00
Wade Simmons c9f5763a70 S3: support disabling ACL with none value
This change lets you disable ACL when using S3 by using a configuration
value of `none`. This way we maintain backward compatibility with the
default setting being `private`.

Fixes: #1067
2022-06-22 11:26:13 +02:00
Sjoerd Simons f61514edaf Allow disabling bzip2 compression for index files
Using bzip2 generates smaller index files (roughly 20% smaller Packages
files) but it comes with a big performance penalty.  When publishing a
debian mirror snapshot (amd64, arm64, armhf, source) without contents
skipping bzip speeds things up around 1.8 times.

```
$ hyperfine -w 1 -L skip-bz2 true,false  -m 3 -p "aptly -config aptly.conf publish drop bullseye || true" "aptly -config aptly.conf  publish snapshot  --skip-bz2={skip-bz2} --skip-contents --skip-signing bullseye"
Benchmark 1: aptly -config aptly.conf  publish snapshot  --skip-bz2=true --skip-contents --skip-signing bullseye
  Time (mean ± σ):     35.567 s ±  0.307 s    [User: 39.366 s, System: 10.075 s]
  Range (min … max):   35.311 s … 35.907 s    3 runs

Benchmark 2: aptly -config aptly.conf  publish snapshot  --skip-bz2=false --skip-contents --skip-signing bullseye
  Time (mean ± σ):     64.740 s ±  0.135 s    [User: 68.565 s, System: 10.129 s]
  Range (min … max):   64.596 s … 64.862 s    3 runs

Summary
  'aptly -config aptly.conf  publish snapshot  --skip-bz2=true --skip-contents --skip-signing bullseye' ran
    1.82 ± 0.02 times faster than 'aptly -config aptly.conf  publish snapshot  --skip-bz2=false --skip-contents --skip-signing bullseye'
```

Allow skipping bz2 creation for setups where faster publishing is more
important then Package file size.

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2022-06-22 11:25:45 +02:00
Sjoerd Simons 2aca913e92 Use parallel gzip instead of gzip for compression
golangs compress/gzip isn't a parallel implementation, so it's quite a
bit slower on most modern servers then pgzip. The below benchmark
run shows that publishing a debian bullseye mirror snapshot (amd64, arm64,
armhf, source) shows a gain of about 35% in publishing time (when skipping
bz2 using MR #1081)

```
 hyperfine -w 1 -m 3 -L aptly aptly-nobz2,aptly-nobz2-pgzip -p "{aptly} -config aptly.conf publish drop bullseye || true" "{aptly} -config aptly.conf  publish snapshot --skip-bz2=true --skip-contents --skip-signing bullseye"
Benchmark 1: aptly-nobz2 -config aptly.conf  publish snapshot --skip-bz2=true --skip-contents --skip-signing bullseye
  Time (mean ± σ):     35.548 s ±  0.378 s    [User: 39.465 s, System: 10.046 s]
  Range (min … max):   35.149 s … 35.902 s    3 runs

Benchmark 2: aptly-nobz2-pgzip -config aptly.conf  publish snapshot --skip-bz2=true --skip-contents --skip-signing bullseye
  Time (mean ± σ):     26.592 s ±  0.069 s    [User: 42.207 s, System: 9.676 s]
  Range (min … max):   26.521 s … 26.660 s    3 runs

Summary
  'aptly-nobz2-pgzip -config aptly.conf  publish snapshot --skip-bz2=true --skip-contents --skip-signing bullseye' ran
    1.34 ± 0.01 times faster than 'aptly-nobz2 -config aptly.conf  publish snapshot --skip-bz2=true --skip-contents --skip-signing bullseye'
```

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2022-06-21 15:43:58 +02:00
Sjoerd Simons 26254a0ad8 Run go mod tidy
Seems go.mod had some modules that are no longer used since the last
version bumps? Running `make modules` or really `go mod tidy`
automagically cleans those up.

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2022-06-21 15:43:58 +02:00
Benj Fassbind 6f130e1583 Add codecov configuration 2022-06-20 13:23:28 +02:00
Benj Fassbind 35ad6cacc8 Upload code coverage 2022-06-20 13:23:28 +02:00
Benj Fassbind f519ecded7 Update azure dependency 2022-06-20 12:50:24 +02:00
Michael Stürmer 4b2efeec7a Cope with zero-length http downloads 2022-06-20 09:47:41 +02:00
Sjoerd Simons a687df2f4f Use python3 for system tests
Most modern distribution use python3 for python (3). Default to that to
make it a bit simpler to run systems tests on Debian

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2022-06-20 09:39:23 +02:00
Sjoerd Simons 29deae6fe0 api: allow parameters with urlencoded names
Aptly allows create e.g. repos with a / to use those with the REST api
the router needs to allow urlencoded parameters in various places to
represent this. A specific example of this is the /api/repos/:name/packages path

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
2022-06-15 17:21:15 +02:00
Chuan Liu f9f1c8ee75 Update azurite dir 2022-06-09 10:45:13 +02:00
myml a0544dc2b5 fix: typo in the comments 2022-06-06 13:13:27 +02:00
Chuan Liu 0a1798869a Enable Azure publish unit tests in Github actions 2022-04-29 21:23:41 +02:00
Russell Greene 751fd2f9ba add myself to authors 2022-04-27 13:50:14 +02:00
Russell Greene 954b222fb6 Use proper version comparisions for querys 2022-04-27 13:50:14 +02:00
Samuel Mutel 4c04e77489 enh: Give info when unable to load list of repos 2022-04-25 12:58:06 +02:00
Chuan Liu 152538ccc1 Support custom Azure publish endpoint 2022-04-25 11:41:04 +02:00
Benj Fassbind d955b06f03 Fix artifacts publishing 2022-04-13 09:27:50 +02:00
Markus Muellner db19a56458 Add functional test for metrics endpoint 2022-04-12 14:39:16 +02:00
Markus Muellner 6539e1b856 Add metrics endpoint with http metrics using Prometheus client lib 2022-04-12 14:39:16 +02:00
Benj Fassbind 8046fb1eb9 Fix failing checks 2022-04-05 11:41:14 +02:00
Benj Fassbind 0302e39d57 Update gin and jwt-go dependencies 2022-04-05 11:41:14 +02:00
Benj Fassbind c29ccaadbc Fix typo in ci config 2022-04-05 11:41:14 +02:00
Benj Fassbind d2d168f363 Fix system test env setup 2022-04-05 09:58:02 +02:00
Benj Fassbind cf98718a79 Fix default branch name in ci 2022-04-05 09:58:02 +02:00
Benj Fassbind 47bda055e0 Publish releases and nightly builds from ci 2022-04-04 20:07:08 +02:00
Reinhold Gschweicher c1e577c1ac Add unittest for zstd compression support 2022-04-04 17:51:21 +02:00
Ubuntu 5b98039291 Remove 1.14 from CI 2022-04-04 17:51:21 +02:00
Matt Bearup 5a23f71a7f Add support for zst compression 2022-04-04 17:51:21 +02:00
Maciej Gol c46f12f0d6 Update the gpg key of the repo.aptly.info repository in the documentation 2022-03-30 14:25:04 +02:00
Lorenzo Bolla f89350e6cd Timeout CI build job after 30 minutes
Fix #1032
2022-02-13 21:07:50 +01:00
Lorenzo Bolla fd404064c9 Use University of Utah mirror in tests
Fix #1034
2022-02-13 20:44:28 +01:00
Benj Fassbind 21029c326b Add release to CI 2022-02-11 08:36:21 +01:00
Lorenzo Bolla e8ec6385f3 Fix linting errors 2022-02-08 11:18:50 +01:00
Lorenzo Bolla 1361bf20dd Revive skipped tests 2022-02-08 11:18:50 +01:00
Lorenzo Bolla 5d98546e1d Use a more recent GPG key server 2022-02-08 11:18:50 +01:00
Luciano Lionello ff5eb53f48 Fix: typo in aptly web page link 2022-02-05 09:28:44 +01:00
Ratchanan Srirattanamet 814d4dbb51 deb: fix importing dbgsym packages with versioned Source field
dpkg-gencontrol can be called with -v flag which set binary package's
version separated from source version. When this happen, the Source
field will contain version number in addition to source package name.
This tripped Aptly's dbgsym restriction, which check for exact source
package name, which in turn prevents the dbgsym & the whole .changes
file from being imported.

From the git history, it seems like this condition is a leftover from
when Aptly filter dbgsym packages using "*-dbgsym". So, I decided to
remove it. A test case has been added to prevent regression.
2022-01-31 11:14:18 +01:00
Lorenzo Bolla 2c68175b5c Update man pages 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 551a370c13 Basic tests for Grab downloader 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 1afcd68e01 Make downloader type configurable 2022-01-31 10:32:54 +01:00
Lorenzo Bolla cc30ef3ee2 Remove vendor directory
We use go modules now.
2022-01-31 10:32:54 +01:00
Lorenzo Bolla 235e35a2f3 Rate limit 0 effectively disables rate limiting 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 4c54f967b7 Fix error checking 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 8925949be3 Support rate limiting in grab downloader 2022-01-31 10:32:54 +01:00
Lorenzo Bolla e96372c999 Implement ignore checksum mismatch
Also, update "pkg/errors" library.
2022-01-31 10:32:54 +01:00
Lorenzo Bolla e5d9d27069 Wrap errors with more context 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 853c990b6e Handle checksums 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 86c1ffab2a Add logs for checksum 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 952287a787 Reenable checksums 2022-01-31 10:32:54 +01:00
Lorenzo Bolla b5d90b7b13 Add more logging 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 3e06af8515 Disable checksum for now 2022-01-31 10:32:54 +01:00
Lorenzo Bolla eff2e56d0d Specify output filename instead of directory
"temp" downloader uses its own naming for downloaded files.
2022-01-31 10:32:54 +01:00
Lorenzo Bolla eaac04ccf6 Improve logging in grab downloader 2022-01-31 10:32:54 +01:00
Lorenzo Bolla 894192851e Grab downloader 2022-01-31 10:32:54 +01:00
Benj Fassbind f93bc6ef0f Fix badges 2022-01-27 15:18:32 +01:00
Lorenzo Bolla 035d5314b0 Convert tests to Python 3
Fix #938
2022-01-27 15:06:33 +01:00
Benj Fassbind a40cfc679c Only run system test with latest go version 2022-01-27 09:30:14 +01:00
Benj Fassbind bda6eb4200 Update minimum required go version 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 70f7d7409a Allow to check for empty output in tests 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 48635c8057 Strip irrelevant lines from test output
It may happen that aptly retries to download data during tests (maybe because
of a network issue), but our fixtures doesn't account for it. So, we strip
those irrelevant lines before comparison.
2022-01-27 09:30:14 +01:00
Ximon Eighteen 122ff609e8 Typo correction in GHA workflow comment 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 30e94064e5 Ignore dates in test 2022-01-27 09:30:14 +01:00
Lorenzo Bolla d60e575af5 Re-enable testing on go 1.17 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 91c3ed8ec4 Fix failing tests 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 0dc49d2a70 Silence unhelpful linter error
See #1012
2022-01-27 09:30:14 +01:00
Benj Fassbind a83dea705f Build for newer go versions 2022-01-27 09:30:14 +01:00
Benj Fassbind ed7a960e31 Trigger CI on every push 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 9bf1a44f75 Fix test after merge 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 8ecd01be1f Temporarily skip test failing on CI 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 4933e3caf4 Try to fix test failing on CI
PublishRepo26Test fails to run because something in the CI environment forces
gpg to ask for the user's password. Try to require gpg1 for the test, which
seems to run fine in other environments.
2022-01-27 09:30:14 +01:00
Lorenzo Bolla 4a9a5bc713 Fix some failing system tests 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 7412b84e04 Fix flake8 lint errors 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 3775d69a60 Fix linting errors 2022-01-27 09:30:14 +01:00
Ximon Eighteen 4cf57ae84d govet: compose literal uses unkeyed fields 2022-01-27 09:30:14 +01:00
Ximon Eighteen ef2541776b govet: compose literal uses unkeyed fields 2022-01-27 09:30:14 +01:00
Ximon Eighteen 78082bc10f Add a comment referring to the original Travis CI config from the new workflow. 2022-01-27 09:30:14 +01:00
Ximon Eighteen 5342e549cd govet: compose literal uses unkeyed fields 2022-01-27 09:30:14 +01:00
Ximon Eighteen e2d1e9a7df govet: compose literal uses unkeyed fields 2022-01-27 09:30:14 +01:00
Ximon Eighteen 9aa9917952 golint: "Json" in func name should be "JSON". 2022-01-27 09:30:14 +01:00
Ximon Eighteen c1cdb69f56 golint: "Json" in func name should be "JSON". 2022-01-27 09:30:14 +01:00
Ximon Eighteen 5b8c909ac3 Disable testing against Go master for now. 2022-01-27 09:30:14 +01:00
Ximon Eighteen 20b038bfb7 continue-on-error value must be a boolean 2022-01-27 09:30:14 +01:00
Ximon Eighteen 9f9a1a138b Matrix elements must be arrays. 2022-01-27 09:30:14 +01:00
Ximon Eighteen 4cb9ac5357 Fix syntax error. 2022-01-27 09:30:14 +01:00
Ximon Eighteen cd76e481fd Initial attempt at a GitHub Actions workflow to emulate the previously used Travis CI setup. 2022-01-27 09:30:14 +01:00
Ximon Eighteen 8e309b57b3 Workaround differences in the GHA Ubuntu 18.04 environment compared to the Travis CI Ubuntu 16.04 environment. 2022-01-27 09:30:14 +01:00
Ximon Eighteen beb9d43f4d Use newer golangci-lint source as the former is abandoned. 2022-01-27 09:30:14 +01:00
Lorenzo Bolla b281819cba Make truthy function less surprising 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 6826efc723 Fix pure-go unittests
So they can run on e.g. LXC containers as root, or other conceivable setups.
2022-01-27 09:30:14 +01:00
Lorenzo Bolla 370e3cdfea Fix unittests 2022-01-27 09:30:14 +01:00
Lorenzo Bolla fb8b05e7fd Fix rebase 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 8c94973cf9 Fix indentation 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 787cc8e3ee Fix system tests 2022-01-27 09:30:14 +01:00
Lorenzo Bolla ff51c46915 More informative return value for task.Process 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 0914cd16af Use global async flag as fallback on per-request flag
This way, if no pre-request flag is specified, the globally configured default
is used.
2022-01-27 09:30:14 +01:00
Lorenzo Bolla 9b28d8984f Configurable background task execution 2022-01-27 09:30:14 +01:00
Lorenzo Bolla bd4c3a246d Add new AUTHORS 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 914ddf4859 Fix syntax error 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 2fa3adee1d Don't use transactions when direct db access is enough
For read-only action transactions are not necessary and they risk to deadlock
if multiple go-routines try to read the database.
2022-01-27 09:30:14 +01:00
Lorenzo Bolla fd83c1a5bf Cap delay to sleep to avoid overflow 2022-01-27 09:30:14 +01:00
Lorenzo Bolla de2be9b8ae Sleep between retries to download from http
Fix #1
2022-01-27 09:30:14 +01:00
André Roth 209b030502 gpg: fix downloading multiple keys
each key needs to be provided as separate argument to gpg1 --recv-keys
2022-01-27 09:30:14 +01:00
Lorenzo Bolla 5a65ce6adb mirror: add more logging 2022-01-27 09:30:14 +01:00
Lorenzo Bolla 79a7cf864e mirror: interrupt goroutine when done
This should avoid deadlocking when context is destroyed.
2022-01-27 09:30:14 +01:00
Lorenzo Bolla 19f7b0fe8d mirror: increase logging for easier debugging 2022-01-27 09:30:14 +01:00
André Roth 2b7bb24c92 api gpg: show gpg command 2022-01-27 09:30:14 +01:00
Lorenzo Bolla faf2d588b1 Use verifier from context 2022-01-27 09:30:14 +01:00
André Roth 8e02a03170 fix gpg keys 2022-01-27 09:30:14 +01:00
André Roth d13de0464e api: allow renaming repos 2022-01-27 09:30:14 +01:00
André Roth c0528888f4 log download retries 2022-01-27 09:30:14 +01:00
Oliver Sauder b4efe6a810 Add db cleanup api 2022-01-27 09:30:14 +01:00
Oliver Sauder f09a273ad7 Add publish output progress counting remaining number of packages 2022-01-27 09:30:14 +01:00
Oliver Sauder 3cd168c44d Combine publish list progress into one 2022-01-27 09:30:14 +01:00
Oliver Sauder b0ab8f417d Added gpg api so mirror updates are fully functional from api 2022-01-27 09:30:14 +01:00
Oliver Sauder d7ccf95499 Added mirror api based on task list 2022-01-27 09:30:14 +01:00
Oliver Sauder 6ab5e60833 Add task api and resource locking ability 2022-01-27 09:30:14 +01:00
Oliver Sauder e63d74dff2 Fixed not running tests 2022-01-27 09:30:14 +01:00
Oliver Sauder 25d7d7c037 Solving progress not safe issue for api
Progress is not safe so for api its always nil and
code needs to take care of this
2022-01-27 09:30:14 +01:00
Oliver Sauder 1c7c07ace7 db batch may not be a global resource
This way db usage is safe.
2022-01-27 09:30:14 +01:00
Oliver Sauder f7f42a9cd8 Database changes of resources need to be atomic 2022-01-27 09:30:14 +01:00
Oliver Sauder 1e7731c317 Removed obsolete RWMutexes 2022-01-27 09:30:14 +01:00
Oliver Sauder 208a2151c1 every go routine needs to have its own collection factory
this is needed so concurrent reads and writes are possible.
2022-01-27 09:30:14 +01:00
Andrej Shadura 4a6d53e16d Include AzurePublishEndpoints in the manpage template
Signed-off-by: Andrej Shadura <andrew.shadura@collabora.co.uk>
2022-01-21 11:46:36 +01:00
Chuan Liu a778ff8903 Fix the storage string format.
Co-authored-by: Andrej Shadura <andrew@shadura.me>
2022-01-21 11:46:36 +01:00
chuan bb42a2158d Add support for Azure storage as a publishing backend
This adds a new configuration setting: AzurePublishEndpoints, similar
to the existing S3PublishEndpoints and SwiftPublishEndpoints.

For each endpoint, the following has to be defined:
 - accountName
 - accountKey
 - container
 - prefix

Azure tests require the following environment variables to be set:
 - AZURE_STORAGE_ACCOUNT
 - AZURE_STORAGE_ACCESS_KEY

With either of these not set, Azure-specific tests are skipped.
2022-01-21 11:46:36 +01:00
Sylvain Baubeau ab2f5420c6 Export RemoteRepo package list 2021-11-02 15:08:19 +01:00
Vítězslav Dvořák 174943cd0f Proposed keyserver changed to functional one #990 2021-11-02 15:01:17 +01:00
Joshua Colson 0bc66032d2 Resolve PR #976 review comments
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Joshua Colson 899ed92ebc Add -json flag to publish list|show
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Joshua Colson 129eb8644d Add -json flag to mirror list|show
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Joshua Colson d582f9bab2 Add Debian 11 keys to test fixture keyring
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Joshua Colson 0f1575d5af Add -json flag to snapshot show|list
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Joshua Colson f9c0d99790 Refactor repo list into json and txt output
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Joshua Colson 1f56fb86e3 Add -json output flag to repo list|show
Signed-off-by: Joshua Colson <joshua.colson@gmail.com>
2021-09-24 10:29:33 +02:00
Ratchanan Srirattanamet f9d08e1377 .goxc.json: list os/arch explicitly to avoid darwin/386
Go 1.15 drops support for darwin/386 GOOS/GOARCH pair [1]. So, we have
to skip this pair, and thus cannot use simple os multiply arch anymore.
Switch to goxc's BuildConstraints config, which uses the same syntax as
Go's build constraint header [2].

[1] https://github.com/golang/go/issues/37610
[2] https://golang.org/cmd/go/#hdr-Build_constraints
2021-04-29 14:41:24 +02:00
Max Bruckner cbf0416d7e Filter command: Fix typo Priorioty -> Priority 2021-03-21 09:59:39 +01:00
Andrej Shadura 2422d3ab40 When ETag doesn’t look like MD5, use the value from metadata instead
The S3 backend relies on ETag S3 returns being equal to the MD5 of the
object, but it’s not necessarily true. When the value returned clearly
doesn’t look like a valid MD5 hash (length isn’t exactly 32 characters),
attempt to retrieve the MD5 hash possibly stored in the metadata.

We cannot always do this since user-defined metadata isn’t returned by
the ListObjects call, so verifying it for each object is expensive as it
requires one HEAD request per each object.

This commit fixes #923.

Signed-off-by: Andrej Shadura <andrew.shadura@collabora.co.uk>
2021-03-02 13:37:17 +00:00
Andrej Shadura 960cf76c42 Store MD5 in a separate metadata field as well
The S3 backend relies on ETag S3 returns being equal to the MD5 of the
object, but it’s not necessarily true. For that purpose we store the MD5
object in a separate metadata field as well to make sure it isn’t lost.

From https://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html:

> The entity tag is a hash of the object. The ETag reflects changes only
> to the contents of an object, not its metadata. The ETag may or may not
> be an MD5 digest of the object data. Whether or not it depends on how
> the object was created and how it is encrypted as described below:
>
> Objects created by the PUT Object, POST Object, or Copy operation,
> or through the AWS Management Console, and are encrypted by SSE-S3 or
> plaintext, have ETags that are an MD5 digest of their object data.
>
> Objects created by the PUT Object, POST Object, or Copy operation,
> or through the AWS Management Console, and are encrypted by SSE-C or
> SSE-KMS, have ETags that are not an MD5 digest of their object data.
>
> If an object is created by either the Multipart Upload or Part Copy
> operation, the ETag is not an MD5 digest, regardless of the method
> of encryption.

Signed-off-by: Andrej Shadura <andrew.shadura@collabora.co.uk>
2021-03-02 13:37:17 +00:00
Lorenzo Bolla af2564c580 Use newer Go for mod file 2021-02-12 09:23:24 +01:00
Lorenzo Bolla b385b1e975 Fix test breaking on newer versions of Go
Apparently, Go error message slightly changed in newer versions.
2021-02-12 09:23:24 +01:00
Lorenzo Bolla ce1d4b852a Test against more recent versions of Go
Run basic tests for all minor versions since 1.11 and full tests for the last
two most recent versions.

Fix #939
2021-02-12 09:23:24 +01:00
Lorenzo Bolla c43d31f693 Don't fail hard if we can't clean Swift up 2021-02-08 10:52:27 +01:00
Lorenzo Bolla e4259c5045 Always try to get a version
Even if a simple git hash.
2021-02-08 10:52:27 +01:00
Lorenzo Bolla 993dd2ad1c Print test exception right away, in case the full test run crashes 2021-02-08 10:52:27 +01:00
Lorenzo Bolla 3201244d9b Fix tests and fixtures relying on expired pgp keys
PGP tests relied on expired gpg keys: upgrade with newer Debian keys from
https://ftp-master.debian.org/keys.html.
Download new fixtures files from http://ftp.debian.org/debian/dists/buster/
2021-02-08 10:52:27 +01:00
Lorenzo Bolla f4dc87fa44 Use a hostname more likely to be non-existent than localhost
Otherwise, it's possible that certain network configuration defining
*.localhost cause the tests to fail.
2021-02-08 10:52:27 +01:00
Don Kuntz 24a027194e Remove unused variable 2019-10-18 18:29:38 +03:00
Don Kuntz 62c4dc1472 Update authors 2019-10-18 18:29:38 +03:00
Don Kuntz b7f74b4e55 Allow GPGFinder to work with nonstandard GPG version strings
Specifically, I have MacGPG installed instead of upstream GPG, which
results in the version string reading
  gpg (GnuPG/MacGPG2) 2.2.17

instead of the expected
  gpg (GnuPG) 2.2.17
2019-10-18 18:29:38 +03:00
Andrey Smirnov 2da853dcbe Bump golangci-lint to 19.1 2019-09-27 15:44:33 +03:00
Andrey Smirnov 0438a7c76b Upgrade AWS SDK to the latest version 2019-09-27 15:39:48 +03:00
Andrey Smirnov c86c3a803f Really upgrade goleveldb to the latest master version
PR #876 actually upgraded goleveldb to 1.0.0, not to the latest master.

Recent changes to goleveldb should improve performance
https://github.com/syndtr/goleveldb/issues/226#issuecomment-477568827
2019-09-27 14:19:39 +03:00
Andrey Smirnov 19db62d74f Add new Go modules stuff 2019-09-27 13:59:19 +03:00
Andrey Smirnov 0146411483 Remove vendor/ tree, and dep files 2019-09-27 13:59:19 +03:00
Andrey Smirnov b731e17850 Update nvidia repo key 2019-09-27 13:01:03 +03:00
Andrey Smirnov bb66b2296d Vendor update goleveldb
There are number of changes which went in recently which should improve
performance: https://github.com/syndtr/goleveldb/issues/226#issuecomment-477568827
2019-09-18 16:49:50 +04:00
Andrey Smirnov c75ef8546e Fix system tests for Debian Stretch 9.11 2019-09-18 01:23:58 +03:00
Andrey Smirnov d80c2b6104 Fix system tests 2019-09-06 23:42:56 +03:00
Andrey Smirnov ec4bf35647 Regen aptly.1 2019-09-06 23:42:56 +03:00
Raúl Benencia 669d99bebc Update documentation 2019-09-06 23:42:56 +03:00
Raúl Benencia 715af5950f Add suite completion 2019-09-06 23:42:56 +03:00
Raúl Benencia 7a5ac3dbc2 Tests for custom and default suite 2019-09-06 23:42:56 +03:00
Raúl Benencia ae61cbb4c0 Allow definition of custom Suite 2019-09-06 23:42:56 +03:00
Raphael Medaer bde6e6bda4 Test dependency architecture without version.
As asked by Andrey in #868.
2019-09-06 15:41:59 +03:00
Raphael Medaer a656241d5e Parse dependency architecture even without version
This commit closes: #145

The dependency format "pkg:arch" (e.g. "python3:any") was not well
parsed if not any version is given. This commit splits the dependency
name and architecture in all cases.
2019-09-06 15:41:59 +03:00
Andrey Smirnov 7ae5a12f4a Bump Go supported version to 1.11-1.13
This might allow to switch to Go modules as the next step.
2019-09-05 16:41:50 +03:00
Andrey Smirnov 769e984ef4 Fix issues with progress == nil causing panics
Part of PR #459

This prepares for more methods to be exposed via the API.
2019-09-03 20:28:28 +04:00
Frank Steinborn 98e75f6d97 Make database open attempts configurable also via config file 2019-09-03 00:52:24 +03:00
Nabil BENDAFI 586f879e80 [DOC] Note about legacy file structure 2019-09-03 00:23:25 +03:00
Nabil BENDAFI e2112670bf [DOC] Uploaded package file structure 2019-09-03 00:23:25 +03:00
Andrey Smirnov 060c6669c1 Remove test which relied on now gone mongodb repository
Looks like Mongo doesn't provide any regular structure repository
anymore (only flat one?).
2019-09-02 23:54:07 +03:00
Stephan Eicher aa02c5cbe9 Fix #827 - passhprase typos 2019-09-02 23:26:37 +03:00
Andrey Smirnov 77d7c3871a Consistently use transactions to update database
For any action which is multi-step (requires updating more than 1 DB
key), use transaction to make update atomic.

Also pack big chunks of updates (importing packages for importing and
mirror updates) into single transaction to improve aptly performance and
get some isolation.

Note that still layers up (Collections) provide some level of isolation,
so this is going to shine with the future PRs to remove collection
locks.

Spin-off of #459
2019-08-11 00:11:53 +03:00
Andrey Smirnov 67e38955ae Refactor database code to support standalone batches, transactions.
This is spin-off of changes from #459.

Transactions are not being used yet, but batches are updated to work
with the new API.

`database/` package was refactored to split abstract interfaces and
implementation via goleveldb. This should make it easier to implement
new database types.
2019-08-09 00:46:40 +03:00
Andrey Smirnov 26098f6c8d Print redirects being followed, drop mirror.yandex.ru.
Use CDN-backed Debian mirror to make tests run faster hopefully for
everyone. Redirects might be important to know what exactly is going on
when items are being downloaded.
2019-08-07 21:10:04 +03:00
Andrey Smirnov 021b6f694b Fix flakey tests related to identity name ordering. 2019-08-07 20:47:52 +03:00
Andrey Smirnov f0a370db24 Rework HTTP downloader retry logic
Apply retries as global, config-level option `downloadRetries` so that
it can be applied to any aptly command which downloads objects.

Unwrap `errors.Wrap` which is used in downloader.

Unwrap `*url.Error` which should be the actual error returned from the
HTTP client, catch more cases, be more specific around failures.
2019-08-07 20:23:05 +03:00
Andrey Smirnov 2e7f624b34 Add test for publishing with non-empty Origin & Label
See also #848
2019-07-19 22:58:56 +03:00
Shengjing Zhu b63c0c7dfc Update AUTHORS 2019-07-15 21:51:09 +03:00
Shengjing Zhu 906cbf1e6f Fix time.Time msgpack decoding backwards compatibility
See https://github.com/ugorji/go-codec/issues/269
2019-07-15 21:51:09 +03:00
Shengjing Zhu 5aefc741f2 Add codec tag to fields which are ignored in new codec package
github.com/ugorji/go/codec 1.1.4 ignores field with json:"-" tag
2019-07-15 21:51:09 +03:00
Shengjing Zhu 5c28ea3064 Update github.com/ugorji/go to v1.1.4 2019-07-15 21:51:09 +03:00
Andrey Smirnov 70cd11e30f Revert "Don't remove API file socket if it exists and it's usable"
See PR #807

Fixes: #849

This reverts commit 22848b010d.
2019-07-13 00:45:54 +03:00
Andrey Smirnov 94a72b23ff Update Go AWS SDK to the latest version 2019-07-13 00:19:00 +03:00
Andrey Smirnov d08be990ef Skip uploading release versions of aptly to nightly repo
This breaks releases, as two versions of the package with same version
might end up in internal.aptly.info.
2019-07-11 01:52:00 +03:00
Andrey Smirnov ca5b7758ce Print when test is skipped 2019-07-11 00:49:36 +03:00
Andrey Smirnov bb1def2910 Try Travis on xenial workers 2019-07-11 00:16:20 +03:00
Andrey Smirnov 673abae1be Update system tests after Debian buster was released. 2019-07-10 22:27:11 +03:00
Andrey Smirnov 3b8c067e70 Merge pull request #850 from smira/no-bintray
Bintray no longer used for artifacts. [ci skip]
2019-07-10 19:54:09 +03:00
Andrey Smirnov 528459eeb2 Bintray no longer used for artifacts. [ci skip] 2019-07-10 19:53:07 +03:00
Andrey Smirnov bc1ab4e55c Merge pull request #847 from smira/fix-repo-name
Fix repo name in release script
2019-07-06 16:03:14 +03:00
Andrey Smirnov 5aefd0b393 Fix repo name in release script 2019-07-06 16:02:43 +03:00
Andrey Smirnov 7bc53a4253 Merge pull request #846 from smira/fix-releases-api-key
Fix releases API key
2019-07-06 00:05:25 +03:00
Andrey Smirnov 8b12dccd76 Fix releases API key 2019-07-06 00:04:46 +03:00
Andrey Smirnov 952afb6040 Merge pull request #845 from smira/upload-artifacts-fix
Fix upload artifacts script to fail, add release upload script
2019-07-05 22:02:59 +03:00
Andrey Smirnov 56ca5e9e62 Temporary disable test as linux.dell.com is NXDOMAIN 2019-07-05 21:32:41 +03:00
Andrey Smirnov a834461752 Fix upload artifacts script to fail, add release upload script
This should improve reliability
2019-07-05 20:08:31 +03:00
Andrey Smirnov 37166af321 Merge pull request #842 from aptly-dev/bump-go-version
Bump Go versions for Travis, fix tests
2019-07-04 00:44:52 +03:00
Andrey Smirnov 2c91bcdc30 Bump Go versions for Travis, fix tests
Replace gometalinter with golangci-lint.

Fix system tests (wheezy is gone, replace with stretch).

Fix linter warnings.
2019-07-04 00:16:12 +03:00
Andrey Smirnov e2d6a53de5 Merge pull request #803 from stb-tester/deterministic-stanza-WriteTo
Stanza.WriteTo: Sort extra fields alphabetically
2019-01-25 17:34:34 +03:00
Andrey Smirnov 89537b1521 Merge branch 'master' into deterministic-stanza-WriteTo 2019-01-25 01:27:31 +03:00
Oliver Sauder 152b3cae90 Merge pull request #808 from aptly-dev/797-no-such-bucket-s3
Ignore 'NoSuchBucket' error when deleting S3 objects
2019-01-24 08:09:29 +01:00
Andrey Smirnov f104e53fd4 Ignore 'NoSuchBucket' error when deleting S3 objects
Also ignore any removal errors when `-force-drop` is used.
2019-01-23 18:17:08 +03:00
William Manley fd99ae0e59 Merge branch 'master' into deterministic-stanza-WriteTo 2019-01-21 13:48:07 +00:00
Andrey Smirnov 4b6c159e3a Vendor update github.com/pkg/errors 2019-01-20 22:54:13 +03:00
Andrey Smirnov 50f8cfbc15 Merge pull request #807 from aptly-dev/806-file-socket
Don't remove API file socket if it exists and usable
2019-01-20 22:52:30 +03:00
Andrey Smirnov 22848b010d Don't remove API file socket if it exists and it's usable 2019-01-20 00:01:44 +03:00
Andrey Smirnov 3b5840e248 Fix linter list and fix errors discovered by new staticcheck 2019-01-20 00:01:17 +03:00
William Manley f955707201 Add William Manley (@wmanley) to AUTHORS
My measly contribution hardly merits it but it's a requirement in
`CONTRIBUTING.md`.
2019-01-08 15:14:39 +00:00
William Manley 86dc10028f Stanza.WriteTo: Sort extra fields alphabetically
This makes the output deterministic.  This is important to me as I am
using `Packages` index files as a kind of lockfile and committing it
to my git repository.  Without this we get a lot of noise in the diff
whenever the file is regenerated because
[go randomises map iteration order][1].

[1]: https://nathanleclaire.com/blog/2014/04/27/a-surprising-feature-of-golang-that-colored-me-impressed/
2019-01-08 15:12:34 +00:00
Andrey Smirnov a64807efda Merge pull request #779 from aptly-dev/pgp-finder
Compatibility with GnuPG 1.x and 2.x, auto-detect GnuPG version
2018-10-10 17:27:52 +03:00
Andrey Smirnov 61e00b5fbd Test updates for Travis CI
Travis is running Trusty with GPG 2.0.x, which is
much different from 2.1.x.

Add tests for default key signing.

Add test for gpg1/2 in functional.
2018-10-10 01:34:58 +03:00
Andrey Smirnov 1b2fccb615 Compatibility with GnuPG 1.x and 2.x, auto-detect GnuPG version
* aptly can sign and verify without issues with GnuPG 1.x and 2.x
* aptly auto-detects GnuPG version and adapts accordingly
* aptly automatically finds suitable GnuPG version

Majority of the work was to get unit-tests which can work with GnuPG 1.x & 2.x.
Locally I've verified that aptly supports GnuPG 1.4.x & 2.2.x. Travis CI
environment is based on trusty, so it runs gpg2 tests with GnuPG 2.0.x.

Configuration parameter gpgProvider now supports three values for GnuPG:

* gpg (same as before, default): use GnuPG 1.x if available (checks gpg, gpg1),
otherwise uses GnuPG 2.x; for aptly users who already have GnuPG 1.x
environment (as it was the only supported version) nothing should change; new
users might start with GnuPG 2.x if that's their installed version

* gpg1 looks for GnuPG 1.x only, fails otherwise

* gpg2 looks for GnuPG 2.x only, fails otherwise
2018-10-10 01:34:00 +03:00
Oliver Sauder 702c1ff217 Merge pull request #680 from sliverc/with_installer
Add support to mirror non package installer files
2018-09-27 09:50:37 +02:00
Oliver Sauder d1b2814ec6 Merge branch 'master' into with_installer 2018-09-27 09:35:09 +02:00
Andrey Smirnov ec57d1786c Merge pull request #780 from aptly-dev/773-non-armored-sig
Support for non-armored detached signatures
2018-09-26 16:37:42 +03:00
Andrey Smirnov 9f7c1f90ec Support for non-armored detached signatures 2018-09-26 01:36:52 +03:00
Oliver Sauder e23e30eb44 Merge branch 'master' into with_installer 2018-09-21 13:26:15 +02:00
Andrey Smirnov 2b4a61b84c Merge pull request #778 from aptly-dev/go-1-11
Bump Go versions with Go 1.11 release
2018-09-20 20:15:52 +03:00
Andrey Smirnov fbafde6e27 Bump Go versions with Go 1.11 release 2018-09-19 01:23:17 +03:00
Andrey Smirnov 14e5a75d35 Merge pull request #776 from urpylka/master
Replace Docker container w aptly & nginx in README
2018-09-14 23:49:58 +03:00
Artem Smirnov ea32d8627e Update AUTHORS 2018-09-14 01:29:11 +03:00
Artem Smirnov 814a0498df Little syntax fix 2018-09-14 01:24:21 +03:00
Artem Smirnov e45f85cc1e Replace to new docker container w aptly & nginx
Old project not supported long time
2018-09-14 01:22:32 +03:00
Oliver Sauder 1e9c032072 Merge pull request #774 from nuclearsandwich/update-contributing-docs
Use github.com/aptly-dev/aptly as the go package path.
2018-09-13 08:42:58 +02:00
Steven! Ragnarök c741cca5f2 Use github.com/aptly-dev/aptly as the go package path.
Without this change the first time setup instructions fail at the `make install` stage.
2018-09-13 00:21:14 -04:00
Andrey Smirnov 72ff71f59c Merge pull request #766 from aptly-dev/761-more-lazy
Reimplement DB collections for mirrors, repos and snapshots
2018-08-21 17:00:08 +03:00
Andrey Smirnov 699323e2e0 Reimplement DB collections for mirrors, repos and snapshots
See #765, #761

Collections were relying on keeping in-memory list of all the objects
for any kind of operation which doesn't scale well the number of
objects in the database.

With this rewrite, objects are loaded only on demand which might
be pessimization in some edge cases but should improve performance
and memory footprint signifcantly.
2018-08-21 01:08:14 +03:00
Andrey Smirnov fb5985bbbe Merge pull request #767 from aptly-dev/fix-sys-test-take-N
Fix system tests on `master` branch
2018-08-20 17:55:54 +03:00
Andrey Smirnov 5a9f4bee12 Fix system tests on master branch 2018-08-17 18:11:49 +03:00
Andrey Smirnov 4717793d8e Merge pull request #765 from aptly-dev/761-lazy-iteration
Implement lazy iteration (ForEach) over collections
2018-08-16 16:44:05 +03:00
Andrey Smirnov de38011dd2 Add simple benchmark for SnapshotCollection.ForEach() 2018-08-14 00:56:15 +03:00
Andrey Smirnov 0f4bbc4752 Implement lazy iteration (ForEach) over collections
See #761

aptly had a concept of loading small amount of info per each object
into memory once collection is accessed for the first time.

This might have simplified some operations, but it doesn't scale well
with huge aptly databases.

This is just intermediate step towards better memory management -
list of objects is not loaded unless some method is called.
`ForEach` method (mainly used in cleanup) is reimplemented to
iterate over database without ever loading all the objects into memory.

Memory was even worse with previous approach, as for each item usually
`LoadComplete()` is called, which pulls even more data into memory
and item stays in memory till the end of the iteration as it is referenced
from `collection.list`.

For the subsequent PR: reimplement `ByUUID()` and probably other methods
to avoid loading all the items into memory, at least for all the collecitons
except for published repos. When published repository is being loaded, it
might pull source local repo which in turn would trigger loading for all the
local repos which is not acceptable.
2018-08-04 00:26:02 +03:00
Andrey Smirnov 86a1c41e5d Merge pull request #762 from aptly-dev/761-flush-collections
Lower memory usage for `aptly db cleanup`
2018-08-01 00:29:18 +03:00
Andrey Smirnov 021b8c4cff Lower memory usage for aptly db cleanup
This is not a complete fix, but the easiest first step.

During `db cleanup`, aptly is loading every repo/mirror/... into memory,
and even though each object is processed only once, collection holds
a reference to all the loaded objects, so they won't be GC'd until
process exits.

CollectionFactory.Flush() releases pointers to collection objects,
making objects egligble for GC.

This is not a complete fix, as during iteration we could have tried
to release a link to every object being GCed and that would have
helped much more.
2018-07-20 01:04:51 +03:00
Andrey Smirnov bcacb7b7f0 Merge pull request #760 from aptly-dev/756-fix
Keep checksum of not compressed index file even if it's not uploaded
2018-07-16 23:50:48 +03:00
Andrey Smirnov 747b9752ce Keep checksum of not compressed index file even if it's not uploaded
Fixes: #756
2018-07-14 00:17:36 +03:00
Andrey Smirnov b0be6c8a7a Merge pull request #755 from aptly-dev/gpg2-gpg1
Unit tests for PGP signing/verification
2018-07-11 19:19:37 +03:00
Andrey Smirnov 58c7358113 Unit tests for PGP signing/verification
These unit-tests cover operations via both PGP providers:
built-in `openpgp` and external `gpg`.

Next step is to run these tests for gpg1 & gpg2
as separate entities.
2018-07-11 01:07:13 +03:00
Oliver Sauder b1a2523ef0 Add unit test for remote and http 2018-07-06 15:02:37 +02:00
Oliver Sauder b7323db31b Add detached signature to installer hashsum file 2018-07-06 15:02:37 +02:00
Oliver Sauder 2e52692ba6 Test LinkFromPool with nested filenames 2018-07-06 15:02:37 +02:00
Oliver Sauder 0075ead526 Simplify package function signature LinkFromPool 2018-07-06 15:02:37 +02:00
Oliver Sauder 6df4a746f1 Clarify doc strings 2018-07-06 15:02:37 +02:00
Oliver Sauder 074904ee92 Allow editing of with-installer mirror flag 2018-07-06 15:02:37 +02:00
Oliver Sauder 108b0ea226 Add support to mirror non package installer files 2018-07-06 15:02:37 +02:00
Andrey Smirnov 9a704de43b Merge pull request #754 from aviau/lzma
switch to packaged lzma package
2018-06-23 00:58:50 +03:00
aviau 7dfc12d138 switch to packaged lzma package 2018-06-22 12:44:23 -04:00
Harald Sitter 9000446663 Merge pull request #753 from aviau/official-uuid
dep: use official uuid package
2018-06-22 11:14:35 +02:00
aviau 814ac6c28c dep: use official uuid package 2018-06-21 16:12:45 -04:00
Oliver Sauder d1a284298f Merge pull request #751 from sliverc/repo_include_api
Expose repo include through API
2018-06-19 16:02:22 +02:00
Oliver Sauder 9509629bcf Add changes test to increase coverage 2018-06-19 15:40:38 +02:00
Oliver Sauder f1882cfe2c Expose repo include through API 2018-06-19 15:39:09 +02:00
Andrey Smirnov 90e446ec16 Merge pull request #743 from aptly-dev/gpg2-skip
Skip GPG version check `APTLY_SKIP_GPG_VERSION_CHECK=1` is set in the env
2018-06-19 00:42:04 +03:00
Oliver Sauder 464ed8269b Merge pull request #750 from sliverc/fix_nvidia_test
Fix failing SHA512 checksums only test
2018-06-18 08:59:52 +02:00
Oliver Sauder 57a51d94ed Fix failing SHA512 checksums only test
This test has been failing very often because of changes in nvidia
repository. As this test is not related to filtering
remove number of filtered packages from output for a more robust test.
2018-06-15 15:43:37 +02:00
Andrey Smirnov 53c557271d Merge pull request #744 from aptly-dev/nightly-builds
Move release build to Travis CI
2018-06-12 00:48:00 +03:00
Andrey Smirnov b6fe16095b Move nightly builds to Travis CI
This updates previous work in #739 to build
Debian packages and zip files for other OS.

All the build artifacts are uploaded to S3
public bucket `aptly-nightly` so that there's
archive for all the builds.

All `.deb` packages are automatically uploaded
to repo.aptly.info/nightly on build.
2018-06-12 00:26:44 +03:00
Oliver Sauder 6a1c439325 Merge pull request #747 from tomascassidy/patch-1
Fix typo
2018-06-05 21:54:34 +02:00
tomascassidy 06b0be7bad Fix typo 2018-06-05 16:23:06 +10:00
Andrey Smirnov e5acf22285 Skip GPG version check APTLY_SKIP_GPG_VERSION_CHECK=1 is set in the environment
This allows to force using GnuPG 2.x even if aptly is not 100% ready
to use it.
2018-05-25 00:23:50 +03:00
Harald Sitter 5f904a164c Merge pull request #739 from aptly-dev/travis-binaries
add support for travis attaching build artifacts to releases
2018-05-15 08:29:14 +02:00
Harald Sitter 9a30a11786 add support for travis attaching build artifacts to releases
- new phony target build: same as install but creating aptly-$version and
  putting it into a build/ subdir
- env TRAVIS_TAG in the makefile now overrides the TAG lookup, this ensures
  that the tag travis is working with is actually the one being used to
  construct the version number
- subdir is gitignored
- travis runs new target - lists artifacts - deploys artifacts to github

all of the above only happens on builds that are a tag and DEPLOY_BINARIES
is set to yes (which is only the case for latest stable go version)
2018-05-14 17:27:14 +02:00
Strajan Sebastian Ioan d31144b9ae Buffer increase (#738)
Increase Scanner buffer size for Stanza reader
2018-05-14 17:41:33 +03:00
Harald Sitter c7a3a10846 Merge pull request #735 from aptly-dev/nvidia-repo-update
update nvidia reference again
2018-05-03 11:59:57 +02:00
Harald Sitter 2be0b7859d update nvidia reference again
package count chainged again -.-

I am working on a fixture set for repositories so we can stop talking to
live repositories. that's quite the undertaking though, so let's fix the
output reference to unbreak the test in the meantime
2018-05-03 10:45:01 +02:00
Oliver Sauder 65528fc357 Merge pull request #734 from aptly-dev/gpg1
introduce a gpg and gpgv version compatibility check and fall back to v1
2018-04-26 10:34:03 +02:00
Harald Sitter 5a713534c6 fix gpg setting
Init is actually never called and I have no clue why it is there if it is
not called.
Take this opportunity to introduce a New function which only does the
helper lookup and panics iff that fails. Panic may be a bit too aggressive,
but seems the most certain way to get out of not finding a suitable gpg1
binary.
2018-04-26 09:18:06 +02:00
Harald Sitter f89e322ece move away from assert package
we don't actually use it anywhere else
2018-04-25 15:35:01 +02:00
Harald Sitter cd6075ba94 introduce a gpg and gpgv version compatibility check and fall back to v1
Newer versions of debian and ubuntu come with gpg pointing to gpg2.
We can currently only handle gpg1 CLIs though. Luckily the old gpg is still
available in the package gnupg1 (providing bin/gpg1).

As a bit of a stop-gap, until #657 can be resolved properly, we'll detect
the version of bin/gpg. If it is unsuitable we'll fall back and try
bin/gpg1. If neither is found to be suitable the signer/verifier will
not work.

Same applies to gpgv/gpgv1.
2018-04-25 15:05:53 +02:00
Andrey Smirnov 77033df27b Merge pull request #732 from aptly-dev/move-to-aptly-dev
Fix paths after repository transfer to aptly-dev
2018-04-18 22:43:53 +03:00
Andrey Smirnov b8c5303fdb Fix paths after repository transfer to aptly-dev 2018-04-18 21:19:43 +03:00
Andrey Smirnov eaab66da58 Merge pull request #731 from smira/build-improvements
Change build settings to speed up builds
2018-04-18 12:11:01 +03:00
Andrey Smirnov 2a8aff9746 Change build settings to speed up builds
1. Don't run long steps for Go versions other than 1.9 & 1.10
according to Golang Release Policy (two latest versions).

2. Switch to codecov.io, collect coverage only on Go 1.10 which
has fixes for multi-module coverage & ./... ignoring vendor.

3. Simplify Makefile.
2018-04-18 01:19:26 +03:00
Andrey Smirnov 797b2dd996 Merge pull request #730 from smira/s3-creds
Replace S3 creds
2018-04-16 11:37:46 +03:00
Andrey Smirnov e65fff058c Fix build on Travis 2018-04-13 23:55:49 +03:00
Andrey Smirnov 548dcdb242 Add google_compute_engine boto dependency (why???) 2018-04-13 23:53:01 +03:00
Andrey Smirnov 9a65bbe12c Add debug output on tests being skipped 2018-04-13 23:42:07 +03:00
Andrey Smirnov 72d741a9b7 Replace S3 creds 2018-04-13 23:18:53 +03:00
Andrey Smirnov 2f5bf96fc9 Merge pull request #461 from jacksgt/service-file
Add systemd service for aptly http server
2018-04-11 16:32:41 +03:00
Andrey Smirnov 4d3b42eb11 Merge pull request #729 from smira/667-legacy-content-indexes
Implement 'legacy' Contents indexes to match Ubuntu <=16.04
2018-04-11 10:50:38 +03:00
Andrey Smirnov 5b85522400 Implement 'legacy' Contents indexes to match Ubuntu <=16.04
Another index is created which unifies data for all the components.

This certainly requires more resources as we have to build yet another
index.
2018-04-11 00:57:15 +03:00
Andrey Smirnov 5f96abc271 Merge pull request #728 from smira/openpgp-leveldb-update
Update vendored deps, including AWS SDK, openpgp, ftp, ...
2018-04-11 00:28:34 +03:00
Andrey Smirnov 0e6ee35942 Update vendored deps, including AWS SDK, openpgp, ftp, ... 2018-04-10 23:49:16 +03:00
Jack Henschel 14798b2063 Add systemd service for aptly http server and aptly api
The systemd service files can be placed under `/etc/systemd/system/`
(for users/administrators) or `/lib/systemd/system/` (for distributions)
2018-04-05 17:13:26 +02:00
Andrey Smirnov cef4fefc40 Merge pull request #726 from smira/dep-update
Update to recent version of `dep`, fix lock files
2018-04-05 17:27:03 +03:00
Andrey Smirnov 181806a9a2 Update to recent version of dep, fix lock files 2018-04-05 16:25:39 +03:00
Andrey Smirnov 99a205c716 Merge pull request #713 from apachelogger/fix-711
fix prefix length in error message
2018-04-04 23:17:11 +03:00
Andrey Smirnov dd78c026c1 Merge branch 'master' into fix-711 2018-04-04 01:37:27 +03:00
Andrey Smirnov 20516dbbc2 Merge pull request #724 from smira/gui-link
Add link to aptly GUI
2018-04-02 12:06:10 -04:00
Andrey Smirnov ba80f377a9 Add link to aptly GUI
See also #341
2018-03-31 17:07:07 -04:00
Andrey Smirnov dc7bbf35eb Merge pull request #719 from smira/new-key
New signing key for aptly repo, and small fixes
2018-03-16 16:07:50 +03:00
Andrey Smirnov b3b0dbb217 Go 1.10 fix 2018-03-16 13:02:38 +03:00
Andrey Smirnov d76259496d Disable FTP tests in Travis 2018-03-16 11:32:22 +03:00
Andrey Smirnov aa3a2ab595 New signing key for aptly repo, and small fixes
Build on Go 1.10, drop Go 1.7

Remove references to now defunct pgp.mit.edu, fix system test
2018-03-16 01:27:57 +03:00
Harald Sitter 0f1fd1bca6 fix prefix length in error message
Fixes #711
2018-03-07 12:44:01 +01:00
Andrey Smirnov 581876df9a Merge pull request #707 from apachelogger/batch-contents
batch updates to the temporary db when publishing
2018-02-28 00:04:07 +03:00
Andrey Smirnov d0101be955 Merge pull request #703 from steinymity/master
Add zsh completion function
2018-02-28 00:01:34 +03:00
Andrey Smirnov caa5433787 Merge pull request #705 from apachelogger/prevent-root-remove
prevent removal of a PublishedStorage's root dir
2018-02-27 23:59:41 +03:00
Harald Sitter 9125745416 batch updates to the temporary db when publishing
updates with contents generation were super syscall-heavy. for each path
in a package (so at least 2-4, but ordinarily >4) we'd do a db.Put in
ContentsIndex which results in one syscall.Write. so, for every package in
a published repo we'd have to do *at least* 2 but ordinarily >4 syscalls.
this gets abysmally slow very quickly depending on the available system
specs.

instead, start a batch inside each package and finish it when we are done
with the package. this should keep the memory footprint negligible, but
reduce the write() calls from N to 1.

on one of KDE's servers I have seen update publishing of 7600 packages go
from ~28s to ~9s when using batch putting on an HDD.
on my local system the same set of packages go from ~14s to ~6s on an SSD.
(all inodes in cache in both cases)
2018-02-26 16:19:15 +01:00
Harald Sitter b893c0a7ca prevent removal of a PublishedStorage's root dir
presently there is no use case where we need this. on the other hand,
passing empty paths into any of the remove methods is indicative of a bug.
this is particularly dangerous as this can temporarily smash the publish
root but later restore it again when actually publishing. this makes
for super nasty and hard to track down problems.

to guard against this simply disallow root dir removal using empty
strings. should we find a use case for this in the future we can always
revisit this (FTR: I think very explicitly API should be used so everyone
knows what is going on and you can't accidentally run it)
2018-02-26 11:09:03 +01:00
Andrey Smirnov 02ac416561 Merge pull request #706 from apachelogger/fix-by-index-cleanup
Fix by by-hash index cleanup + root dir data "loss"
2018-02-23 02:34:24 +03:00
Harald Sitter 00bb0ca8f3 fix a serious file leak in the by-index publishing
the logic here was wrong.
if we managed to find the link target (the physical index file) pointed to
by our old symlink we want to remove it (this is basically "cleaning up old
index" logic).
previously we'd try to only delete it when the ReadLink came back with
error. which had two serious issues with it:

a) linkTarget was empty, so we basically called Remove("") which would
   delete the storage -> root <- directory if the root is a symlink!
b) we'd leak old indexes as the cleanup logic only ran if there was en
   error which would ordinarily never be

new code correctly cleans up unless there was an error.

this relates to a previous bugfix of readLink which incorrectly returned
absolute paths ultimately rendering the Remove call also broken.
2018-02-19 17:32:06 +01:00
Harald Sitter 2d0baef3b1 make code less repetitive and more readable
by using the power of variables!
2018-02-19 17:22:36 +01:00
Harald Sitter 3ea803e3bb fix PublishedStorage's ReadLink to return a relative path
previously it'd return an absolute path which makes the path absolutely
useless as all other functions of PublishedStorage need relative input
and will prepend them with the rootPath, so getting an absolute ReadLink
and then trying to remove that'd would ultimately try to remove the
absolute path `$root/AbsoluteRoot/LinkTarget` instead of `$root/LinkTarget`

add a unit test to actually verify readlink
2018-02-19 17:13:41 +01:00
Maximilian Stein 2fa9d7402f Add zsh completion function
* imported from https://github.com/steinymity/aptly-zsh
2018-02-17 17:29:32 +01:00
Andrey Smirnov 3c04c56639 Merge pull request #697 from pjediny/issues-692
Issues 692
2018-02-09 23:26:09 +03:00
Andrey Smirnov 7cd4b7a908 Merge pull request #696 from apachelogger/AcquireByHash
properly expose AcquireByHash through the api
2018-01-27 22:55:51 +03:00
Andrey Smirnov 9242ea4d72 Merge branch 'master' into issues-692 2018-01-27 17:18:55 +03:00
Andrey Smirnov 58790dadc6 Merge branch 'master' into AcquireByHash 2018-01-27 17:16:24 +03:00
Andrey Smirnov 182fbdef50 Merge pull request #698 from apachelogger/fix-filter
update nvidia mirror output ref
2018-01-27 00:19:16 +03:00
Harald Sitter 4fb57f65fb update nvidia mirror output ref
this repo now contains 12 packages not 8
2018-01-19 13:09:29 +01:00
Petr Jediný 12e2982362 S3 SymLink fix
The copy source should be the name of the source bucket and key name
of the source object, separated by a slash (/).
2018-01-17 14:25:45 +01:00
Petr Jediný 60fb415150 S3 FileExists fix
According to https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD
must not have response body so the AWS error code NoSuchKey
cannot be received from S3 and we need to fallback to HTTP NotFound
error code.
2018-01-17 11:27:35 +01:00
Harald Sitter 75c4d6da3b properly expose AcquireByHash through the api
- new publish calls can now enable AcquireByHash by right away (previously
  one would have had to create a new publishing endpoint and then
  explicitly switch it to AcquireByHash)
- all json marshals of PublishedRepo now contain AcquireByHash (allows
  inspecting if a given endpoint has AcquireByHash enabled already; also
  enables verification that a switch/update actually applied a
  potential AcquireByHash change
- update all tests to reflect that default state of AcquireByHash
- update creation and switch testing to explicitly toggle AcquireByHash to
  make sure state mutation works as expected
2018-01-15 17:04:05 +01:00
Andrey Smirnov 1aa88701fb Merge pull request #688 from smira/686-race-fix
Fix race in API related to `LoadComplete()`
2017-12-13 15:49:37 +03:00
Andrey Smirnov 43ddcd27cb Fix race in API related to LoadComplete()
LoadComplete() modifies object, so it would cause issues if it runs
concurrently with other methods. Uprage mutex locks to write
locks when LoadComplete() is being used.
2017-12-13 12:40:06 +03:00
Andrey Smirnov 9cb2a302f8 Merge pull request #683 from smira/545-download-contxt
Use Go context to abort gracefully mirror updates
2017-12-01 00:27:26 +03:00
Andrey Smirnov d836334767 Merge pull request #682 from tirolerstefan/remove-buildinfo
#679: added *.buildinfo file to processedFile list (will be removed)
2017-12-01 00:23:49 +03:00
Andrey Smirnov 565fcf4390 Merge pull request #664 from sliverc/acquire-by-hash
Support Acquire-By-Hash for index files
2017-12-01 00:19:07 +03:00
Andrey Smirnov b7490fe909 Refactor to embed gocontext.Context into aptly context 2017-11-30 23:44:04 +03:00
Oliver Sauder b2bf4f7884 Adjust FileExists to differentiate between error and actual file existence 2017-11-30 09:46:02 +01:00
Oliver Sauder e504fdcd54 Build src path on basis of storage prefix when symlinking 2017-11-30 09:46:02 +01:00
Oliver Sauder 3efa1052fa Implement FileExists in files storage as simple stat to improve performance 2017-11-30 09:46:02 +01:00
Oliver Sauder 2e488608ca Simplify packaging indexing by hash and stop when there is an error 2017-11-30 09:46:02 +01:00
Oliver Sauder 674a0e84be Order publish parameters in bash completion
This makes it easier maintainable.
2017-11-30 09:46:02 +01:00
Oliver Sauder f5e1e194b3 Update man page and bash completion 2017-11-30 09:46:02 +01:00
Oliver Sauder b4f3573d11 Add acquire by hash when updating publish 2017-11-30 09:46:02 +01:00
Oliver Sauder 4718625388 Avoid exception when failing tests doesn't have a doc string 2017-11-30 09:46:02 +01:00
Oliver Sauder d6b4b795a5 Fix linting errors 2017-11-30 09:46:02 +01:00
Oliver Sauder 2bd0b786ea Extend publish snapshot test with acquire by hash 2017-11-30 09:46:02 +01:00
Oliver Sauder 092a7ed8f3 Rename AccessByHash to AcquireByHash for consistency with other flags 2017-11-30 09:46:02 +01:00
Oliver Sauder 438e206b3d Extend swift storage with link and file exists methods 2017-11-30 09:46:02 +01:00
Oliver Sauder 7498fd8fc8 Extend s3 storage with link and file exists methods 2017-11-30 09:46:02 +01:00
André Roth e07912770e Extend PublishedStorage interface for Acquire-By-Hash
Signed-off-by: André Roth <neolynx@gmail.com>
2017-11-30 09:46:02 +01:00
André Roth bb2db7e500 Support Acquire-By-Hash for index files
The added "aptly publish repo" option "-access-by-hash" publishes
the index files (Packages*, Sources*) also as hardlinked hashes.
Example:
 /dists/yakkety/main/binary-amd64/by-hash/SHA512/31833ec39acc...
The Release files indicate this with the option "Acquire-By-Hash: yes"

This is used by apt >= 1.2.0 and prevents the "Hash sum mismatch" race
condition between a server side "aptly publish repo" and "apt-get update"
on a client.
See: http://www.chiark.greenend.org.uk/~cjwatson/blog/no-more-hash-sum-mismatch-errors.html

This implementation uses symlinks in the by-hash/*/ directory for keeping
only two versions of the index files and deleting older files
automatically.

Note: this only works with aptly.FileSystemPublishedStorage

Closes: #536

Signed-off-by: André Roth <neolynx@gmail.com>
2017-11-30 09:46:02 +01:00
Stefan c94e048198 Merge branch 'master' into remove-buildinfo 2017-11-30 06:34:50 +01:00
Stefan Felkel 3b4c06d28d gofmt 2017-11-30 06:31:50 +01:00
Andrey Smirnov 15618c8ea8 Use Go context to abort gracefully mirror updates
There are two fixes here:

1. Abort package download immediately as ^C is pressed.
2. Import all the already downloaded files into package pool,
so that next time mirror is updated, aptly won't download them
once again.
2017-11-30 00:49:37 +03:00
Andrey Smirnov a037615962 Merge pull request #677 from sliverc/edit_mirror_archive_url
Allow editing of mirror archive url
2017-11-29 23:23:08 +03:00
Oliver Sauder 5d301fb1b7 Prepare archive root when editing it 2017-11-27 11:08:31 +01:00
Stefan Felkel 8a4d866810 #679: added *.buildinfo file to processedFile list (will be removed, afterwards) 2017-11-24 14:23:26 +01:00
Oliver Sauder b98abcc049 Allow editing of mirror archive url
This is needed in case a mirror has moved or is down and need to move
to new mirror.
2017-11-21 16:31:49 +01:00
Andrey Smirnov 10e0966edc Merge pull request #674 from smira/fix-formatting
Fix formatting
2017-11-19 21:53:09 +03:00
Andrey Smirnov 340d1fdd7c Fix formatting 2017-11-19 19:53:24 +03:00
Andrey Smirnov 14d4a2706c Merge pull request #673 from AgNO3/s3-removal
S3 backend: include path prefix in removal requests.
2017-11-19 19:24:04 +03:00
Moritz Bechler 308ea83cc0 S3 backend: include path prefix in removal requests.
DELETE requests, both for temporary files and no longer referenced
packages, lacked the configured path prefix and therefor were not
removed if a prefix is configured.
2017-11-13 14:48:25 +01:00
Andrey Smirnov 9c018ce636 Merge pull request #668 from smira/flx42-sha512-release-file
Handle SHA512 in Release files
2017-11-08 22:25:23 +03:00
Andrey Smirnov 359cda9d99 Add system test for repo with SHA512-only checksums 2017-11-08 19:45:22 +03:00
Felix Abecassis e682639b20 Handle SHA512 in Release files
Fix: #656
2017-11-08 11:54:19 +03:00
Andrey Smirnov afd2c5fcea Merge pull request #665 from smira/upd-goleveldb
Update goleveldb vendored dependency (see #662)
2017-11-08 11:49:54 +03:00
Andrey Smirnov 5a1d006850 Update goleveldb vendored dependency (see #662) 2017-11-08 00:49:56 +03:00
Andrey Smirnov 67c26368ee Merge pull request #658 from apachelogger/control-tar-xz-support
make deb reader handle new control.tar options introduced in dpkg 1.17.6
2017-11-02 00:43:54 +03:00
Harald Sitter 1885cbd6a2 make deb reader handle new control.tar options introduced in dpkg 1.17.6
newly supported is uncompressed control.tar and xz compressed
control.tar.xz. latter is used by ubuntu for dbgsym ddebs.

Fixes #655
2017-10-31 14:43:00 +01:00
Andrey Smirnov 79d68ec3a7 Merge pull request #659 from apachelogger/fix-align-lint
fix linting by using new maligned linter instead of aligncheck
2017-10-31 16:38:32 +03:00
Harald Sitter f43801cb96 whitelist falke E722 in system/lib.py
'E722 do not use bare except' wants us not to use except without type
restriction as it catches everything and the kitchen sink. Since we use
them to catch exceptions in test cases this is intentional as we implement
general purpose error handling on test failure there.
2017-10-31 12:42:11 +01:00
Harald Sitter 46c2182ade fix linting by using new maligned linter instead of aligncheck
upstream switched the alignment check backend and in doing so fails to run
if the old backend is defined in the config.

also skip alignment linting on a struct we use for byte decoding as we have
no choice in its member order.
2017-10-31 12:24:31 +01:00
Andrey Smirnov 5ef45bddda Merge pull request #650 from smira/278-import-files-from-pool
Allow using files from the pool while importing source packages
2017-09-29 23:46:06 +03:00
Andrey Smirnov 0d94f29c27 Allow using files from the pool while importing source packages
Sometimes source packages reference files already present in the pool.

Allow for those file to be omitted when importing packages either via
`repo add` or `repo include`. If file is missing, aptly would make
an attempt to look up file in the package pool (by checksum) and
use it.

Fixes: #278
2017-09-29 22:39:51 +03:00
Andrey Smirnov 04b7543dea Merge pull request #649 from smira/647-sse-put-copy
Enforce SSE/StorageClass in PUT Object Copy
2017-09-28 21:07:30 +03:00
Andrey Smirnov 9051f13ce6 Merge branch 'master' into 647-sse-put-copy 2017-09-28 19:30:48 +03:00
Andrey Smirnov 1b704db5c0 Merge pull request #648 from smira/upgrade-aws-sdk
Upgrade AWS SDK to the latest version
2017-09-28 19:30:30 +03:00
Andrey Smirnov 2d66a4ca0a Enforce SSE/StorageClass in PUT Object Copy
"RenameFile" is implemented in S3 using `PUT Object Copy`, which
should enforce SSE/StorageClass same way as regular `PUT Object`.

Fixes: #647
2017-09-28 18:30:50 +03:00
Andrey Smirnov 182c21e38c Upgrade AWS SDK to the latest version 2017-09-28 17:57:05 +03:00
Andrey Smirnov 9a767b7631 Merge pull request #646 from smira/update-gin
Upgrade gin-gonic to latest master, fix compatibility issues
2017-09-28 01:42:42 +03:00
Andrey Smirnov 3756db2491 Upgrade gin-gonic to latest master, fix compatibility issues 2017-09-28 00:33:59 +03:00
Andrey Smirnov ff8e4a8659 Merge pull request #645 from smira/man-no-false
Remove `=false` in usage and man page
2017-09-27 16:35:05 +03:00
Andrey Smirnov aec6c2f2e2 Remove =false in usage and man page 2017-09-27 01:01:01 +03:00
Andrey Smirnov d611d0d829 Merge pull request #620 from cavedon/skipCleanup
Add -skip-cleanup option for publish commands.
2017-09-27 00:35:47 +03:00
Andrey Smirnov b4deedda01 Merge branch 'master' into skipCleanup 2017-09-27 00:14:24 +03:00
Andrey Smirnov 0f14143141 Merge pull request #644 from smira/615-all-variants
Fix incomplete dependencies with follow-all-variants
2017-09-26 15:45:26 +03:00
Andrey Smirnov e5198178a5 Fix incomplete dependencies with follow-all-variants
When `-dep-follow-all-variants` option is enabled, dependency resolving
process shouldn't stop even if dependency is already satisfied - there
mgiht be other ways to satisfy dependency.

Also fix issue with parsing multiarch specs like
`python:any`.
2017-09-26 00:09:15 +03:00
Andrey Smirnov 1c44b4f787 Merge pull request #643 from smira/618-prefer-exact-match
Prefer exact match on package name over provides match
2017-09-25 20:42:43 +03:00
Andrey Smirnov 6d2f265980 Prefer exact match on package name over provides match
When searching for packages which might satisfy given dependency,
aptly was first returning packages which `Provides` mentioned
name. By default aptly is picking up only first match (unless
follow all variants options is enabled), so `Provides:` takes
precedence over exact package name match.

Invert this logic by searching first for package name match.
2017-09-25 18:24:45 +03:00
Andrey Smirnov 325d391007 Merge pull request #630 from skyscrapers/expose-context
Expose the context outside of the cmd package.
2017-09-22 19:20:01 +03:00
Ringo De Smet 91a3dc9e94 Expose the context outside of the cmd package. 2017-09-22 16:45:10 +02:00
Andrey Smirnov 31f4af5722 Merge branch 'master' into skipCleanup 2017-09-20 17:53:24 +03:00
Andrey Smirnov e0aaa8bb80 Merge pull request #640 from smira/636-upcase-package-name
Allow uppercase package name in package query expressions
2017-09-18 22:34:32 +03:00
Andrey Smirnov 50035d5bc4 Allow uppercase package name in package query expressions
Fixes: #636

Before this fix, aptly was always treating strings starting with
uppercase letter as field name, which was breaking package queries
like `VMware-Horizon-Client_4.5.0_all`.

Now aptly accepts only fields which don't contain underscore, and
everything else would be parsed as package reference.
2017-09-18 21:36:06 +03:00
Andrey Smirnov 985f1a17b5 Merge pull request #638 from smira/fix-sys-tests
Update PD GPG key id to fix the tests
2017-09-18 19:14:45 +03:00
Andrey Smirnov 72ac1bc33c Update PD GPG key id to fix the tests 2017-09-18 18:09:36 +03:00
Andrey Smirnov f0d6b1c29f Merge pull request #637 from smira/fix-linter-go-1.9
Fix lint warning & add Go 1.9 to the mix
2017-09-16 21:36:51 +03:00
Andrey Smirnov bd5fc8ae62 Varnish repos got moved 2017-09-15 23:43:51 +03:00
Andrey Smirnov 9ca81ff3bc Fix lint warning & add Go 1.9 to the mix 2017-09-15 22:54:39 +03:00
Andrey Smirnov d9607cf88c Merge pull request #624 from smira/no-go-16
Drop support for Go 1.6, only Go 1.7+ is supported
2017-08-17 22:02:30 +03:00
Andrey Smirnov 4f56f34d82 Merge pull request #623 from smira/446-package-query-duplicates
Allow package queries to return duplicate entries on `PackageCollection`
2017-08-17 17:37:57 +03:00
Ludovico Cavedon e2956a84ce Merge branch 'master' into skipCleanup 2017-08-16 14:44:33 -07:00
Andrey Smirnov 00a9eb72d8 Drop support for Go 1.6, only Go 1.7+ is supported 2017-08-17 00:44:04 +03:00
Andrey Smirnov cbc8051c5c Merge pull request #622 from smira/619-s3-prefix
Fix S3 path caching double-prefix
2017-08-17 00:42:08 +03:00
Andrey Smirnov a27b489ba2 Allow package queries to return duplicate entries on PackageCollection
Fixes #446
2017-08-17 00:40:34 +03:00
Andrey Smirnov 790d85881b Fix S3 path caching double-prefix
Original PR: #621
Fixes: #619

I've added unit-test to Martyn's PR.

Without this fix, if `prefix` is set on S3 publish endpoint,
aptly would incorrectly build path cache and re-upload every object
on publish.
2017-08-16 23:57:41 +03:00
Ludovico Cavedon d6a3917141 Add -skip-cleanup option for publish commands.
Allow skipping unreferenced files cleanup on publish switch/update/drop
via the -skip-cleanup command line option.
Also support API SkipCleanup parameter.

Fixes #570.
2017-08-15 19:08:17 -07:00
Andrey Smirnov 35e2253944 Merge pull request #614 from smira/600-fix-double-mirror-update
Fix bug with `PoolPath` field being overwritten on mirror update
2017-08-11 20:48:04 +03:00
Andrey Smirnov a584b2e058 Fix bug with PoolPath field being overwritten on mirror update
While updating mirror, if package file is already in pool path,
field `PoolPath` was left as empty which results in package file
being unavailable later on while publishing.
2017-08-11 20:05:55 +03:00
Andrey Smirnov 587bfd742f Merge pull request #613 from smira/607-trim-slashes
Trim slashes while parsing publish prefix
2017-08-09 13:36:35 +03:00
Andrey Smirnov 84ef963d7d Trim slashes while parsing publish prefix
Fixes: #607
2017-08-09 01:26:47 +03:00
Andrey Smirnov e70ef0a518 Merge pull request #612 from smira/610-stderr
Print error messagge 'unable to open database' to stderr
2017-08-09 01:00:18 +03:00
Andrey Smirnov e05768737f Print error messagge 'unable to open database' to stderr
Fixes #610
2017-08-09 00:01:51 +03:00
Andrey Smirnov a626e4693b Merge pull request #604 from AlekSi/patch-1
Try to reduce build duration
2017-08-08 22:04:49 +03:00
Alexey Palazhchenko 4d9b4298d8 Merge branch 'master' into patch-1 2017-07-31 22:13:27 +03:00
Andrey Smirnov 4cca7272ce Merge pull request #603 from smira/improve-pgp-internal
Improve internal PGP provider
2017-07-31 20:57:43 +03:00
Andrey Smirnov e9b2c18e2f Attempt to fix the tests 2017-07-28 22:37:20 +03:00
Andrey Smirnov cbb576cbcc Fix up system tests 2017-07-28 22:37:20 +03:00
Alexey Palazhchenko bcc83bff31 Try to reduce build duration 2017-07-28 10:10:34 +03:00
Andrey Smirnov 68da8a674a Improve internal PGP provider
1. Print additional details about keys being used for signing
2. Skip expired keys
3. Add `\n` to logged messages
2017-07-28 00:53:50 +03:00
Andrey Smirnov cafa82f018 Merge pull request #601 from AlekSi/patch-1
Use SVG badges
2017-07-28 00:47:08 +03:00
Andrey Smirnov 83a9c394f3 Merge pull request #602 from AlekSi/patch-2
Update Travis CI configuration
2017-07-28 00:46:55 +03:00
Alexey Palazhchenko 2c0a1b836c Update Travis CI configuration
* Use Ubuntu 14.04 with Docker.
* Use latest patch versions of Go.
* Replace hacks for forks with an official solution.
2017-07-28 00:08:27 +03:00
Alexey Palazhchenko 28ae18792d Use SVG badges 2017-07-27 23:57:13 +03:00
Andrey Smirnov 2811ad02d5 Merge pull request #581 from smira/pgp-golang
Implement new PGP provider via Go internal library
2017-07-26 22:14:46 +03:00
Andrey Smirnov ab20c2d329 Add publishing & repo include tests 2017-07-26 00:31:27 +03:00
Andrey Smirnov d137bcf8d4 Fix up/add mirror update tests 2017-07-26 00:00:06 +03:00
Andrey Smirnov 3674e1adee System tests for mirror create/update with internal PGP implementation 2017-07-21 01:09:20 +03:00
Andrey Smirnov 05a5e69483 Fix misspelling 2017-07-21 01:01:58 +03:00
Andrey Smirnov 5e9515a912 Add --batch in batch mode (fixes #519) 2017-07-21 01:01:58 +03:00
Andrey Smirnov 84a6d573f8 Implement GPG signer 2017-07-21 01:01:58 +03:00
Andrey Smirnov 6228a399cf Mute goconst warnings 2017-07-21 01:01:58 +03:00
Andrey Smirnov 0e9f966dd1 Fix up other code to support new GPG provider structure 2017-07-21 01:01:58 +03:00
Andrey Smirnov 07fde3177b GoVerifier implementation 2017-07-21 01:01:58 +03:00
Andrey Smirnov f9377b2aa6 Update bash completion for new flag 2017-07-21 01:01:58 +03:00
Andrey Smirnov 499ab35012 Implement flag/config falue for GPG provider 2017-07-21 01:01:58 +03:00
Andrey Smirnov 3c95f92b95 Now using openpgp package from golang.org/x/crypto 2017-07-21 01:01:58 +03:00
Andrey Smirnov d7a7aa93a4 Merge pull request #596 from smira/s3-opts-man
Document additional S3 options [ci skip]
2017-07-21 01:00:45 +03:00
Andrey Smirnov 58ab4e8902 Document additional S3 options [ci skip] 2017-07-21 00:57:06 +03:00
Andrey Smirnov fcd453118b Merge pull request #590 from smira/1.1-fixups
Small fixups for upcoming 1.1.0 release
2017-07-07 01:17:37 +03:00
Andrey Smirnov 7d179dd405 Small fixups for upcoming 1.1.0 release
Fix system tests, add -db-open-attempts to bash completion, small nits
for man page.
2017-07-07 00:14:03 +03:00
Andrey Smirnov 20b874f81f Merge pull request #577 from sliverc/backport_support
Added support for NotAutomatic, ButAutomaticUpgrades and Origin fields
2017-07-06 00:18:18 +03:00
Oliver Sauder e3f1880ad4 Added support for NotAutomatic, ButAutomaticUpgrades and Origin fields 2017-07-05 15:08:02 +02:00
Andrey Smirnov 39293d7faf Merge pull request #589 from smira/api-db-no-lock-fix
Rework the way database is open/re-open in aptly
2017-07-05 12:57:26 +03:00
Andrey Smirnov c13eb99925 Style fixups 2017-07-05 00:36:48 +03:00
Andrey Smirnov 211ac0501f Rework the way database is open/re-open in aptly
Allow database to be initialized without opening, unify all the
open paths to retry on failure.

In API router make sure open requests are matched with acks in explicit
way.

This also enables re-open attempts in all the aptly commands, so it
should make running aptly CLI much easier now hopefully.

Fix up system tests for oldoldstable ;)
2017-07-05 00:17:48 +03:00
Andrey Smirnov af2f7baf63 Merge pull request #556 from smira/contributing-documentation
Docs on contributing to aptly [ci skip]
2017-06-27 01:15:25 +03:00
Andrey Smirnov 3c25db3ffb Docs on contributing to aptly [ci skip] 2017-06-27 01:13:18 +03:00
Andrey Smirnov 12a6b0ceb8 Merge pull request #575 from smira/pgp-refactoring
Refactor GPG signer/verifier
2017-05-24 19:24:38 +03:00
Andrey Smirnov 0d041898ca Merge pull request #574 from smira/376-checksum-search-fix
Fix checksum matching from Release file
2017-05-23 21:53:06 +03:00
Andrey Smirnov 982c093fbf Fix system tests 2017-05-23 16:24:49 +03:00
Andrey Smirnov f54e798eac Add system test for fixed checksum matching 2017-05-23 03:00:16 +03:00
Andrey Smirnov cafb89f30f Re-work the way checksum matching works against Release file
Break up URL into base part and relative path. Match checksum against relative path
and never against full URL.

This might be fixing security issue if aptly was incorrectly matching against
wrong part of Release file.
2017-05-23 03:00:15 +03:00
Andrey Smirnov f0360cf2d3 Use longest suffix match to pick up checksum 2017-05-23 03:00:15 +03:00
Andrey Smirnov 1be8d39105 Refactor GPG signer/verifier
Goal is to make it easier to plug in another implementation.
2017-05-23 02:54:56 +03:00
Andrey Smirnov c026106352 Merge pull request #571 from sliverc/travis_for_forks
Making travis CI run again on forks
2017-05-19 22:41:15 +03:00
Oliver Sauder c507d0620b Making travis CI run again on forks 2017-05-19 16:37:04 +02:00
Andrey Smirnov f84672239a Merge pull request #569 from smira/linters-4
Enable goconst & interfacer linters
2017-05-17 15:33:02 +03:00
Andrey Smirnov c9bd7b4b5d Merge pull request #567 from smira/431-500-api
Fix possible cause for spurious 500s
2017-05-17 15:32:52 +03:00
Andrey Smirnov 470165a419 Enable goconst & interfacer linters 2017-05-17 00:53:10 +03:00
Andrey Smirnov 9de9fbe6bd Merge branch 'master' into 431-500-api 2017-05-17 00:52:32 +03:00
Andrey Smirnov e396a2e6c3 Merge pull request #568 from smira/fix-flat-tests
Fix tests for flat repos
2017-05-17 00:52:19 +03:00
Andrey Smirnov 829ea2e65c Fix tests for flat repos 2017-05-17 00:09:18 +03:00
Andrey Smirnov 39d2d273dc Fix possible cause for spurious 500s
When DB fails to be open, aptly was skipping "close" phase, so that next
request considered database to be still open (while it's closed) leading
to panic.

Fixes: #431
2017-05-16 00:42:59 +03:00
Andrey Smirnov 5a3e660c0d Merge pull request #566 from smira/135-sort-search
Sort package lists when searching or showing objects
2017-05-10 00:19:53 +03:00
Andrey Smirnov 589dc93380 Sort package lists when searching or showing objects
Fixes #135
Fixes #214
2017-05-05 18:42:46 +03:00
Andrey Smirnov 33357c1fe4 Merge pull request #565 from smira/linters-3
Enable `vetshadow` linter
2017-05-05 17:43:09 +03:00
Andrey Smirnov 5ce6bf8718 Enable vetshadow linter 2017-05-04 23:00:13 +03:00
Andrey Smirnov d7bcf372c4 Merge pull request #564 from jyundt/add_mirror_edit_bash_autocomplete
Add mirror edit option to bash auto-complete
2017-05-04 21:26:11 +03:00
Jacob Yundt 3aa044d722 Add mirror edit option to bash auto-complete
Fixes smira/aptly#563
2017-05-04 13:24:34 -04:00
Andrey Smirnov a9a5a73dfd Merge pull request #560 from smira/linters-2
More Go linters enabled, issues fixed
2017-05-04 02:13:06 +03:00
Andrey Smirnov 66b44e68a9 Attempt to workaround Travis "10 minutes without output". 2017-05-03 20:28:57 +03:00
Andrey Smirnov 51213899b7 More Go linters enabled, issues fixed
Ref: #528

Enables "staticcheck", "varcheck", "structcheck", "aligncheck"
2017-05-03 18:23:14 +03:00
Andrey Smirnov 7a7c9cd26c Merge pull request #559 from smira/linters-extend-deadline
Extend linters deadline
2017-05-02 22:12:45 +03:00
Andrey Smirnov 1b9ab46c5f Extend linters deadline
Ref: #528
2017-05-02 20:35:16 +03:00
Andrey Smirnov 2cbed28446 Merge pull request #558 from smira/linters-python
Add system's requirements.txt, enforce flake8 linter
2017-04-28 16:14:32 +03:00
Andrey Smirnov 39aa0fdbfe Merge pull request #557 from smira/linters-1
Enable `gosimple` and `ineffasign` linters
2017-04-28 00:29:20 +03:00
Andrey Smirnov c983810e2d Manually create system/env, workaround Travis issues 2017-04-28 00:16:21 +03:00
Andrey Smirnov c798db8056 Add system's requirements.txt, enforce flake8 linter
Fix style issues in functional tests.
2017-04-28 00:05:11 +03:00
Andrey Smirnov 1e4a80252e Extend linter deadline to 1 minute 2017-04-27 22:32:35 +03:00
Andrey Smirnov bae3f949b4 Enable gosimple and ineffasign linters 2017-04-27 18:34:30 +03:00
Andrey Smirnov 7a7b981d4f Merge pull request #539 from smira/public-pool-paths
New package pool with configurable hashing base
2017-04-27 16:24:14 +03:00
Andrey Smirnov 2ffefeb1e0 Add man page for skipLegacyPool 2017-04-27 00:51:46 +03:00
Andrey Smirnov 1941418c10 Add system test for disabled legacy pool path support 2017-04-27 00:51:46 +03:00
Andrey Smirnov 186bb2dff0 Add flag to disable/enable support for legacy pool paths
Legacy pool paths are enabled by default, but for new aptly installations
(when aptly config is first generated), it would be disabled explicitly.
2017-04-26 23:37:31 +03:00
Andrey Smirnov 2308632683 Add system tests for legacy pool files 2017-04-26 23:17:04 +03:00
Andrey Smirnov ee21b69402 Add test for mirror without MD5 checksums 2017-04-26 23:17:04 +03:00
Andrey Smirnov 01512df853 Rework mirror update to support closing/reoping DB for the download duration
This requires splitting up import file phase as separate step in then end,
it should be pretty fast, as it only does file move (hardlink) and
DB update for new checksums.
2017-04-26 23:17:04 +03:00
Andrey Smirnov 7dcc0d597d Fix S3/Swift tests 2017-04-26 23:17:04 +03:00
Andrey Smirnov 154ef7fe65 Fix system tests for aptly repo include 2017-04-26 23:17:04 +03:00
Andrey Smirnov 4601f07349 Fix system tests for aptly repo add 2017-04-26 23:17:04 +03:00
Andrey Smirnov b7b9f12c88 Update system tests for SHA512 checksums being generated 2017-04-26 23:17:04 +03:00
Andrey Smirnov b48e8425ec Fix bug with file not being updated properly 2017-04-26 23:17:04 +03:00
Andrey Smirnov 3ce8227122 Add baseName to LinkFromPool as explicit argument 2017-04-26 23:17:04 +03:00
Andrey Smirnov 0bc3f71d27 Use SHA256 as main checksum in the pool 2017-04-26 23:17:04 +03:00
Andrey Smirnov c1d4c0fb88 Temporarily disable db close/open cycle (to be addressed later) 2017-04-26 23:17:04 +03:00
Andrey Smirnov 8078f3b588 We should remove file only when checksums are calculated 2017-04-26 23:17:04 +03:00
Andrey Smirnov 5dd11a2ec2 Pull original packages when skipping existing packages 2017-04-26 23:17:04 +03:00
Andrey Smirnov cc34a021ce Fix misspellings 2017-04-26 23:17:04 +03:00
Andrey Smirnov 10c096fbb6 Update all other pieces for the CheckumStorage and Verify 2017-04-26 23:17:04 +03:00
Andrey Smirnov a85d8b6f90 Mock for ChecksumStorage 2017-04-26 23:17:04 +03:00
Andrey Smirnov 5566111a7b New ChecksumStorage and new PackagePool interfaces 2017-04-26 23:17:04 +03:00
Andrey Smirnov 6994e35119 ChecksumCollection implementation 2017-04-26 23:17:04 +03:00
Andrey Smirnov 4eedb62418 Fix misspelling 2017-04-26 23:17:04 +03:00
Andrey Smirnov 1f3cb2db5d When downloading/importing packages, enforce all checksums 2017-04-26 23:17:04 +03:00
Andrey Smirnov c40025a335 Add progress bar on package saving progress 2017-04-26 23:17:03 +03:00
Andrey Smirnov 4171a73995 Fix up system test 2017-04-26 23:17:03 +03:00
Andrey Smirnov 29e5f4ca10 Vendor import github.com/pkg/errors 2017-04-26 23:17:03 +03:00
Andrey Smirnov 05f6c75743 Add PR #506 original author [ci skip] 2017-04-26 23:17:03 +03:00
Andrey Smirnov 45d187bc14 Fix up system test 2017-04-26 23:17:03 +03:00
Andrey Smirnov bc7903f86e Rework mirror update (download packages) implementation
`PackageDownloadTask` is just a reference to file now. Whole process
was rewritten to follow pattern: download to temp location inside the pool,
verify/update checksums, import into pool as final step.

This removes a lot of edge cases when aptly internal state might be broken
if updating from rogue mirror.

Also this changes whole memory model: package list/files are kept in memory
now during the duration of `mirror update` command and saved to disk
only in the end.
2017-04-26 23:17:03 +03:00
Andrey Smirnov 72d233b587 Final round of updates, everything except mirror download should be ready 2017-04-26 23:17:03 +03:00
Andrey Smirnov 2535367c3c Update Swift published storage to work with new package pool 2017-04-26 23:17:03 +03:00
Andrey Smirnov f4ff8d957f Fix S3 published storage to use new PackagePool interface
Change PackagePool to return Seeker interface from Open call.
2017-04-26 23:17:03 +03:00
Andrey Smirnov 7bad358408 Local package pool and local publishing rewritten with new constraints
Local package pool now implements more generic package pool API.
The base idea is to never expose full paths to files, so that other
kinds of package pools (e.g. package pool in S3) could be used to implement
the same interface.

Files get into the pool only using `Import` method. `Import` method is
now more smart, it supports moving files into the pool, it can detect if
files reside on the same filesystem and use hardlinking instead of copying.
This will make direct mirror downloads still as fast as they were with previous
version which was performing download directly to package pool.

New package pool doesn't have two things implemented yet:

1. New file placement according to SHA256 or other configured hash

2. Calculate at least SHA256/MD5 for each imported files.
MD5 would be required for S3/Swift publishing
2017-04-26 23:17:03 +03:00
Andrey Smirnov 94b49818a1 Refactor HTTP downloader package
* Drop multi-threaded downloader. It doesn't really belong here -
some places require it, some do not, but it's definitely not the
right place to handle it, as it's being used only when updating
mirrors
* Pass expectedChecksums as pointer, so it's easy to drive `nil` value,
and also downloader can fill back checksums (not implemented right now).
* Break down downloader and tests into more files
* Use pkg/errors instead of fmt
2017-04-26 23:17:03 +03:00
Andrey Smirnov a245b722a8 Merge pull request #555 from smira/288-empty-repo-snapshot
Allow snapshot to be created from empty local repo
2017-04-26 01:11:40 +03:00
Andrey Smirnov 8dc6a14766 Allow snapshot to be created from empty local repo
Fixes #288
2017-04-26 00:33:09 +03:00
Andrey Smirnov d66185ca03 Merge pull request #554 from smira/flat-system-test
Fix system tests for flat repos
2017-04-24 23:45:58 +03:00
Andrey Smirnov c3acabe303 Fix system tests for flat repos
Old mirror used for testing is gone, switch to CRAN.
2017-04-24 23:04:48 +03:00
Andrey Smirnov 4697d8eaf8 Merge pull request #550 from smira/549-fix-deps
Rewrite `dep` files into new format
2017-04-14 22:48:53 +03:00
Andrey Smirnov 8bf71a5561 Rewrite dep files into new format
Conversion to TOML plus some manual fixups.
2017-04-14 17:24:49 +03:00
Andrey Smirnov 898cbd2c83 Merge pull request #548 from smira/546-fix-depends
Update `Depends:` for homegrown packages
2017-04-14 16:22:03 +03:00
Andrey Smirnov 62762f1616 Merge branch 'master' into 546-fix-depends 2017-04-14 00:57:36 +03:00
Andrey Smirnov 4d38e0bc87 Merge pull request #521 from seeraven/feature_localfiles_publishstorage
Implemented local filesystem endpoint with support for hardlinks, symlinks and copy
2017-04-14 00:52:49 +03:00
Clemens Rabe 25f9c29f00 Implemented filesystem endpoint with support for hardlinks, symlinks and copy. 2017-04-13 20:25:40 +02:00
Andrey Smirnov 096b30b5e8 Update Depends: for homegrown packages
This should match upstream Debian package, at the same time
`goxc` seems to be broken and it ignores `Suggests`.
2017-04-13 01:33:30 +03:00
Andrey Smirnov ac475c0a10 Merge pull request #544 from smira/543-tmp-dirs-leftover
Fix temporary contents DB being left behind after publishing
2017-04-11 00:28:13 +03:00
Andrey Smirnov 60800b5f25 Fix temporary contents DB being left behind after publishing
NB: Go `defer` order execution is reverse to the order `defer` statements
are executed.

So before the change, `Drop()` was called before `Close()`, which was no-op.

Change that to explicit order in single func, print errors if they happen.
2017-04-10 23:43:33 +03:00
Andrey Smirnov 36a4d78162 Merge pull request #535 from smira/public-pool-checksums
Refactoring: use checksums instead of MD5 for pool/published
2017-04-03 17:31:04 +03:00
Andrey Smirnov 50cf2b49bd Refactoring: use checksums instead of MD5 for pool/published
This is related to #506

As a first step, don't pass MD5 explicitly, pass checksum info object,
so that as a next step we can choose which hash to use.

There should be no functional changes so far.

Next step: stop returning explicit paths from public package pool.
2017-04-01 00:12:31 +03:00
Andrey Smirnov 675d35c7a1 Merge pull request #508 from smira/dep-verbose-resolve
Add new option for detailed logging on dependency resolving
2017-03-31 19:58:23 +03:00
Andrey Smirnov bc469eecfb Merge branch 'master' into dep-verbose-resolve 2017-03-31 19:17:57 +03:00
Andrey Smirnov bc01d9ed5b Merge pull request #534 from smira/533-ignore-contents-failures
When contents generation fails, don't bail out
2017-03-31 16:38:25 +03:00
Andrey Smirnov 7a5be6736d When contents generation fails, don't bail out
This replaces `panic` which aborts aptly execution with warning
message on console. So aptly continues publishing actions, but
`Contents` indexes might be incomplete.

Error will be printed every time contents generation is triggered.
2017-03-31 00:57:18 +03:00
Andrey Smirnov eb48460b7b Update bash completion 2017-03-28 22:58:53 +03:00
Andrey Smirnov 85b4a8b1ae Add new option for detailed logging on dependency resolving
This adds command-line arg and config option, with option enabled
aptly is more verbose on internal depeendency resolving cycles:

```
Missing dependencies: file-rc (>= 0.8.16) [amd64], python:any (>= 2.7.1-0ubuntu2) [amd64], python3:any (>= 3.3.2-2~) [amd64], file-rc [amd64], perl (<< 5.17) [amd64], iptables-router (>= 1.2.3) [amd64], systemd [amd64], sgml-base (>= 1.26+nmu2) [amd64], sed (>= 4.1.2-8) [amd64]
Unsatisfied dependency: file-rc (>= 0.8.16) [amd64]
Unsatisfied dependency: python:any (>= 2.7.1-0ubuntu2) [amd64]
Unsatisfied dependency: python3:any (>= 3.3.2-2~) [amd64]
Unsatisfied dependency: file-rc [amd64]
Unsatisfied dependency: perl (<< 5.17) [amd64]
Unsatisfied dependency: iptables-router (>= 1.2.3) [amd64]
Unsatisfied dependency: systemd [amd64]
Injecting package: sgml-base_1.26+nmu4ubuntu1_all
Injecting package: sed_4.2.2-4ubuntu1_amd64
```
2017-03-28 22:58:07 +03:00
Andrey Smirnov e6bad637fd Merge pull request #532 from smira/530-bash-completion
Move bash completion to main aptly repo
2017-03-28 22:22:10 +03:00
Andrey Smirnov 47b5cc27c8 Move bash completion to main aptly repo
Fixes #530

Original repository: https://github.com/aptly-dev/aptly-bash-completion
2017-03-28 21:41:57 +03:00
Andrey Smirnov ca16841223 Merge pull request #520 from seeraven/feature_skip_existing_packages_latest
Add option -skip-existing-packages to mirror update to speed up download queue setup
2017-03-28 21:39:36 +03:00
Andrey Smirnov 800c5c1e06 Merge branch 'master' into feature_skip_existing_packages_latest 2017-03-28 21:26:28 +03:00
Andrey Smirnov 7fd8bd0171 Merge pull request #531 from smira/release-1.0.0-preparation
Prepare for new release, update build instructions [ci skip]
2017-03-28 00:17:08 +03:00
Andrey Smirnov 4707efe4d6 Prepare for new release, update build instructions [ci skip] 2017-03-28 00:15:41 +03:00
Andrey Smirnov 8ae61f9448 Merge pull request #523 from smira/versioning
Automatic versioning for aptly
2017-03-27 16:02:25 +03:00
Andrey Smirnov a138d0111d Update README to use go install which will build with version 2017-03-26 19:24:32 +03:00
Andrey Smirnov af1adb44ce Remove -x flag for go install 2017-03-26 19:23:53 +03:00
Clemens Rabe 4ddf85bbc1 Rebuilt man page with patched ronn. 2017-03-25 08:52:08 +01:00
Clemens Rabe 9978595c59 Merge branch 'master' into feature_skip_existing_packages_latest 2017-03-25 08:50:24 +01:00
Andrey Smirnov 2943422d5d Automatic versioning for aptly
New version format:

* for releases, `x.y.z` (follows tag without leading `v`)
* for nightly builds, `x.y.z+N+hash` (previous version, not the upcoming one)

This means that each nightly build `aptly` would report
correct version now.

Version is now complied into the aptly binary, system tests
automatically check for current version, no need to update them
anymore.
2017-03-25 00:18:45 +03:00
Andrey Smirnov 91219e3a0a Merge pull request #522 from smira/man-gen-rework
Rework man generator with new `go install` format
2017-03-24 21:50:28 +03:00
Andrey Smirnov 7f8db9087a Rework man generator with new go install format
With previous version, `go install` automatically picks up
package `man` and installs `gen.go` as `main` to the $GOPATH/bin which
is not what is expected. Move man page generator to separate
private folder.
2017-03-24 21:07:38 +03:00
Clemens Rabe aa16899c60 Adaption of tests. 2017-03-24 06:25:46 +01:00
Clemens Rabe 16a0d0d428 Added option --skip-existing-packages to speed up mirror update. 2017-03-23 22:01:11 +01:00
Clemens Rabe 66f51d2b17 Added option --skip-existing-packages to speed up mirror update. 2017-03-23 21:55:22 +01:00
Andrey Smirnov 92c844b8ac Merge pull request #517 from smira/goreportcard
Fix goreportcard badge [ci skip]
2017-03-23 18:33:00 +03:00
Andrey Smirnov 2b56a3937b Fix goreportcard badge [ci skip] 2017-03-23 18:32:00 +03:00
Andrey Smirnov 9cea9b6470 Merge pull request #512 from smira/500-xdg-open
Customize viewer per platform
2017-03-23 18:28:24 +03:00
Andrey Smirnov e3e68b9f22 Customize viewer per platform 2017-03-23 17:12:34 +03:00
Andrey Smirnov d56839664d Merge pull request #513 from smira/gometalinter
Switch to gometalinter
2017-03-23 16:32:33 +03:00
Andrey Smirnov 516dd7b044 Switch to gometalinter
Only small amount of required checks is enabled,
plan is to enable more linters as issues are fixed in the code.
2017-03-23 01:51:08 +03:00
Andrey Smirnov 53e59d3765 Merge pull request #509 from smira/golint-govet
Add govet/golint into Travis CI build
2017-03-22 22:14:30 +03:00
Andrey Smirnov 11d828b3b1 Add govet/golint into Travis CI build
Fix current issues
2017-03-22 21:49:16 +03:00
Andrey Smirnov 07472bec50 Merge pull request #511 from smira/dep-experiment
Convert to regular Go vendor + `dep` tool
2017-03-22 20:34:36 +03:00
Andrey Smirnov f737787c01 Fix up system tests 2017-03-22 19:56:23 +03:00
Andrey Smirnov c6c1012330 Conver to regular Go vendor + dep tool 2017-03-22 19:24:06 +03:00
Andrey Smirnov 070347295e Merge pull request #505 from smira/rmedaer-master
Added '.xz' extension in HTTP download.
2017-03-17 00:47:33 +03:00
Andrey Smirnov acd8d4a6ea Go 1.6 compatibility 2017-03-17 00:17:07 +03:00
Andrey Smirnov c9768416ed Fix up tests 2017-03-16 23:06:41 +03:00
Raphael Medaer bfb9ffad1d Added expected error on 'Packages.xz' for TestDownload[WithSources]Flat. 2017-03-16 22:41:25 +03:00
Raphael Medaer 9cfe1307e3 Added download tests for xz compression. 2017-03-16 22:41:25 +03:00
Raphael Medaer db8595711b Added '.xz' reader in HTTP download. This fixed issue when you try to mirror a distribution with only Packages.xz (example 'debian/experimental'). 2017-03-16 22:41:25 +03:00
Andrey Smirnov ce0001f94c Merge pull request #504 from smira/go-1.8
Add Travis CI build on Go 1.8
2017-03-16 22:16:01 +03:00
Andrey Smirnov b102562478 Fix up system test for Go 1.8 2017-03-16 20:58:21 +03:00
Andrey Smirnov 69cbe10690 Add Travis CI build on Go 1.8 2017-03-16 18:46:03 +03:00
Andrey Smirnov e3e4ea91bd Merge pull request #502 from smira/aws-sdk-bump
Bump Go AWS SDK to the latest version
2017-03-16 18:45:26 +03:00
Andrey Smirnov 02c582e227 Merge pull request #503 from smira/pr-template-update
Add bash completion to PR template [ci skip]
2017-03-16 01:31:52 +03:00
Andrey Smirnov 6e96cd29dc Add bash completion to PR template [ci skip] 2017-03-16 01:30:00 +03:00
Andrey Smirnov 17044f43dc Bump Go AWS SDK to the latest version
This seems to fix `NotImplemented` S3 error
2017-03-16 01:28:44 +03:00
Andrey Smirnov 5d3b170ffc Merge pull request #497 from smira/repo-create-from-snap
Implement new command `aptly repo create ... from snapshot ...`
2017-03-16 01:12:09 +03:00
Andrey Smirnov a0f7b2242d Merge pull request #499 from sobczyk/dbgsym
include dbgsym packages
2017-03-14 22:51:24 +03:00
Szymon Sobik b8e7ad9022 update changes unit test to account for dbgsym matching 2017-03-08 10:32:11 +01:00
Szymon Sobik 1b80d55ea4 since -dbgsym is for each binary package use that for PackageQuery 2017-03-08 10:31:44 +01:00
Szymon Sobik a0832adfa5 include dbgsym packages
fixes #331
2017-03-07 17:06:59 +01:00
Andrey Smirnov f17d398e8f Implement new command aptly repo create ... from snapshot ... 2017-03-04 00:12:18 +03:00
Andrey Smirnov bc3b2ed5a8 Merge pull request #495 from apachelogger/systemd-activation
support systemd activation for `api serve`
2017-03-03 22:55:47 +03:00
Harald Sitter 07cf8925f9 support systemd activation for api serve
systemd has a feature called socket activation where initially systemd
manages and listens on ports/uds and only invokes a service when traffic
appears. to then hand over the involved sockets, systemd will pass the
relevant FDs into the invoked process and defines them in the environment.

use coreos/go-systemd to grab the active listeners passed by systemd and
use them to serve the api routes. only one listener may be specified right
now as we also only support one -listen argument for the binary.

this allows admins to craft a systemd socket and service file for aptly
where systemd manages the socket, its permission and its live time, and
lazy start aptly when needed.
2017-03-01 11:12:10 +01:00
Andrey Smirnov 564ebf3130 Merge pull request #493 from apachelogger/api-over-socket
support serving the API over unix domain socket
2017-02-28 23:41:09 +03:00
Harald Sitter dbee214259 support serving the API over unix domain socket
`unix://$PATH` as listen argument will bind aptly to a unix domain socket
rather than TCP.

This allows binding the API to a UDS rather than a port.
Since aptly has no concept of authentication or any amount of high level
API hardening one needs to bottle it up in some other manner. Binding
to a localhost port is often a step in the right direction, ultimately is
still a scary insecure setup as any user on that host getting compromised
would mean that the entire archive is compromised as well.
UDS on the other hand are basically files and have their access managed
by regular file permission. As such, binding to a socket is in fact
the least insecure way to listen as you'd have to explicitly open up the
socket permissions to an access qualified group. In the most conservative
scenario that means no one but the aptly user can talk to the API, in a
more practical setup apache might get access as well and proxy the UDS
with authentication or limited to GET operations.

Using UDS allows reducing the attack surface of the API server while
preserving all the flexibility.
2017-02-28 09:58:39 +01:00
Andrey Smirnov 6267c5cb25 Merge pull request #490 from smira/contents-low-footprint
Use temporary LevelDB to store contents index
2017-02-27 17:26:11 +03:00
Andrey Smirnov 4c06e26d85 Throttle compaction on temporary DB 2017-02-23 01:01:17 +03:00
Andrey Smirnov f2dc4eeec9 Generating contents indexes via temporary LevelDB 2017-02-21 19:09:51 +03:00
Andrey Smirnov f86e6ebf1f Merge pull request #491 from charz/master
Fix URL path for Swift.
2017-02-17 00:37:47 +03:00
Charles Hsu 0d208c93bc Merge branch 'master' of https://github.com/smira/aptly 2017-02-16 23:14:02 +08:00
Charles Hsu 485f311498 Fix URL path for Swift. 2017-02-16 23:09:18 +08:00
Andrey Smirnov 46b0d637e2 Merge pull request #484 from jola5/master
Abort serve command if rootDir is inaccessible
2017-02-15 23:54:42 +03:00
jola5 5a71847b7f Simplify test implementation 2017-02-15 20:18:47 +01:00
jola5 38a9917815 Handle dependencies in gomfile 2017-02-15 20:18:47 +01:00
jola5 4456f8da57 Refactor 2017-02-15 20:18:47 +01:00
jola5 970b1a424a Fix bugged implementation 2017-02-15 20:18:47 +01:00
jola5 edffa24658 Test startup checks for serve command 2017-02-15 20:18:47 +01:00
jola5 3040e7360a Fix golang.org/x/sys/unix dependency issue 2017-02-15 20:18:47 +01:00
jola5 b948180b4e Abort serve command if rootDir is inaccesible 2017-02-15 20:18:47 +01:00
Andrey Smirnov f58d2627c1 Add temporary DB and prefix methods to Storage 2017-02-14 02:26:32 +03:00
Andrey Smirnov ab0d77f6f9 Merge pull request #488 from smira/empty-filters
Allow filter to be empty for `aptly * search` commands
2017-02-14 01:43:46 +03:00
Andrey Smirnov 33d6cd8c0a Allow filter to be empty for aptly * search commands
Empty filter implies "select all packages".
2017-02-10 23:07:06 +03:00
Andrey Smirnov 4eef4f1803 Merge pull request #481 from smira/data-tar-gz-as-tar
Add workaround for reading data.tar.gz as data.tar
2017-01-24 20:19:50 +03:00
Andrey Smirnov c75d4c749c Add workaround for reading data.tar.gz as data.tar
It seems that in the wild there are .deb package which have
`data.tar.gz` which is actually `.tar` archive.

Add magic detection based on signature.
2017-01-24 19:30:53 +03:00
Andrey Smirnov c8a1b9a1f0 Merge pull request #482 from smira/fix-travis
Fixing Travis build
2017-01-24 19:27:58 +03:00
Andrey Smirnov d8d8973ad5 Fixing Travis build 2017-01-24 18:56:01 +03:00
Andrey Smirnov d1ded5c224 Merge pull request #480 from smira/man-generator
Add `make` automation to re-generate man page [ci skip]
2017-01-20 23:55:22 +03:00
Andrey Smirnov 155a801bc1 Add make automation to re-generate man page [ci skip]
This also updates man page with latest changes
2017-01-20 23:53:00 +03:00
Andrey Smirnov 6212b39264 Merge pull request #475 from jola5/master
Support a vertical graph layout in addition to the existing horizontal
2017-01-20 23:41:56 +03:00
jolo 92116072c2 Fix and enable broken graph layout tests 2017-01-20 02:19:45 +01:00
jolo b0ab39e07f Manually undo unintended changes 2017-01-20 02:19:44 +01:00
jola5 4bf27d1dae Merge branch 'master' into master 2017-01-19 23:07:49 +01:00
Andrey Smirnov 207ebffbb8 Merge pull request #472 from sliverc/print_sources
Print sources details of snapshots and published repositories
2017-01-19 01:05:53 +03:00
Andrey Smirnov b0dd83335f Merge branch 'master' into print_sources 2017-01-19 00:50:13 +03:00
Andrey Smirnov 8df6457931 Merge pull request #478 from smira/476-sorted-paths
Sort paths when generating checksums for `Release`/`InRelease`
2017-01-19 00:28:42 +03:00
Andrey Smirnov 7d2a396b27 Merge pull request #474 from apachelogger/support-graph.dot
Allow requesting the unrendered dot graph from the graph endpoint
2017-01-18 23:53:42 +03:00
Andrey Smirnov d5df049630 Sort paths when generating checksums for Release/InRelease 2017-01-18 23:50:22 +03:00
jolo 7c62a706c4 Disable tests failing due to inappropriate test data 2017-01-17 01:04:07 +01:00
jolo 96948d6f18 Basic test of graph layout 2017-01-17 00:46:51 +01:00
jolo 43e6498713 Add me to authors 2017-01-16 22:39:47 +01:00
jolo 91561b40f6 Change 'vertical' argument to a more generic 'layout', fix api 2017-01-16 22:13:13 +01:00
jolo 0e8ea6363a Support vertical graph layouts 2017-01-14 02:18:56 +01:00
Harald Sitter 345fa02fdc Allow requesting the unrendered dot graph from the graph endpoint
When api/graph.{dot,gv} is requested the raw string for dot gets returned.
This allows client-side rendering rather than server-side. It also makes
the optional dependency on graphivz for dot unnecessary to use the graph
endpoint.
2017-01-13 12:57:42 +01:00
Oliver Sauder 064adbae57 generate aptly.1 man page with patched ronn 2017-01-12 13:23:21 +01:00
Oliver Sauder ab458f4dfc Updated aptly man page and authors 2017-01-10 11:14:09 +01:00
Oliver Sauder 0fdee9cbf6 Added publish show command 2017-01-10 10:59:07 +01:00
Oliver Sauder 50e3e93166 print snapshot sources in snapshot show command 2017-01-09 17:29:01 +01:00
Andrey Smirnov 570835227b Merge pull request #470 from smira/templates
Add PR and issue templates
2016-12-30 17:07:54 +03:00
Andrey Smirnov 781c22e256 Add PR and issue templates 2016-12-30 00:11:45 +03:00
Andrey Smirnov babccfa21f Merge pull request #469 from smira/code-of-conduct
Add Contributor Covenant Code of Conduct [ci skip]
2016-12-29 00:03:35 +03:00
Andrey Smirnov 891113717e Add Contributor Covenant Code of Conduct [ci skip] 2016-12-29 00:01:17 +03:00
Andrey Smirnov bfb9045fa9 Merge pull request #465 from SHyx0rmZ/allow-empty-repo-edits-in-api
Allow comment and defaults to be empty when editing a repo through the API
2016-12-08 18:26:45 +03:00
Patrick Pokatilo 1c6b174b8a Make comment and defaults nullable in repo edit 2016-12-08 15:45:19 +01:00
Patrick Pokatilo fb27fb01ea Allow comment and defaults to be empty when editing a repo through the API 2016-12-08 02:14:31 +01:00
Andrey Smirnov b6327ecc43 Merge pull request #463 from sliverc/download_tries
Added --max-tries option to mirror update command
2016-11-29 17:12:36 +03:00
Oliver Sauder af71b9541c Fixing typo 2016-11-29 09:00:16 +01:00
Oliver Sauder f31b5ec3f8 Adjusted test with new maxTries param for download 2016-11-28 17:02:24 +01:00
Oliver Sauder 6becd5a3aa Added max-tries flag for mirror update 2016-11-28 17:02:24 +01:00
Andrey Smirnov 653255c728 Merge pull request #462 from smira/goxc-cleanup
Fix up build for recent versions of Go
2016-11-28 18:19:42 +03:00
Andrey Smirnov 5f0ce38161 Fix compression test for Go 1.7+ 2016-11-28 17:14:36 +03:00
Andrey Smirnov d100033b46 Fix up build for recent versions of Go
Forbid broken goxc tasks.
2016-11-28 16:37:47 +03:00
Andrey Smirnov d008cabf07 Add link to aptly_api_cli by @TimSusa 2016-11-16 02:27:01 +03:00
Andrey Smirnov f3214144a4 Merge pull request #458 from sliverc/travis-fix2
Make Travis CI be able to run on forks
2016-11-15 22:21:52 +03:00
Oliver Sauder d41841b84a Make Travis CI be able to run on forks 2016-11-15 16:19:55 +01:00
Andrey Smirnov 2a95e0eb1b Merge pull request #441 from ZettaIO/v3-auth
Identity v3 support for Swift
2016-11-11 00:19:06 +03:00
Andrey Smirnov 81f8ab2691 Merge pull request #433 from Pryz/master
Add TubeMogul Puppet module to README.rst
2016-11-09 23:55:28 +03:00
Andrey Smirnov 4e61db8d0f Fix man page (help) for aptly package show. 2016-11-09 23:07:41 +03:00
Andrey Smirnov 273d4cfa1b Merge pull request #449 from adfinis-forks/bug_415_memoryhandling
only create bzip file if needed. #415
2016-11-09 22:55:08 +03:00
Andrey Smirnov d290950d4f Merge pull request #453 from sliverc/update_leveldb
Updating goleveldb dependency
2016-11-09 22:52:29 +03:00
Oliver Sauder 2ade5b8a7f Updating leveldb fixing memory usage issue
See fix for issue https://github.com/syndtr/goleveldb/issues/138
2016-11-03 16:49:36 +01:00
Oliver Sauder fcd4429370 only create bzip file if needed. #415 2016-10-13 13:48:28 +02:00
Einar Forselv 8e62620880 Fixing tests after adding v3 swift auth 2016-09-14 20:53:01 +02:00
Einar Forselv 511fb439ac Identity v3 support for Swift
ncw/swift was bumped to latest version
2016-09-14 15:29:11 +02:00
Andrey Smirnov 34ea7e8d61 Merge pull request #418 from btkostner/test-fix
update unit tests
2016-08-22 22:25:37 +03:00
Julien Fabre 543c986885 Add TubeMogul Puppet module to README.rst 2016-08-08 09:20:28 -07:00
Blake Kostner f939532461 added exit code 2 for go 1.5 support 2016-06-22 21:59:56 -07:00
Blake Kostner aa4e225455 fix unit tests 2016-06-22 21:00:51 -07:00
Andrey Smirnov 65541a1df2 Fix system tests. 2016-05-16 13:02:00 +03:00
Andrey Smirnov 0a74b50a12 Merge branch 'dstelter-master' 2016-05-16 12:06:29 +03:00
Andrey Smirnov 902c6487da Merge branch 'master' of https://github.com/dstelter/aptly into dstelter-master 2016-05-16 12:06:00 +03:00
Andrey Smirnov 1d5b7f59cf Update system tests. 2016-05-16 11:38:20 +03:00
Daniel Stelter-Gliese 1c45c79cc1 Allow overriding architecture info from Release file
Adds a flag -force-architectures to ignore missing architectures from
mirrors. This flag can be used in cases where the mirrored repository
does not provide an "Architecture: " line.

Example Release file:
http://mitaka-jessie.pkgs.mirantis.com/debian/dists/jessie-mitaka-backports/Release
2016-05-16 03:25:00 +02:00
Andrey Smirnov 85c5aeddae Merge pull request #391 from karras/master
fix missing comma in man page example
2016-04-29 17:01:46 +03:00
Michael Hofer a95e409f52 fix missing comma in man page example 2016-04-29 09:57:06 +02:00
Andrey Smirnov 53b571d6fc Attempt to fix the build. 2016-04-25 15:13:50 +03:00
Andrey Smirnov 7a8af044ee 0.9.8~dev version. 2016-04-21 12:24:36 +03:00
Andrey Smirnov a667744502 Updte system tests. 2016-04-18 13:16:34 +03:00
Andrey Smirnov aa53b8da15 Go 1.6. 2016-04-18 12:47:00 +03:00
Andrey Smirnov 52f7c83f95 Release 0.9.7. 2016-04-18 12:30:36 +03:00
Andrey Smirnov d7665119e4 Few more fixes. 2016-03-28 13:44:19 +03:00
Andrey Smirnov 587086beb4 Misc style and simple mistakes fixes. 2016-03-28 13:34:05 +03:00
Andrey Smirnov 644d24d1cc Attempt to lower memory usage when publishing with contents. 2016-03-28 13:28:26 +03:00
Andrey Smirnov 2fe8cfdc12 Allow credentials for S3 SigV2 to be specified in config once again. #356 2016-03-28 12:52:50 +03:00
Andrey Smirnov 2ecd933d50 Merge pull request #372 from amalakar/add_sbt_aptly
Add link to scala sbt plugin
2016-03-27 23:02:15 +03:00
Andrey Smirnov 90ea1111e2 Merge pull request #371 from smira/s3-list-fix
Replace object listing with SDK-standard iteration.
2016-03-27 23:01:53 +03:00
Andrey Smirnov 165a1c53b5 Merge pull request #368 from smira/225-duplicates-package-search
Fix package search missing duplicate packages. #225
2016-03-27 23:01:38 +03:00
Arup Malakar 876935050a Add link to scala sbt plugin 2016-03-26 10:35:32 -07:00
Andrey Smirnov d9a1299f6b Replace object listing with SDK-standard iteration. 2016-03-24 13:04:16 +03:00
Andrey Smirnov ff52d2655a Fix package search missing duplicate packages. #225
Implement package list with duplicate entries, use it when
initiating search from PackageCollection.
2016-03-22 12:23:13 +03:00
Andrey Smirnov bc438ff694 Add @bentoi and @geofft to AUTHORS. [ci skip] 2016-03-20 22:12:28 +03:00
Andrey Smirnov 0db3cac281 Merge pull request #366 from geofft/signing
gpg: Sign with SHA-256 for compatibility with APT 1.2.7
2016-03-20 22:10:48 +03:00
Andrey Smirnov 9ed6e8dbbd Merge pull request #367 from bentoi/mkdir_mask_bug
Fixed mkdir mode from 755 to 777
2016-03-20 22:10:32 +03:00
Andrey Smirnov 7294241c08 Merge branch 's3-sigv2-debug' 2016-03-20 22:02:03 +03:00
Andrey Smirnov 60cca0245b Fix system tests. 2016-03-20 22:01:41 +03:00
Andrey Smirnov 75b860e0b1 Support SigV2 and S3 debug for publishing. 2016-03-20 20:11:19 +03:00
Benoit Foucher 7f5a7323a6 Fixed mkdir mode from 755 to 777 2016-03-18 09:37:18 +00:00
Geoffrey Thomas 1069458aee gpg: Sign with SHA-256 for compatibility with APT 1.2.7 2016-03-15 17:04:15 -04:00
Andrey Smirnov 76edf9649b Update goleveldb with latest fix. 2016-03-15 12:47:47 +03:00
Andrey Smirnov 8b0d293c6a Update public key ID for repo.aptly.info 2016-03-15 12:30:32 +03:00
Andrey Smirnov 281d0dd68d Merge branch 'apachelogger-issue/361' 2016-03-10 18:42:49 +03:00
Andrey Smirnov cfaa8f3881 Fix system tests. 2016-03-10 18:42:40 +03:00
Harald Sitter f1b6841757 add Checksums-Sha512 to isMultilineField
Otherwise line breaks are not properly handled and the output contains
excess newlines

Fixes #361
2016-03-10 14:40:10 +01:00
Andrey Smirnov b966b2eabf Fix HOME expansion. 2016-03-02 13:56:11 +03:00
Andrey Smirnov a4e573bb07 Fix system tests after squeeze->squeeze-lts move. 2016-03-02 13:25:12 +03:00
Andrey Smirnov 067d197dac Update to latest version of goleveldb. 2016-03-01 12:53:25 +03:00
Andrey Smirnov 18d04c7977 Fix failure not being reported from API. #290 2016-03-01 12:52:54 +03:00
Andrey Smirnov a29453805c Publish under root using :. explicit prefix. #339 2016-03-01 12:34:59 +03:00
Andrey Smirnov 05b1296144 Merge pull request #354 from smira/313-sha512
Support for SHA-512 hashes on publishing/downloads.
2016-02-18 13:06:44 +03:00
Andrey Smirnov 29e33069aa Merge pull request #355 from smira/remove-s3-retrying-client
Remove S3 retrying client which is leftover from goamz times.
2016-02-18 13:05:46 +03:00
Andrey Smirnov ee05bb23c9 Fix Swift tests for SHA512. 2016-02-18 12:29:12 +03:00
Andrey Smirnov 505da096e6 Remove S3 retrying client which is leftover from goamz times.
Also workaround go vet warnings in s3/sever_test.go
2016-02-18 12:03:04 +03:00
Andrey Smirnov 77be7b9e3b Support for SHA-512 hashes on publishing/downloads. 2016-02-18 12:01:51 +03:00
Andrey Smirnov ffafed472c Merge pull request #347 from smira/skip-contents
Make 'skipContents' configurable in API. #345
2016-02-14 15:06:49 +03:00
Andrey Smirnov 8c9cc41099 Fix nil pointer dereference on S3 publishing. #338 2016-02-14 14:52:49 +03:00
Andrey Smirnov f50e008763 Make 'SkipContents' configurable in API. #345
Also add global configuration to disable 'skipContents' by
default for all new published repos/snapshots.
2016-02-14 14:49:16 +03:00
Andrey Smirnov 64b04c2764 Merge pull request #346 from smira/api-no-lock-fix
Flush collection contents on each DB unlock in API.
2016-02-14 14:13:52 +03:00
Andrey Smirnov d6c7a9a89c Flush collection contents on each DB unlock in API.
See #343
2016-02-13 13:36:35 +03:00
Andrey Smirnov 0339f0fe23 Allow additional options for goxc [ci skip] 2016-02-11 12:46:09 +03:00
Andrey Smirnov fcedaa3fc5 Merge branch 'bitglue-aws_sdk' #344 2016-02-09 12:00:16 +03:00
Andrey Smirnov 7acfc84c9d Add @bitglue to AUTHORS. [ci skip] 2016-02-09 11:47:29 +03:00
Andrey Smirnov 02b937ad17 Fix unit-tests. 2016-02-08 14:42:30 +03:00
Andrey Smirnov 7ad1c1ad17 Fix dependencies. #344 2016-02-04 11:05:37 +03:00
Phil Frost 640bd2b530 Use official AWS SDK; support STS credentials
Now that there's an official Go AWS SDK from Amazon, use that instead of
goamz. goamz isn't getting much love these days.

Implement support for STS credentials, as in assumed roles and EC2
instance profiles. The configuration is extended to support a session
token, though I'm not sure why anyone would put temporary credentials in
a configuration file. More likely, no credentials will be explicitly
configured at all, and they will be discovered through the standard SDK
mechanisms described at
<https://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs>.

Resolves #342.
2016-02-03 15:13:01 -05:00
Andrey Smirnov 06149ef2bb Merge pull request #340 from bryanhong/master
another example of aptly in Docker
2016-02-03 17:44:00 +03:00
Bryan Hong b271e8fe31 another example of aptly in Docker 2016-02-03 00:33:03 -08:00
Andrey Smirnov efc6ab27db goxc-based build system 2016-02-02 13:03:18 +03:00
Andrey Smirnov 05c063839d Update to smira/lzma with import path fixed. 2016-02-02 11:56:13 +03:00
Andrey Smirnov fd30b37a0e Bump version to 0.9.7~dev. 2016-01-24 23:15:23 +03:00
Andrey Smirnov 9738687116 Add -no-lock to aptly api serve to excercise locking. 2016-01-24 23:02:46 +03:00
Andrey Smirnov 219315c01d Fix one more system test on version. 2016-01-24 22:45:53 +03:00
Andrey Smirnov 62f44e53fd Version bump to 0.9.6. 2016-01-24 21:48:33 +03:00
Andrey Smirnov b25f8e438c Re-generate man [ci skip] 2016-01-24 21:46:45 +03:00
Andrey Smirnov f14fce01e9 Merge pull request #300 from vincentbernat/fix/api-serve-lock
Add a flag to unlock database after each API request
2016-01-24 19:53:17 +03:00
Andrey Smirnov a790770a19 Add @x539 to AUTHORS. [ci skip] 2015-12-24 14:11:26 +03:00
Andrey Smirnov 7bb052ac37 Fix unit-tests. #324 2015-12-24 14:08:37 +03:00
Andrey Smirnov 631fe44c6b Security: don't download files we don't have checksums for. #324 2015-12-22 13:52:53 +03:00
Andrey Smirnov ca319c804e Print warning message to stderr. #311 2015-12-03 13:18:45 +03:00
Andrey Smirnov 3e368690fd Stop building Go 1.3, only Go 1.4 supported. 2015-12-03 12:47:23 +03:00
Andrey Smirnov 339bf0a90b Add new comitters to AUTHORS. 2015-12-03 12:23:29 +03:00
Andrey Smirnov c5b48f0362 Merge pull request #307 from vincentbernat/fix/defer-lock
Fix lock handling in cache flusher for API
2015-12-03 12:12:35 +03:00
Andrey Smirnov d5f50732c1 Merge pull request #320 from paul-krohn/master
add diagnostic output
2015-12-03 12:11:25 +03:00
Paul Krohn 9d973aeceb shorten regex to match generated error only 2015-11-26 00:03:51 +00:00
Paul Krohn 7caeac7515 add diagnostic output 2015-11-25 23:48:47 +00:00
Vincent Bernat 7f6a52019f Add a flag to unlock database after each API request
After the first API request, the database was locked as long as the API
server is running. This prevents a user to also use the command-line
client. This commit adds a new flag `-no-lock` that will close the
database after each API request.

Closes #234
2015-10-02 20:04:48 +02:00
Vincent Bernat 16101b56fe Fix lock handling in cache flusher for API
Unlocking the different elements in cache flusher was deferred to the
end of the function. Unfortunately, being a for loop wrapped in a
goroutine, deferred were never executed.
2015-10-02 19:59:47 +02:00
Andrey Smirnov a294a91685 Cache filepath list in s3.LinkFromPool instead of doing Get checks #297
This speeds up publishing with many files already present in the pool
2015-10-01 14:02:32 +03:00
Andrey Smirnov cf644289a3 Lower limit for goleveldb open files cache to 256 #260 2015-10-01 12:37:43 +03:00
Andrey Smirnov 33c905ce02 Upgrade goleveldb to the latest version. #260 2015-10-01 12:27:19 +03:00
Andrey Smirnov 8fdc222196 Fix retry policy. #297 2015-09-24 13:21:13 +03:00
Andrey Smirnov 1e4d825d36 Disable keep alives, fix return on last retry. #297 2015-09-24 12:21:31 +03:00
Andrey Smirnov 76bf7cba04 Fix handling of folded fields in Stanza. #270
Discovered by @sobczyk

When whitespace is stripped from folded stanza fields, some fields
values are glued together without whitespace which might change its meaning.
2015-09-24 11:59:49 +03:00
Andrey Smirnov 08bc5ac934 Fix system tests for Go 1.5.
Some error strings in Go 1.5 have changed. Until we have something
better for that, use string replace.
2015-09-22 12:40:42 +03:00
Andrey Smirnov c160cbccc7 Fix mirror system tests. 2015-09-22 11:42:05 +03:00
Andrey Smirnov c473a5cba8 Really randomize port in Swift unit-tests. 2015-09-22 11:25:10 +03:00
Andrey Smirnov 84801bce78 Fix unit-tests
Go 1.5 has different error message, randomize port number in test to avoid
collisions.
2015-09-22 11:18:57 +03:00
Andrey Smirnov b95b3473bf Switch to Travis CI new build infra. 2015-09-21 13:58:17 +03:00
Andrey Smirnov f1d5caab8b Enable build for Go 1.5. 2015-09-20 13:14:33 +03:00
Andrey Smirnov 6a973554ad Update goar to the latest version. #275 2015-09-20 13:13:18 +03:00
Andrey Smirnov 698e239f45 Include all aptly contributors in man section AUTHORS. 2015-07-04 13:16:15 +03:00
Andrey Smirnov 205297d0b8 Update license to mention that there are many contributors. 2015-07-04 13:06:58 +03:00
Andrey Smirnov ba4669a9c4 Man page for package display format in search commands. #254 2015-07-04 13:02:33 +03:00
Andrey Smirnov 8bda799545 Support for Go-style templating in format for aptly * search. #254 2015-07-02 12:19:41 +03:00
Andrey Smirnov 6c28e3aca8 Update flat repository. 2015-06-26 13:24:31 +03:00
Andrey Smirnov 901babe500 Fix test. 2015-06-26 13:15:11 +03:00
Andrey Smirnov 0c6f38ab08 Fix system test. 2015-06-26 13:09:57 +03:00
Andrey Smirnov a131d6093c Fix unit-test. 2015-06-26 12:56:02 +03:00
Andrey Smirnov 974cec3e73 Fix publish tests. 2015-06-26 03:14:22 +03:00
Andrey Smirnov 442c5f090f Fix package tests. 2015-06-26 03:08:22 +03:00
Andrey Smirnov d04f08c1cf Correctly handle multine fields in Release/non-Release files. #266 2015-06-26 03:07:33 +03:00
Andrey Smirnov 767c7ca0db Merge branch '261-fix-multiline-fields' 2015-06-20 19:04:06 +03:00
Andrey Smirnov dd27aad751 Add @sobczyk to the list of AUTHORS. #266 2015-06-20 19:03:41 +03:00
Andrey Smirnov ddfdeaf2d0 Merge pull request #266 from sobczyk/master
Fix EOF error during mirror update.
2015-06-20 18:59:21 +03:00
Szymon Sobik 4c51350517 fix EOF during mirror update
see http://stackoverflow.com/a/19006050
2015-06-19 12:36:28 +02:00
Andrey Smirnov 40e48c963a Revert "Update publish tests, as some multiline fields are actually multiline."
This reverts commit a030e24b96.
2015-06-18 03:33:09 +03:00
Andrey Smirnov c44d347540 Don't need to manually insert \n, multiline fields are handled correctly. 2015-06-18 03:32:35 +03:00
Andrey Smirnov 4a54bff225 Add missing return statements. 2015-06-18 03:32:23 +03:00
Andrey Smirnov e39736153d Update package tests. 2015-06-18 03:29:12 +03:00
Andrey Smirnov f032196d70 Mirror has been updated. 2015-06-18 02:15:39 +03:00
Andrey Smirnov a030e24b96 Update publish tests, as some multiline fields are actually multiline. 2015-06-18 02:13:15 +03:00
Andrey Smirnov c2993c6691 Use other port that doesn't interfere with PostgreSQL. 2015-06-18 01:52:37 +03:00
Andrey Smirnov 7d4a70ba25 Make first line of multiline field empty for all fields except for Description. #261 2015-06-10 13:44:03 +03:00
Andrey Smirnov 38dfe3435a For plusWorkaround, correctly handle cleanup, deletions. #239 2015-05-29 02:13:59 +03:00
Andrey Smirnov 313c71dff6 Rework s3 retry policy by copying sources from goamz :( #255 2015-05-29 01:47:02 +03:00
Andrey Smirnov a88d92436f Fix system test for mirrors list. 2015-05-29 00:15:54 +03:00
Andrey Smirnov 9d298dee51 Remove deadline timeout. #255 2015-05-28 12:52:29 +03:00
Andrey Smirnov 9abc772b16 Change our flat repo for testing, old one is dead. 2015-05-28 12:14:36 +03:00
Andrey Smirnov 2f1df39204 Use S3 retrying transport. #255 2015-05-28 11:45:37 +03:00
Andrey Smirnov 0f328ec1fe Seed Python random generator on start. 2015-05-28 11:40:11 +03:00
Andrey Smirnov 78b6d6ca7b Send error messages to stderr. #249 2015-05-28 11:30:35 +03:00
Andrey Smirnov 5cd3c33854 Update goamz library to latest version. #253 2015-05-28 11:21:57 +03:00
Andrey Smirnov 9af76843b5 Fix README formatting. [ci skip] 2015-05-28 11:17:01 +03:00
Andrey Smirnov 53506124a4 Add integrations to README, aptly API CLI. [ci skip] 2015-05-28 11:12:55 +03:00
Andrey Smirnov 9bbf9c7b13 Merge branch 'graph-output-filename' 2015-05-18 00:39:41 +03:00
Andrey Smirnov 82e6e8242e Update man page. #242 2015-05-18 00:39:01 +03:00
Andrey Smirnov 2bf11a556c Update custom output filename generation. #242 2015-05-18 00:38:15 +03:00
Andrey Smirnov c62828bf14 Merge branch 'graph-specific-output-filename' of https://github.com/gdbdzgd/aptly into graph-output-filename 2015-05-18 00:30:41 +03:00
Andrey Smirnov b53cf7e710 Merge pull request #246 from GLolol/typofix
Typo fix (depdency -> dependency)
2015-05-18 00:22:42 +03:00
Andrey Smirnov 780277d0a6 Build on go 1.4 as well. 2015-05-14 12:51:01 +03:00
Andrey Smirnov a6f5631542 Fix system tests. 2015-05-14 12:27:01 +03:00
Andrey Smirnov 52b1501ec0 Update to new debian archive keyring. 2015-05-14 12:26:50 +03:00
James Lu c9339f5cca Typo fix (depdency -> dependency) 2015-05-11 18:01:21 -07:00
Andrey Smirnov a9c23fb4aa Fix silly bug with non-encodable value being encoded. 2015-04-26 09:02:40 +02:00
Andrey Smirnov 72e3eaebfe Add optional notice. 2015-04-26 08:56:29 +02:00
Zhang, Guodong f3bcaa6cfb aptly graph specific output filename
https://github.com/smira/aptly/issues/241

	modify:     cmd/graph.go

Signed-off-by: Zhang, Guodong <gdzhang@linx-info.com>
2015-04-24 11:14:04 +08:00
Andrey Smirnov 1c8f1517f8 Update man. #218 [ci skip] 2015-04-17 01:26:37 +03:00
Andrey Smirnov 50ae34cc19 Create S3 with endpoint/multidel param from config. #218 2015-04-17 01:20:58 +03:00
Andrey Smirnov 8cc7d1345b Support for new S3 configuration options: endpoint & multi del disabling. #218 2015-04-17 01:18:34 +03:00
Andrey Smirnov 0791c88a02 Support for custom endpoints and multi del disabling. #218 2015-04-17 01:16:50 +03:00
Andrey Smirnov ba08ffe38b Add dependency on xz-utils. #142 2015-04-14 23:16:41 +03:00
Andrey Smirnov 1bec1e4dc4 Use external binary 'xz' implementation. #142 2015-04-14 23:16:16 +03:00
Andrey Smirnov bcf8074f31 Revert "Use Go native lzma implementation, so that there are no external dependencies. #142"
This reverts commit 709e14ecc1.
2015-04-10 22:24:45 +03:00
Andrey Smirnov 6a2d564eee Revert "Add liblzma-dev to list of build dependencies in Travis. #142"
This reverts commit 4651e41247.
2015-04-10 22:09:56 +03:00
Andrey Smirnov 709e14ecc1 Use Go native lzma implementation, so that there are no external dependencies. #142 2015-04-10 22:09:18 +03:00
Andrey Smirnov 5b1f446a6b Ignore empty 'Depends:' while parsing control file. #233 2015-04-10 21:10:53 +03:00
Andrey Smirnov f41146c750 Revert "Build static binaries on !OS X. #142"
This reverts commit d56ac81fd6.
2015-04-06 00:18:25 +03:00
Andrey Smirnov d56ac81fd6 Build static binaries on !OS X. #142 2015-04-05 22:52:08 +03:00
Andrey Smirnov fb213ef6eb Fix system tests. #142 2015-04-05 22:47:19 +03:00
Andrey Smirnov 933b019f71 Fix -skip-contents + system tests. #142 2015-04-05 21:55:41 +03:00
Andrey Smirnov 6293ca3206 Add -skip-contents flag. #142 2015-04-05 21:27:35 +03:00
Andrey Smirnov d46d8de5f7 Make sure contents don't have duplicate package entries. #142 2015-04-05 21:12:25 +03:00
Andrey Smirnov 4e3284cd98 Check contents of contents index being generated. #142 2015-04-02 01:19:51 +03:00
Andrey Smirnov 10876b99f5 Check for contents file generation. #142 2015-04-02 01:00:28 +03:00
Andrey Smirnov 61d31ce7c0 Check that contents are generated on repo publish. #142 2015-04-02 00:49:08 +03:00
Andrey Smirnov e0f284d68f Check that contents files are generated. #142 2015-04-02 00:45:08 +03:00
Andrey Smirnov df887d871b Skipping contents generation. #142 2015-04-02 00:29:49 +03:00
Andrey Smirnov 99f6ffe1ca Fix system test for content generation. #142 2015-04-02 00:29:49 +03:00
Andrey Smirnov 138f9f7994 Generate only .gz file for Contents index. #142 2015-04-02 00:29:49 +03:00
Andrey Smirnov 3886db9d4f Merge pull request #231 from seaninspace/patch-1
Update import.go
2015-03-31 23:19:41 +03:00
Sean b877e06a02 Update import.go
Add support for Automatic Debug Packages (.ddeb's)
2015-03-31 12:17:32 -07:00
Andrey Smirnov 38f4fc209b Contents index support. #142 2015-03-31 00:08:23 +03:00
Andrey Smirnov b223acdecb Contents index generator. #142 2015-03-31 00:07:42 +03:00
Andrey Smirnov cc8a87b448 Cached calculation of package contents. #142 2015-03-31 00:07:07 +03:00
Andrey Smirnov ee3d414ed5 Don't use shared encodeBuffer, not safe for API mode. 2015-03-30 23:59:00 +03:00
Andrey Smirnov d791aa0f15 Remove debugging output. #142 2015-03-30 23:54:36 +03:00
Andrey Smirnov 393ae8adbd Regenerate man page. #163 2015-03-30 23:54:11 +03:00
Andrey Smirnov 7037c6be7e Rename -output to -format. #163 2015-03-30 23:53:37 +03:00
Andrey Smirnov c10645f4f2 Support custom output formats for aptly graph. #163 2015-03-30 20:26:05 +03:00
Andrey Smirnov 27da1015af Test case for filters. #227 2015-03-30 19:56:53 +03:00
Andrey Smirnov 78b0fe0e90 Fix system tests. 2015-03-25 00:42:02 +03:00
Andrey Smirnov 4651e41247 Add liblzma-dev to list of build dependencies in Travis. #142 2015-03-24 23:35:24 +03:00
Andrey Smirnov a6c40f3193 Getting contents from .deb files. #142 2015-03-24 22:09:36 +03:00
Andrey Smirnov 3e138fd6db Add dependency on .xz and .lzma decompression. This introduces dependency on liblzma. #142 2015-03-24 22:09:03 +03:00
Andrey Smirnov 3c20b5472e Tests for aptly repo include with per-repo uploaders.json. #71 2015-03-22 19:24:37 +03:00
Andrey Smirnov 8b782ce370 Support for per-repo uploader.json in aptly repo commands. #71 2015-03-22 19:02:20 +03:00
Andrey Smirnov a160a39d53 -uploaders-file for aptly repo edit/create. #71 2015-03-22 18:48:17 +03:00
Andrey Smirnov 1c4b44e772 Uploaders to JSON transformation via String. #71 2015-03-22 12:39:37 +03:00
Andrey Smirnov b4b03f2752 Update system tests. #71 2015-03-20 22:29:50 +03:00
Andrey Smirnov 1d21d3cfeb Uploader.json from repo overrides global uploaders.json. #71 2015-03-20 22:29:11 +03:00
Andrey Smirnov d2ce33e66a Allow local repo to carry uploaders.json config. #71 2015-03-20 22:28:45 +03:00
Andrey Smirnov f0fbb8259b Document uploaders.json file in man. #71 2015-03-20 00:21:50 +03:00
Andrey Smirnov 962c4a842d Additional tests for aptly repo include. #71 2015-03-20 00:18:32 +03:00
Andrey Smirnov 54e21afee7 Use uploaders.json in repo include. #71 2015-03-20 00:18:12 +03:00
Andrey Smirnov cc3f5149c6 Return detailed error if uploaders deny upload. #71 2015-03-20 00:11:30 +03:00
Andrey Smirnov c8713aa412 Fix bugs. #71 2015-03-19 23:58:48 +03:00
Andrey Smirnov 02a82f3545 Use relaxed config reader. #71 2015-03-19 23:54:35 +03:00
Andrey Smirnov c573746896 Refactor to get Keys from Changes. #71 2015-03-19 01:36:39 +03:00
Andrey Smirnov 813b9593fa Uploaders rules facility: controlling who can upload .changes. #71 2015-03-19 01:05:09 +03:00
Andrey Smirnov bc68513708 Implementation of PackageLike interface for Changes. #71 2015-03-19 00:26:18 +03:00
Andrey Smirnov c4692bec3d Matching short/long GPG key IDs. #71 2015-03-19 00:25:54 +03:00
Andrey Smirnov c53060d95a Add system test on package restriction check. #71 2015-03-18 23:32:21 +03:00
Andrey Smirnov 22c656d18e Style fix [ci skip]. #71 2015-03-18 23:27:21 +03:00
Andrey Smirnov 4d622e467c Refactoring: make PackageQuery work on PackageLike objects (not necessarily Packages). #71 2015-03-18 23:25:20 +03:00
Andrey Smirnov 36326788b0 When importing package into local repo, verify that it matches package restriction based on .changes file. #71 2015-03-18 22:20:52 +03:00
Andrey Smirnov 782ac1a36a ChangesFile can produce Query each package file should satisfy. #71 2015-03-18 22:19:12 +03:00
Andrey Smirnov 8ca07d9acd Fix unit tests. #71 2015-03-18 22:10:49 +03:00
Andrey Smirnov 4a57fe3c39 Refactoring: make gpg verification return missing/good key IDs. #71
Eliminate "hint" on missing keys which doesn't apply to .changes.

Would be good to eventually stop using GPG and start calling golang.org/x/crypto/openpgp
2015-03-18 21:34:54 +03:00
Andrey Smirnov 7579f1998c Fix system tests. #71 2015-03-17 01:09:09 +03:00
Andrey Smirnov 67a31d5eaa Merge branch '71-changes-support' 2015-03-17 00:19:28 +03:00
Andrey Smirnov 5b9d287b62 Add aptly repo include to man. #71 2015-03-17 00:19:06 +03:00
Andrey Smirnov 775670181c System tests for -ignore-signatures + -accept-unsigned. #71 2015-03-17 00:17:43 +03:00
Andrey Smirnov 2a3bd5546a Unsigned files shouldn't be accepted. #71 2015-03-17 00:15:45 +03:00
Andrey Smirnov 197e230ef1 System tests: wrong signature. #71 2015-03-17 00:08:47 +03:00
Andrey Smirnov c6eeac11a4 System test for wrong checkums. #71 2015-03-17 00:02:39 +03:00
Andrey Smirnov 90d3b623b4 Check file size as well as checksums. #71 2015-03-17 00:01:58 +03:00
Andrey Smirnov a59c2ac859 Tests for file removal + missing files. #71 2015-03-16 23:55:47 +03:00
Andrey Smirnov 103fa5310f First pack of system tests for aptly repo include. #71 2015-03-16 22:50:58 +03:00
Andrey Smirnov 71b7de7a63 Initialize empty verifier if -ignore-signatures is given to check for signature. #71 2015-03-16 22:49:41 +03:00
Andrey Smirnov a937ebc744 First version aptly repo include command processing .changes files. #71 2015-03-15 21:30:54 +03:00
Andrey Smirnov 925882b253 Collect .changes file in directory hiearchy. #71 2015-03-15 21:26:58 +03:00
Andrey Smirnov 615a5ee3f9 Example of package upload with .changes file. #71 2015-03-15 21:21:23 +03:00
Andrey Smirnov 4a6d6a85f7 Remove unused error argument. 2015-03-15 20:06:59 +03:00
Andrey Smirnov 2937435960 Add missing commands api, config. 2015-03-15 18:44:43 +03:00
Andrey Smirnov 2f3b5f5a51 Refactor Changes structure, new method prepare to verify checksums and copy files. #71 2015-03-15 18:16:11 +03:00
Andrey Smirnov 5b4563f250 Simple CopyFile utility function. #71 2015-03-15 18:15:46 +03:00
Andrey Smirnov 5da4bde428 Fix reference to go-uuid. 2015-03-15 14:07:38 +03:00
Andrey Smirnov 42c4644be3 Move go-uuid to GitHub. No more code.google.com. RIP. 2015-03-15 14:06:40 +03:00
Andrey Smirnov 1845c493f4 Update mxk/flowcontrol package from Google Code to mxk/flowrate from GitHub. 2015-03-15 14:00:04 +03:00
Andrey Smirnov 8a0f754fe2 Snappy has moved, remove reference. 2015-03-15 13:51:37 +03:00
Andrey Smirnov 77bb4d423d Update import path for gographviz. 2015-03-15 13:51:14 +03:00
Andrey Smirnov 1d483dc817 Update reference to gographviz (code.google.com is going to be shut down). 2015-03-15 13:50:08 +03:00
Andrey Smirnov a7103623af .changes files parsing. #71 2015-03-13 21:46:32 +03:00
Andrey Smirnov 903e999cdc Refactor checksum parsing out of package parsing code. #71 2015-03-13 21:23:22 +03:00
Andrey Smirnov 69eff97b34 Relax .dsc checkshums parsing. #71 2015-03-13 20:53:53 +03:00
Andrey Smirnov 8e20daa927 Refactor out IsClearSigned to separate method. #71 2015-03-13 18:42:34 +03:00
Andrey Smirnov 9e39dbf81e Version bump to 0.9.6~dev. 2015-03-13 16:07:25 +03:00
Andrey Smirnov 7a4feebe6f Version bump to 0.9.5. 2015-03-13 13:26:39 +03:00
Andrey Smirnov 1d1561c6c3 Add missing files. 2015-03-12 00:50:27 +03:00
Andrey Smirnov 9a5b3aeedc System test. #193 2015-03-11 23:38:16 +03:00
Andrey Smirnov ed931e7ed4 Fix unit-tests. #153 2015-03-11 23:29:07 +03:00
Andrey Smirnov 5ff9cecc5a Regenerate man page. #153 2015-03-11 22:20:43 +03:00
Andrey Smirnov f8bca463bb Add -force-drop to aptly publish drop, ?force=1 to DELETE publish/... to drop
published repositories even if cleanup fails. #153
2015-03-11 22:02:11 +03:00
Andrey Smirnov d5c6f0b623 Collect and report unused package reference sources. #217 2015-03-11 21:40:53 +03:00
Andrey Smirnov 7e57f443ed Style fix. #193 2015-03-11 21:40:04 +03:00
Andrey Smirnov b4cf2e7065 Canonical case fixes. #193 2015-03-11 01:25:54 +03:00
Andrey Smirnov 2ceabb69e6 Remove extra \n, system tests. #217 2015-03-11 01:22:49 +03:00
Andrey Smirnov aa9d3360ba Canonical case-folding for Debian stanzas. #193 2015-03-11 00:46:39 +03:00
Andrey Smirnov 4580a64192 Make import skip file if Name/Version/Arch is empty. #193 2015-03-11 00:34:48 +03:00
Andrey Smirnov 4cb0526980 Commands * search should exit with failure on no results. #213 2015-03-11 00:31:53 +03:00
Andrey Smirnov 03e2a8d558 Regenerate man page. #217 2015-03-11 00:17:08 +03:00
Andrey Smirnov ab09cbfe3c Add -verbose and -dry-run to aptly db cleanup. #217 2015-03-11 00:16:23 +03:00
Andrey Smirnov 0467e0c929 More sophisticated color codes stripper. #217 2015-03-11 00:15:56 +03:00
Andrey Smirnov 6e1c9afdd9 Bump version to 0.9.5~dev. 2015-03-09 22:26:25 +03:00
Andrey Smirnov 4b3b961b69 Version bump to 0.9.1 2015-03-06 15:05:33 +03:00
Andrey Smirnov e63adffdf5 Introduce back reflist merging without conflict removal. aptly db cleanup requires
full reference list collection. #217

Fixes bug with aptly db cleanup removing conflicting packages.
2015-03-06 14:54:29 +03:00
Andrey Smirnov d00659b0cb Recommend graphviz for Debian packages. 2015-03-03 22:04:10 +03:00
Andrey Smirnov 66e73782e5 Version bump to 0.9. 2015-03-03 21:00:10 +03:00
Andrey Smirnov 68f332628d Tests on publish update/switch and delete. #116 2015-03-03 20:57:40 +03:00
Andrey Smirnov 01c0d19243 Fix bug with components not being cleaned up after publish update. #116 2015-03-03 20:49:49 +03:00
Andrey Smirnov eb0443ed51 Tests for publish list API + verify that publishing actually creates files. #116 2015-03-03 20:35:48 +03:00
Andrey Smirnov 4b974b038c Test for snapshot publishing. #116 2015-03-01 22:36:22 +03:00
Andrey Smirnov 2d9ee81c95 Update status code. #116 2015-03-01 22:33:05 +03:00
Andrey Smirnov 5c9d4d2844 More tests for repo drop. #116 2015-03-01 22:32:41 +03:00
Andrey Smirnov 49a9ad79dd Adjust return code for publish create call. #116 2015-03-01 22:31:56 +03:00
Andrey Smirnov 7e60466c7b Fix system test. 2015-03-01 22:14:37 +03:00
Andrey Smirnov 233ad2528f Update system tests for new publishing APIs. #116 2015-03-01 19:56:47 +03:00
Andrey Smirnov 2f1afa54c2 Publish update/drop APIs, rework prefix, move publishing APIs. #116 2015-03-01 19:55:02 +03:00
Andrey Smirnov 6bf910ea56 Update dependency on ssh/terminal. 2015-02-28 23:00:45 +03:00
Andrey Smirnov 8fcfedf708 Lock down package pool to make sure that we have no concurrent access. #116 2015-02-28 22:10:21 +03:00
Andrey Smirnov 26b46ee2a0 Fix confusing comment. #116 2015-02-28 19:55:52 +03:00
Andrey Smirnov e33a2a6f96 Reverting, as this change was making API LESS RESTful. My bad.
Revert "Make snapshot creation API more RESTful: accept snapshot name from URL. #116"

This reverts commit 06dc1ef9a4.
2015-02-28 19:55:09 +03:00
Andrey Smirnov 06dc1ef9a4 Make snapshot creation API more RESTful: accept snapshot name from URL. #116 2015-02-28 19:32:28 +03:00
Andrey Smirnov 4c57c358b7 One more attempt to fix system tests. 2015-02-26 09:28:12 +03:00
Andrey Smirnov 65532b3dbf Fix system tests. 2015-02-25 23:07:32 +03:00
Andrey Smirnov fb25dec58e Consistently rename response fields to CamelCase. #116 2015-02-25 22:24:03 +03:00
Andrey Smirnov e320499f84 Mention SwiftPublishEndpoints. smira/aptly#191 2015-02-25 21:24:48 +03:00
Andrey Smirnov 4715b12f16 Simpler apt-key command. #202 2015-02-22 17:41:15 +03:00
Andrey Smirnov c6a30a30de Update README. 2015-02-22 17:39:26 +03:00
Andrey Smirnov 618d06678c Style fixes from go vet. 2015-02-22 14:36:14 +03:00
Andrey Smirnov 903d4cefba gofmt -s 2015-02-22 14:29:09 +03:00
Andrey Smirnov 79292dc6c8 Update system tests. #191 2015-02-22 13:47:14 +03:00
Andrey Smirnov 43414be2ee Fix bug with aptly locking up on swift published storage lookup. #191 2015-02-22 13:42:06 +03:00
Andrey Smirnov 3c34ae6071 Update CLI help for aptly publish switch. #208 2015-02-21 22:32:51 +03:00
Andrey Smirnov 642957e3a3 Update Brightbox Orbit credentials. 2015-02-21 22:26:20 +03:00
Andrey Smirnov e5d646c007 Merge branch 'sbadia-swift'
Conflicts:
	AUTHORS
2015-02-21 01:08:48 +03:00
Andrey Smirnov e0f811dab1 Travis CI setup to run Swift tests. #191 2015-02-20 23:59:46 +03:00
Andrey Smirnov 48b8311150 Style fixes. #191 2015-02-20 23:58:00 +03:00
Andrey Smirnov 8111460e36 Update aptly man page. #191 2015-02-20 23:48:55 +03:00
Andrey Smirnov 0490d0c928 Remove .swift.sh, it is now private. 2015-02-20 23:47:55 +03:00
Andrey Smirnov b323e315d1 Add comments where required. 2015-02-20 23:47:17 +03:00
Andrey Smirnov 77f928db69 Fix variable shadowing. 2015-02-20 23:45:25 +03:00
Andrey Smirnov b67f3dd6f7 gofmt #191 2015-02-20 23:43:40 +03:00
Andrey Smirnov 88ff4493b0 Publish list API. #116 2015-02-20 11:12:16 +03:00
Andrey Smirnov 6e8fd6e907 Update to latest cheggaaa/pb with my pull request merged. 2015-02-20 00:46:13 +03:00
Andrey Smirnov 9c3095e42c Fix data race in p.Bar being read and written from different goroutines. 2015-02-19 01:53:32 +03:00
Andrey Smirnov c737b8c544 Flush CollectionFactory every 15 minutes. #116 2015-02-16 00:46:31 +03:00
Andrey Smirnov 87cecac4ea Lock down Context. #116 2015-02-16 00:33:10 +03:00
Andrey Smirnov 76ee53e9f8 Eliminate data races by using API without Progress. #116 2015-02-16 00:32:45 +03:00
Andrey Smirnov f153c7c3ea We're building on go1.3+, so remove workaround for FreeBSD. 2015-02-16 00:15:54 +03:00
Andrey Smirnov 36792bba29 Update progressbar version. 2015-02-16 00:15:30 +03:00
Andrey Smirnov 0b05964faa Add ability to Flush CollectionFactory. #116 2015-02-15 23:57:54 +03:00
Andrey Smirnov ff00a5a026 Add @alexanderguy to AUTHORS. [ci skip] 2015-02-13 20:14:56 +03:00
Andrey Smirnov fc0310f468 Merge pull request #201 from alexanderguy/add-suite
d-i requires the Suite field in order to validate a mirror.
2015-02-13 20:14:08 +03:00
Alexander Guy 63bf30b890 d-i requires the Suite field in order to validate a mirror.
Debian's installer validates a mirror by downloading a Release,
and then cross-checking it based on its Codename and Suite.  Without
a Suite field, the installer becomes unhappy (e.g. segfaults) and
won't continue the install.

Making the Codename and Suite the same validates with no problem.
2015-02-12 13:38:56 -08:00
Andrey Smirnov 3004473bbb Update to correct reference to ncw/swift. 2015-02-11 22:07:36 +03:00
Andrey Smirnov 4356fe5cbe Merge branch 'sbadia-swift' of github.com:sbadia/aptly into sbadia-swift 2015-02-11 21:58:51 +03:00
Sylvain Baubeau d6271b6542 Use upstream version of ncw/swift 2015-02-11 21:52:16 +03:00
Sebastien Badia 26a65b2336 swift: Fallback to TempAuth 2015-02-11 15:36:16 +01:00
Sebastien Badia 20adfd49a7 swift: Add support for Swift API v1 (without Keystone)
This commit also add a workaround for bug/feature[1]
the password is changed every time :-)

[1]https://github.com/ccollicutt/docker-swift-onlyone/commit/c9f5e41b745eee18e7ddc807481bc9729d8cdac0
2015-02-11 15:35:45 +01:00
Sylvain Baubeau 355a98b51f Use upstream version of ncw/swift 2015-02-10 09:17:54 +01:00
Andrey Smirnov 1f73a34a54 Don't put empty Source: fields into package stanza. #195 2015-02-09 19:23:41 +03:00
Andrey Smirnov 7925af9fd6 Exit with failure if aptly package search yields no results. #188 2015-02-07 23:33:44 +03:00
Andrey Smirnov fb03a3baf9 Add @shadeslayer and @rra to list of AUTHORS. [ci skip] 2015-02-07 23:24:10 +03:00
Andrey Smirnov f097cd20c1 Add @sbadia to AUTHORS. #191 2015-02-07 20:05:16 +03:00
Andrey Smirnov 0489ba9d16 Swift startup script. #191
(Doesn't work yet)
2015-02-07 20:03:51 +03:00
Andrey Smirnov 46b3f8fbaf PEP8 fixes. #191 2015-02-07 19:39:58 +03:00
Andrey Smirnov cacd0cf103 Merge branch 'swift' of https://github.com/sbadia/aptly into sbadia-swift 2015-02-07 19:18:04 +03:00
Andrey Smirnov c933668c16 Merge branch 'mkoval-feature/RepoUpdateAPI' 2015-02-07 19:01:24 +03:00
Andrey Smirnov 24418ab0a4 Small fixes to publish update API. #174 2015-02-07 19:01:06 +03:00
Andrey Smirnov 4963d0a1d7 Add @mkoval to list of AUTHORS. 2015-02-07 18:53:25 +03:00
Andrey Smirnov ea8bfeb8a7 Merge branch 'feature/RepoUpdateAPI' of https://github.com/mkoval/aptly into mkoval-feature/RepoUpdateAPI 2015-02-07 18:52:29 +03:00
Andrey Smirnov a582493a6e Packages show API with tests. #116 2015-02-07 18:50:52 +03:00
Andrey Smirnov 930f76887b Final version of system tests for snapshot API. #168 2015-02-07 18:37:10 +03:00
Andrey Smirnov a4201a40d2 Allow to override architectures when publishing. #116 2015-02-07 18:36:42 +03:00
Andrey Smirnov 4990bb98e5 Update gin to latest available version. #116 2015-02-07 18:36:14 +03:00
Andrey Smirnov 00d4674aa5 Update (fix) system test. 2015-02-07 13:23:30 +03:00
Andrey Smirnov 06b4016338 More fixes related to locking and overall operations. #168 2015-02-06 22:44:25 +03:00
Andrey Smirnov c1b2e4fabb Fix for snapshot creation APIs: locking, package existence checks, consistency checks. #168
More system tests.
2015-02-06 22:37:57 +03:00
Andrey Smirnov f438637a98 Don't expose UUIDs in API. #168
Probably we should expose sources, but not as UUIDs. TODO.
2015-02-06 22:37:14 +03:00
Andrey Smirnov ce208f347e Merge branch 'lebauce-snapshot-api' 2015-02-06 20:18:52 +03:00
Andrey Smirnov 06502584cf Check component names (that they do exist) before publish switching. #192 2015-02-06 20:16:00 +03:00
Sebastien Badia 0f22dc590a Fix config tests and update man page
Fix ConfigSuite.TestSaveConfig, ConfigShowTest and CreateConfigTest
tests
2015-02-05 21:27:09 +01:00
Sylvain Baubeau 11716f06f0 Add test suite for the Swift backend 2015-02-05 18:03:35 +01:00
Sylvain Baubeau 1ba06e828d Remove prefix in Filelist and RemoveDir 2015-02-05 17:56:16 +01:00
Sebastien Badia bc357a19a1 Added swift python tests 2015-02-05 17:56:09 +01:00
Sylvain Baubeau 9004f8578c Detect if bulk-delete is supported 2015-02-05 17:54:09 +01:00
Sebastien Badia 7f038be1cb Add swift backend for repository publishing 2015-02-05 17:54:09 +01:00
Andrey Smirnov 13fc1122f0 Use Python requests URL params instead of manual GET params. #168 2015-02-05 01:56:55 +03:00
Andrey Smirnov cb99cbec58 Fix final system test. #168 2015-02-05 01:53:07 +03:00
Andrey Smirnov 5d16cf06cf Gobuild is gone. [ci skip] 2015-02-05 01:49:29 +03:00
Andrey Smirnov b0489117c8 Update system tests for new Package serialization. #168 2015-02-05 01:48:14 +03:00
Andrey Smirnov fa2eef564c Enhance Package JSON representation with Key, ShortKey and FilesHash. #168 2015-02-05 01:47:10 +03:00
Andrey Smirnov d20300b152 Whitespace fix. #168 2015-02-05 01:46:57 +03:00
Andrey Smirnov 398303235a Custom JSON marshalling for PackageDiff, updated test for snapshot diff API. #168 2015-02-05 01:34:02 +03:00
Andrey Smirnov 25d048fe49 Add Package serialization to JSON via stanza. #168 2015-02-05 01:26:55 +03:00
Andrey Smirnov 8c15a0ca95 Add AlekSi/pointer to dependencies. #168 2015-02-05 01:24:44 +03:00
Andrey Smirnov 8e8ff8ba65 Revert "Make files hash a type for proper JSON serialization"
This reverts commit e138212593.
2015-02-05 00:27:14 +03:00
Andrey Smirnov 1b0eb9d45a Attempt to fix #189 and #130: disable Amazon workaround when using proxy. 2015-02-03 21:49:55 +03:00
Andrey Smirnov 403c7272cd When loading package index for the mirror, ignore duplicate packages (and print about them). #183 2015-01-31 21:27:26 +03:00
Andrey Smirnov 0412646151 Add @bcandrea to list of authors. 2015-01-30 19:26:57 +03:00
Andrey Smirnov 0725003107 Merge pull request #186 from bcandrea/master
Update dependency definition in conflicts (fixes #185)
2015-01-30 19:24:56 +03:00
Andrea Bernardo Ciddio 7a1553dc55 Update dependency definition in conflicts (fixes #185)
The dependency specified when looking for conflicting packages
does not take into account the package version, as by default the
Relation field in the Dependency structure is set to VersionDontCare.
This should fix #185.
2015-01-30 16:22:27 +00:00
Andrey Smirnov 8375a2c30f Update system test. 2015-01-30 18:39:50 +03:00
Andrey Smirnov 5bbbdb3c19 Use long gpg key IDs. #178 2015-01-30 18:33:05 +03:00
Andrey Smirnov 1fd80c40d0 Add --no-default-keyring to example command. #182 2015-01-30 18:24:30 +03:00
Andrey Smirnov ae5ab2d138 Use https:// in example when talking about Release.key download. #179 2015-01-30 18:19:28 +03:00
Andrey Smirnov eb087fd291 When generating index files, make udeb forced false for "source" architecture. #180
Otherwise two index files are generated (source arch, "udeb" true/false) which end up
sharing same final filename, and empty one might overwrite "real" one.
2015-01-26 21:16:44 +03:00
Andrey Smirnov 3f6491b8a3 Non-working test on format=details. 2015-01-26 21:06:28 +03:00
Andrey Smirnov 9250479846 Extract common part of show and search packages from snapshots and repos. #168 2015-01-24 22:23:16 +03:00
Andrey Smirnov 9c60421bd6 Python style fixes. #168 2015-01-24 21:53:06 +03:00
Andrey Smirnov ebea4f10a0 Make snapshot diff GET, not POST (as it doesn't change anything in the system). #168 2015-01-24 21:51:33 +03:00
Andrey Smirnov d828732307 Refactoring: make snapshot sorting non-intrusive to collection contents. #168 2015-01-22 22:01:00 +03:00
Andrey Smirnov 7c3629337c Merge branch 'snapshot-api' of https://github.com/lebauce/aptly into lebauce-snapshot-api
Conflicts:
	api/router.go
	system/t12_api/__init__.py
2015-01-22 21:29:58 +03:00
Michael Koval a29034caa5 Implemented apiPublishUpdateSwitch. 2015-01-21 02:23:08 -05:00
Andrey Smirnov c1fd633ed7 Add Sylvain Baubeau to authors. [ci skip] 2015-01-19 22:06:15 +03:00
Andrey Smirnov bd2cc45524 Fix order of 'Component' and 'Architecture' fields. #172 2015-01-19 22:03:31 +03:00
Andrey Smirnov 0665f2231a Sort packages when generating Packages index file. #172 2015-01-19 21:39:02 +03:00
Andrey Smirnov 836abdc81e Bring back "Archive" into canonical order. #172 2015-01-19 21:33:00 +03:00
Andrey Smirnov 876eeedb14 Update canonical order of fields in stanza to match what apt tools generate. #172 2015-01-19 21:17:58 +03:00
Andrey Smirnov fd502264a9 Install graphviz in Travis. #169 2015-01-13 22:16:30 +03:00
Andrey Smirnov b155eaa91c Merge branch 'lebauce-graph-api' 2015-01-13 22:15:37 +03:00
Andrey Smirnov a0d7ae28bf Simple tests for graph generation API. #169 2015-01-13 22:15:06 +03:00
Andrey Smirnov 2816647809 Allow to generate graph in formats supported by dot. #169 2015-01-13 19:22:24 +03:00
Andrey Smirnov 67ce828eeb Lock collections before building graph. #169 2015-01-13 19:17:19 +03:00
Andrey Smirnov 427c42f4b8 Move graph into deb/ package, passing collection factory. #169 2015-01-13 19:10:39 +03:00
Andrey Smirnov b50cb70a0e Merge branch 'graph-api' of https://github.com/lebauce/aptly into lebauce-graph-api 2015-01-13 19:04:07 +03:00
Andrey Smirnov c832a5cdc4 Add missed file. #167 2015-01-13 18:50:09 +03:00
Andrey Smirnov 1bd625f17f Merge branch 'lebauce-api-version' 2015-01-13 18:49:57 +03:00
Andrey Smirnov a0fa0becc2 Add system test on version API. #167 2015-01-13 18:49:32 +03:00
Andrey Smirnov d489694ea9 Refactoring: simplify version generation. Rename API to /api/version. #167 2015-01-13 18:47:41 +03:00
Andrey Smirnov 982b5dc886 Merge branch 'api-version' of https://github.com/lebauce/aptly into lebauce-api-version 2015-01-13 18:44:10 +03:00
Andrey Smirnov 1ddaecfb94 New Debian version: update system tests. 2015-01-12 20:14:18 +03:00
Andrey Smirnov 129c34806c Pass --no-use-agent when running with --passphare flag. #162 2015-01-12 20:00:18 +03:00
Sylvain Baubeau 6c7f3b3bbd Add /api route to show API version #116 2015-01-12 10:56:54 +01:00
Sylvain Baubeau 38cb6bd133 Graph REST API #116 2015-01-12 10:56:28 +01:00
Andrey Smirnov 98ca0cdf33 Publish repo REST API. #116 2015-01-07 16:11:34 +03:00
Andrey Smirnov 6e32e3dcf4 Fix some variable shadowing. 2015-01-07 16:11:13 +03:00
Sylvain Baubeau 6a1a871dda Lock snapshot collection before sorting 2015-01-06 18:06:59 +01:00
Sylvain Baubeau 6bc7048166 Fix wrong method comment 2015-01-06 18:06:53 +01:00
Andrey Smirnov 87fbd5201b Add Chris Read to authors. 2015-01-05 14:24:47 +03:00
Andrey Smirnov dcf5798229 Merge remote-tracking branch 'cread/gocheck' 2015-01-05 14:23:38 +03:00
Andrey Smirnov 382ad10cf7 Update man page. 2014-12-28 13:44:24 +03:00
Antonio Santos ddb2dd7eb6 Fix typo 2014-12-26 17:51:51 +01:00
Andrey Smirnov 9b1b43c8b4 Add custom JSON representation of PublishedRepo. #116 2014-12-26 00:58:23 +03:00
Andrey Smirnov ee7d84205b Update system test. 2014-12-23 01:19:37 +03:00
Andrey Smirnov 93e8e18ca6 Document lock order acquisition. #116 [ci skip] 2014-12-23 00:59:29 +03:00
Andrey Smirnov d586f31247 Move ParsePrefix into common code. #116 2014-12-23 00:50:28 +03:00
Sylvain Baubeau dd9fc8e40e Allow API creation of snapshots using package references 2014-12-18 18:17:43 +01:00
Sylvain Baubeau d847cba870 Make repos and snapshots API return JSON objects for packages when asked 2014-12-18 18:16:35 +01:00
Sylvain Baubeau d983e10d08 Add snapshots API test suite 2014-12-18 16:33:15 +01:00
Sylvain Baubeau a6fc65ff4e Sanitize snapshots API return codes 2014-12-18 12:05:16 +01:00
Sylvain Baubeau 85f38cd739 Allow setting description on snapshots using API 2014-12-18 11:55:54 +01:00
Sylvain Baubeau acde6ff2b2 Fix wrong methods comments 2014-12-18 11:55:45 +01:00
Sylvain Baubeau c733129de9 Add search API for packages in snapshots 2014-12-18 11:16:49 +01:00
Sylvain Baubeau e138212593 Make files hash a type for proper JSON serialization 2014-12-18 11:14:56 +01:00
Sylvain Baubeau 64ef342121 Add /snapshots/ API. #116
Implements :
	- CRUD
	- Difference between snapshots
2014-12-15 10:47:35 +01:00
Sylvain Baubeau 66c9bb86f5 Move command line snapshot sorting to common snapshot code 2014-12-15 10:44:46 +01:00
Andrey Smirnov 923e2e1e50 Update system test. 2014-12-12 09:27:17 +03:00
Andrey Smirnov 0e552eda55 When merging reflists, never allow duplicate (name, version, arch) tuples. #154
Even for conficting packages.
2014-12-11 21:56:50 +03:00
Andrey Smirnov ba32d16c8a Fix system test. 2014-12-09 11:44:40 +03:00
Andrey Smirnov 4320144024 Merge pull request #155 from davewongillies/master
Typo in an error message fix
2014-12-09 11:26:18 +03:00
David Gillies 2564564601 Typo in an error message fix 2014-12-09 15:57:26 +11:00
Andrey Smirnov f228ad811b Accept MD5 in package from 'MD5Sum' as well. #151 2014-11-29 16:18:23 +03:00
Andrey Smirnov 5fe442f191 Fix issue with missing comma in JSON. 2014-11-28 22:57:01 +03:00
Andrey Smirnov 50c4aba9ab Upgrade to latest goleveldb. #150 2014-11-28 22:56:28 +03:00
Andrey Smirnov b3627738c2 Add Gitter.im badge. [ci skip] 2014-11-27 01:25:58 +03:00
Andrey Smirnov eec6743fe4 Use ${HOME}, fix test. #146 2014-11-27 00:55:28 +03:00
Andrey Smirnov 42bf2f5e98 Fix one more system test. #147 2014-11-27 00:53:31 +03:00
Andrey Smirnov 35b9a8ea91 Don't sign repo in test. #146 2014-11-27 00:34:04 +03:00
Andrey Smirnov 26c0502307 Introduce new flag -force-components to aptly mirror create to ignore
components in Release file while doing checks. #147
2014-11-27 00:32:34 +03:00
Andrey Smirnov 5fa487e2dc Add gitter.im notification. 2014-11-26 23:51:21 +03:00
Andrey Smirnov d9c62780c2 When doing db cleanup, consider package references stored in PublishedRepos
of local repo publishes. #146
2014-11-26 23:41:18 +03:00
Andrey Smirnov 8c54e15a11 Add information about nightly builds, go 1.3. [ci skip] 2014-11-25 23:46:58 +03:00
Andrey Smirnov cc2cc16004 Fix system test. 2014-11-18 12:16:47 +03:00
Andrey Smirnov 726f12c537 Repos APIs: searching for packages, adding and deleting packages from the repo. #116 2014-11-18 00:50:59 +03:00
Andrey Smirnov f1c235f5c5 Fix error message. 2014-11-16 14:01:46 +03:00
Andrey Smirnov 9072ba5981 Revert part of test that shouldn't have been comitted yet. #140 2014-11-14 00:25:40 +03:00
Andrey Smirnov 7beb90d4fc Strings() for PackageList: turning list into sequence of package Ids. #116 2014-11-14 00:19:58 +03:00
Andrey Smirnov 61e22743af Fix for stripping part with slashes from component names. #140
Now aptly won't strip by default, but if distribution contains slash (like 'wheezy/updates')
and component starts with last part of distribution ('updates/main'), it would be stripped.
2014-11-14 00:13:58 +03:00
Andrey Smirnov 036baa2264 Exclude "source" architecture from list of Release architectures. #140 2014-11-14 00:01:41 +03:00
Andrey Smirnov ccd8c2551f Update location of go tool cover. 2014-11-11 01:25:52 +03:00
Andrey Smirnov 74f9787884 Implementation of upload file to local repo APIs. #116 2014-11-11 01:12:52 +03:00
Andrey Smirnov 83af66a8f6 Refactoring: move package files importing code to common deb from command. #116 2014-11-06 00:13:16 +03:00
Chris Read daf887e54f Upgrade gocheck 2014-11-05 13:27:15 -06:00
Andrey Smirnov 552b11e28d Workaround for '+' escaping in Amazon S3. #130 2014-11-05 02:46:01 +03:00
Andrey Smirnov 80a88a2248 Merge branch 'shadeslayer-master' 2014-10-26 19:54:11 +03:00
Andrey Smirnov 4c1d6d1463 Fix operator precedence. #131 2014-10-26 19:53:49 +03:00
Rohan Garg 140a11c04a Continue even when a server replies with 403
Amazon S3 replies with a 403 when accessing files that don't exist,
while this should be fixed at Amazon, we can workaround it in aptly
for the moment.
2014-10-25 21:41:57 +02:00
Andrey Smirnov 7be2ef8b85 Don't fallback between compression methods available unless we get strictly HTTP 404. #129 #125
Prior to that, some real errors could have been masked away by that fallback.
2014-10-24 08:51:04 +04:00
Andrey Smirnov d2d21c3df7 Update system test. 2014-10-23 11:03:19 +04:00
Andrey Smirnov f81a91bde9 First step of aptly repo add refactoring: extract collection of files. #116 2014-10-23 01:07:53 +04:00
Andrey Smirnov 7efd0de67c Fix shadowing of variable. #116 2014-10-23 00:57:27 +04:00
Andrey Smirnov 6a9db17460 Stubs for API calls. #116 2014-10-23 00:41:24 +04:00
Andrey Smirnov b1053826e3 Debian 7.7 has been released, update tests. 2014-10-20 12:22:19 +04:00
Andrey Smirnov 18953c1c90 Merge pull request #128 from rra/master
Allow variation of formatting of Debian control.tar.gz
2014-10-20 10:12:23 +04:00
Russ Allbery aeb85a1b3c Allow variation of formatting of Debian control.tar.gz
While all the normal Debian package building tools create a
control.tar.gz archive member that contains files with a leading
"./" path element, such as "./control", dpkg and other archive tools
like reprepro are happy with omitting the "./" path element and
having files like "control".  Allow for either for compatibility
with weird packages that people may want to import into aptly.
2014-10-17 13:36:58 -07:00
Andrey Smirnov 0a6d57ea1a Fixing system tests. 2014-10-17 02:03:48 +04:00
Andrey Smirnov aa4dee3c60 Merge branch 'queeno-fix_truncation_bug' 2014-10-17 00:55:09 +04:00
Andrey Smirnov 9fbe33b356 System test for file override from pool. #127 2014-10-17 00:54:42 +04:00
Andrey Smirnov 2a9871e2e9 We should never ever overwrite files in package pool. #127 2014-10-17 00:54:15 +04:00
Simon Aquino 951b6e9004 Test to avoid published file truncation when added to repo
This test will make sure that when a published file is added to repo, it
doesn't get truncated.
2014-10-16 16:57:50 +01:00
Simon Aquino 2173d3ab65 Fix file truncation bug
This commix prevents files from being truncated when attempting to add
a hard-linked package which already exists inside the pool directory.
2014-10-16 15:01:36 +01:00
Andrey Smirnov 9c834f410c API for file upload. #116 2014-10-16 00:04:50 +04:00
Andrey Smirnov eef44f5cd5 Correctly set config override. #123
Without that, config override gets set in parent class propagating
to all tests.
2014-10-14 19:27:47 +04:00
Andrey Smirnov aa77ea2835 Test for config show. #123 2014-10-14 18:28:53 +04:00
Andrey Smirnov 119bb0195b Merge branch 'queeno-add_config_show_command-2' 2014-10-14 18:26:45 +04:00
Andrey Smirnov 017dca57ed Re-generate man page. #123 #96 2014-10-14 18:26:27 +04:00
Andrey Smirnov 88351503b0 Fix misprint. #96 2014-10-14 18:26:12 +04:00
Andrey Smirnov 37b2d49aea A bit more style fixes. #123 2014-10-14 18:22:57 +04:00
Andrey Smirnov 0afb1f4306 Style fixes. 2014-10-14 18:20:09 +04:00
Andrey Smirnov 6ac0658478 Merge branch 'add_config_show_command' of https://github.com/queeno/aptly into queeno-add_config_show_command-2 2014-10-14 18:17:21 +04:00
Andrey Smirnov 50c8e35a90 Re-enable task run command. #96 2014-10-14 00:47:17 +04:00
Andrey Smirnov d6c3389d7c First steps towards /files/ API (file upload). #116 2014-10-14 00:47:17 +04:00
Andrey Smirnov 6b83213cf4 Add UploadPath to Context. #116 2014-10-14 00:47:17 +04:00
Simon Aquino 24927f9a29 config_show should output pretty json
This commit changes the output of aptly config show to be pretty json
rather than YAML.
2014-10-13 18:29:45 +01:00
Simon Aquino ecbb9ad20c Fixed failing system test
Added config command to aptly main output.
2014-10-13 15:58:41 +01:00
Simon Aquino 81e9189853 Config show now outputs a clean data structure 2014-10-13 15:58:41 +01:00
Simon Aquino 8efb7903b2 Added map to to_string and tabs to config_show 2014-10-13 15:58:41 +01:00
Simon Aquino c1995beff1 Config_show prints out strings,structs,ints,bools
More development for aptly config show
2014-10-13 15:58:41 +01:00
Simon Aquino 192152b215 Config command created - config show started 2014-10-13 15:58:41 +01:00
Andrey Smirnov 972e8c1373 Merge pull request #124 from queeno/queeno_username_change
Change simonaquino's github username to queeno
2014-10-11 20:57:00 +04:00
Simon Aquino c501fc63f8 Change simonaquino's github username to queeno
simonaquino changed its github username to queeno.
2014-10-11 16:39:48 +00:00
Andrey Smirnov bf08ad800f Attempt to build in Travis with python & virtualenv. #116 2014-10-10 19:28:04 +04:00
Andrey Smirnov ebc223a895 System tests for API. #116 2014-10-10 18:35:39 +04:00
Andrey Smirnov 01b1f23d6b Merge branch '122-gpg-batch' 2014-10-10 17:54:50 +04:00
Andrey Smirnov 6b08b64d62 Add latest contributors. 2014-10-10 17:53:49 +04:00
Andrey Smirnov 89bb20388f Fix unit tests. #122 2014-10-10 17:52:09 +04:00
Andrey Smirnov 9857789204 Regenerate man page. #122 2014-10-10 17:52:00 +04:00
Andrey Smirnov 6d1efe0200 Docstrings, gofmt. #122 2014-10-10 17:50:43 +04:00
Andrey Smirnov a85aa11ecd Update flag description/include it everywhere. #122 2014-10-10 17:50:08 +04:00
Andrey Smirnov 27ea769ad3 Merge branch 'master' of https://github.com/freehck/aptly into 122-gpg-batch 2014-10-10 17:44:55 +04:00
Andrey Smirnov 523d0d0945 Library for API system tests. #116 2014-10-10 17:43:04 +04:00
Andrey Smirnov 53f7fef4cf Force flag for API repos delete. #116 2014-10-10 17:18:47 +04:00
Dmitrii Kashin b590efa45f Merge branch 'master' of https://github.com/freehck/aptly 2014-10-10 04:22:43 +04:00
Dmitrii Kashin 59055d7fbd Add batch flag for publish commands 2014-10-10 04:04:44 +04:00
Ivan Kurnosov 22bcacf143 Typo in a error message fix
`s/Unknwon/Unknown/`
2014-10-09 16:19:15 +13:00
Andrey Smirnov 877109b3b7 Don't build under go1.2 (gin incompatible), use go 1.3.3. #116 2014-10-08 17:27:44 +04:00
Andrey Smirnov 10056b8571 Add first /repos/ API, command api serve. #116 2014-10-08 16:19:15 +04:00
Andrey Smirnov ac983ff65d Add RwMutexes to all collections. #116 2014-10-08 16:16:07 +04:00
Andrey Smirnov 2ed76f1e4c Add method to convert reflist to list of strings. #116 2014-10-08 16:15:54 +04:00
Andrey Smirnov cb6b18acfe Add missing dependency to github.com/jlaffaye/ftp. 2014-10-08 16:15:12 +04:00
Andrey Smirnov 3cd8c5adab Add gin and its dependencies. #116 2014-10-08 16:13:26 +04:00
Andrey Smirnov dd7b7b5f20 Refactor RefList.FilterLatestRefs to be method instead of standalone func. 2014-10-07 19:29:01 +04:00
Andrey Smirnov 1f6880fcad Disable api command, it's not committed yet. #117 2014-10-07 15:26:03 +04:00
Andrey Smirnov c8d9bef686 Close database before writing first byte to stdout. #117
Should fix conflict with commands like 'aptly xxx list -raw | xargs -n 1 aptly xxxx'
2014-10-07 15:20:27 +04:00
Andrey Smirnov 8a787d2c35 Refactor by separating AptlyContext into separate package. #116 2014-10-06 21:54:15 +04:00
Andrey Smirnov 159608cef3 Make LocalRepo JSON-serializable. #116 2014-10-06 21:08:46 +04:00
Andrey Smirnov 52c5934eb6 Lock down CollectionFactory. #116 2014-10-06 19:55:46 +04:00
Andrey Smirnov e4b9e974d2 bytes.Equal should be faster than bytes.Compare. 2014-10-06 15:17:25 +04:00
Andrey Smirnov d541b4f137 Version bump to 0.9~dev. 2014-10-06 15:07:31 +04:00
Andrey Smirnov eece643ea5 Include bash completion into source tarball. 2014-10-05 00:40:47 +04:00
Andrey Smirnov 14bd443d4d Disable support for aptly task for now. #96 2014-10-03 12:52:46 +04:00
Andrey Smirnov 9109c60c43 Version bump to 0.8. 2014-10-03 12:20:21 +04:00
Andrey Smirnov 445ecbe8f3 Update man page. #45 #114 2014-10-03 11:06:14 +04:00
Andrey Smirnov 27de979733 More comments. #45 #114 2014-10-03 11:02:31 +04:00
Andrey Smirnov ad11053412 Support for locking, unlocking, interruption, cleanup. #45 #114 2014-10-03 01:34:22 +04:00
Andrey Smirnov a356f3dff9 Marking RemoteRepo as being updated, with worker PID, checking for locks. #45 #114 2014-10-03 01:32:19 +04:00
Andrey Smirnov 1042894123 Abort downloader on shutdown, don't wait for downloads to finish. #45 #114 2014-10-03 01:31:38 +04:00
Andrey Smirnov 43eb993160 Don't panic on double re-open/close, ignore it. #45 #114 2014-10-03 01:20:26 +04:00
Andrey Smirnov d190ffd39a Update goleveldb to the latest version. 2014-10-03 01:19:36 +04:00
Andrey Smirnov 93c1c7aaab Support for closing and re-opening database. #45 #114 2014-10-02 21:41:58 +04:00
Andrey Smirnov 3e5ba27cb7 Ability to re-open db after close. #45 #114 2014-10-02 21:13:56 +04:00
Andrey Smirnov cd3b24799a Use less files for the download test. 2014-10-02 21:00:00 +04:00
Andrey Smirnov a0870f6726 Refactor mirror download code, split it into separate methods. #45 #114 2014-10-02 19:30:37 +04:00
Andrey Smirnov d45b456334 Update test. #26 2014-10-02 14:32:52 +04:00
Andrey Smirnov 91c753ad2f Add URL to all download errrors, so that they're easier to understand. #26 2014-10-02 14:12:43 +04:00
Andrey Smirnov 40509f73b3 Update system test. 2014-10-02 10:55:57 +04:00
Andrey Smirnov 1daa076d65 Don't allow '/' in distribution name, auto-replace '/' with '-' while guessing. #110 2014-10-01 22:59:05 +04:00
Andrey Smirnov aeae6009c4 Introduce plusWorkaround: generate copy of file with '+' -> ' ' to workaround S3/apt bug. #98 2014-10-01 21:32:56 +04:00
Andrey Smirnov 8049d69793 Update goamz to version with fix for multidel, re-enable S3 delete test. 2014-10-01 19:41:32 +04:00
Andrey Smirnov 8aa1954ba7 Support for custom storage class and encryption method. #105 2014-10-01 19:16:15 +04:00
Andrey Smirnov a02a90a3d8 Remove validate argument, not supported by Travi CI boto version. 2014-10-01 18:39:35 +04:00
Andrey Smirnov f303aabf26 Another way to install boto. 2014-10-01 18:17:55 +04:00
Andrey Smirnov 735cbac60d Install boto library for system tests. 2014-10-01 18:09:51 +04:00
Andrey Smirnov 5d69871ca4 Tests for publishing to Amazon S3. 2014-10-01 17:48:51 +04:00
Andrey Smirnov 1afbae8f7c Add AWS credentials. 2014-10-01 17:29:40 +04:00
Andrey Smirnov 1ed647e1b0 List storage & prefix in publish list. #113 2014-10-01 00:44:01 +04:00
Andrey Smirnov 01b8e9eda5 Fix system tests. #108 2014-10-01 00:31:10 +04:00
Andrey Smirnov f43d514804 Merge branch '108-udebs' 2014-09-30 23:29:27 +04:00
Andrey Smirnov 7e8f692b2c Use better words. #108 2014-09-30 23:24:51 +04:00
Andrey Smirnov 4b50f817d7 Fix system tests. #108 2014-09-30 23:09:57 +04:00
Andrey Smirnov e123e4dfac .udebs are supported now. #108 2014-09-30 21:53:19 +04:00
Andrey Smirnov 4fb09d9e85 Update man page. #108 2014-09-30 21:52:25 +04:00
Andrey Smirnov d9b23167bc Test on publishing repo with .udebs. #108 2014-09-30 21:51:38 +04:00
Andrey Smirnov 2c84faaf8d System test for repo adding .udebs. #108 2014-09-30 21:26:28 +04:00
Andrey Smirnov 6514b87e3e Add keyring for publish. #108 2014-09-30 21:25:52 +04:00
Andrey Smirnov bd34ba4088 Pregenerate all udebs indexes if at least one udeb has been discovered. #108 2014-09-30 21:11:01 +04:00
Andrey Smirnov fae6e977c3 System tests for publishing snapshot from mirror with .udebs. #108 2014-09-30 19:40:16 +04:00
Andrey Smirnov 2ae34cd873 Use Package.MatchesArchitecture instead of homegrown function. #108 2014-09-30 19:39:31 +04:00
Andrey Smirnov b365e5e0b2 System test: regular publish doesn't generate debian-installer files. #108 2014-09-27 02:15:08 +04:00
Andrey Smirnov e171f90fd5 Restore ${HOME} links. #108 2014-09-27 01:56:35 +04:00
Andrey Smirnov db499f872d Major refactoring of the publishing method, now uses helper indexFile(s). #108 2014-09-27 01:39:02 +04:00
Andrey Smirnov 8f9944117c Update tests on show mirror format change. #108 2014-09-25 23:38:45 +04:00
Andrey Smirnov ea399a335a Update tests on show mirror format change. #108 2014-09-25 23:37:11 +04:00
Andrey Smirnov 976ddb5ff9 Fix tests on arguments help. #108 2014-09-25 23:34:53 +04:00
Andrey Smirnov 7d8600b840 Add support for mirroring, showing, and editing remote repos with .udebs. #108 2014-09-25 22:12:59 +04:00
Andrey Smirnov 7ad1bb387b Support for .udeb downloads from remote mirrors. #108 2014-09-25 19:34:16 +04:00
Andrey Smirnov 2fbf465fbf Support for .udeb in deb.Package. #108 2014-09-25 19:31:21 +04:00
Andrey Smirnov fa786332de Allow changing "download sources" option for the mirror. #109 2014-09-22 19:36:48 +04:00
Andrey Smirnov 5e1bd0ff0e Correctly parse boolean flags in combination with config options. #104
Config option should act as default, while flag should override it only if set.
2014-09-22 13:41:26 +04:00
Andrey Smirnov 9c92b81706 Remove -dry-run flag for aptly snapshot filter, as it is useless. #82 2014-09-22 01:54:35 +04:00
Andrey Smirnov 144ccbf809 Make order of configuration file loading clear. 2014-09-21 00:55:23 +04:00
Andrey Smirnov a11805efb4 Update to goleveldb with misspellings fixed. 2014-09-20 21:46:45 +04:00
Andrey Smirnov 5b6cea2d62 Fix system test after spelling fixes. 2014-09-20 21:39:08 +04:00
Andrey Smirnov d4699a3b24 Fix system test. 2014-09-20 18:21:32 +04:00
Andrey Smirnov 09a695a128 Fix spelling mistakes. 2014-09-20 18:10:13 +04:00
Andrey Smirnov ec4d2bcefe Fix spelling mistakes found by lintian. 2014-09-20 18:09:47 +04:00
Andrey Smirnov 3040aceb7f Update goleveldb to the version which reduces memory usage significantly. 2014-09-20 18:03:24 +04:00
Andrey Smirnov 61d8639a8a System tests for aptly snapshot filter. #82 2014-09-01 22:25:17 +04:00
Andrey Smirnov b47754a106 Update man page. #82 2014-09-01 22:11:07 +04:00
Andrey Smirnov 1b08b7311f Implementation of command aptly snapshot filter. #82 2014-09-01 22:09:58 +04:00
Andrey Smirnov 0130fc0392 Add -force-replace flag to repo aptly add to replace conflicting packages. #83 2014-09-01 17:59:29 +04:00
Andrey Smirnov de32595d29 Fix test. #94 2014-09-01 16:03:35 +04:00
Andrey Smirnov 95e5fdd34a Update README. [ci skip] 2014-09-01 15:30:31 +04:00
Andrey Smirnov a05f00d9f1 Regenerate man page. #94 2014-09-01 15:13:54 +04:00
Andrey Smirnov 97158ef37b Support for --passphrase & --passphrase-file arguments on publishing. #94 2014-09-01 15:13:07 +04:00
Andrey Smirnov f01ac06d97 Remove extra whitespace [ci skip] 2014-08-30 01:23:48 +04:00
Andrey Smirnov a549778754 Fix system tests. #48 2014-08-30 01:16:42 +04:00
Andrey Smirnov 47d952f712 System test for ftp:// download. #48 2014-08-29 19:46:58 +04:00
Andrey Smirnov 166f31c34d Regenerate man page. #48 2014-08-29 19:39:17 +04:00
Andrey Smirnov 4940fdc951 Note support of FTP. #48 2014-08-29 19:38:25 +04:00
Andrey Smirnov 7ae785f5a3 Implementation of ftp:// support for downloading. #48 2014-08-29 19:37:10 +04:00
Andrey Smirnov 09c8421648 Update man page. 2014-08-29 00:58:47 +04:00
Andrey Smirnov 6a2059150f Add Vincent Batoufflet to list of authors. 2014-08-29 00:53:56 +04:00
Andrey Smirnov 9b3dfe920d Merge branch 'vbatoufflet-mirror-edit-arch' 2014-08-29 00:53:15 +04:00
Andrey Smirnov 72f8e4ab61 Check architectures before applying arch change. #99 2014-08-29 00:52:47 +04:00
Andrey Smirnov 755944652f System tests for mirror edit with architectures. #99 2014-08-29 00:52:03 +04:00
Andrey Smirnov b29d42d023 Fix system test, use ${HOME}. #80 2014-08-29 00:46:59 +04:00
Andrey Smirnov f19ece776d Merge branch 'mirror-edit-arch' of https://github.com/vbatoufflet/aptly into vbatoufflet-mirror-edit-arch 2014-08-28 22:49:19 +04:00
Andrey Smirnov 839763c0b9 Command package show with tests. #80 2014-08-28 22:47:41 +04:00
Andrey Smirnov c56ecab06f Method PackageRefList.Has(). #80 2014-08-28 22:25:19 +04:00
Andrey Smirnov 02d86422a8 Fix tests by introducing stable sort. #80 2014-08-28 22:03:06 +04:00
Andrey Smirnov 65efe0cd2a System tests for aptly package search. #80 2014-08-28 21:44:41 +04:00
Andrey Smirnov 468b1f11b9 New command: package search to search whole package DB for matching packages. #80 2014-08-28 19:42:47 +04:00
Andrey Smirnov 608870265c New common interface for PackgeCollection & PackageList: PackageCatalog. #80 2014-08-28 19:41:30 +04:00
Andrey Smirnov ed03a7c69e New algorithm for dependency resolution, tests. #100 #81 2014-08-28 19:07:39 +04:00
Andrey Smirnov 5a42c60af4 Simplify and make more deterministic algorithm for dependency pulling. #100 2014-08-28 19:05:32 +04:00
Vincent Batoufflet f66302ef31 Add ability to edit mirror architectures 2014-08-26 23:22:51 +02:00
Andrey Smirnov 346a7bcce9 System tests for mirror, snapshot, repo search. #81 2014-08-27 00:04:01 +04:00
Andrey Smirnov 9bee7cdd08 Simplify dependency verification code. #81 2014-08-27 00:03:46 +04:00
Andrey Smirnov 74eee3496c Capture test results in prepared format. #81 2014-08-26 23:58:25 +04:00
Andrey Smirnov 3ef5429212 System tests for aptly mirror search. #81 2014-08-26 19:38:27 +04:00
Andrey Smirnov 3030e66d4c Fix -with-deps searching. #81 2014-08-26 19:25:02 +04:00
Andrey Smirnov 9ae5a5ffb2 Update system tests. #96 2014-08-26 02:03:58 +04:00
Andrey Smirnov 833d37d22c Update system tests. #81 2014-08-26 02:03:06 +04:00
Andrey Smirnov 03ec1f97a7 Fix after style fix. #96 2014-08-26 02:02:11 +04:00
Andrey Smirnov a2df51b40e Commands mirror/repo/snapshot search. #81 2014-08-26 02:01:11 +04:00
Andrey Smirnov ae906f525e Style fixes. #96 2014-08-26 01:09:49 +04:00
Andrey Smirnov b4a5a55cac Style fixes. #96 2014-08-26 01:06:33 +04:00
Andrey Smirnov 6003764ff5 Add more system tests. #96 2014-08-25 22:15:21 +04:00
Andrey Smirnov 099a82c816 Style fixes. #96 2014-08-25 22:06:25 +04:00
Andrey Smirnov ef992e2b44 Merge branch 'queeno-script_run_command' 2014-08-25 22:05:34 +04:00
Andrey Smirnov 68e600974d Refactoring: remove context switching, another way to catch panics, colored output. #96 2014-08-25 22:05:02 +04:00
Andrey Smirnov 39a1f0ec2d Use go1.3.1. 2014-08-25 21:22:55 +04:00
Andrey Smirnov 318fc5b7f4 Merge branch 'script_run_command' of https://github.com/queeno/aptly into queeno-script_run_command 2014-08-23 22:04:50 +04:00
Simon Aquino 72e54aa3d1 Fixed a bug with the context switching
The context switching wasn't really happening. Now the issue is fixed.
2014-08-16 23:32:38 +00:00
Simon Aquino 91ff904ac4 Adding filename flag to specify task run filename.
Just realised commands can not have any subcommands and therefore
consist of single words (for example serve or version). Hence I can't
assume that if len(args)==1 then the user has entered the filename.
I have created the filename flag so the user is forced to specify it
when they wish to run aptly tasks from files.
2014-08-16 22:13:24 +00:00
Simon Aquino b59471ad35 Added RunTask acceptance tests
Added a basic RunTask test to test the functionality of the new aptly
task run command. More to come...
2014-08-16 15:11:14 +00:00
Simon Aquino 6ff601f4a2 Making sure context is initialised before using it
Now checkong context is not nil before setting panicked = true
2014-08-16 14:33:36 +00:00
Simon Aquino 0c09bdedaa Fixed t03_help:MainTest failing due to new cmd 2014-08-16 14:22:04 +00:00
Simon Aquino dfc1f27d4c Better wording for task run message. 2014-08-16 14:18:35 +00:00
Simon Aquino 005cee572e Aptly script has now become aptly task
It makes more sense. Multiple lines of aptly commands can now be called
'aptly tasks' which could potentially be automated, in the future?
2014-08-16 14:14:56 +00:00
Simon Aquino 18e3ed5d64 aptly script run implementation
This new aptly command will allow to run multiple commands within a single
aptly command, running in a single thread. The commands can be included
in a text file or added to the aptly script run command in one line,
separated by comas ','.
If one command returns an error, then all the subsequent commands will
be skipped.
Each command output will appear on the console within coloured strings,
clearly stating which command is running and the start and the end of
the received output.
2014-08-16 13:55:13 +00:00
Simon Aquino 3c7696ef7e Refactored main.go
The main function - whuch runs aptly commands - has been taken out from
main.go and included to the cmd package. This is useful for the aptly
script run command, which should use that behaviour.
2014-08-16 13:55:13 +00:00
Simon Aquino b2779d7a88 Go-shellwords added to Gomfile
This packages allows us to parse shell commands. Useful for
script_run.go (later added)
2014-08-16 13:55:13 +00:00
Simon Aquino cdd34b4759 Added panicked attribute to context.go
This attribute is set to false during initalisation, and it's set to
true when error arises.
2014-08-16 13:55:13 +00:00
Simon Aquino 1f2ddca32b Add switchContext function to context.go 2014-08-16 13:55:13 +00:00
Simon Aquino df06dc356b Added script cmd in cmd.go 2014-08-16 13:54:46 +00:00
Simon Aquino b6c82f073f Added new script command 2014-08-16 10:17:44 +00:00
Andrey Smirnov 9a03b5f696 Update leveldb to the latest version. 2014-08-15 21:50:41 +04:00
Andrey Smirnov 047270540a Version bump: 0.8~dev 2014-08-06 23:34:51 +04:00
Andrey Smirnov eff3823edf Upload src-package to bintray. 2014-08-06 13:40:13 +04:00
Andrey Smirnov 9d02f057c6 Version bump: 0.7.1. 2014-08-06 02:24:43 +04:00
Andrey Smirnov 8387586cc8 Man page update: -force-overwrite flag. #90 2014-08-06 02:04:25 +04:00
Andrey Smirnov b433e7dad5 Workaround for broken time.Time encoding in msgpack with go < 1.2. #89
Decoding looses value of time.Time field, but that is not critical.
2014-08-06 01:56:13 +04:00
Andrey Smirnov dec4bdee71 Merge branch 'patch-1' of https://github.com/guilhem/aptly 2014-08-05 17:01:55 +04:00
Andrey Smirnov bb6593d21e Add -force-overwrite flag to publish update, switch, snapshot and repo commands. #90
Includes new and updated system tests.
2014-08-05 17:01:18 +04:00
Andrey Smirnov fe879acf9c Remove Makefile part specific for go1.1 2014-08-05 15:59:32 +04:00
Andrey Smirnov 5b8390c644 Uncomment and fix publish updat tests. 2014-08-05 15:58:47 +04:00
Andrey Smirnov d558791070 Add -force-overwrite command flag. #90 2014-08-05 15:47:38 +04:00
Andrey Smirnov 38ea595c9a Add forceOverwrite on the path to LinkFromPool. #90 2014-08-05 15:47:23 +04:00
Andrey Smirnov c03b7929d4 Fix line ends: system tests. 2014-08-05 15:44:12 +04:00
Andrey Smirnov d122ab6013 Fix system tests. 2014-08-05 15:27:39 +04:00
Andrey Smirnov a7b594d076 Drop support for go1.1 2014-08-05 15:26:41 +04:00
Andrey Smirnov e07bcf8e51 Fix style and add comments. #90 2014-08-05 14:50:15 +04:00
Andrey Smirnov da6d5b7cf8 Add 'force' to LinkFromPool method: overwrite file even if exists and different content. #90 2014-08-05 14:50:06 +04:00
Guilhem Lettron 15ef5c63c5 Add gobuild.io badge 2014-07-31 14:52:22 +02:00
Andrey Smirnov 625a38c578 aptly version 0.7 2014-07-29 00:33:53 +04:00
Andrey Smirnov 03a79ebe4c Update goamz to fixed version with signing & encoding. #15 2014-07-28 23:41:59 +04:00
Andrey Smirnov 60fa0aa68e Update command usage. 2014-07-28 19:17:21 +04:00
Andrey Smirnov 04bd9929e1 Update man page: S3, package queries. 2014-07-28 19:17:10 +04:00
Andrey Smirnov 8407e70347 Fix system tests. #15 2014-07-28 16:20:38 +04:00
Andrey Smirnov bf91744078 <endpoint> in command usage. #15 2014-07-28 15:03:55 +04:00
Andrey Smirnov 2c470c1535 Rename config option to endpoint. #15 2014-07-28 15:01:51 +04:00
Andrey Smirnov a18011bdc0 Update goamz: fixed bug with '+' in filenames. #15 2014-07-27 02:49:05 +04:00
Andrey Smirnov af8af0f3d7 Fix tests on aptly mirror edit. #63 2014-07-26 18:22:47 +04:00
Andrey Smirnov 89d26b7dc6 Man for aptly mirror edit. #63 2014-07-26 18:02:01 +04:00
Andrey Smirnov 8649ee3b37 Command aptly mirror edit with tests. #63 2014-07-26 17:59:46 +04:00
Andrey Smirnov b9c8a8d9da System tests for mirror/repo/snapshot rename commands. #63 2014-07-26 17:28:16 +04:00
Andrey Smirnov c5922737ed Man page updates for 'rename' commands. #63 2014-07-26 17:12:00 +04:00
Andrey Smirnov 772111ad26 Commands mirror/repo/snapshot rename. #63 2014-07-26 17:11:26 +04:00
Andrey Smirnov d7ef1a0c4b Allow saving snapshot without package refs loaded. #63 2014-07-26 17:09:47 +04:00
Andrey Smirnov bd221bf869 Sort dependencies. 2014-07-26 16:42:59 +04:00
Andrey Smirnov 0485a36de1 Add Recommends: dependency on bzip2. #84 2014-07-26 01:44:30 +04:00
Andrey Smirnov 77d6a10984 Implementation of Rename method for S3 PublishedStorage. #15 2014-07-26 01:11:23 +04:00
Andrey Smirnov 8015966663 Optimize package encoding/decoding a bit by reusing codec handle. 2014-07-25 16:46:52 +04:00
Andrey Smirnov 94114f2c3d Use idiomatic chan struct{} when there's nothing to send. 2014-07-24 01:25:59 +04:00
Andrey Smirnov 2906369a3b Reuse default HTTP transport options. 2014-07-24 01:17:58 +04:00
Andrey Smirnov 521c52f600 Remove unused field. 2014-07-24 01:15:01 +04:00
Andrey Smirnov 52bb33dc69 Fix bugs with prefix/storage parsing. #15 2014-07-22 00:27:49 +04:00
Andrey Smirnov 71d90947c9 Remove debugging output. #15 2014-07-22 00:27:38 +04:00
Andrey Smirnov b3a4936e06 Fix system tests. 2014-07-21 18:14:09 +04:00
Andrey Smirnov 237d25fe5b Fix issue with ETag/MD5 comparison, add extra info in error messages. #15 2014-07-21 17:43:42 +04:00
Andrey Smirnov de0954732a Style fix. 2014-07-21 17:43:35 +04:00
Andrey Smirnov 915b0d1697 Integrate PublishedRepos with storages & context. #15 2014-07-21 17:43:12 +04:00
Andrey Smirnov 6d026afc69 Support for multiple storages in PublishedRepository. #15 2014-07-21 17:19:13 +04:00
Andrey Smirnov 27a5578d30 Fix system tests. #15 2014-07-18 19:21:29 +04:00
Andrey Smirnov 96e878a2e0 Separate out LocalPublishedStorage interface. #15 2014-07-18 17:44:54 +04:00
Andrey Smirnov 7a7bb56557 Config options for S3 storage. #15 2014-07-18 17:37:08 +04:00
Andrey Smirnov 076ecd586f Fix style issues. #15 2014-07-17 18:09:13 +04:00
Andrey Smirnov c54406e29f First version of PublishedStorage for S3. #15 2014-07-17 18:05:38 +04:00
Andrey Smirnov b260b0010a Refactoring: add MD5 to LinkFromPool. #15 2014-07-17 18:04:56 +04:00
Andrey Smirnov fbf1bc14b7 Refactoring PublishedStorage interface: leave operations suitable for S3. #15 2014-07-17 00:54:44 +04:00
Andrey Smirnov f12cf935ba GPG signer shouldn't report full path name. #15 2014-07-17 00:53:36 +04:00
Andrey Smirnov 4e169c3d10 Update system tests on command help. #64 2014-07-16 14:10:40 +04:00
Andrey Smirnov ea2bfea2a3 Man page with new flags for aptly mirror create. #64 2014-07-16 13:58:07 +04:00
Andrey Smirnov cf4619784e System tests for mirror show & update with filters. #64 2014-07-16 13:58:02 +04:00
Andrey Smirnov 69ad2ccd84 System tests for mirror create with filter. #64 2014-07-16 13:45:46 +04:00
Andrey Smirnov fe1046a7a3 Support for filters in mirror create/update/show. #64 2014-07-16 13:28:11 +04:00
Andrey Smirnov ce1df9447d Support for filters in RemoteRepo: filtering mirror contents by query. #62 2014-07-16 02:27:29 +04:00
Andrey Smirnov 2a7a2de84a Allow empty source in PackageList.Filter. #64 2014-07-16 02:23:55 +04:00
Andrey Smirnov 238bdfad96 Add String() method for queries. 2014-07-15 21:49:30 +04:00
Andrey Smirnov 56d777af0a Introduce regexp query matching. 2014-07-15 19:00:06 +04:00
Andrey Smirnov a632469890 Support for quoted string arguments. 2014-07-14 23:20:31 +04:00
Andrey Smirnov 3601cc15ed Fix unit-tests for new $Architecture matching. 2014-07-14 19:45:27 +04:00
Andrey Smirnov 61cd4c6af1 Use special matcher for $Architecture, so that 'any' matches any arch. 2014-07-14 19:39:19 +04:00
Andrey Smirnov 401bb768d7 Fix bug with architectures query: it was always true. 2014-07-14 19:37:12 +04:00
Andrey Smirnov 5880d11899 Fix refactoring leftover bug. 2014-07-14 19:16:02 +04:00
Andrey Smirnov ed6e261bd0 Rewrite snapshot pull to use PackageList.Filter instead of homebrew algorithm. 2014-07-14 19:02:15 +04:00
Andrey Smirnov fb660efeb5 Make list sort really stable: if all properties match, use architecture
as sort key.
2014-07-14 18:51:07 +04:00
Andrey Smirnov 80de65f28d Fix system test. #62 2014-07-13 16:19:03 +04:00
Andrey Smirnov 9893e4af3d Add flag to control downlod limit in aptly mirror update. #62 2014-07-13 16:11:18 +04:00
Andrey Smirnov 7416cc403d Update system test config file. #62 2014-07-13 16:11:07 +04:00
Andrey Smirnov 83ceee1e3f Remove debug output. #62 2014-07-13 16:10:53 +04:00
Andrey Smirnov a54a366c95 New config setting: downloadSpeedLimit to limit download speed. #62 2014-07-13 15:47:44 +04:00
Andrey Smirnov fb1e28b91b Setting for downloader to limit download speed to specified level. #62 2014-07-12 23:56:32 +04:00
Andrey Smirnov 86206df58d Use flowcontrol library. #62 2014-07-12 23:55:54 +04:00
Andrey Smirnov 1d49a717b9 Unit-tests for queries. 2014-07-12 23:26:08 +04:00
Andrey Smirnov 3b0b0b76ec One more fix for Debian 7.6 2014-07-12 23:18:20 +04:00
Andrey Smirnov 904b9e101b Update tests: wheezy 7.6 released. 2014-07-12 21:54:13 +04:00
Andrey Smirnov 9fb8a0ea4b Capturing results for other command output. 2014-07-12 21:53:47 +04:00
Andrey Smirnov bc27c6e14d System test on using complex query when importing. 2014-07-12 18:02:57 +04:00
Andrey Smirnov ae3c98c210 Implementation for FieldQuery. 2014-07-12 18:02:33 +04:00
Andrey Smirnov 34f545b8cf Package index may not be indexed when schanning. 2014-07-12 18:02:13 +04:00
Andrey Smirnov d523d2b415 Package.GetField implementation for querying. 2014-07-12 18:00:13 +04:00
Andrey Smirnov e320ac31d5 Switch aptly repo move/copy/import/remove to use new filters based on queries. 2014-07-12 13:58:38 +04:00
Andrey Smirnov e08d44ff0a Fix bug with matching in Search method. 2014-07-12 13:58:22 +04:00
Andrey Smirnov 898870038a Rename s/Searchable/Fast/ 2014-07-12 00:30:53 +04:00
Andrey Smirnov c485cf41f7 PkgQueries, concept of 'Searchable', rewrite Filter using PackageQueries. 2014-07-12 00:15:33 +04:00
Andrey Smirnov d54ef1e921 Fix bug with special chars handling in strings, detect package key queries,
arch condition for dependency-like queries.
2014-07-12 00:14:49 +04:00
Andrey Smirnov b42fd71acf MatchesDependency should check on Provides: as well. 2014-07-11 16:30:41 +04:00
Andrey Smirnov ede5449440 Refactoring: move query tree to deb package. 2014-07-11 00:09:31 +04:00
Andrey Smirnov eef49516ef Fix bugs after style fixes. 2014-07-10 23:31:53 +04:00
Andrey Smirnov e745747370 Style fixes as suggested by tools. 2014-07-10 21:34:52 +04:00
Andrey Smirnov 7e5b2ae8f5 Bugfix: unit-test was creating dirs in source directory. 2014-07-10 21:31:12 +04:00
Andrey Smirnov ada3ae0094 Introduce query language (resembling reprepro syntax). 2014-07-10 21:28:02 +04:00
Andrey Smirnov d262a131cc Preparation for query matching: introduce Regexp + PatternMatch. 2014-07-10 21:16:30 +04:00
Andrey Smirnov f0e69144ed Merge branch 'simonaquino-pull_multiple_packages' 2014-07-10 00:56:16 +04:00
Andrey Smirnov a7cb40ee7a System tests for aptly snapshot pull -all-matches. #70 2014-07-10 00:55:53 +04:00
Andrey Smirnov 2a9b2f87f9 Update man page. #70 2014-07-10 00:48:24 +04:00
Andrey Smirnov 9af10bc422 Refactoring: simplification. #70 2014-07-10 00:43:42 +04:00
Andrey Smirnov bdbb5acb11 Remove 'allMatches' on version equal. #70 2014-07-10 00:20:28 +04:00
Andrey Smirnov 81d506b226 Dependencies should be matched for each package one by one. #70 2014-07-10 00:13:19 +04:00
Andrey Smirnov 1c30b2b9de Simplification: we are already able to search for all packages. #70 2014-07-10 00:10:53 +04:00
Andrey Smirnov 566604d4ba Revert changes related to NextVersion. #70
It would be done in a bit different way: by introducing powerful search queries.
2014-07-10 00:04:48 +04:00
Andrey Smirnov 58a57f2b2c Merge branch 'pull_multiple_packages' of https://github.com/simonaquino/aptly into simonaquino-pull_multiple_packages 2014-07-09 18:16:46 +04:00
Andrey Smirnov 20d744f398 Fix system tests (whitespace). 2014-07-08 13:12:10 +04:00
Andrey Smirnov 360981de4a Merge branch 'simonaquino-sort_snapshots_time' 2014-07-07 23:37:40 +04:00
Andrey Smirnov 79016f7f98 Slight refactoring, make wrong param real error. #73 2014-07-07 23:36:59 +04:00
Andrey Smirnov 1a92d8bfe9 Add --capture to auto-create 'gold' results when fail. 2014-07-07 23:29:36 +04:00
Andrey Smirnov d3707b4cfe Add system test on wrong --sort parameter. #73 2014-07-07 23:29:21 +04:00
Andrey Smirnov de1fa85127 Update man page. #73 2014-07-07 23:14:32 +04:00
Andrey Smirnov d9b35cea01 Allow running system tests by mask.
E.g. system/run.py 'ListSnapshot*'.
2014-07-07 23:13:49 +04:00
Andrey Smirnov b75b4d1488 Merge branch 'sort_snapshots_time' of https://github.com/simonaquino/aptly into simonaquino-sort_snapshots_time 2014-07-07 18:18:58 +04:00
Andrey Smirnov da55f18b0e Fix system tests. 2014-07-02 10:10:39 +04:00
Andrey Smirnov 165dd0053e Merge branch 'specify_long_tests' of https://github.com/simonaquino/aptly 2014-07-02 10:03:19 +04:00
Andrey Smirnov 22a4e6b67b Update goleveldb to fixed version (dropping data after compaction + recover). syndtr/goleveldb#53 2014-07-02 09:54:20 +04:00
Simon Aquino 20513e1c16 Specify individual long tests to run
The test script currently only allows to run all the long test. This
change will allow a user to specify individual long tests to run.
2014-07-01 00:51:58 +01:00
Simon Aquino b4ea963744 List snapshots by time: added integration tests
Added a couple of integration tests for the new list snapshot by
creation time feature.
2014-06-30 20:23:20 +01:00
Simon Aquino 429788db0f List snapshots by time
Users now have the choice of listing the snapshot by time as well as
name (default behaviour).
An additional flag has been added '--sort=' which controls the sort
method applied to the list produced by aptly snapshot list.

The possible values are:
--sort=name (default): sorts the snapshot list by name (lexicographic
order)
--sort=time: sorts the snapshot list in chronological order (oldest to
newest)
2014-06-30 19:25:13 +01:00
Andrey Smirnov 1e70e954da System test on pulling latest version by default. #67 2014-06-29 10:12:18 +04:00
Andrey Smirnov 319f3e6bb2 Updated fixture: sensu mirror, new mirror contents. 2014-06-29 09:56:30 +04:00
Andrey Smirnov 56915c4357 Fix order of Component & Archive fields. 2014-06-29 09:55:51 +04:00
Simon Aquino e1348ab88f Merge smira/master into pull_multiple_packages
Resolved conflicts arisen following smira's new commits into master.
2014-06-28 01:34:28 +01:00
Andrey Smirnov 026dc540d2 Merge branch 'simonaquino-deterministic_package_search' 2014-06-28 00:37:33 +04:00
Andrey Smirnov 44ce4c8a77 Insert into right position when adding as well. #67 2014-06-28 00:26:56 +04:00
Andrey Smirnov 980102462b Simplify Makefile. 2014-06-28 00:10:35 +04:00
Andrey Smirnov 86b0860463 Enable system tests fixture under go1.3 as well. #72 2014-06-27 23:53:37 +04:00
Andrey Smirnov e311d41dd7 Add Simon Aquino to authors. #67 2014-06-27 23:49:05 +04:00
Andrey Smirnov c3ce886990 Merge branch 'deterministic_package_search' of https://github.com/simonaquino/aptly into simonaquino-deterministic_package_search 2014-06-27 23:47:57 +04:00
Andrey Smirnov 959ecf696c Build under go1.3 as well. #72 2014-06-27 23:39:32 +04:00
Andrey Smirnov 48d01f5700 Fix reference to home directory. #24 2014-06-27 23:30:04 +04:00
Andrey Smirnov aeecc1ec91 System test on conflicting files when publishing. #65 2014-06-27 22:29:01 +04:00
Andrey Smirnov 685a4de4e7 Fix link to Ryan Uber's profile. 2014-06-27 22:18:03 +04:00
Andrey Smirnov 667efc2b90 Add AUTHORS files. 2014-06-27 21:44:43 +04:00
Simon Aquino 3cf281965b Implementation of all-matches functionality + tests
When performing an *aptly snapshot pull*, users might list dependency
versions that can potentially match multiple packages in the source
snapshot. However, the current implementation of the 'snapshot pull'
command only allows one package to be pulled from a snapshot at a time
for a given dependency.

The newly implemented all-matches flag allows users to pull all the
matching packages from a source snapshot, provided that they satisfy the
version requirements indicated by the dependencies.

The all-matches flag defaults to false and only produces the described
behaviour when it is explicitly set to true.
2014-06-27 03:36:03 +01:00
Simon Aquino e19a615641 In PackageList, sort the package version from latest to oldest
This enables us to return the latest version of a package when no version is specified rather than the oldest.
Therefore, we don't need to modify the search algorithm to return the latest version of a package.
2014-06-13 12:06:33 +01:00
Simon Aquino ff77fbf5d9 Sort PackagesList by name and version 2014-06-13 11:42:20 +01:00
Andrey Smirnov 856dd7021c System tests for publish update empty -> empty. #66 2014-06-11 20:42:13 +04:00
Andrey Smirnov ebc47f7d5d Add unit-test. #65 2014-06-11 20:32:45 +04:00
Andrey Smirnov 082fda62b5 Add unit-test. #66 2014-06-11 20:28:56 +04:00
Andrey Smirnov 3199fd85fb Fix publish updating (switching) for empty -> empty scenario. #66 2014-06-11 20:27:49 +04:00
Andrey Smirnov 0c6951fcd2 When linking, check that inode file matches if linking to same file. #65
Otherwise files from conflicting packages might override each other in the published
pool. This is explicitly POSIX-only.
2014-06-11 20:03:34 +04:00
Andrey Smirnov 35e57026ac Version bump: 0.7~dev 2014-06-11 20:02:50 +04:00
Andrey Smirnov 28e050c14e Fix style warnings. 2014-06-07 18:00:36 +04:00
Andrey Smirnov 8fb399026d Version bump for 0.6. 2014-06-07 17:58:33 +04:00
Andrey Smirnov e15f23962a Add missing files. #61 2014-06-07 17:27:24 +04:00
Andrey Smirnov 81af5882b9 Test on per-component/arch Release contents. #61 2014-06-07 17:19:02 +04:00
Andrey Smirnov e554d8befa Update tests with introduction of Release files under each arch/component. #61 2014-06-07 17:01:37 +04:00
Andrey Smirnov 17c564358a Refactoring, support for atomic updates and checksumming. #61 2014-06-07 16:44:54 +04:00
Andrey Smirnov 2e4c1c491e Merge branch 'debinst' of https://github.com/ryanuber/aptly into ryanuber-debinst
Conflicts:
	deb/publish_test.go
2014-06-07 16:34:39 +04:00
Andrey Smirnov e7230d9ee6 One more test fix. #36 2014-06-07 16:28:15 +04:00
Andrey Smirnov 0f1074a721 Fix tests. #36 2014-06-07 16:17:59 +04:00
Andrey Smirnov 17ed34fdaa System tests for aptly publish switch with multiple repositories. #36 2014-06-07 15:18:58 +04:00
Andrey Smirnov 17b320eac4 Sort components when doing string representation. #36 2014-06-07 15:18:36 +04:00
Andrey Smirnov e3a71c81e1 Both components should contain same architectures. #36 2014-06-06 03:05:22 +04:00
Andrey Smirnov bf900deb4b Remove whitespace. 2014-06-06 03:03:58 +04:00
Andrey Smirnov 142387311b Tests for aptly publish update and multi-component repository. #36 2014-06-06 03:00:44 +04:00
Andrey Smirnov 68fbb0cbb9 Fix tests, don't break pool contents. #36 2014-06-06 03:00:29 +04:00
Andrey Smirnov 835da9cb3c One more test fix: duplicate archs. #36 2014-06-06 02:37:09 +04:00
Andrey Smirnov 7cd0d394d4 Test fix: source shouldn't be part of Architectures: field. #36 2014-06-06 02:25:11 +04:00
Andrey Smirnov 2040be2f8a Deduplicate architectures when guessed. #36 2014-06-06 02:14:15 +04:00
Andrey Smirnov 1957c811e8 Tests for multi-component and aptly publish list/snapshot/repo, aptly serve. #36 2014-06-06 02:09:13 +04:00
Andrey Smirnov 2dae9b01a1 Grammar fix. #36 2014-06-06 02:09:00 +04:00
Andrey Smirnov 9a34b4ff1f Update commands to handle multiple components repositories. #36 2014-06-04 17:43:16 +04:00
Andrey Smirnov d218159455 Multiple connections for published repo. #36 2014-06-04 17:06:34 +04:00
Andrey Smirnov 8be6911238 Fix multiple component in aptly graph. #36 2014-06-04 17:04:05 +04:00
Andrey Smirnov 20a7c5ae2d A bit more unit-tests with multi-component repositories. #36 2014-06-04 01:07:32 +04:00
Andrey Smirnov e161313efa Fix aptly serve: correct components list. #36 2014-06-03 17:25:15 +04:00
Andrey Smirnov 7192049c16 Update to new PublishedRepo with multiple components. #36
Multiple component publishing doesn't work yet, but old features are working.
2014-06-03 17:09:00 +04:00
Andrey Smirnov ee71b93669 Major change: published repo now supports multiple components <> snapshots (local repos). #36 2014-06-03 14:34:26 +04:00
Andrey Smirnov 43ee735aa4 Fix error capitalization. 2014-06-03 14:33:19 +04:00
Andrey Smirnov da5b0c9a66 Test fixes. #55 2014-05-31 21:27:36 +04:00
Andrey Smirnov e1dbab6988 Allow publishing of empty snapshots and local repos. #55 2014-05-31 21:13:30 +04:00
Andrey Smirnov bcdfb7d99a Fix system tests: update test data. 2014-05-31 19:37:31 +04:00
Andrey Smirnov ac85a0897a Update gographviz to fixed version with XML escaping. #58 2014-05-30 22:08:11 +04:00
Ryan Uber 9a4543500c Handle source repos while creating dist release file 2014-05-29 22:24:19 -07:00
Ryan Uber b0f9a4a419 Added tests for Release file in distribution directory 2014-05-29 22:07:59 -07:00
Ryan Uber 71ea2be6c1 deb: added Release file to each individual arch dir for d-i. 2014-05-29 14:04:06 -07:00
Andrey Smirnov b717caeda4 Make 'Package:' line field first when serializing stanza. #49 2014-05-30 00:43:19 +04:00
Andrey Smirnov fcc283bdb1 Regenerate man page (-no-remove for aptly snapshot merge). #57 2014-05-30 00:05:15 +04:00
Andrey Smirnov d3d41dd1c9 Add missing files. #57 2014-05-29 20:49:35 +04:00
Andrey Smirnov c72ef05a2a Support for -no-remove while merging snapshots. #57 2014-05-29 18:05:52 +04:00
Andrey Smirnov a1e360b07b Conflict detection for packages in one list. #60 2014-05-29 18:01:07 +04:00
Andrey Smirnov 90bba977d7 Introduce Package.ShortKey(): packages should have no conflict on that key in one list. #60 2014-05-29 18:00:14 +04:00
Andrey Smirnov dc248c5603 Fix diff algorithm for reflists with duplicate entries. #57 2014-05-29 17:58:59 +04:00
Andrey Smirnov f007465d18 Change the way package key works: now it includes FilesHash. #60
Now duplicate packages (the same name/version/arch) but with different set of files
would be handled as separate entities.
2014-05-29 16:49:05 +04:00
Andrey Smirnov 869e83713d Remove debugging output. #51 2014-05-29 00:49:26 +04:00
Andrey Smirnov 7b9e3429fd Use gographviz escaping instead of homegrown escape function. #51 #58
It doesn't fix the bug #58, waiting for maintainer of gographviz to
accept the patch: https://code.google.com/p/gographviz/issues/detail?id=2
2014-05-29 00:47:37 +04:00
Andrey Smirnov 5b75dbc481 Fix system tests. #54 2014-05-18 00:16:00 +04:00
Andrey Smirnov d96839f99d Fix unit tests. #54 2014-05-17 22:47:47 +04:00
Andrey Smirnov 8b2920d5dd aptly repo add now exists with non-zero exit code if some files fail to add. #54 2014-05-17 22:23:20 +04:00
Andrey Smirnov 4240b134e6 Fix spelling. 2014-05-17 22:08:31 +04:00
Andrey Smirnov 1d31a5c25f Don't use fixed cap, as it might be more than length. #53 2014-05-16 00:29:43 +04:00
Andrey Smirnov 05a42f4cba aptly exits with 2 on command/flag parse error. #52 2014-05-16 00:22:51 +04:00
Andrey Smirnov 2cbb486f6b Update tests with new Varnish repo. 2014-05-13 13:39:14 +04:00
Andrey Smirnov d0ff11390b Revert "Version bump for 0.5.1."
This reverts commit b5d025f141.
2014-05-13 12:29:13 +04:00
1194 changed files with 992369 additions and 575224 deletions
+10
View File
@@ -0,0 +1,10 @@
.go/
.git/
obj-x86_64-linux-gnu/
obj-aarch64-linux-gnu/
obj-arm-linux-gnueabihf/
obj-i686-linux-gnu/
unit.out
aptly.test
build/
dpkgs/
+7
View File
@@ -0,0 +1,7 @@
[flake8]
max-line-length = 240
ignore = E126,E241,E741,W504
include =
system
exclude =
system/env
+1
View File
@@ -0,0 +1 @@
github: aptly-dev
+14
View File
@@ -0,0 +1,14 @@
<!--- Provide a general summary of the issue in the Title above -->
## Detailed Description
<!--- Provide a detailed description of the change or addition you are proposing -->
## Context
<!--- Why is this change important to you? How would you use it? -->
<!--- How can it benefit other users? -->
## Possible Implementation
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
+27
View File
@@ -0,0 +1,27 @@
Fixes #
## Requirements
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
<!--
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)
- [ ] bash completion updated (if applicable)
- [ ] documentation updated
- [ ] author name in `AUTHORS`
+378
View File
@@ -0,0 +1,378 @@
name: CI
on:
pull_request:
push:
tags:
- 'v*'
branches:
- 'master'
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-tests
mkdir -p out/coverage
mv unit.out out/coverage/
- uses: actions/upload-artifact@v4
with:
name: unit-tests-coverage
path: out/
test:
name: "System Test"
runs-on: ubuntu-22.04
continue-on-error: false
timeout-minutes: 30
env:
NO_FTP_ACCESS: yes
BOTO_CONFIG: /dev/null
GO111MODULE: "on"
GOPROXY: "https://proxy.golang.org"
steps:
- name: "Install Test Packages"
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8 faketime
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
# fetch the whole repo for `git describe` to work
fetch-depth: 0
- name: "Run flake8"
run: |
make flake8
- name: "Read Go Version"
run: |
gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod)
echo "Go Version: $gover"
echo "GOVER=$gover" >> $GITHUB_OUTPUT
id: goversion
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: ${{ steps.goversion.outputs.GOVER }}
- name: "Install Azurite"
id: azuright
uses: potatoqualitee/azuright@v1.1
with:
directory: ${{ runner.temp }}
- name: "Run Benchmark"
run: |
mkdir -p out/coverage
COVERAGE_DIR=$PWD/out/coverage make bench
- name: "Run System 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
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 coverage/*.out > coverage.txt
awk 'FNR==1 && NR!=1 {next} {print}' coverage/*.out > coverage.txt
- name: "Upload Code Coverage"
if: github.actor != 'dependabot[bot]'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.txt
fail_ci_if_error: true
ci-debian-build:
name: "Build"
needs:
- coverage
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
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/trixie"
suite: trixie
image: debian:trixie-slim
- name: "Debian 12/bookworm"
suite: bookworm
image: debian:bookworm-slim
- name: "Debian 11/bullseye"
suite: bullseye
image: debian:bullseye-slim
- name: "Ubuntu 26.04"
suite: resolute
image: ubuntu:26.04
- name: "Ubuntu 24.04"
suite: noble
image: ubuntu:24.04
- name: "Ubuntu 22.04"
suite: jammy
image: ubuntu:22.04
- name: "Ubuntu 20.04"
suite: focal
image: ubuntu:20.04
container:
image: ${{ matrix.image }}
options: --user root
env:
APT_LISTCHANGES_FRONTEND: none
DEBIAN_FRONTEND: noninteractive
steps:
- name: "Install Build Packages"
run: |
apt-get update
apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang jq bash-completion lintian \
binutils-i686-linux-gnu binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf \
libc6-dev-i386-cross libc6-dev-armhf-cross libc6-dev-arm64-cross \
gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
# fetch the whole repo for `git describe` to work
fetch-depth: 0
- name: "Read Go Version"
run: |
gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod)
echo "Go Version: $gover"
echo "GOVER=$gover" >> $GITHUB_OUTPUT
id: goversion
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: ${{ steps.goversion.outputs.GOVER }}
- name: "Ensure CI build"
if: github.ref == 'refs/heads/master'
run: |
echo "FORCE_CI=true" >> $GITHUB_OUTPUT
id: force_ci
- name: "Build Debian packages"
env:
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
run: |
make dpkg DEBARCH=${{ matrix.arch }}
- name: "Check aptly credentials"
env:
APTLY_USER: ${{ secrets.APTLY_USER }}
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
run: |
found=no
if [ -n "$APTLY_USER" ] && [ -n "$APTLY_PASSWORD" ]; then
found=yes
fi
echo "Aptly credentials available: $found"
echo "FOUND=$found" >> $GITHUB_OUTPUT
id: aptlycreds
- name: "Publish CI release to aptly"
if: github.ref == 'refs/heads/master' && steps.aptlycreds.outputs.FOUND == 'yes'
env:
APTLY_USER: ${{ secrets.APTLY_USER }}
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
run: |
.github/workflows/scripts/upload-artifacts.sh ci ${{ matrix.suite }}
- name: "Publish release to aptly"
if: startsWith(github.event.ref, 'refs/tags') && steps.aptlycreds.outputs.FOUND == 'yes'
env:
APTLY_USER: ${{ secrets.APTLY_USER }}
APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }}
run: |
.github/workflows/scripts/upload-artifacts.sh release ${{ matrix.suite }}
- name: "Get aptly version"
env:
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
run: |
aptlyver=$(make -s version)
echo "Aptly Version: $aptlyver"
echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT
id: releaseversion
- name: "Upload CI Artifacts"
if: github.ref != 'refs/heads/master' && !startsWith(github.event.ref, 'refs/tags')
uses: actions/upload-artifact@v4
with:
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.suite }}_${{ matrix.arch }}
path: build/
retention-days: 7
ci-binary-build:
name: "Build"
needs:
- coverage
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, freebsd, darwin]
goarch: ["386", "amd64", "arm", "arm64"]
exclude:
- goos: darwin
goarch: 386
- goos: darwin
goarch: arm
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
# fetch the whole repo for `git describe` to work
fetch-depth: 0
- name: "Read Go Version"
run: |
echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT
id: goversion
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: ${{ steps.goversion.outputs.GOVER }}
- name: "Ensure CI build"
if: github.ref == 'refs/heads/master'
run: |
echo "FORCE_CI=true" >> $GITHUB_OUTPUT
id: force_ci
- name: "Get aptly version"
env:
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
run: |
aptlyver=$(make -s version)
echo "Aptly Version: $aptlyver"
echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT
id: releaseversion
- name: "Build aptly ${{ matrix.goos }}/${{ matrix.goarch }}"
env:
GOBIN: /usr/local/bin
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
run: |
make binaries GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }}
- name: "Upload Artifacts"
uses: actions/upload-artifact@v4
if: startsWith(github.event.ref, 'refs/tags')
with:
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}
path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
compression-level: 0 # no compression
- name: "Upload CI Artifacts"
uses: actions/upload-artifact@v4
if: "!startsWith(github.event.ref, 'refs/tags')"
with:
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}
path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
compression-level: 0 # no compression
retention-days: 7
gh-release:
name: "Github Release"
runs-on: ubuntu-latest
continue-on-error: false
needs: ci-binary-build
if: startsWith(github.event.ref, 'refs/tags')
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
- name: "Get aptly version"
env:
FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }}
run: |
aptlyver=$(make -s version)
echo "Aptly Version: $aptlyver"
echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT
id: releaseversion
- name: "Download Artifacts"
uses: actions/download-artifact@v4
with:
path: out/
- name: "Create Release Notes"
run: |
echo -e "## Changes\n\n" > out/release-notes.md
dpkg-parsechangelog -S Changes | tail -n +4 >> out/release-notes.md
- name: "Release"
uses: softprops/action-gh-release@v2
with:
name: "Aptly Release ${{ steps.releaseversion.outputs.VERSION }}"
files: "out/**/aptly_*.zip"
body_path: "out/release-notes.md"
+72
View File
@@ -0,0 +1,72 @@
name: golangci-lint
on:
push:
branches:
- master
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Read go version from go.mod"
run: |
echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT
id: goversion
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: ${{ steps.goversion.outputs.GOVER }}
- name: Create VERSION file
run: |
make -s version | tr -d '\n' > VERSION
shell: sh
- name: Install and initialize swagger
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -q --propertyStrategy pascalcase --markdownFiles docs
shell: sh
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
# Require: The version of golangci-lint to use.
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
version: v1.64.5
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
#
# Note: By default, the `.golangci.yml` file should be at the root of the repository.
# The location of the configuration file can be changed by using `--config=`
# args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
# Optional: if set to true, then all caching functionality will be completely disabled,
# takes precedence over all other caching options.
# skip-cache: true
# Optional: if set to true, then the action won't cache or restore ~/go/pkg.
# skip-pkg-cache: true
# Optional: if set to true, then the action won't cache or restore ~/.cache/go-build.
# skip-build-cache: true
# Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'.
# install-mode: "goinstall"
+170
View File
@@ -0,0 +1,170 @@
#!/bin/sh
set -e
builds=build/
packages=${builds}*.deb
folder=`mktemp -u tmp.XXXXXXXXXXXXXXX`
aptly_user="$APTLY_USER"
aptly_password="$APTLY_PASSWORD"
aptly_api="https://aptly-ops.aptly.info"
version=`make version`
action=$1
dist=$2
usage() {
echo "Usage: $0 ci buster|bullseye|bookworm|focal|jammy|noble" >&2
echo " $0 release" >&2
}
# repos and publish must be created beforehand:
#!/bin/sh
#for dist in buster bullseye bookworm focal jammy noble
#do
# for build in ci release
# do
# echo
# echo "# Creating and publishing $build/$dist"
# aptly repo create -distribution=$dist -component=main aptly-$build-$dist
# aptly publish repo -multi-dist -architectures="amd64,i386,arm64,armhf" -acquire-by-hash -component=main \
# -distribution=$dist -batch -keyring=aptly.pub \
# aptly-$build-$dist \
# s3:repo.aptly.info:$build
# done
#done
if [ -z "$action" ]; then
usage
exit 1
fi
if [ "action" = "ci" ] && [ -z "$dist" ]; then
usage
exit 1
fi
if [ -z "$aptly_user" ] || [ -z "$aptly_password" ]; then
usage
echo Error: please set APTLY_USER and APTLY_PASSWORD
exit 1
fi
echo "Publishing version '$version' to $action for $dist...\n"
upload()
{
echo "\nUploading files:"
for file in $packages; do
echo " - $file"
jsonret=`curl -fsS -X POST -F "file=@$file" -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder`
done
}
cleanup() {
echo "\nCleanup..."
jsonret=`curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/files/$folder`
}
trap cleanup EXIT
sleeptime=5
retries=60
wait_task()
{
_id=$1
_success=0
sleep $sleeptime
for t in `seq $retries`
do
jsonret=`curl -fsS -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_id`
_state=`echo $jsonret | jq .State`
if [ "$_state" = "2" ]; then
_success=1
curl -fsS -X DELETE -u $aptly_user:$aptly_password ${aptly_api}/api/tasks/$_id
break
fi
if [ "$_state" = "3" ]; then
echo Error: task failed
return 1
fi
sleep $sleeptime
done
if [ "$_success" -ne 1 ]; then
echo Error: task timeout
return 1
fi
return 0
}
add_packages() {
_aptly_repository=$1
_folder=$2
jsonret=`curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$_aptly_repository/file/$_folder?_async=true`
_task_id=`echo $jsonret | jq .ID`
wait_task $_task_id
if [ "$?" -ne 0 ]; then
echo "Error: adding packages to $_aptly_repository failed"
exit 1
fi
}
update_publish() {
_publish=$1
_dist=$2
jsonret=`curl -fsS -X PUT -H 'Content-Type: application/json' --data \
'{"AcquireByHash": true, "MultiDist": true,
"Signing": {"Batch": true, "Keyring": "aptly.repo/aptly.pub", "secretKeyring": "aptly.repo/aptly.sec", "PassphraseFile": "aptly.repo/passphrase"}}' \
-u $aptly_user:$aptly_password ${aptly_api}/api/publish/$_publish/$_dist?_async=true`
_task_id=`echo $jsonret | jq .ID`
wait_task $_task_id
if [ "$?" -ne 0 ]; then
echo "Error: publish failed"
exit 1
fi
}
if [ "$action" = "ci" ]; then
if echo "$version" | grep -vq "+"; then
# skip ci when on release tag
exit 0
fi
aptly_repository=aptly-ci-$dist
aptly_published=s3:repo.aptly.info:ci
elif [ "$action" = "release" ]; then
aptly_repository=aptly-release-$dist
aptly_published=s3:repo.aptly.info:release
fi
upload
echo "\nAdding packages to $aptly_repository ..."
add_packages $aptly_repository $folder
echo "\nUpdating published repo $aptly_published ..."
update_publish $aptly_published $dist
# if [ "$action" = "OBSOLETErelease" ]; then
# aptly_repository=aptly-release
# aptly_snapshot=aptly-$version
# aptly_published=s3:repo.aptly.info:./squeeze
#
# echo "\nAdding packages to $aptly_repository..."
# curl -fsS -X POST -u $aptly_user:$aptly_password ${aptly_api}/api/repos/$aptly_repository/file/$folder
# echo
#
# echo "\nCreating snapshot $aptly_snapshot from repo $aptly_repository..."
# curl -fsS -X POST -u $aptly_user:$aptly_password -H 'Content-Type: application/json' --data \
# "{\"Name\":\"$aptly_snapshot\"}" ${aptly_api}/api/repos/$aptly_repository/snapshots
# echo
#
# echo "\nSwitching published repo $aptly_published to use snapshot $aptly_snapshot..."
# curl -fsS -X PUT -H 'Content-Type: application/json' --data \
# "{\"AcquireByHash\": true, \"Snapshots\": [{\"Component\": \"main\", \"Name\": \"$aptly_snapshot\"}],
# \"Signing\": {\"Batch\": true, \"Keyring\": \"aptly.repo/aptly.pub\",
# \"secretKeyring\": \"aptly.repo/aptly.sec\", \"PassphraseFile\": \"aptly.repo/passphrase\"}}" \
# -u $aptly_user:$aptly_password ${aptly_api}/api/publish/$aptly_published
# echo
# fi
+48 -4
View File
@@ -2,11 +2,14 @@
*.o
*.a
*.so
unit.out
# Folders
_obj
_test
tmp/
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
@@ -22,13 +25,54 @@ _testmain.go
*.exe
*.test
coverage.txt
coverage.out
coverage.html
coverage*.out
*.pyc
_vendor/
xc-out/
root/
gen
man/aptly.1.html
man/aptly.1.ronn
man/aptly.1.ronn
system/env/
# created by make build for release artifacts
VERSION
aptly.test
build/
system/files/aptly2.gpg~
system/files/aptly2_passphrase.gpg~
*.creds
.go/
obj-x86_64-linux-gnu/
obj-aarch64-linux-gnu/
obj-arm-linux-gnueabihf/
obj-i686-linux-gnu/
# debian
debian/.debhelper/
debian/aptly.substvars
debian/aptly/
debian/debhelper-build-stamp
debian/files
debian/aptly-api/
debian/*.debhelper
debian/*.debhelper.log
debian/aptly-api.substvars
debian/aptly-dbg.substvars
debian/aptly-dbg/
usr/bin/aptly
dpkgs/
debian/changelog.dpkg-bak
docs/docs.go
docs/swagger.json
docs/swagger.yaml
docs/swagger.conf
+11
View File
@@ -0,0 +1,11 @@
version: "2"
linters:
settings:
staticcheck:
checks:
- "all"
- "-QF1004" # could use strings.ReplaceAll instead
- "-QF1012" # Use fmt.Fprintf(...) instead of WriteString(fmt.Sprintf(...))
- "-QF1003" # could use tagged switch
- "-ST1000" # at least one file in a package should have a package comment
- "-QF1001" # could apply De Morgan's law
-20
View File
@@ -1,20 +0,0 @@
language: go
go:
- 1.1
- 1.2.1
- tip
env:
global:
- secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc="
install:
- make prepare
script: make travis
matrix:
allow_failures:
- go: tip
+86
View File
@@ -0,0 +1,86 @@
List of contributors, in chronological order:
* Andrey Smirnov (https://github.com/smira)
* Sebastien Binet (https://github.com/sbinet)
* Ryan Uber (https://github.com/ryanuber)
* Simon Aquino (https://github.com/queeno)
* Vincent Batoufflet (https://github.com/vbatoufflet)
* Ivan Kurnosov (https://github.com/zerkms)
* Dmitrii Kashin (https://github.com/freehck)
* Chris Read (https://github.com/cread)
* Rohan Garg (https://github.com/shadeslayer)
* Russ Allbery (https://github.com/rra)
* Sylvain Baubeau (https://github.com/lebauce)
* Andrea Bernardo Ciddio (https://github.com/bcandrea)
* Michael Koval (https://github.com/mkoval)
* Alexander Guy (https://github.com/alexanderguy)
* Sebastien Badia (https://github.com/sbadia)
* Szymon Sobik (https://github.com/sobczyk)
* Paul Krohn (https://github.com/paul-krohn)
* Vincent Bernat (https://github.com/vincentbernat)
* x539 (https://github.com/x539)
* Phil Frost (https://github.com/bitglue)
* Benoit Foucher (https://github.com/bentoi)
* Geoffrey Thomas (https://github.com/geofft)
* Oliver Sauder (https://github.com/sliverc)
* Harald Sitter (https://github.com/apachelogger)
* Johannes Layher (https://github.com/jola5)
* Charles Hsu (https://github.com/charz)
* Clemens Rabe (https://github.com/seeraven)
* TJ Merritt (https://github.com/tjmerritt)
* Matt Martyn (https://github.com/MMartyn)
* Ludovico Cavedon (https://github.com/cavedon)
* Petr Jediny (https://github.com/pjediny)
* Maximilian Stein (https://github.com/steinymity)
* Strajan Sebastian (https://github.com/strajansebastian)
* Artem Smirnov (https://github.com/urpylka)
* William Manley (https://github.com/wmanley)
* Shengjing Zhu (https://github.com/zhsj)
* Nabil Bendafi (https://github.com/nabilbendafi)
* Raphael Medaer (https://github.com/rmedaer)
* Raul Benencia (https://github.com/rul)
* Don Kuntz (https://github.com/dkuntz2)
* Joshua Colson (https://github.com/freakinhippie)
* Andre Roth (https://github.com/neolynx)
* Lorenzo Bolla (https://github.com/lbolla)
* Benj Fassbind (https://github.com/randombenj)
* Markus Muellner (https://github.com/mmianl)
* Chuan Liu (https://github.com/chuan)
* Samuel Mutel (https://github.com/smutel)
* Russell Greene (https://github.com/russelltg)
* Wade Simmons (https://github.com/wadey)
* Steven Stone (https://github.com/smstone)
* Josh Bayfield (https://github.com/jbayfield)
* Boxjan (https://github.com/boxjan)
* Mauro Regli (https://github.com/reglim)
* Alexander Zubarev (https://github.com/strike)
* Nicolas Dostert (https://github.com/acdn-ndostert)
* Ryan Gonzalez (https://github.com/refi64)
* Paul Cacheux (https://github.com/paulcacheux)
* Nic Waller (https://github.com/sf-nwaller)
* iofq (https://github.com/iofq)
* Noa Resare (https://github.com/nresare)
* Ramon N.Rodriguez (https://github.com/runitonmetal)
* Golf Hu (https://github.com/hudeng-go)
* Cookie Fei (https://github.com/wuhuang26)
* Andrey Loukhnov (https://github.com/aol-nnov)
* Christoph Fiehe (https://github.com/cfiehe)
* Blake Kostner (https://github.com/btkostner)
* Leigh London (https://github.com/leighlondon)
* Gordian Schoenherr (https://github.com/schoenherrg)
* Silke Hofstra (https://github.com/silkeh)
* Itay Porezky (https://github.com/itayporezky)
* Alejandro Guijarro Monerris (https://github.com/alguimodd)
* JupiterRider (https://github.com/JupiterRider)
* Agustin Henze (https://github.com/agustinhenze)
* Tobias Assarsson (https://github.com/daedaluz)
* Yaksh Bariya (https://github.com/thunder-coding)
* Juan Calderon-Perez (https://github.com/gaby)
* Ato Araki (https://github.com/atotto)
* Roman Lebedev (https://github.com/LebedevRI)
* Brian Witt (https://github.com/bwitt)
* Ales Bregar (https://github.com/abregar)
* Tim Foerster (https://github.com/tonobo)
* Zhang Xiao (https://github.com/xzhang1)
* Tom Nguyen (https://github.com/lecafard)
* Philip Cramer (https://github.com/PhilipCramer)
+74
View File
@@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team on [Aptly Discussions](https://github.com/aptly-dev/aptly/discussions). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+253
View File
@@ -0,0 +1,253 @@
# Contributing to aptly
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to [aptly](https://github.com/aptly-dev/aplty) and related repositories, which are hosted in the [aptly-dev Organization](https://github.com/aptly-dev) on GitHub.
These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
## What should I know before I get started?
### Code of Conduct
This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code.
Please report unacceptable behavior on [https://github.com/aptly-dev/aptly/discussions](https://github.com/aptly-dev/aptly/discussions)
### List of Repositories
* [aptly-dev/aptly](https://github.com/aptly-dev/aptly) - aptly source code, functional tests, man page
* [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
## How Can I Contribute?
### Reporting Bugs
1. Please search for similar bug report in [issue tracker](https://github.com/aptly-dev/aptly/issues)
2. Please verify that bug is not fixed in latest aptly nightly ([download information](https://www.aptly.info/download/))
3. Steps to reproduce increases chances for bug to be fixed quickly. If possible, submit PR with new functional test which fails.
4. If bug is reproducible with specific package, please provide link to package file.
5. Open issue at [GitHub](https://github.com/aptly-dev/aptly/issues)
### Suggesting Enhancements
1. Please search [issue tracker](https://github.com/aptly-dev/aptly/issues) for similar feature requests.
2. Describe why enhancement is important to you.
3. Include any additional details or implementation details.
### Improving Documentation
There are two kinds of documentation:
* [aptly website](https://www.aptly.info)
* aptly `man` page
Core content is mostly the same, but website contains more information, tutorials, examples.
If you want to update `man` page, please open PR to [main aptly repo](https://github.com/aptly-dev/aptly),
details in [man page](#man-page) section.
If you want to update website, please follow steps below:
1. Install [hugo](http://gohugo.io/)
2. Fork [website source](https://github.com/aptly-dev/aptly-dev.github.io) and clone it
3. Launch hugo in development mode: `hugo -w server`
4. Navigate to `http://localhost:1313/`: you should see aptly website
5. Update documentation, most of the time editing Markdown is all you need.
6. Page in browser should reload automatically as you make changes to source files.
We're always looking for new contributions to [FAQ](https://www.aptly.info/doc/faq/), [tutorials](https://www.aptly.info/tutorial/),
general fixes, clarifications, misspellings, grammar mistakes!
### Code Contribution
Please follow [next section](#development-setup) on development process. When change is ready, please submit PR
following [PR template](.github/PULL_REQUEST_TEMPLATE.md).
Make sure that purpose of your change is clear, all the tests and checks pass, and all new code is covered with tests
if that is possible.
### Get the Source
To clone the git repo, run the following commands:
```
git clone git@github.com:aptly-dev/aptly.git
cd aptly
```
## Development Setup
Working on aptly code can be done locally on the development machine, or for convenience by using docker. The next sections describe the setup process.
### Docker Development Setup
This section describes the docker setup to start contributing to aptly.
#### Dependencies
Install the following on your development machine:
- docker
- make
- git
##### Docker installation on macOS
1. Install [Docker Desktop on Mac](https://docs.docker.com/desktop/setup/install/mac-install/) (or via [Homebrew](https://brew.sh/))
2. Allow directory sharing
- Open Docker Desktop
- Go to `Settings → Resources → File Sharing → Virtual File Shares`
- Add the aptly git repository path to the shared list (eg. /home/Users/john/aptly)
#### Create docker container
To build the development docker image, run:
```
make docker-image
```
#### Build aptly
To build the aptly in the development docker container, run:
```
make docker-build
```
#### Running aptly commands
To run aptly commands in the development docker container, run:
```
make docker-shell
```
Example:
```
$ make docker-shell
aptly@b43e8473ef81:/work/src$ aptly version
aptly version: 1.5.0+189+g0fc90dff
```
#### Running unit tests
In order to run aptly unit tests, enter the following:
```
make docker-unit-tests
```
#### Running system tests
In order to run aptly system tests, enter the following:
```
make docker-system-tests
```
#### Running golangci-lint
In order to run aptly unit tests, run:
```
make docker-lint
```
#### More info
Run `make help` for more information.
### Local Development Setup
This section describes local setup to start contributing to aptly.
#### Dependencies
Building aptly requires go version 1.22.
On Debian bookworm with backports enabled, go can be installed with:
apt install -t bookworm-backports golang-go
#### Building
To build aptly, run:
make build
Run aptly:
build/aptly
To install aptly into `$GOPATH/bin`, run:
make install
#### Unit-tests
aptly has two kinds of tests: unit-tests and functional (system) tests. Functional tests are preferred way to test any
feature, but some features are much easier to test with unit-tests (e.g. algorithms, failure scenarios, ...)
aptly is using standard Go unit-test infrastructure plus [gocheck](http://labix.org/gocheck). Run the unit-tests with:
make test
#### Functional Tests
Functional tests are implemented in Python, and they use custom test runner which is similar to Python unit-test
runner. Most of the tests start with clean aptly state, run some aptly commands to prepare environment, and finally
run some aptly commands capturing output, exit code, checking any additional files being created and so on. API tests
are a bit different, as they re-use same aptly process serving API requests.
The easiest way to run functional tests is to use `make`:
make system-test
This would check all the dependencies and run all the tests. Some tests (S3, Swift) require access credentials to
be set up in the environment. For example, it needs AWS credentials to run S3 tests (they would be used to publish to S3).
If credentials are missing, tests would be skipped.
You can also run subset of tests manually:
system/run.py t04_mirror
This would run all the mirroring tests under `system/t04_mirror` folder.
Or you can run tests by test name mask:
system/run.py UpdateMirror*
Or, you can run specific test by name:
system/run.py UpdateMirror7Test
Test runner can update expected output instead of failing on mismatch (this is especially useful while
working on new tests):
system/run.py --capture <test>
Output for some tests might contain environment-specific things, e.g. your home directory. In that case
you can use `${HOME}` and similar variable expansion in expected output files.
Some tests depend on fixtures, for example pre-populated GPG trusted keys. There are also test fixtures
captured after mirror update which contain pre-build aptly database and pool contents. They're useful if you
don't want to waste time in the test on populating aptly database while you need some packages to work with.
There are some packages available under `system/files/` directory which are used to build contents of local repos.
*WARNING*: tests are running under current `$HOME` directory with aptly default settings, so they clear completely
`~/.aptly.conf` and `~/.aptly` subdirectory between the runs. So it's not wise to have non-dev aptly being used with
this default location. You can run aptly under different user or by using non-default config location with non-default
aptly root directory.
### man Page
aptly is using combination of [Go templates](http://godoc.org/text/template) and automatically generated text to build `aptly.1` man page. If either source
template [man/aptly.1.ronn.tmpl](man/aptly.1.ronn.tmpl) is changed or any command help is changed, run `make man` to regenerate
final rendered man page [man/aptly.1](man/aptly.1). In the end of the build, new man page is displayed for visual
verification.
Man page is built with small helper [\_man/gen.go](man/gen.go) which pulls in template, command-line help from [cmd/](cmd/) folder
and runs that through [forked copy](https://github.com/smira/ronn) of [ronn](https://github.com/rtomayko/ronn).
### Bash and Zsh Completion
Bash and Zsh completion for aptly reside in the same repo under in [completion.d/aptly](completion.d/aptly) and
[completion.d/\_aptly](completion.d/_aptly), respectively. It's all hand-crafted.
When new option or command is introduced, bash completion should be updated to reflect that change.
When aptly package is being built, it automatically pulls bash completion and man page into the package.
-22
View File
@@ -1,22 +0,0 @@
gom 'code.google.com/p/go-uuid/uuid', :commit => '5fac954758f5'
gom 'code.google.com/p/go.crypto/ssh/terminal', :commit => '7aa593ce8cea'
gom 'code.google.com/p/gographviz', :commit => '212766062629'
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827'
gom 'github.com/smira/commander', :commit => '082a3ce267a8225a8ccf94deaf18901223d38fed'
gom 'github.com/smira/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841'
gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b'
gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'ff3719c6816e2cd194f05058452d660608e178ac'
gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b'
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
group :test do
gom 'launchpad.net/gocheck'
end
group :development do
gom 'github.com/golang/lint/golint'
gom 'github.com/mattn/goveralls'
gom 'github.com/axw/gocov/gocov'
gom 'code.google.com/p/go.tools/cmd/cover'
end
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2013-2014 Andrey Smirnov. All rights reserved.
Copyright 2013-2015 aptly authors. All rights reserved.
MIT License
+219 -62
View File
@@ -1,87 +1,244 @@
GOVERSION=$(shell go version | awk '{print $$3;}')
PACKAGES=database deb files http utils
ALL_PACKAGES=aptly cmd console database deb files http utils
BINPATH=$(abspath ./_vendor/bin)
GOM_ENVIRONMENT=-test
PYTHON?=python
GOPATH=$(shell go env GOPATH)
VERSION=$(shell make -s version)
PYTHON?=python3
BINPATH?=$(GOPATH)/bin
GOLANGCI_LINT_VERSION=v2.0.2 # version supporting go 1.24
COVERAGE_DIR?=$(shell mktemp -d)
GOOS=$(shell go env GOHOSTOS)
GOARCH=$(shell go env GOHOSTARCH)
ifeq ($(GOVERSION), devel)
TRAVIS_TARGET=coveralls
GOM_ENVIRONMENT+=-development
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
TRAVIS_TARGET=test
COVERAGE_ARG_BUILD := -coverpkg="./..."
COVERAGE_ARG_TEST := --coverage-dir $(COVERAGE_DIR)
endif
ifeq ($(TRAVIS), true)
GOM=$(HOME)/gopath/bin/gom
else
GOM=gom
# export CAPUTRE=1 for regenrating test gold files
ifeq ($(CAPTURE),1)
CAPTURE_ARG := --capture
endif
all: test check system-test
help: ## Print this help
@grep -E '^[a-zA-Z][a-zA-Z0-9_-]*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
prepare:
go get -u github.com/mattn/gom
$(GOM) $(GOM_ENVIRONMENT) install
prepare: ## Install go module dependencies
# Prepare go modules
go mod verify
go mod tidy -v
# Generate VERSION file
go generate
coverage.out:
rm -f coverage.*.out
for i in $(PACKAGES); do $(GOM) test -coverprofile=coverage.$$i.out -covermode=count ./$$i; done
echo "mode: count" > coverage.out
grep -v -h "mode: count" coverage.*.out >> coverage.out
rm -f coverage.*.out
releasetype: # Print release type: ci (on any branch/commit), release (on a tag)
@reltype=ci ; \
gitbranch=`git rev-parse --abbrev-ref HEAD` ; \
if [ "$$gitbranch" = "HEAD" ] && [ "$$FORCE_CI" != "true" ]; then \
gittag=`git describe --tags --exact-match 2>/dev/null` ;\
if echo "$$gittag" | grep -q '^v[0-9]'; then \
reltype=release ; \
fi ; \
fi ; \
echo $$reltype
coverage: coverage.out
$(GOM) exec go tool cover -html=coverage.out
rm -f coverage.out
version: ## Print aptly version
@ci="" ; \
if [ "`make -s releasetype`" = "ci" ]; then \
ci=`TZ=UTC git show -s --format='+%cd.%h' --date=format-local:'%Y%m%d%H%M%S'`; \
fi ; \
if which dpkg-parsechangelog > /dev/null 2>&1; then \
echo `dpkg-parsechangelog -S Version`$$ci; \
else \
echo `grep ^aptly -m1 debian/changelog | sed 's/.*(\([^)]\+\)).*/\1/'`$$ci ; \
fi
check:
$(GOM) exec go tool vet -all=true -shadow=true $(ALL_PACKAGES:%=./%)
$(GOM) exec golint $(ALL_PACKAGES:%=./%)
swagger-install:
# Install swag
@test -f $(BINPATH)/swag || GOOS= GOARCH= go install github.com/swaggo/swag/cmd/swag@latest
# Generate swagger.conf
cp docs/swagger.conf.tpl docs/swagger.conf
echo "// @version $(VERSION)" >> docs/swagger.conf
azurite-start:
azurite -l /tmp/aptly-azurite > ~/.azurite.log 2>&1 & \
echo $$! > ~/.azurite.pid
azurite-stop:
@kill `cat ~/.azurite.pid`
swagger: swagger-install
# Generate swagger docs
@PATH=$(BINPATH)/:$(PATH) swag init --propertyStrategy pascalcase --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
etcd-install:
# Install etcd
test -d /tmp/aptly-etcd || system/t13_etcd/install-etcd.sh
flake8: ## run flake8 on system test python files
flake8 system/
lint: prepare
# Install golangci-lint
@test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
# Running lint
@NO_COLOR=true PATH=$(BINPATH)/:$(PATH) golangci-lint run --max-issues-per-linter=0 --max-same-issues=0
build: prepare swagger ## Build aptly
go build -o build/aptly
install:
$(GOM) build -o $(BINPATH)/aptly
@echo "\e[33m\e[1mBuilding aptly ...\e[0m"
# go generate
@go generate
# 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
system-test: install
ifeq ($(GOVERSION),$(filter $(GOVERSION),go1.2 go1.2.1 devel))
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 &
@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
@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
# build coverage binary
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
endif
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long
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_ARG_TEST) $(CAPTURE_ARG) $(TEST)
travis: $(TRAVIS_TARGET) system-test
bench:
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
go test -v ./deb -run=nothing -bench=. -benchmem
test:
$(GOM) test -v ./... -gocheck.v=true
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
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
coveralls: coverage.out
$(GOM) exec $(BINPATH)/goveralls -service travis-ci.org -coverprofile=coverage.out -repotoken=$(COVERALLS_TOKEN)
dpkg: prepare swagger ## Build debian packages
@test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1)
# set debian version
@if [ "`make -s releasetype`" = "ci" ]; then \
echo CI Build, setting version... ; \
test ! -f debian/changelog.dpkg-bak || mv debian/changelog.dpkg-bak debian/changelog ; \
cp debian/changelog debian/changelog.dpkg-bak ; \
DEBEMAIL="CI <ci@aptly.info>" dch -v `make -s version` "CI build" ; \
fi
# clean
rm -rf obj-i686-linux-gnu obj-arm-linux-gnueabihf obj-aarch64-linux-gnu obj-x86_64-linux-gnu
# Run dpkg-buildpackage
@buildtype="any" ; \
if [ "$(DEBARCH)" = "amd64" ]; then \
buildtype="any,all" ; \
fi ; \
echo "\e[33m\e[1mBuilding: $$buildtype\e[0m" ; \
cmd="dpkg-buildpackage -us -uc --build=$$buildtype -d --host-arch=$(DEBARCH)" ; \
echo "$$cmd" ; \
$$cmd
lintian ../*_$(DEBARCH).changes || true
# cleanup
@test ! -f debian/changelog.dpkg-bak || mv debian/changelog.dpkg-bak debian/changelog; \
mkdir -p build && mv ../*.deb build/ ; \
cd build && ls -l *.deb
binaries: prepare swagger ## Build binary releases (FreeBSD, macOS, Linux generic)
# build aptly
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o build/tmp/aptly -ldflags='-extldflags=-static'
# install
@mkdir -p build/tmp/man build/tmp/completion/bash_completion.d build/tmp/completion/zsh/vendor-completions
@cp man/aptly.1 build/tmp/man/
@cp completion.d/aptly build/tmp/completion/bash_completion.d/
@cp completion.d/_aptly build/tmp/completion/zsh/vendor-completions/
@cp README.rst LICENSE AUTHORS build/tmp/
@gzip -f build/tmp/man/aptly.1
@path="aptly_$(VERSION)_$(GOOS)_$(GOARCH)"; \
rm -rf "build/$$path"; \
mv build/tmp build/"$$path"; \
rm -rf build/tmp; \
cd build; \
zip -r "$$path".zip "$$path" > /dev/null \
&& echo "Built build/$${path}.zip"; \
rm -rf "$$path"
docker-image: ## Build aptly-dev docker image
@docker build -f system/Dockerfile . -t aptly-dev
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) -t aptly-dev /work/src/system/docker-wrapper build
docker-shell: ## Run aptly and other commands in docker container
@$(DOCKER_RUN) -it -p 3142:3142 aptly-dev /work/src/system/docker-wrapper || true
docker-deb: ## Build debian packages in docker container
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64
docker-unit-tests: ## Run unit tests in docker container (add TEST=regex to specify which tests to run)
$(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 \
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
test TEST=$(TEST) \
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) -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) CAPTURE=$(CAPTURE) COVERAGE_SKIP=$(COVERAGE_SKIP) \
azurite-stop
docker-serve: ## Run development server (auto recompiling) on http://localhost:3142
@$(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) -t aptly-dev /work/src/system/docker-wrapper lint
docker-binaries: ## Build binary releases (FreeBSD, macOS, Linux generic) in docker container
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper binaries
docker-man: ## Create man page in docker container
@$(DOCKER_RUN) -t aptly-dev /work/src/system/docker-wrapper man
mem.png: mem.dat mem.gp
gnuplot mem.gp
open mem.png
package:
rm -rf root/
mkdir -p root/usr/bin/ root/usr/share/man/man1/ root/etc/bash_completion.d
cp $(BINPATH)/aptly root/usr/bin
cp man/aptly.1 root/usr/share/man/man1
(cd root/etc/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/master/aptly)
gzip root/usr/share/man/man1/aptly.1
fpm -s dir -t deb -n aptly -v $(VERSION) --url=http://www.aptly.info/ --license=MIT --vendor="Andrey Smirnov <me@smira.ru>" \
-f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" -C root/ .
mv aptly_$(VERSION)_*.deb ~
man: ## Create man pages
make -C man
src-package:
rm -rf aptly-$(VERSION)
mkdir -p aptly-$(VERSION)/src/github.com/smira/aptly/
cd aptly-$(VERSION)/src/github.com/smira/ && git clone https://github.com/smira/aptly && cd aptly && git checkout v$(VERSION)
cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . -name .git -print | xargs rm -rf
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . -name .bzr -print | xargs rm -rf
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . -name .hg -print | xargs rm -rf
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin}
tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
rm -rf aptly-$(VERSION)
clean: ## remove local build and module cache
# Clean all generated and build files
test ! -e .go || find .go/ -type d ! -perm -u=w -exec chmod u+w {} \;
rm -rf .go/
rm -rf build/ obj-*-linux-gnu* tmp/
rm -f unit.out aptly.test VERSION docs/docs.go docs/swagger.json docs/swagger.yaml docs/swagger.conf
find system/ -type d -name __pycache__ -exec rm -rf {} \; 2>/dev/null || true
.PHONY: coverage.out
.PHONY: help man prepare swagger version binaries build docker-release docker-system-tests docker-unit-test docker-lint docker-build docker-image docker-man docker-shell docker-serve clean releasetype dpkg serve flake8
+106 -39
View File
@@ -1,68 +1,135 @@
=====
.. image:: https://github.com/aptly-dev/aptly/actions/workflows/ci.yml/badge.svg
:target: https://github.com/aptly-dev/aptly/actions
.. image:: https://codecov.io/gh/aptly-dev/aptly/branch/master/graph/badge.svg
:target: https://codecov.io/gh/aptly-dev/aptly
.. image:: https://badges.gitter.im/Join Chat.svg
:target: https://matrix.to/#/#aptly:gitter.im
.. image:: https://goreportcard.com/badge/github.com/aptly-dev/aptly
:target: https://goreportcard.com/report/aptly-dev/aptly
aptly
=====
.. image:: https://travis-ci.org/smira/aptly.png?branch=master
:target: https://travis-ci.org/smira/aptly
.. image:: https://coveralls.io/repos/smira/aptly/badge.png?branch=HEAD
:target: https://coveralls.io/r/smira/aptly?branch=HEAD
Aptly is a swiss army knife for Debian repository management.
Documentation is available at `http://www.aptly.info/ <http://www.aptly.info/>`_. For support use
mailing list `aptly-discuss <https://groups.google.com/forum/#!forum/aptly-discuss>`_.
.. image:: http://www.aptly.info/img/aptly_logo.png
:target: http://www.aptly.info/
Aptly features: ("+" means planned features)
Documentation is available at `http://www.aptly.info/ <http://www.aptly.info/>`_. For support please use
open `issues <https://github.com/aptly-dev/aptly/issues>`_ or `discussions <https://github.com/aptly-dev/aptly/discussions>`_.
Aptly features:
* make mirrors of remote Debian/Ubuntu repositories, limiting by components/architectures
* take snapshots of mirrors at any point in time, fixing state of repository at some moment of time
* publish snapshot as Debian repository, ready to be consumed by apt
* controlled update of one or more packages in snapshot from upstream mirror, tracking dependencies
* merge two or more snapshots into one
* filter repository by search query, pulling dependencies when required (+)
* publish self-made packages as Debian repositories (+)
* mirror repositories "as-is" (without resigning with user's key) (+)
* support for yum repositories (+)
* filter repository by search query, pulling dependencies when required
* publish self-made packages as Debian repositories
* REST API for remote access
Current limitations:
Any contributions are welcome! Please see `CONTRIBUTING.md <CONTRIBUTING.md>`_.
* debian-installer and translations not supported yet
Installation
=============
Download
--------
Aptly can be installed on several operating systems.
To install aptly on Debian/Ubuntu, add new repository to /etc/apt/sources.list::
Debian / Ubuntu
----------------
deb http://repo.aptly.info/ squeeze main
Aptly is provided in the following debian packages:
And import key that is used to sign the release::
* **aptly**: Includes the main Aptly binary, man pages, and shell completions
* **aptly-api**: A systemd service for the REST API, using the global /etc/aptly.conf
* **aptly-dbg**: Debug symbols for troubleshooting
$ gpg --keyserver keys.gnupg.net --recv-keys 2A194991
$ gpg -a --export 2A194991 | sudo apt-key add -
The packages can be installed on official `Debian <https://packages.debian.org/search?keywords=aptly>`_ and `Ubuntu <https://packages.ubuntu.com/search?keywords=aptly>`_ distributions.
After that you can install aptly as any other software package::
Upstream Debian Packages
~~~~~~~~~~~~~~~~~~~~~~~~~
$ apt-get update
$ apt-get install aptly
If a newer version (not available in Debian/Ubuntu) of aptly is required, upstream debian packages (built from git tags) can be installed as follows:
Don't worry about squeeze part in repo name: aptly package should work on Debian squeeze+,
Ubuntu 10.0+. Package contains aptly binary, man page and bash completion.
Install the following APT key (as root)::
Binary executables (depends almost only on libc) are available for download from `Bintray <http://dl.bintray.com/smira/aptly/>`_.
wget -O /etc/apt/keyrings/aptly.asc https://www.aptly.info/pubkey.txt
If you have Go environment set up, you can build aptly from source by running (go 1.1+ required)::
Define Release APT sources in ``/etc/apt/sources.list.d/aptly.list``::
go get -u github.com/mattn/gom
mkdir -p $GOPATH/src/github.com/smira/aptly
git clone https://github.com/smira/aptly $GOPATH/src/github.com/smira/aptly
cd $GOPATH/src/github.com/smira/aptly
gom -production install
gom build -o $GOPATH/bin/aptly
deb [signed-by=/etc/apt/keyrings/aptly.asc] http://repo.aptly.info/release DIST main
Aptly is using `gom <https://github.com/mattn/gom>`_ to fix external dependencies, so regular ``go get github.com/smira/aptly``
should work as well, but might fail or produce different result (if external libraries got updated).
Where DIST is one of: ``bullseye``, ``bookworm``, ``trixie``, ``focal``, ``jammy``, ``noble``
If you don't have Go installed (or older version), you can easily install Go using `gvm <https://github.com/moovweb/gvm/>`_.
Install aptly packages::
apt-get update
apt-get install aptly
apt-get install aptly-api # REST API systemd service
CI Builds
~~~~~~~~~~
For testing new features or bugfixes, recent builds are available as CI builds (built from master, may be unstable!) and can be installed as follows:
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: ``bullseye``, ``bookworm``, ``trixie``, ``focal``, ``jammy``, ``noble``
Note: same gpg key is used as for the Upstream Debian Packages.
Other Operating Systems
------------------------
Binary executables (depends almost only on libc) are available on `GitHub Releases <https://github.com/aptly-dev/aptly/releases>`_ for:
- macOS / darwin (amd64, arm64)
- FreeBSD (amd64, arm64, 386, arm)
- Generic Linux (amd64, arm64, 386, arm)
Integrations
=============
Vagrant:
- `Vagrant configuration <https://github.com/sepulworld/aptly-vagrant>`_ by
Zane Williamson, allowing to bring two virtual servers, one with aptly installed
and another one set up to install packages from repository published by aptly
Docker:
- `Docker container <https://github.com/mikepurvis/aptly-docker>`_ with aptly inside by Mike Purvis
- `Docker container <https://github.com/urpylka/docker-aptly>`_ with aptly and nginx by Artem Smirnov
With configuration management systems:
- `Chef cookbook <https://github.com/hw-cookbooks/aptly>`_ by Aaron Baer
(Heavy Water Operations, LLC)
- `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
CLI for aptly API:
- `Ruby aptly CLI/library <https://github.com/sepulworld/aptly_cli>`_ by Zane Williamson
- `Python aptly CLI (good for CI) <https://github.com/TimSusa/aptly_api_cli>`_ by Tim Susa
GUI for aptly API:
- `Python aptly GUI (via pyqt5) <https://github.com/chnyda/python-aptly-gui>`_ by Cedric Hnyda
Scala sbt:
- `sbt aptly plugin <https://github.com/amalakar/sbt-aptly>`_ by Arup Malakar
Molior:
- `Molior Debian Build System <https://github.com/molior-dbs/molior>`_ by André Roth
+18
View File
@@ -0,0 +1,18 @@
# Creating a Release
- create branch release/1.x.y
- update debian/changelog
- create PR, merge when approved
- on updated master, create release:
```
version=$(dpkg-parsechangelog -S Version)
echo Releasing prod version $version
git tag -a v$version -m 'aptly: release $version'
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
+27 -6
View File
@@ -2,16 +2,17 @@ package main
import (
"fmt"
"github.com/smira/aptly/cmd"
"github.com/smira/commander"
"github.com/smira/flag"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
"github.com/aptly-dev/aptly/cmd"
"github.com/smira/commander"
"github.com/smira/flag"
)
func allFlags(flags *flag.FlagSet) []*flag.Flag {
@@ -43,22 +44,42 @@ func capitalize(s string) string {
return strings.Join(parts, " ")
}
var authorsS string
func authors() string {
return authorsS
}
func main() {
command := cmd.RootCommand()
command.UsageLine = "aptly"
command.Dispatch(nil)
_, _File, _, _ := runtime.Caller(0)
_File, _ = filepath.Abs(_File)
_File, _ := filepath.Abs("./man")
templ := template.New("man").Funcs(template.FuncMap{
"allFlags": allFlags,
"findCommand": findCommand,
"toUpper": strings.ToUpper,
"capitalize": capitalize,
"authors": authors,
})
template.Must(templ.ParseFiles(filepath.Join(filepath.Dir(_File), "aptly.1.ronn.tmpl")))
authorsF, err := os.Open(filepath.Join(filepath.Dir(_File), "..", "AUTHORS"))
if err != nil {
log.Fatal(err)
}
authorsB, err := ioutil.ReadAll(authorsF)
if err != nil {
log.Fatal(err)
}
authorsF.Close()
authorsS = string(authorsB)
output, err := os.Create(filepath.Join(filepath.Dir(_File), "aptly.1.ronn"))
if err != nil {
log.Fatal(err)
+332
View File
@@ -0,0 +1,332 @@
// Package api provides implementation of aptly REST API
package api
import (
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"sync/atomic"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/aptly-dev/aptly/task"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
// Lock order acquisition (canonical):
// 1. RemoteRepoCollection
// 2. LocalRepoCollection
// 3. SnapshotCollection
// 4. PublishedRepoCollection
type aptlyVersion struct {
// Aptly Version
Version string `json:"Version"`
}
// @Summary Aptly Version
// @Description **Get aptly version**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/version
// @Description {"Version":"0.9~dev"}
// @Description ```
// @Tags Status
// @Produce json
// @Success 200 {object} aptlyVersion
// @Router /api/version [get]
func apiVersion(c *gin.Context) {
version := aptlyVersion{
Version: aptly.Version,
}
c.JSON(200, version)
}
type aptlyStatus struct {
// Aptly Status
Status string `json:"Status" example:"'Aptly is ready', 'Aptly is unavailable', 'Aptly is healthy'"`
}
// @Summary Get Ready State
// @Description **Get aptly ready state**
// @Description
// @Description Return aptly ready state:
// @Description - `Aptly is ready` (HTTP 200)
// @Description - `Aptly is unavailable` (HTTP 503)
// @Tags Status
// @Produce json
// @Success 200 {object} aptlyStatus "Aptly is ready"
// @Failure 503 {object} aptlyStatus "Aptly is unavailable"
// @Router /api/ready [get]
func apiReady(isReady *atomic.Value) func(*gin.Context) {
return func(c *gin.Context) {
if isReady == nil || !isReady.Load().(bool) {
c.JSON(503, gin.H{"Status": "Aptly is unavailable"})
return
}
status := aptlyStatus{Status: "Aptly is ready"}
c.JSON(200, status)
}
}
// @Summary Get Health State
// @Description **Get aptly health state**
// @Description
// @Description Return aptly health state:
// @Description - `Aptly is healthy` (HTTP 200)
// @Tags Status
// @Produce json
// @Success 200 {object} aptlyStatus
// @Router /api/healthy [get]
func apiHealthy(c *gin.Context) {
c.JSON(200, gin.H{"Status": "Aptly is healthy"})
}
type dbRequestKind int
const (
acquiredb dbRequestKind = iota
releasedb
)
type dbRequest struct {
kind dbRequestKind
err chan<- error
}
var dbRequests chan dbRequest
// Acquire database lock and release it when not needed anymore.
//
// Should be run in a goroutine!
func acquireDatabase() {
clients := 0
for request := range dbRequests {
var err error
switch request.kind {
case acquiredb:
if clients == 0 {
err = context.ReOpenDatabase()
}
request.err <- err
if err == nil {
clients++
}
case releasedb:
clients--
if clients == 0 {
err = context.CloseDatabase()
} else {
err = nil
}
request.err <- err
}
}
}
// Should be called before database access is needed in any api call.
// Happens per default for each api call. It is important that you run
// runTaskInBackground to run a task which accquire database.
// Important do not forget to defer to releaseDatabaseConnection
func acquireDatabaseConnection() error {
if dbRequests == nil {
return nil
}
errCh := make(chan error)
dbRequests <- dbRequest{acquiredb, errCh}
return <-errCh
}
// Release database connection when not needed anymore
func releaseDatabaseConnection() error {
if dbRequests == nil {
return nil
}
errCh := make(chan error)
dbRequests <- dbRequest{releasedb, errCh}
return <-errCh
}
// runs tasks in background. Acquires database connection first.
func runTaskInBackground(name string, resources []string, proc task.Process) (task.Task, *task.ResourceConflictError) {
return context.TaskList().RunTaskInBackground(name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
err := acquireDatabaseConnection()
if err != nil {
return nil, err
}
defer func() { _ = releaseDatabaseConnection() }()
return proc(out, detail)
})
}
func truthy(value interface{}) bool {
if value == nil {
return false
}
switch v := value.(type) {
case string:
switch strings.ToLower(v) {
case "n", "no", "f", "false", "0", "off":
return false
default:
return true
}
case int:
return v != 0
case bool:
return v
}
return true
}
func maybeRunTaskInBackground(c *gin.Context, name string, resources []string, proc task.Process) {
// Run this task in background if configured globally or per-request
background := truthy(c.DefaultQuery("_async", strconv.FormatBool(context.Config().AsyncAPI)))
if background {
log.Debug().Msg("Executing task asynchronously")
task, conflictErr := runTaskInBackground(name, resources, proc)
if conflictErr != nil {
AbortWithJSONError(c, 409, conflictErr)
return
}
c.JSON(202, task)
} else {
log.Debug().Msg("Executing task synchronously")
task, conflictErr := runTaskInBackground(name, resources, proc)
if conflictErr != nil {
AbortWithJSONError(c, 409, conflictErr)
return
}
// wait for task to finish
_, _ = context.TaskList().WaitForTaskByID(task.ID)
retValue, _ := context.TaskList().GetTaskReturnValueByID(task.ID)
err, _ := context.TaskList().GetTaskErrorByID(task.ID)
_, _ = context.TaskList().DeleteTaskByID(task.ID)
if err != nil {
AbortWithJSONError(c, retValue.Code, err)
return
}
if retValue != nil {
c.JSON(retValue.Code, retValue.Value)
} else {
c.JSON(http.StatusOK, nil)
}
}
}
// Common piece of code to show list of packages,
// with searching & details if requested
func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory *deb.CollectionFactory) {
result := []*deb.Package{}
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
queryS := c.Request.URL.Query().Get("q")
if queryS != "" {
q, err := query.Parse(c.Request.URL.Query().Get("q"))
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
withDeps := c.Request.URL.Query().Get("withDeps") == "1"
architecturesList := []string{}
if withDeps {
if len(context.ArchitecturesList()) > 0 {
architecturesList = context.ArchitecturesList()
} else {
architecturesList = list.Architectures(false)
}
sort.Strings(architecturesList)
if len(architecturesList) == 0 {
AbortWithJSONError(c, 400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
return
}
}
list.PrepareIndex()
list, err = list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{q},
WithDependencies: withDeps,
Source: nil,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
})
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err))
return
}
}
// filter packages by version
if c.Request.URL.Query().Get("maximumVersion") == "1" {
list.PrepareIndex()
_ = list.ForEach(func(p *deb.Package) error {
versionQ, err := query.Parse(fmt.Sprintf("Name (%s), $Version (<= %s)", p.Name, p.Version))
if err != nil {
fmt.Println("filter packages by version, query string parse err: ", err)
_ = c.AbortWithError(500, fmt.Errorf("unable to parse %s maximum version query string: %s", p.Name, err))
} else {
tmpList, err := list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{versionQ},
})
if err == nil {
if tmpList.Len() > 0 {
_ = tmpList.ForEach(func(tp *deb.Package) error {
list.Remove(tp)
return nil
})
_ = list.Add(p)
}
} else {
fmt.Println("filter packages by version, filter err: ", err)
_ = c.AbortWithError(500, fmt.Errorf("unable to get %s maximum version: %s", p.Name, err))
}
}
return nil
})
}
if c.Request.URL.Query().Get("format") == "details" {
_ = list.ForEach(func(p *deb.Package) error {
result = append(result, p)
return nil
})
c.JSON(200, result)
} else {
c.JSON(200, list.Strings())
}
}
func AbortWithJSONError(c *gin.Context, code int, err error) {
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = c.AbortWithError(code, err)
}
+175
View File
@@ -0,0 +1,175 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"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"
)
func Test(t *testing.T) {
TestingT(t)
}
type APISuite struct {
context *ctx.AptlyContext
flags *flag.FlagSet
configFile *os.File
router http.Handler
}
var _ = Suite(&APISuite{})
func createTestConfig() *os.File {
file, err := os.CreateTemp("", "aptly")
if err != nil {
return nil
}
jsonString, err := json.Marshal(gin.H{
"architectures": []string{},
"enableMetricsEndpoint": true,
})
if err != nil {
return nil
}
_, _ = file.Write(jsonString)
return file
}
func (s *APISuite) setupContext() error {
aptly.Version = "testVersion"
file := createTestConfig()
if nil == file {
return fmt.Errorf("unable to create the test configuration file")
}
s.configFile = file
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
context, err := ctx.NewContext(s.flags)
if nil != err {
return err
}
s.context = context
s.router = Router(context)
return nil
}
func (s *APISuite) SetUpSuite(c *C) {
err := s.setupContext()
c.Assert(err, IsNil)
}
func (s *APISuite) TearDownSuite(c *C) {
_ = os.Remove(s.configFile.Name())
s.context.Shutdown()
}
func (s *APISuite) SetUpTest(c *C) {
}
func (s *APISuite) TearDownTest(c *C) {
}
func (s *APISuite) HTTPRequest(method string, url string, body io.Reader) (*httptest.ResponseRecorder, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
return w, nil
}
func (s *APISuite) TestGinRunsInReleaseMode(c *C) {
c.Check(gin.Mode(), Equals, gin.ReleaseMode)
}
func (s *APISuite) TestGetVersion(c *C) {
response, err := s.HTTPRequest("GET", "/api/version", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, "{\"Version\":\""+aptly.Version+"\"}")
}
func (s *APISuite) TestGetReadiness(c *C) {
response, err := s.HTTPRequest("GET", "/api/ready", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is ready\"}")
}
func (s *APISuite) TestGetHealthiness(c *C) {
response, err := s.HTTPRequest("GET", "/api/healthy", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, "{\"Status\":\"Aptly is healthy\"}")
}
func (s *APISuite) TestGetMetrics(c *C) {
response, err := s.HTTPRequest("GET", "/api/metrics", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
b := strings.Replace(response.Body.String(), "\n", "", -1)
c.Check(b, Matches, ".*# TYPE aptly_api_http_requests_in_flight gauge.*")
c.Check(b, Matches, ".*# TYPE aptly_api_http_requests_total counter.*")
c.Check(b, Matches, ".*# TYPE aptly_api_http_request_size_bytes summary.*")
c.Check(b, Matches, ".*# TYPE aptly_api_http_response_size_bytes summary.*")
c.Check(b, Matches, ".*# TYPE aptly_api_http_request_duration_seconds summary.*")
c.Check(b, Matches, ".*# TYPE aptly_build_info gauge.*")
c.Check(b, Matches, ".*aptly_build_info.*version=\"testVersion\".*")
}
func (s *APISuite) TestRepoCreate(c *C) {
body, err := json.Marshal(gin.H{
"Name": "dummy",
})
c.Assert(err, IsNil)
_, err = s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body))
c.Assert(err, IsNil)
}
func (s *APISuite) TestTruthy(c *C) {
c.Check(truthy("no"), Equals, false)
c.Check(truthy("n"), Equals, false)
c.Check(truthy("off"), Equals, false)
c.Check(truthy("false"), Equals, false)
c.Check(truthy("0"), Equals, false)
c.Check(truthy(false), Equals, false)
c.Check(truthy(0), Equals, false)
c.Check(truthy("y"), Equals, true)
c.Check(truthy("yes"), Equals, true)
c.Check(truthy("t"), Equals, true)
c.Check(truthy("true"), Equals, true)
c.Check(truthy("1"), Equals, true)
c.Check(truthy(true), Equals, true)
c.Check(truthy(1), Equals, true)
c.Check(truthy(nil), Equals, false)
c.Check(truthy("foobar"), Equals, true)
c.Check(truthy(-1), Equals, true)
c.Check(truthy(gin.H{}), Equals, true)
}
+188
View File
@@ -0,0 +1,188 @@
package api
import (
"fmt"
"sort"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/task"
"github.com/aptly-dev/aptly/utils"
"github.com/gin-gonic/gin"
)
// @Summary DB Cleanup
// @Description **Cleanup Aptly DB**
// @Description Database cleanup removes information about unreferenced packages and deletes files in the package pool that arent used by packages anymore.
// @Description It is a good idea to run this command after massive deletion of mirrors, snapshots or local repos.
// @Tags Database
// @Produce json
// @Param _async query bool false "Run in background and return task object"
// @Success 200 {object} string "Output"
// @Failure 404 {object} Error "Not Found"
// @Router /api/db/cleanup [post]
func apiDBCleanup(c *gin.Context) {
resources := []string{string(task.AllResourcesKey)}
maybeRunTaskInBackground(c, "Clean up db", resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
var err error
collectionFactory := context.NewCollectionFactory()
// collect information about referenced packages...
existingPackageRefs := deb.NewPackageRefList()
out.Printf("Loading mirrors, local repos, snapshots and published repos...")
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
e := collectionFactory.RemoteRepoCollection().LoadComplete(repo)
if e != nil {
return e
}
if repo.RefList() != nil {
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
}
return nil
})
if err != nil {
return nil, err
}
err = collectionFactory.LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
e := collectionFactory.LocalRepoCollection().LoadComplete(repo)
if e != nil {
return e
}
if repo.RefList() != nil {
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
}
return nil
})
if err != nil {
return nil, err
}
err = collectionFactory.SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
e := collectionFactory.SnapshotCollection().LoadComplete(snapshot)
if e != nil {
return e
}
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false, true)
return nil
})
if err != nil {
return nil, err
}
err = collectionFactory.PublishedRepoCollection().ForEach(func(published *deb.PublishedRepo) error {
if published.SourceKind != deb.SourceLocalRepo {
return nil
}
e := collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if e != nil {
return e
}
for _, component := range published.Components() {
existingPackageRefs = existingPackageRefs.Merge(published.RefList(component), false, true)
}
return nil
})
if err != nil {
return nil, err
}
// ... and compare it to the list of all packages
out.Printf("Loading list of all packages...")
allPackageRefs := collectionFactory.PackageCollection().AllPackageRefs()
toDelete := allPackageRefs.Subtract(existingPackageRefs)
// delete packages that are no longer referenced
out.Printf("Deleting unreferenced packages (%d)...", toDelete.Len())
// database can't err as collection factory already constructed
db, _ := context.Database()
if toDelete.Len() > 0 {
batch := db.CreateBatch()
_ = toDelete.ForEach(func(ref []byte) error {
_ = collectionFactory.PackageCollection().DeleteByKey(ref, batch)
return nil
})
err = batch.Write()
if err != nil {
return nil, fmt.Errorf("unable to write to DB: %s", err)
}
}
// now, build a list of files that should be present in Repository (package pool)
out.Printf("Building list of files referenced by packages...")
referencedFiles := make([]string, 0, existingPackageRefs.Len())
err = existingPackageRefs.ForEach(func(key []byte) error {
pkg, err2 := collectionFactory.PackageCollection().ByKey(key)
if err2 != nil {
tail := ""
return fmt.Errorf("unable to load package %s: %s%s", string(key), err2, tail)
}
paths, err2 := pkg.FilepathList(context.PackagePool())
if err2 != nil {
return err2
}
referencedFiles = append(referencedFiles, paths...)
return nil
})
if err != nil {
return nil, err
}
sort.Strings(referencedFiles)
// build a list of files in the package pool
out.Printf("Building list of files in package pool...")
existingFiles, err := context.PackagePool().FilepathList(out)
if err != nil {
return nil, fmt.Errorf("unable to collect file paths: %s", err)
}
// find files which are in the pool but not referenced by packages
filesToDelete := utils.StrSlicesSubstract(existingFiles, referencedFiles)
// delete files that are no longer referenced
out.Printf("Deleting unreferenced files (%d)...", len(filesToDelete))
countFilesToDelete := len(filesToDelete)
taskDetail := struct {
TotalNumberOfPackagesToDelete int
RemainingNumberOfPackagesToDelete int
}{
countFilesToDelete, countFilesToDelete,
}
detail.Store(taskDetail)
if countFilesToDelete > 0 {
var size, totalSize int64
for _, file := range filesToDelete {
size, err = context.PackagePool().Remove(file)
if err != nil {
return nil, err
}
taskDetail.RemainingNumberOfPackagesToDelete--
detail.Store(taskDetail)
totalSize += size
}
out.Printf("Disk space freed: %s...", utils.HumanBytes(totalSize))
}
out.Printf("Compacting database...")
return nil, db.CompactDB()
})
}
+5
View File
@@ -0,0 +1,5 @@
package api
type Error struct {
Error string `json:"error"`
}
+305
View File
@@ -0,0 +1,305 @@
package api
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"github.com/aptly-dev/aptly/utils"
"github.com/gin-gonic/gin"
"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)) {
if part == ".." || part == "." {
return false
}
}
return true
}
func verifyDir(c *gin.Context) bool {
if !verifyPath(c.Params.ByName("dir")) {
AbortWithJSONError(c, 400, fmt.Errorf("wrong dir"))
return false
}
return true
}
// @Summary List Directories
// @Description **Get list of upload directories**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/files
// @Description ["aptly-0.9"]
// @Description ```
// @Tags Files
// @Produce json
// @Success 200 {array} string "List of files"
// @Router /api/files [get]
func apiFilesListDirs(c *gin.Context) {
list := []string{}
listLock := &sync.Mutex{}
err := walker.Walk(context.UploadPath(), func(path string, info os.FileInfo) error {
if path == context.UploadPath() {
return nil
}
if info.IsDir() {
listLock.Lock()
defer listLock.Unlock()
list = append(list, filepath.Base(path))
return filepath.SkipDir
}
return nil
})
if err != nil && !os.IsNotExist(err) {
AbortWithJSONError(c, 400, err)
return
}
c.JSON(200, list)
}
// @Summary Upload Files
// @Description **Upload files to a directory**
// @Description
// @Description - one or more files can be uploaded
// @Description - existing uploaded are overwritten
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X POST -F file=@aptly_0.9~dev+217+ge5d646c_i386.deb http://localhost:8080/api/files/aptly-0.9
// @Description ["aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb"]
// @Description ```
// @Tags Files
// @Accept multipart/form-data
// @Param dir path string true "Directory to upload files to. Created if does not exist"
// @Param files formData file true "Files to upload"
// @Produce json
// @Success 200 {array} string "list of uploaded files"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir} [post]
func apiFilesUpload(c *gin.Context) {
if !verifyDir(c) {
return
}
path := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir")))
err := os.MkdirAll(path, 0777)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
err = c.Request.ParseMultipartForm(10 * 1024 * 1024)
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
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
}
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
}
_, 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)
}
// @Summary List Files
// @Description **Show uploaded files in upload directory**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/files/aptly-0.9
// @Description ["aptly_0.9~dev+217+ge5d646c_i386.deb"]
// @Description ```
// @Tags Files
// @Produce json
// @Param dir path string true "Directory to list"
// @Success 200 {array} string "Files found in directory"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir} [get]
func apiFilesListFiles(c *gin.Context) {
if !verifyDir(c) {
return
}
list := []string{}
listLock := &sync.Mutex{}
root := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir")))
err := filepath.Walk(root, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return err
}
if path == root {
return nil
}
listLock.Lock()
defer listLock.Unlock()
list = append(list, filepath.Base(path))
return nil
})
if err != nil {
if os.IsNotExist(err) {
AbortWithJSONError(c, 404, err)
} else {
AbortWithJSONError(c, 500, err)
}
return
}
c.JSON(200, list)
}
// @Summary Delete Directory
// @Description **Delete upload directory and uploaded files within**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9
// @Description {}
// @Description ```
// @Tags Files
// @Produce json
// @Param dir path string true "Directory"
// @Success 200 {object} string "msg"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir} [delete]
func apiFilesDeleteDir(c *gin.Context) {
if !verifyDir(c) {
return
}
err := os.RemoveAll(filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir"))))
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
c.JSON(200, gin.H{})
}
// @Summary Delete File
// @Description **Delete a uploaded file in upload directory**
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb
// @Description {}
// @Description ```
// @Tags Files
// @Produce json
// @Param dir path string true "Directory to delete from"
// @Param name path string true "File to delete"
// @Success 200 {object} string "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/files/{dir}/{name} [delete]
func apiFilesDeleteFile(c *gin.Context) {
if !verifyDir(c) {
return
}
dir := utils.SanitizePath(c.Params.ByName("dir"))
name := utils.SanitizePath(c.Params.ByName("name"))
if !verifyPath(name) {
AbortWithJSONError(c, 400, fmt.Errorf("wrong file"))
return
}
err := os.Remove(filepath.Join(context.UploadPath(), dir, name))
if err != nil {
if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) {
AbortWithJSONError(c, 500, err)
return
}
}
c.JSON(200, gin.H{})
}
+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)
}
+311
View File
@@ -0,0 +1,311 @@
package api
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/aptly-dev/aptly/pgp"
"github.com/aptly-dev/aptly/utils"
"github.com/gin-gonic/gin"
)
type gpgKeyInfo struct {
// 16-character key ID (short form)
KeyID string `json:"KeyID" example:"8B48AD6246925553"`
// Full fingerprint
Fingerprint string `json:"Fingerprint" example:"D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0"`
// Key validity (u=unknown, f=fulltrust, m=marginal, n=never)
Validity string `json:"Validity" example:"u"`
// User ID(s) associated with this key
UserIDs []string `json:"UserIDs" example:"John Doe <john@example.com>"`
// Creation date (Unix timestamp format from gpg)
CreatedAt string `json:"CreatedAt" example:"2023-01-15"`
}
type gpgKeyListResponse struct {
Keys []gpgKeyInfo `json:"Keys"`
}
type gpgAddKeyParams struct {
// Keyring for adding the keys (default: trustedkeys.gpg)
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
// Add ASCII armored gpg public key, do not download from keyserver
GpgKeyArmor string `json:"GpgKeyArmor" example:""`
// Keyserver to download keys provided in `GpgKeyID`
Keyserver string `json:"Keyserver" example:"hkp://keyserver.ubuntu.com:80"`
// Keys do download from `Keyserver`, separated by space
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500 8B48AD6246925553"`
}
type gpgDeleteKeyParams struct {
// Keyring to delete keys from (default: trustedkeys.gpg)
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
// Key ID or fingerprint to delete
GpgKeyID string `json:"GpgKeyID" example:"8B48AD6246925553"`
}
// @Summary Add GPG Keys
// @Description **Adds GPG keys to aptly keyring**
// @Description
// @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)
// @Description * By providing a `Keyserver` and one or more key IDs in `GpgKeyID`, separated by space (leave GpgKeyArmor empty)
// @Description
// @Tags Mirrors
// @Consume json
// @Param request body gpgAddKeyParams true "Parameters"
// @Produce json
// @Success 200 {object} string "OK"
// @Failure 400 {object} Error "Bad Request"
// @Router /api/gpg/key [post]
func apiGPGAddKey(c *gin.Context) {
b := gpgAddKeyParams{}
if c.Bind(&b) != nil {
return
}
b.Keyserver = utils.SanitizePath(b.Keyserver)
b.GpgKeyID = utils.SanitizePath(b.GpgKeyID)
b.GpgKeyArmor = utils.SanitizePath(b.GpgKeyArmor)
// b.Keyring can be an absolute path
var err error
args := []string{"--no-default-keyring", "--allow-non-selfsigned-uid"}
keyring := "trustedkeys.gpg"
if len(b.Keyring) > 0 {
keyring = b.Keyring
}
args = append(args, "--keyring", keyring)
if len(b.Keyserver) > 0 {
args = append(args, "--keyserver", b.Keyserver)
}
if len(b.GpgKeyArmor) > 0 {
var tempdir string
tempdir, err = os.MkdirTemp(os.TempDir(), "aptly")
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
defer func() { _ = os.RemoveAll(tempdir) }()
keypath := filepath.Join(tempdir, "key")
keyfile, e := os.Create(keypath)
if e != nil {
AbortWithJSONError(c, 400, e)
return
}
if _, e = keyfile.WriteString(b.GpgKeyArmor); e != nil {
AbortWithJSONError(c, 400, e)
}
args = append(args, "--import", keypath)
}
if len(b.GpgKeyID) > 0 {
keys := strings.Fields(b.GpgKeyID)
args = append(args, "--recv-keys")
args = append(args, keys...)
}
finder := pgp.GPGDefaultFinder()
gpg, _, err := finder.FindGPG()
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
// it might happened that we have a situation with an erroneous
// gpg command (e.g. when GpgKeyID and GpgKeyArmor is set).
// there is no error handling for such as gpg will do this for us
cmd := exec.Command(gpg, args...)
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
out, err := cmd.CombinedOutput()
if err != nil {
c.JSON(400, string(out))
return
}
c.JSON(200, string(out))
}
// @Summary List GPG Keys
// @Description **Lists all GPG keys in aptly keyring**
// @Description
// @Description Returns all public keys currently installed in the aptly GPG keyring.
// @Description
// @Tags Mirrors
// @Param keyring query string false "Keyring file to list keys from (default: trustedkeys.gpg)" example(trustedkeys.gpg)
// @Produce json
// @Success 200 {object} gpgKeyListResponse "OK"
// @Failure 400 {object} Error "Bad Request"
// @Router /api/gpg/keys [get]
func apiGPGListKeys(c *gin.Context) {
keyring := c.DefaultQuery("keyring", "trustedkeys.gpg")
keyring = utils.SanitizePath(keyring)
finder := pgp.GPGDefaultFinder()
gpg, _, err := finder.FindGPG()
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
args := []string{
"--no-default-keyring",
"--with-colons",
"--keyring", keyring,
"--list-keys",
}
cmd := exec.Command(gpg, args...)
out, err := cmd.CombinedOutput()
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("failed to list keys: %s", string(out)))
return
}
keys := parseGPGOutput(string(out))
c.JSON(200, gpgKeyListResponse{Keys: keys})
}
// @Summary Delete GPG Key
// @Description **Deletes a GPG key from aptly keyring**
// @Description
// @Description Removes a public key from the aptly GPG keyring. This is useful for removing
// @Description compromised keys or cleaning up obsolete keys.
// @Description
// @Tags Mirrors
// @Consume json
// @Param request body gpgDeleteKeyParams true "Parameters"
// @Produce json
// @Success 200 {object} string "OK"
// @Failure 400 {object} Error "Bad Request"
// @Router /api/gpg/key [delete]
func apiGPGDeleteKey(c *gin.Context) {
b := gpgDeleteKeyParams{}
if c.Bind(&b) != nil {
AbortWithJSONError(c, 400, fmt.Errorf("invalid request body"))
return
}
if len(strings.TrimSpace(b.GpgKeyID)) == 0 {
AbortWithJSONError(c, 400, fmt.Errorf("GpgKeyID is required"))
return
}
b.GpgKeyID = utils.SanitizePath(b.GpgKeyID)
// b.Keyring can be an absolute path
finder := pgp.GPGDefaultFinder()
gpg, _, err := finder.FindGPG()
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
args := []string{
"--no-default-keyring",
"--allow-non-selfsigned-uid",
"--batch",
"--yes",
}
keyring := "trustedkeys.gpg"
if len(b.Keyring) > 0 {
keyring = b.Keyring
}
args = append(args, "--keyring", keyring)
args = append(args, "--delete-keys", b.GpgKeyID)
cmd := exec.Command(gpg, args...)
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
out, err := cmd.CombinedOutput()
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("failed to delete key: %s", string(out)))
return
}
c.JSON(200, string(out))
}
// parseGPGOutput parses the output of `gpg --with-colons --list-keys`
// and returns a structured list of keys
func parseGPGOutput(output string) []gpgKeyInfo {
var keys []gpgKeyInfo
var currentKey *gpgKeyInfo
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
parts := strings.Split(line, ":")
if len(parts) < 10 {
continue
}
recordType := parts[0]
// pub: public key record
if recordType == "pub" {
// Save previous key if it exists
if currentKey != nil && currentKey.KeyID != "" {
keys = append(keys, *currentKey)
}
// Create new key entry
// Format: pub:trust:length:algo:keyid:created:expires:uidhash:...
keyID := parts[4]
if len(keyID) >= 16 {
keyID = keyID[len(keyID)-16:] // Last 16 chars = short key ID
}
validity := parts[1]
createdAt := parts[5]
currentKey = &gpgKeyInfo{
KeyID: keyID,
Validity: validity,
CreatedAt: createdAt,
UserIDs: []string{},
Fingerprint: "",
}
}
// uid: user ID record
if recordType == "uid" && currentKey != nil {
// Format: uid:trust:created:expires:keyid:uidhash:uidtype:validity:userID:...
if len(parts) >= 10 {
userID := parts[9]
if userID != "" {
currentKey.UserIDs = append(currentKey.UserIDs, userID)
}
}
}
// fpr: fingerprint record
if recordType == "fpr" && currentKey != nil {
// Format: fpr:::::::::fingerprint:
if len(parts) >= 10 {
fingerprint := parts[9]
currentKey.Fingerprint = fingerprint
}
}
}
// Don't forget the last key
if currentKey != nil && currentKey.KeyID != "" {
keys = append(keys, *currentKey)
}
return keys
}
+451
View File
@@ -0,0 +1,451 @@
package api
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
. "gopkg.in/check.v1"
)
type GPGSuite struct {
APISuite
}
var _ = Suite(&GPGSuite{})
func (s *GPGSuite) withFakeGPG(c *C, scriptBody string, test func(scriptPath string)) {
tempDir, err := os.MkdirTemp("", "aptly-fake-gpg")
c.Assert(err, IsNil)
defer func() { _ = os.RemoveAll(tempDir) }()
scriptPath := filepath.Join(tempDir, "gpg")
err = os.WriteFile(scriptPath, []byte(scriptBody), 0o755)
c.Assert(err, IsNil)
oldPath := os.Getenv("PATH")
err = os.Setenv("PATH", tempDir+string(os.PathListSeparator)+oldPath)
c.Assert(err, IsNil)
defer func() { _ = os.Setenv("PATH", oldPath) }()
test(scriptPath)
}
func (s *GPGSuite) fakeGPGScript(c *C, listOutput string, deleteOutput string, deleteError string) string {
return "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then\n" +
" echo 'gpg (GnuPG) 2.2.27'\n" +
" exit 0\n" +
"fi\n" +
"args=\"$*\"\n" +
"if printf '%s' \"$args\" | grep -q -- '--list-keys'; then\n" +
" cat <<'EOF'\n" + listOutput + "\nEOF\n" +
" exit 0\n" +
"fi\n" +
"if printf '%s' \"$args\" | grep -q -- '--delete-keys'; then\n" +
" if [ -n \"" + strings.ReplaceAll(deleteError, "\n", "") + "\" ]; then\n" +
" echo '" + strings.ReplaceAll(deleteError, "'", "'\\''") + "'\n" +
" exit 1\n" +
" fi\n" +
" cat <<'EOF'\n" + deleteOutput + "\nEOF\n" +
" exit 0\n" +
"fi\n" +
"echo 'unexpected invocation' >&2\n" +
"exit 1\n"
}
// TestParseGPGOutputEmpty tests parsing of empty GPG output
func (s *GPGSuite) TestParseGPGOutputEmpty(c *C) {
output := ""
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 0)
}
// TestParseGPGOutputSingleKeyMinimal tests parsing a single key with minimal fields
func (s *GPGSuite) TestParseGPGOutputSingleKeyMinimal(c *C) {
// Minimal valid GPG output with one key
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
key := keys[0]
c.Check(key.KeyID, Equals, "8B48AD6246925553")
c.Check(key.Validity, Equals, "u")
c.Check(key.CreatedAt, Equals, "1611864000")
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
c.Check(key.UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
}
// TestParseGPGOutputMultipleKeys tests parsing multiple keys
func (s *GPGSuite) TestParseGPGOutputMultipleKeys(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
pub:f:2048:1:A1B2C3D4E5F67890:1580592000:1612128000:uidhash:::scESC:::::::23::0:
uid:f::::1580592000::0987654321::Jane Smith <jane@example.com>::::::::::0:
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 2)
// First key
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
c.Check(keys[0].Validity, Equals, "u")
c.Check(keys[0].UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
// Second key
c.Check(keys[1].KeyID, Equals, "A1B2C3D4E5F67890")
c.Check(keys[1].Validity, Equals, "f")
c.Check(keys[1].UserIDs, DeepEquals, []string{"Jane Smith <jane@example.com>"})
}
// TestParseGPGOutputMultipleUIDs tests a key with multiple user IDs
func (s *GPGSuite) TestParseGPGOutputMultipleUIDs(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
uid:u::::1611864000::1234567891::John Doe <john.doe@company.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
key := keys[0]
c.Check(key.UserIDs, HasLen, 2)
c.Check(key.UserIDs, DeepEquals, []string{
"John Doe <john@example.com>",
"John Doe <john.doe@company.com>",
})
}
// TestParseGPGOutputMalformedLines tests that malformed lines are skipped
func (s *GPGSuite) TestParseGPGOutputMalformedLines(c *C) {
// Mix of valid and invalid lines (too few fields)
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
invalid:line:with:only:three:fields
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
}
// TestParseGPGOutputEmptyLines tests that empty lines are skipped
func (s *GPGSuite) TestParseGPGOutputEmptyLines(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
}
// TestParseGPGOutputKeyWithoutUID tests a public key without user ID
func (s *GPGSuite) TestParseGPGOutputKeyWithoutUID(c *C) {
// Key without uid record (should still be included)
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
key := keys[0]
c.Check(key.KeyID, Equals, "8B48AD6246925553")
c.Check(key.UserIDs, HasLen, 0)
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
}
// TestParseGPGOutputVariousValidity tests different validity values
func (s *GPGSuite) TestParseGPGOutputVariousValidity(c *C) {
output := `pub:u:4096:1:KEY1111111111111:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::Key1::::::::::0:
fpr:::::::::1111111111111111111111111111111111111111:
pub:f:4096:1:KEY2222222222222:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:f::::1611864000::1234567891::Key2::::::::::0:
fpr:::::::::2222222222222222222222222222222222222222:
pub:m:4096:1:KEY3333333333333:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:m::::1611864000::1234567892::Key3::::::::::0:
fpr:::::::::3333333333333333333333333333333333333333:
pub:n:4096:1:KEY4444444444444:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:n::::1611864000::1234567893::Key4::::::::::0:
fpr:::::::::4444444444444444444444444444444444444444:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 4)
validities := []string{"u", "f", "m", "n"}
for i, validity := range validities {
c.Check(keys[i].Validity, Equals, validity)
}
}
// TestParseGPGOutputShortKeyID tests that key IDs are shortened to 16 chars
func (s *GPGSuite) TestParseGPGOutputShortKeyID(c *C) {
// 40-character key ID that should be shortened to last 16 chars
longKeyID := "0123456789ABCDEF0123456789ABCDEF8B48AD62"
output := `pub:u:4096:1:` + longKeyID + `:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should extract the last 16 characters: 89ABCDEF8B48AD62
c.Check(keys[0].KeyID, Equals, "89ABCDEF8B48AD62")
}
// TestParseGPGOutputSpecialCharactersInUID tests user IDs with special characters
func (s *GPGSuite) TestParseGPGOutputSpecialCharactersInUID(c *C) {
// UID with Unicode characters and special formatting
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::J\xc3\xb6hn D\xc3\xb6\xc3\xa9 (D\xc3\xbcss) <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should preserve the encoded special characters
c.Check(keys[0].UserIDs, HasLen, 1)
}
// TestAPIGPGListKeysDefaultKeyring tests the HTTP endpoint with default keyring
func (s *GPGSuite) TestAPIGPGListKeysDefaultKeyring(c *C) {
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:u:4096:1:8B48AD6246925553:1611864000:::::
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`, "", ""), func(_ string) {
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
var result gpgKeyListResponse
err = json.NewDecoder(response.Body).Decode(&result)
c.Assert(err, IsNil)
c.Check(result.Keys, HasLen, 1)
c.Check(result.Keys[0].KeyID, Equals, "8B48AD6246925553")
})
}
// TestAPIGPGListKeysWithKeyringParam tests the HTTP endpoint with custom keyring parameter
func (s *GPGSuite) TestAPIGPGListKeysWithKeyringParam(c *C) {
argFile, err := os.CreateTemp("", "aptly-gpg-args")
c.Assert(err, IsNil)
_ = argFile.Close()
defer func() { _ = os.Remove(argFile.Name()) }()
script := "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
"cat <<'EOF'\n" +
"pub:u:4096:1:8B48AD6246925553:1611864000:::::\n" +
"fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:\n" +
"EOF\n" +
"exit 0\n" +
"fi\n" +
"exit 1\n"
s.withFakeGPG(c, script, func(_ string) {
response, reqErr := s.HTTPRequest("GET", "/api/gpg/keys?keyring=/custom.gpg", nil)
c.Assert(reqErr, IsNil)
c.Check(response.Code, Equals, 200)
argBytes, readErr := os.ReadFile(argFile.Name())
c.Assert(readErr, IsNil)
c.Check(string(argBytes), Matches, `(?s).*--keyring\ncustom\.gpg\n.*`)
})
}
// TestAPIGPGListKeysResponseFormat tests that the response has the correct structure
func (s *GPGSuite) TestAPIGPGListKeysResponseFormat(c *C) {
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:f:4096:1:A1B2C3D4E5F67890:1611864000:::::
uid:f::::1611864000::1234567890::Jane Smith <jane@example.com>::::::::::0:
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`, "", ""), func(_ string) {
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
var result gpgKeyListResponse
err = json.NewDecoder(response.Body).Decode(&result)
c.Assert(err, IsNil)
c.Assert(result.Keys, HasLen, 1)
c.Check(result.Keys[0].KeyID, Equals, "A1B2C3D4E5F67890")
c.Check(result.Keys[0].Validity, Equals, "f")
c.Check(result.Keys[0].CreatedAt, Equals, "1611864000")
})
}
// TestParseGPGOutputEdgeCaseUIDWithoutFields tests UID record with missing fields
func (s *GPGSuite) TestParseGPGOutputEdgeCaseUIDWithoutFields(c *C) {
// UID record with fewer than 10 fields
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should not have user ID since it's in field 9 and this record is too short
c.Check(keys[0].UserIDs, HasLen, 0)
}
// TestParseGPGOutputFingerprintWithoutCurrentKey tests FPR record appearing before any PUB
func (s *GPGSuite) TestParseGPGOutputFingerprintWithoutCurrentKey(c *C) {
// FPR record without a preceding PUB (should be ignored)
output := `fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should only have one key with the correct fingerprint
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
}
// TestParseGPGOutputComplexRealWorldExample tests real-world-like GPG output
func (s *GPGSuite) TestParseGPGOutputComplexRealWorldExample(c *C) {
// Real-world GPG output with multiple keys, UIDs, and other record types (sig, sub)
// Note: sub and sig records are skipped as we only care about pub/uid/fpr
realWorldOutput := `tru::1:1611864000:0:3:1:5
pub:u:4096:1:8B48AD6246925553:1611864000:2023-01-15T00:00:00:::::scESC:::::::23::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
uid:u::::1611864100::1234567891::John Doe <john@work.com>::::::::::0:
pub:f:2048:1:1234567890123456:1580592000:2022-12-31T00:00:00::u:::scESC:::::::23::0:
fpr:::::::::F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9:
uid:f::::1580592000::0987654321::Maintainer Key <maint@example.com>::::::::::0:`
keys := parseGPGOutput(realWorldOutput)
c.Check(keys, HasLen, 2)
// First key should have 2 UIDs
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
c.Check(keys[0].UserIDs, HasLen, 2)
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
// Second key should have 1 UID
c.Check(keys[1].KeyID, Equals, "1234567890123456")
c.Check(keys[1].UserIDs, HasLen, 1)
c.Check(keys[1].Fingerprint, Equals, "F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9")
}
// TestParseGPGOutputConsecutiveEmptyUIDs tests handling of consecutive empty user ID fields
func (s *GPGSuite) TestParseGPGOutputConsecutiveEmptyUIDs(c *C) {
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
uid:u::::1611864000::1234567890:::::::::::0:
uid:u::::1611864000::1234567891::John Doe <john@example.com>::::::::::0:
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
keys := parseGPGOutput(output)
c.Check(keys, HasLen, 1)
// Should skip empty UID but include the non-empty one
c.Check(keys[0].UserIDs, HasLen, 1)
c.Check(keys[0].UserIDs[0], Equals, "John Doe <john@example.com>")
}
// TestGPGDeleteKeyParamsValidation tests gpgDeleteKeyParams validation
func (s *GPGSuite) TestGPGDeleteKeyParamsValidation(c *C) {
// This is a unit test that validates parameter structure (no HTTP needed)
params := gpgDeleteKeyParams{
Keyring: "custom.gpg",
GpgKeyID: "8B48AD6246925553",
}
c.Check(params.Keyring, Equals, "custom.gpg")
c.Check(params.GpgKeyID, Equals, "8B48AD6246925553")
}
// TestAPIGPGDeleteKeyMissingKeyID tests delete with missing key ID parameter
func (s *GPGSuite) TestAPIGPGDeleteKeyMissingKeyID(c *C) {
body, err := json.Marshal(map[string]string{
"Keyring": "trustedkeys.gpg",
// GpgKeyID is missing
})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
}
// TestAPIGPGDeleteKeyInvalidJSON tests delete with invalid JSON request
func (s *GPGSuite) TestAPIGPGDeleteKeyInvalidJSON(c *C) {
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader([]byte("invalid json")))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
}
// TestAPIGPGDeleteKeySuccess tests successful key deletion
func (s *GPGSuite) TestAPIGPGDeleteKeySuccess(c *C) {
argFile, err := os.CreateTemp("", "aptly-gpg-delete-args")
c.Assert(err, IsNil)
_ = argFile.Close()
defer func() { _ = os.Remove(argFile.Name()) }()
script := "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
"if printf '%s' \"$*\" | grep -q -- '--delete-keys'; then\n" +
"echo 'deleted'\n" +
"exit 0\n" +
"fi\n" +
"exit 1\n"
s.withFakeGPG(c, script, func(_ string) {
body, marshalErr := json.Marshal(gpgDeleteKeyParams{
Keyring: "/trustedkeys.gpg",
GpgKeyID: "8B48AD6246925553",
})
c.Assert(marshalErr, IsNil)
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
c.Assert(reqErr, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Matches, `"deleted\\n"`)
argBytes, readErr := os.ReadFile(argFile.Name())
c.Assert(readErr, IsNil)
argText := string(argBytes)
c.Check(argText, Matches, `(?s).*--batch\n--yes\n.*`)
c.Check(argText, Matches, `(?s).*--keyring\n/trustedkeys\.gpg\n.*`)
c.Check(argText, Matches, `(?s).*--delete-keys\n8B48AD6246925553\n.*`)
})
}
// TestAPIGPGListKeysCommandFailure tests list error propagation from gpg
func (s *GPGSuite) TestAPIGPGListKeysCommandFailure(c *C) {
script := "#!/bin/sh\n" +
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
"echo 'keyring missing'\n" +
"exit 1\n" +
"fi\n" +
"exit 1\n"
s.withFakeGPG(c, script, func(_ string) {
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Matches, `(?s).*failed to list keys: keyring missing.*`)
})
}
// TestAPIGPGDeleteKeyCommandFailure tests delete error propagation from gpg
func (s *GPGSuite) TestAPIGPGDeleteKeyCommandFailure(c *C) {
s.withFakeGPG(c, s.fakeGPGScript(c, "", "", "delete failed"), func(_ string) {
body, err := json.Marshal(gpgDeleteKeyParams{
Keyring: "trustedkeys.gpg",
GpgKeyID: "8B48AD6246925553",
})
c.Assert(err, IsNil)
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
c.Assert(reqErr, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Matches, `(?s).*failed to delete key: delete failed.*`)
})
}
+94
View File
@@ -0,0 +1,94 @@
package api
import (
"bytes"
"fmt"
"io"
"mime"
"os"
"os/exec"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
)
// @Summary Graph Output
// @Description **Generate dependency graph**
// @Description
// @Description Command graph generates graph of dependencies:
// @Description
// @Description * between snapshots and mirrors (what mirror was used to create each snapshot)
// @Description * between snapshots and local repos (what local repo was used to create snapshot)
// @Description * between snapshots (pulling, merging, etc.)
// @Description * between snapshots, local repos and published repositories (how snapshots were published).
// @Description
// @Description Graph is rendered to PNG file using graphviz package.
// @Description
// @Description Example URL: `http://localhost:8080/api/graph.svg?layout=vertical`
// @Tags Status
// @Produce image/png, image/svg+xml
// @Param ext path string true "ext specifies desired file extension, e.g. .png, .svg."
// @Param layout query string false "Change between a `horizontal` (default) and a `vertical` graph layout."
// @Success 200 {object} []byte "Output"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/graph.{ext} [get]
func apiGraph(c *gin.Context) {
var (
err error
output []byte
)
ext := c.Params.ByName("ext")
layout := c.Request.URL.Query().Get("layout")
factory := context.NewCollectionFactory()
graph, err := deb.BuildGraph(factory, layout)
if err != nil {
c.JSON(500, err)
return
}
buf := bytes.NewBufferString(graph.String())
if ext == "dot" || ext == "gv" {
// If the raw dot data is requested, return it as string.
// This allows client-side rendering rather than server-side.
c.String(200, buf.String())
return
}
command := exec.Command("dot", "-T"+ext)
command.Stderr = os.Stderr
stdin, err := command.StdinPipe()
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
_, err = io.Copy(stdin, buf)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
err = stdin.Close()
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
output, err = command.Output()
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err))
return
}
mimeType := mime.TypeByExtension("." + ext)
if mimeType == "" {
mimeType = "application/octet-stream"
}
c.Data(200, mimeType, output)
}
+116
View File
@@ -0,0 +1,116 @@
package api
import (
"fmt"
"runtime"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/rs/zerolog/log"
)
var (
apiRequestsInFlightGauge = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "aptly_api_http_requests_in_flight",
Help: "Number of concurrent HTTP api requests currently handled.",
},
[]string{"method", "path"},
)
apiRequestsTotalCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "aptly_api_http_requests_total",
Help: "Total number of api requests.",
},
[]string{"code", "method", "path"},
)
apiRequestSizeSummary = promauto.NewSummaryVec(
prometheus.SummaryOpts{
Name: "aptly_api_http_request_size_bytes",
Help: "Api HTTP request size in bytes.",
},
[]string{"code", "method", "path"},
)
apiResponseSizeSummary = promauto.NewSummaryVec(
prometheus.SummaryOpts{
Name: "aptly_api_http_response_size_bytes",
Help: "Api HTTP response size in bytes.",
},
[]string{"code", "method", "path"},
)
apiRequestsDurationSummary = promauto.NewSummaryVec(
prometheus.SummaryOpts{
Name: "aptly_api_http_request_duration_seconds",
Help: "Duration of api requests in seconds.",
},
[]string{"code", "method", "path"},
)
apiVersionGauge = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "aptly_build_info",
Help: "Metric with a constant '1' value labeled by version and goversion from which aptly was built.",
},
[]string{"version", "goversion"},
)
apiFilesUploadedCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "aptly_api_files_uploaded_total",
Help: "Total number of uploaded files labeled by upload directory.",
},
[]string{"directory"},
)
apiReposPackageCountGauge = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "aptly_repos_package_count",
Help: "Current number of published packages labeled by source, distribution and component.",
},
[]string{"source", "distribution", "component"},
)
)
type metricsCollectorRegistrar struct {
hasRegistered bool
}
func (r *metricsCollectorRegistrar) Register(router *gin.Engine) {
if !r.hasRegistered {
apiVersionGauge.WithLabelValues(aptly.Version, runtime.Version()).Set(1)
router.Use(instrumentHandlerInFlight(apiRequestsInFlightGauge, getBasePath))
router.Use(instrumentHandlerCounter(apiRequestsTotalCounter, getBasePath))
router.Use(instrumentHandlerRequestSize(apiRequestSizeSummary, getBasePath))
router.Use(instrumentHandlerResponseSize(apiResponseSizeSummary, getBasePath))
router.Use(instrumentHandlerDuration(apiRequestsDurationSummary, getBasePath))
r.hasRegistered = true
}
}
var MetricsCollectorRegistrar = metricsCollectorRegistrar{hasRegistered: false}
func countPackagesByRepos() {
err := context.NewCollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
err := context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.NewCollectionFactory())
if err != nil {
msg := fmt.Sprintf(
"Error %s found while determining package count for metrics endpoint (prefix:%s / distribution:%s / component:%s\n).",
err, repo.StoragePrefix(), repo.Distribution, repo.Components())
log.Warn().Msg(msg)
return err
}
components := repo.Components()
for _, c := range components {
count := float64(len(repo.RefList(c).Refs))
apiReposPackageCountGauge.WithLabelValues(fmt.Sprintf("%s", (repo.SourceNames())), repo.Distribution, c).Set(count)
}
return nil
})
if err != nil {
msg := fmt.Sprintf("Error %s found while listing published repos for metrics endpoint", err)
log.Warn().Msg(msg)
}
}
+116
View File
@@ -0,0 +1,116 @@
package api
import (
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog/log"
)
// Only use base path as label value (e.g.: /api/repos) because of time series cardinality
// See https://prometheus.io/docs/practices/naming/#labels
func getBasePath(c *gin.Context) string {
segment0, err := getURLSegment(c.Request.URL.Path, 0)
if err != nil {
return "/"
}
segment1, err := getURLSegment(c.Request.URL.Path, 1)
if err != nil {
return *segment0
}
return *segment0 + *segment1
}
func getURLSegment(url string, idx int) (*string, error) {
urlSegments := strings.Split(url, "/")
// Remove segment at index 0 because it's an empty string
urlSegments = urlSegments[1:cap(urlSegments)]
if len(urlSegments) <= idx {
return nil, fmt.Errorf("index %d out of range, only has %d url segments", idx, len(urlSegments))
}
segmentAtIndex := urlSegments[idx]
s := fmt.Sprintf("/%s", segmentAtIndex)
return &s, nil
}
func instrumentHandlerInFlight(g *prometheus.GaugeVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
return func(c *gin.Context) {
g.WithLabelValues(c.Request.Method, pathFunc(c)).Inc()
defer g.WithLabelValues(c.Request.Method, pathFunc(c)).Dec()
c.Next()
}
}
func instrumentHandlerCounter(counter *prometheus.CounterVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
return func(c *gin.Context) {
c.Next()
counter.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Inc()
}
}
func instrumentHandlerRequestSize(obs prometheus.ObserverVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
return func(c *gin.Context) {
c.Next()
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(float64(c.Request.ContentLength))
}
}
func instrumentHandlerResponseSize(obs prometheus.ObserverVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
return func(c *gin.Context) {
c.Next()
var responseSize = math.Max(float64(c.Writer.Size()), 0)
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(responseSize)
}
}
func instrumentHandlerDuration(obs prometheus.ObserverVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
return func(c *gin.Context) {
now := time.Now()
c.Next()
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(time.Since(now).Seconds())
}
}
// JSONLogger is a gin middleware that takes an instance of Logger and uses it for writing access
// logs that include error messages if there are any.
func JSONLogger() gin.HandlerFunc {
return func(c *gin.Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
ts := time.Now()
if raw != "" {
path = path + "?" + raw
}
errorMessage := strings.TrimSuffix(c.Errors.ByType(gin.ErrorTypePrivate).String(), "\n")
l := log.With().Str("remote", c.ClientIP()).Logger().
With().Str("method", c.Request.Method).Logger().
With().Str("path", path).Logger().
With().Str("protocol", c.Request.Proto).Logger().
With().Str("code", fmt.Sprint(c.Writer.Status())).Logger().
With().Str("latency", ts.Sub(start).String()).Logger().
With().Str("agent", c.Request.UserAgent()).Logger()
if c.Writer.Status() >= 400 && c.Writer.Status() < 500 {
l.Warn().Msg(errorMessage)
} else if c.Writer.Status() >= 500 {
l.Error().Msg(errorMessage)
} else {
l.Info().Msg(errorMessage)
}
}
}
+255
View File
@@ -0,0 +1,255 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"sync/atomic"
"github.com/aptly-dev/aptly/utils"
"github.com/gin-gonic/gin"
. "gopkg.in/check.v1"
)
type MiddlewareSuite struct {
router http.Handler
context *gin.Context
logReader *os.File
logWriter *os.File
}
var _ = Suite(&MiddlewareSuite{})
func (s *MiddlewareSuite) SetUpTest(c *C) {
r, w, err := os.Pipe()
c.Assert(err, IsNil)
utils.SetupJSONLogger("debug", w)
mw := JSONLogger()
router := gin.New()
router.UseRawPath = true
router.Use(mw)
router.Use(gin.Recovery(), gin.ErrorLogger())
root := router.Group("/api")
isReady := &atomic.Value{}
isReady.Store(false)
root.GET("/ready", apiReady(isReady))
root.GET("/healthy", apiHealthy)
s.router = router
s.logReader = r
s.logWriter = w
}
func (s *MiddlewareSuite) TearDownTest(c *C) {
s.router = nil
s.context = nil
s.logReader = nil
s.logWriter = nil
}
func (s *MiddlewareSuite) HTTPRequest(method string, url string, body io.Reader) {
recorder := httptest.NewRecorder()
s.context, _ = gin.CreateTestContext(recorder)
req, _ := http.NewRequestWithContext(s.context, method, url, body)
s.context.Request = req
req.Header.Add("Content-Type", "application/json")
s.router.ServeHTTP(httptest.NewRecorder(), req)
}
func (s *MiddlewareSuite) TestJSONMiddleware4xx(c *C) {
outC := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, s.logReader)
fmt.Println(buf.String())
outC <- buf.String()
}()
s.HTTPRequest(http.MethodGet, "/", nil)
_ = s.logWriter.Close()
capturedOutput := <-outC
var jsonMap map[string]interface{}
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
if val, ok := jsonMap["level"]; ok {
c.Check(val, Equals, "warn")
} else {
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
}
if val, ok := jsonMap["method"]; ok {
c.Check(val, Equals, "GET")
} else {
c.Errorf("Log message didn't have a 'method' key, obtained %s", capturedOutput)
}
if val, ok := jsonMap["path"]; ok {
c.Check(val, Equals, "/")
} else {
c.Errorf("Log message didn't have a 'path' key, obtained %s", capturedOutput)
}
if val, ok := jsonMap["protocol"]; ok {
c.Check(val, Equals, "HTTP/1.1")
} else {
c.Errorf("Log message didn't have a 'protocol' key, obtained %s", capturedOutput)
}
if val, ok := jsonMap["code"]; ok {
c.Check(val, Equals, "404")
} else {
c.Errorf("Log message didn't have a 'code' key, obtained %s", capturedOutput)
}
if _, ok := jsonMap["remote"]; !ok {
c.Errorf("Log message didn't have a 'remote' key, obtained %s", capturedOutput)
}
if _, ok := jsonMap["latency"]; !ok {
c.Errorf("Log message didn't have a 'latency' key, obtained %s", capturedOutput)
}
if _, ok := jsonMap["agent"]; !ok {
c.Errorf("Log message didn't have a 'agent' key, obtained %s", capturedOutput)
}
if _, ok := jsonMap["time"]; !ok {
c.Errorf("Log message didn't have a 'time' key, obtained %s", capturedOutput)
}
}
func (s *MiddlewareSuite) TestJSONMiddleware2xx(c *C) {
outC := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, s.logReader)
fmt.Println(buf.String())
outC <- buf.String()
}()
s.HTTPRequest(http.MethodGet, "/api/healthy", nil)
_ = s.logWriter.Close()
capturedOutput := <-outC
var jsonMap map[string]interface{}
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
if val, ok := jsonMap["level"]; ok {
c.Check(val, Equals, "info")
} else {
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
}
}
func (s *MiddlewareSuite) TestJSONMiddleware5xx(c *C) {
outC := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, s.logReader)
fmt.Println(buf.String())
outC <- buf.String()
}()
s.HTTPRequest(http.MethodGet, "/api/ready", nil)
_ = s.logWriter.Close()
capturedOutput := <-outC
var jsonMap map[string]interface{}
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
if val, ok := jsonMap["level"]; ok {
c.Check(val, Equals, "error")
} else {
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
}
}
func (s *MiddlewareSuite) TestJSONMiddlewareRaw(c *C) {
outC := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, s.logReader)
fmt.Println(buf.String())
outC <- buf.String()
}()
s.HTTPRequest(http.MethodGet, "/api/healthy?test=raw", nil)
_ = s.logWriter.Close()
capturedOutput := <-outC
var jsonMap map[string]interface{}
_ = json.Unmarshal([]byte(capturedOutput), &jsonMap)
fmt.Println(capturedOutput)
if val, ok := jsonMap["level"]; ok {
c.Check(val, Equals, "info")
} else {
c.Errorf("Log message didn't have a 'level' key, obtained %s", capturedOutput)
}
}
func (s *MiddlewareSuite) TestGetBasePath(c *C) {
s.HTTPRequest(http.MethodGet, "", nil)
path := getBasePath(s.context)
c.Check(path, Equals, "/")
s.HTTPRequest(http.MethodGet, "/", nil)
path = getBasePath(s.context)
c.Check(path, Equals, "/")
s.HTTPRequest(http.MethodGet, "/api", nil)
path = getBasePath(s.context)
c.Check(path, Equals, "/api")
s.HTTPRequest(http.MethodGet, "/api/repos/testRepo", nil)
path = getBasePath(s.context)
c.Check(path, Equals, "/api/repos")
}
func (s *MiddlewareSuite) TestGetURLSegment(c *C) {
url := "/"
segment, err := getURLSegment(url, 0)
if err != nil {
c.Error(err)
}
c.Check(*segment, Equals, "/")
_, err = getURLSegment(url, 1)
if err == nil {
c.Error("Invalid return value")
}
url = "/api"
segment, err = getURLSegment(url, 0)
if err != nil {
c.Error(err)
}
c.Check(*segment, Equals, "/api")
_, err = getURLSegment(url, 1)
if err == nil {
c.Error("Invalid return value")
}
url = "/api/repos/testRepo"
segment, err = getURLSegment(url, 0)
if err != nil {
c.Error(err)
}
c.Check(*segment, Equals, "/api")
segment, err = getURLSegment(url, 1)
if err != nil {
c.Error(err)
}
c.Check(*segment, Equals, "/repos")
}
+792
View File
@@ -0,0 +1,792 @@
package api
import (
"fmt"
"net/http"
"os"
"sort"
"strings"
"sync"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/pgp"
"github.com/aptly-dev/aptly/query"
"github.com/aptly-dev/aptly/task"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
func getVerifier(keyRings []string) (pgp.Verifier, error) {
verifier := context.GetVerifier()
for _, keyRing := range keyRings {
verifier.AddKeyring(keyRing)
}
err := verifier.InitKeyring(false)
if err != nil {
return nil, err
}
return verifier, nil
}
// stringSlicesEqual compares two string slices for equality (order matters)
func stringSlicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// uniqueStrings returns a new slice with only unique strings from the input, sorted
func uniqueStrings(input []string) []string {
if len(input) == 0 {
return input
}
seen := make(map[string]struct{}, len(input))
result := make([]string, 0, len(input))
for _, s := range input {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
}
}
sort.Strings(result)
return result
}
// @Summary List Mirrors
// @Description **Show list of currently available mirrors**
// @Description Each mirror is returned as in “show” API.
// @Tags Mirrors
// @Produce json
// @Success 200 {array} remoteRepoResponse
// @Router /api/mirrors [get]
func apiMirrorsList(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
result := []remoteRepoResponse{}
err := collection.ForEach(func(repo *deb.RemoteRepo) error {
err := collection.LoadComplete(repo)
if err != nil {
return err
}
result = append(result, newRemoteRepoResponse(repo))
return nil
})
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
return
}
c.JSON(200, result)
}
type mirrorCreateParams struct {
// Name of mirror to be created
Name string `binding:"required" json:"Name" example:"mirror2"`
// Url of the archive to mirror
ArchiveURL string `binding:"required" json:"ArchiveURL" example:"http://deb.debian.org/debian"`
// Distribution name to mirror
Distribution string ` json:"Distribution" example:"'buster', for flat repositories use './'"`
// Package query that is applied to mirror packages
Filter string ` json:"Filter" example:"xserver-xorg"`
// Components to mirror, if not specified aptly would fetch all components
Components []string ` json:"Components" example:"main"`
// Limit mirror to those architectures, if not specified aptly would fetch all architectures
Architectures []string ` json:"Architectures" example:"amd64"`
// Gpg keyring(s) for verifying Release file
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
// Set "true" to mirror source packages
DownloadSources bool ` json:"DownloadSources"`
// Set "true" to mirror udeb files
DownloadUdebs bool ` json:"DownloadUdebs"`
// Set "true" to mirror installer files
DownloadInstaller bool ` json:"DownloadInstaller"`
// Set "true" to mirror AppStream (DEP-11) metadata
DownloadAppStream bool ` json:"DownloadAppStream"`
// Set "true" to include dependencies of matching packages when filtering
FilterWithDeps bool ` json:"FilterWithDeps"`
// Set "true" to skip if the given components are in the Release file
SkipComponentCheck bool ` json:"SkipComponentCheck"`
// Set "true" to skip the verification of architectures
SkipArchitectureCheck bool ` json:"SkipArchitectureCheck"`
// Set "true" to skip the verification of Release file signatures
IgnoreSignatures bool ` json:"IgnoreSignatures"`
}
// @Summary Create Mirror
// @Description **Create a mirror of a remote repository**
// @Tags Mirrors
// @Consume json
// @Param request body mirrorCreateParams true "Parameters"
// @Produce json
// @Success 200 {object} deb.RemoteRepo
// @Failure 400 {object} Error "Bad Request"
// @Router /api/mirrors [post]
func apiMirrorsCreate(c *gin.Context) {
var err error
var b mirrorCreateParams
b.DownloadSources = context.Config().DownloadSourcePackages
b.IgnoreSignatures = context.Config().GpgDisableVerify
b.Architectures = context.ArchitecturesList()
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
if strings.HasPrefix(b.ArchiveURL, "ppa:") {
b.ArchiveURL, b.Distribution, b.Components, err = deb.ParsePPA(b.ArchiveURL, context.Config())
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
}
if b.Filter != "" {
_, err = query.Parse(b.Filter)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
return
}
}
repo, err := deb.NewRemoteRepo(b.Name, b.ArchiveURL, b.Distribution, b.Components, b.Architectures,
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller, b.DownloadAppStream)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
return
}
repo.Filter = b.Filter
repo.FilterWithDeps = b.FilterWithDeps
repo.SkipComponentCheck = b.SkipComponentCheck
repo.SkipArchitectureCheck = b.SkipArchitectureCheck
repo.DownloadSources = b.DownloadSources
repo.DownloadUdebs = b.DownloadUdebs
verifier, err := getVerifier(b.Keyrings)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
return
}
downloader := context.NewDownloader(nil)
err = repo.Fetch(downloader, verifier, b.IgnoreSignatures)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to fetch mirror: %s", err))
return
}
err = collection.Add(repo)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to add mirror: %s", err))
return
}
c.JSON(201, repo)
}
// @Summary Delete Mirror
// @Description **Delete a mirror**
// @Tags Mirrors
// @Param name path string true "mirror name"
// @Param force query int true "force: 1 to enable"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} task.ProcessReturnValue
// @Failure 404 {object} Error "Mirror not found"
// @Failure 403 {object} Error "Unable to delete mirror with snapshots"
// @Failure 500 {object} Error "Unable to delete"
// @Router /api/mirrors/{name} [delete]
func apiMirrorsDrop(c *gin.Context) {
name := c.Params.ByName("name")
force := c.Request.URL.Query().Get("force") == "1"
collectionFactory := context.NewCollectionFactory()
mirrorCollection := collectionFactory.RemoteRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
repo, err := mirrorCollection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to drop: %s", err))
return
}
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()
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
}
if !force {
snapshots := snapshotCollection.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)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
}
return &task.ProcessReturnValue{Code: http.StatusNoContent, Value: nil}, nil
})
}
// @Summary Get Mirror Info
// @Description **Get mirror information by name**
// @Tags Mirrors
// @Param name path string true "mirror name"
// @Produce json
// @Success 200 {object} deb.RemoteRepo
// @Failure 404 {object} Error "Mirror not found"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/mirrors/{name} [get]
func apiMirrorsShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
name := c.Params.ByName("name")
repo, err := collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to show: %s", err))
return
}
err = collection.LoadComplete(repo)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
return
}
c.JSON(200, repo)
}
// @Summary List Mirror Packages
// @Description **Get a list of packages from a mirror**
// @Tags Mirrors
// @Param name path string true "mirror name"
// @Param q query string false "search query"
// @Param format query string false "format: `details` for more detailed information"
// @Produce json
// @Success 200 {array} deb.Package "List of Packages"
// @Failure 400 {object} Error "Unable to determine list of architectures"
// @Failure 404 {object} Error "Mirror not found"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/mirrors/{name}/packages [get]
func apiMirrorsPackages(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
name := c.Params.ByName("name")
repo, err := collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to show: %s", err))
return
}
err = collection.LoadComplete(repo)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
}
if repo.LastDownloadDate.IsZero() {
AbortWithJSONError(c, 404, fmt.Errorf("unable to show package list, mirror hasn't been downloaded yet"))
return
}
reflist := repo.RefList()
result := []*deb.Package{}
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), nil)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
queryS := c.Request.URL.Query().Get("q")
if queryS != "" {
q, err := query.Parse(c.Request.URL.Query().Get("q"))
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
withDeps := c.Request.URL.Query().Get("withDeps") == "1"
architecturesList := []string{}
if withDeps {
if len(context.ArchitecturesList()) > 0 {
architecturesList = context.ArchitecturesList()
} else {
architecturesList = list.Architectures(false)
}
sort.Strings(architecturesList)
if len(architecturesList) == 0 {
AbortWithJSONError(c, 400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
return
}
}
list.PrepareIndex()
list, err = list.Filter(deb.FilterOptions{
Queries: []deb.PackageQuery{q},
WithDependencies: withDeps,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
})
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to search: %s", err))
}
}
if c.Request.URL.Query().Get("format") == "details" {
_ = list.ForEach(func(p *deb.Package) error {
result = append(result, p)
return nil
})
c.JSON(200, result)
} else {
c.JSON(200, list.Strings())
}
}
type mirrorEditParams struct {
// Package query that is applied to mirror packages
Filter *string ` json:"Filter" example:"xserver-xorg"`
// Set "true" to include dependencies of matching packages when filtering
FilterWithDeps *bool ` json:"FilterWithDeps"`
// Set "true" to mirror installer files
DownloadInstaller *bool `json:"DownloadInstaller"`
// Set "true" to mirror source packages
DownloadSources *bool ` json:"DownloadSources"`
// Set "true" to mirror udeb files
DownloadUdebs *bool ` json:"DownloadUdebs"`
// URL of the archive to mirror
ArchiveURL *string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
// Comma separated list of architectures
Architectures *[]string `json:"Architectures" example:"amd64"`
// Gpg keyring(s) for verifying Release file if a mirror update is required.
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
// Set "true" to skip the verification of Release file signatures
IgnoreSignatures *bool ` json:"IgnoreSignatures"`
}
// @Summary Edit Mirror
// @Description **Edit mirror config**
// @Tags Mirrors
// @Param name path string true "mirror name to edit"
// @Consume json
// @Param request body mirrorEditParams true "Parameters"
// @Produce json
// @Success 200 {object} deb.RemoteRepo "Mirror was edited successfully"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Mirror not found"
// @Failure 409 {object} Error "Aptly db locked"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/mirrors/{name} [post]
func apiMirrorsEdit(c *gin.Context) {
var (
err error
b mirrorEditParams
repo *deb.RemoteRepo
)
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
name := c.Params.ByName("name")
repo, err = collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, fmt.Errorf("unable to edit: %s", err))
return
}
err = repo.CheckLock()
if err != nil {
AbortWithJSONError(c, 409, fmt.Errorf("unable to edit: %s", err))
return
}
if c.Bind(&b) != nil {
return
}
fetchMirror := false
ignoreSignatures := context.Config().GpgDisableVerify
if b.Filter != nil {
repo.Filter = *b.Filter
}
if b.FilterWithDeps != nil {
repo.FilterWithDeps = *b.FilterWithDeps
}
if b.DownloadInstaller != nil {
repo.DownloadInstaller = *b.DownloadInstaller
}
if b.DownloadSources != nil {
repo.DownloadSources = *b.DownloadSources
}
if b.DownloadUdebs != nil {
repo.DownloadUdebs = *b.DownloadUdebs
}
if b.ArchiveURL != nil && *b.ArchiveURL != repo.ArchiveRoot {
repo.SetArchiveRoot(*b.ArchiveURL)
fetchMirror = true
}
if b.Architectures != nil {
uniqueArchitectures := uniqueStrings(*b.Architectures)
if !stringSlicesEqual(uniqueArchitectures, uniqueStrings(repo.Architectures)) {
repo.Architectures = uniqueArchitectures
fetchMirror = true
}
}
if b.IgnoreSignatures != nil {
ignoreSignatures = *b.IgnoreSignatures
}
if repo.IsFlat() && repo.DownloadUdebs {
AbortWithJSONError(c, 400, fmt.Errorf("unable to edit: flat mirrors don't support udebs"))
return
}
if fetchMirror {
verifier, err := getVerifier(b.Keyrings)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG verifier: %s", err))
return
}
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
return
}
}
err = collection.Update(repo)
if err != nil {
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
return
}
c.JSON(200, repo)
}
type mirrorUpdateParams struct {
// Change mirror name to `Name`
Name string ` json:"Name" example:"mirror1"`
// Gpg keyring(s) for verifying Release file
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
// Set "true" to ignore checksum errors
IgnoreChecksums bool ` json:"IgnoreChecksums"`
// Set "true" to skip the verification of Release file signatures
IgnoreSignatures bool ` json:"IgnoreSignatures"`
// Set "true" to force a mirror update even if another process is already updating the mirror (use with caution!)
ForceUpdate bool ` json:"ForceUpdate"`
// Set "true" to skip downloading already downloaded packages
SkipExistingPackages bool ` json:"SkipExistingPackages"`
// Set "true" to download only the latest version per package/architecture
LatestOnly bool ` json:"LatestOnly"`
}
// @Summary Update Mirror
// @Description **Update Mirror and download packages**
// @Tags Mirrors
// @Param name path string true "mirror name to update"
// @Consume json
// @Param request body mirrorUpdateParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} task.ProcessReturnValue "Mirror was updated successfully"
// @Success 202 {object} task.Task "Mirror is being updated"
// @Failure 400 {object} Error "Unable to determine list of architectures"
// @Failure 404 {object} Error "Mirror not found"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/mirrors/{name} [put]
func apiMirrorsUpdate(c *gin.Context) {
var (
err error
remote *deb.RemoteRepo
b mirrorUpdateParams
)
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
remote, err = collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
b.Name = remote.Name
b.IgnoreSignatures = context.Config().GpgDisableVerify
log.Info().Msgf("%s: Starting mirror update", b.Name)
if c.Bind(&b) != nil {
return
}
if b.Name != remote.Name {
_, err = collection.ByName(b.Name)
if err == nil {
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: mirror %s already exists", b.Name))
return
}
}
verifier, err := getVerifier(b.Keyrings)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
return
}
resources := []string{string(remote.Key())}
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
downloader := context.NewDownloader(out)
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)
}
if !b.ForceUpdate {
err = remote.CheckLock()
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
}
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
if remote.DownloadAppStream && !remote.IsFlat() {
err = remote.DownloadAppStreamFiles(out, downloader,
context.PackagePool(), collectionFactory.ChecksumCollection(nil), b.IgnoreChecksums)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
}
if remote.Filter != "" {
var filterQuery deb.PackageQuery
filterQuery, err = query.Parse(remote.Filter)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
_, _, err = remote.ApplyFilter(context.DependencyOptions(), filterQuery, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
}
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages, b.LatestOnly)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
defer func() {
// on any interruption, unlock the mirror
e := context.ReOpenDatabase()
if e == nil {
remote.MarkAsIdle()
_ = collection.Update(remote)
}
}()
remote.MarkAsUpdating()
err = collection.Update(remote)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
context.GoContextHandleSignals()
count := len(queue)
taskDetail := struct {
TotalDownloadSize int64
RemainingDownloadSize int64
TotalNumberOfPackages int
RemainingNumberOfPackages int
}{
downloadSize, downloadSize, count, count,
}
detail.Store(taskDetail)
downloadQueue := make(chan int)
taskFinished := make(chan *deb.PackageDownloadTask)
var (
errors []string
errLock sync.Mutex
)
pushError := func(err error) {
errLock.Lock()
errors = append(errors, err.Error())
errLock.Unlock()
}
go func() {
for idx := range queue {
select {
case downloadQueue <- idx:
case <-context.Done():
return
}
}
close(downloadQueue)
}()
// update of task details need to be done in order
go func() {
for {
task, ok := <-taskFinished
if !ok {
return
}
taskDetail.RemainingDownloadSize -= task.File.Checksums.Size
taskDetail.RemainingNumberOfPackages--
detail.Store(taskDetail)
}
}()
log.Info().Msgf("%s: Spawning background processes...", b.Name)
var wg sync.WaitGroup
for i := 0; i < context.Config().DownloadConcurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case idx, ok := <-downloadQueue:
if !ok {
return
}
task := &queue[idx]
var e error
// provision download location
if pp, ok := context.PackagePool().(aptly.LocalPackagePool); ok {
task.TempDownPath, e = pp.GenerateTempPath(task.File.Filename)
} else {
var file *os.File
file, e = os.CreateTemp("", task.File.Filename)
if e == nil {
task.TempDownPath = file.Name()
_ = file.Close()
}
}
if e != nil {
pushError(e)
continue
}
// download file...
e = context.Downloader().DownloadWithChecksum(
context,
remote.PackageURL(task.File.DownloadURL()).String(),
task.TempDownPath,
&task.File.Checksums,
b.IgnoreChecksums)
if e != nil {
pushError(e)
continue
}
// 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))
if err != nil {
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
pushError(err)
continue
}
// update "attached" files if any
for _, additionalAtask := range task.Additional {
additionalAtask.File.PoolPath = task.File.PoolPath
additionalAtask.File.Checksums = task.File.Checksums
}
task.Done = true
taskFinished <- task
case <-context.Done():
return
}
}
}()
}
// Wait for all download goroutines to finish
log.Info().Msgf("%s: Waiting for background processes to finish...", b.Name)
wg.Wait()
log.Info().Msgf("%s: Background processes finished", b.Name)
close(taskFinished)
defer func() {
for _, task := range queue {
if task.TempDownPath == "" {
continue
}
if err := os.Remove(task.TempDownPath); err != nil && !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Failed to delete %s: %v\n", task.TempDownPath, err)
}
}
}()
select {
case <-context.Done():
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: interrupted")
default:
}
if len(errors) > 0 {
log.Info().Msgf("%s: Unable to update because of previous errors", b.Name)
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n "))
}
log.Info().Msgf("%s: Finalizing download...", b.Name)
_ = remote.FinalizeDownload(collectionFactory, out)
err = collectionFactory.RemoteRepoCollection().Update(remote)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
}
log.Info().Msgf("%s: Mirror updated successfully", b.Name)
return &task.ProcessReturnValue{Code: http.StatusNoContent, Value: nil}, nil
})
}
+105
View File
@@ -0,0 +1,105 @@
package api
import (
"bytes"
"encoding/json"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
. "gopkg.in/check.v1"
)
type MirrorSuite struct {
APISuite
}
var _ = Suite(&MirrorSuite{})
func (s *MirrorSuite) TestGetMirrors(c *C) {
response, _ := s.HTTPRequest("GET", "/api/mirrors", nil)
c.Check(response.Code, Equals, 200)
var mirrors []map[string]interface{}
err := json.Unmarshal(response.Body.Bytes(), &mirrors)
c.Assert(err, IsNil)
}
func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
response, _ := s.HTTPRequest("DELETE", "/api/mirrors/does-not-exist", nil)
c.Check(response.Code, Equals, 404)
c.Check(response.Body.String(), Equals, "{\"error\":\"unable to drop: mirror with name does-not-exist not found\"}")
}
func (s *MirrorSuite) TestCreateMirrorFlatWithAppStream(c *C) {
body, err := json.Marshal(gin.H{
"Name": "test-flat-appstream",
"ArchiveURL": "http://example.com/repo/",
"Distribution": "./",
"DownloadAppStream": true,
})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("POST", "/api/mirrors", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Matches, ".*AppStream.*flat.*")
}
func (s *MirrorSuite) TestCreateMirror(c *C) {
c.ExpectFailure("Need to mock downloads")
body, err := json.Marshal(gin.H{
"Name": "dummy",
"ArchiveURL": "foobar",
})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("POST", "/api/mirrors", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 400)
c.Check(response.Body.String(), Equals, "")
}
func (s *MirrorSuite) TestGetMirrorsIncludesNumPackages(c *C) {
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
repo, err := deb.NewRemoteRepo("count-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
c.Assert(err, IsNil)
err = collection.Add(repo)
c.Assert(err, IsNil)
putRawDBValue(c, &s.APISuite, repo.RefKey(), makePackageRefList(c).Encode())
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 200)
var mirrors []map[string]interface{}
err = json.Unmarshal(response.Body.Bytes(), &mirrors)
c.Assert(err, IsNil)
found := false
for _, mirror := range mirrors {
if mirror["Name"] == "count-mirror" {
found = true
value, ok := mirror["NumPackages"]
c.Assert(ok, Equals, true)
c.Assert(value, Equals, float64(2))
break
}
}
c.Assert(found, Equals, true)
}
func (s *MirrorSuite) TestGetMirrorsReturns500OnCorruptRefList(c *C) {
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
repo, err := deb.NewRemoteRepo("broken-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
c.Assert(err, IsNil)
c.Assert(collection.Add(repo), IsNil)
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 500)
c.Assert(response.Body.String(), Matches, ".*unable to show:.*")
}
+30
View File
@@ -0,0 +1,30 @@
package api
import "github.com/aptly-dev/aptly/deb"
type remoteRepoResponse struct {
*deb.RemoteRepo
NumPackages int `json:"NumPackages"`
}
type localRepoResponse struct {
*deb.LocalRepo
NumPackages int `json:"NumPackages"`
}
type snapshotResponse struct {
*deb.Snapshot
NumPackages int `json:"NumPackages"`
}
func newRemoteRepoResponse(repo *deb.RemoteRepo) remoteRepoResponse {
return remoteRepoResponse{RemoteRepo: repo, NumPackages: repo.NumPackages()}
}
func newLocalRepoResponse(repo *deb.LocalRepo) localRepoResponse {
return localRepoResponse{LocalRepo: repo, NumPackages: repo.NumPackages()}
}
func newSnapshotResponse(snapshot *deb.Snapshot) snapshotResponse {
return snapshotResponse{Snapshot: snapshot, NumPackages: snapshot.NumPackages()}
}
+19
View File
@@ -0,0 +1,19 @@
package api
import (
"github.com/aptly-dev/aptly/deb"
. "gopkg.in/check.v1"
)
func makePackageRefList(c *C) *deb.PackageRefList {
list := deb.NewPackageList()
c.Assert(list.Add(&deb.Package{Name: "libcount", Version: "1.0", Architecture: "amd64"}), IsNil)
c.Assert(list.Add(&deb.Package{Name: "appcount", Version: "2.0", Architecture: "all"}), IsNil)
return deb.NewPackageRefListFromPackageList(list)
}
func putRawDBValue(c *C, s *APISuite, key []byte, value []byte) {
db, err := s.context.Database()
c.Assert(err, IsNil)
c.Assert(db.Put(key, value), IsNil)
}
+41
View File
@@ -0,0 +1,41 @@
package api
import (
_ "github.com/aptly-dev/aptly/deb" // for swagger
"github.com/gin-gonic/gin"
)
// @Summary Get Package Info
// @Description **Show information about package by package key**
// @Description Package keys could be obtained from various GET .../packages APIs.
// @Tags Packages
// @Produce json
// @Param key path string true "package key (unique package identifier)"
// @Success 200 {object} deb.Package "OK"
// @Failure 404 {object} Error "Not Found"
// @Router /api/packages/{key} [get]
func apiPackagesShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
p, err := collectionFactory.PackageCollection().ByKey([]byte(c.Params.ByName("key")))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
c.JSON(200, p)
}
// @Summary List Packages
// @Description **Get list of packages**
// @Tags Packages
// @Consume json
// @Produce json
// @Param q query string false "search query"
// @Param format query string false "format: `details` for more detailed information"
// @Success 200 {array} string "List of packages"
// @Router /api/packages [get]
func apiPackages(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.PackageCollection()
showPackages(c, collection.AllPackageRefs(), collectionFactory)
}
+18
View File
@@ -0,0 +1,18 @@
package api
import (
. "gopkg.in/check.v1"
)
type PackagesSuite struct {
APISuite
}
var _ = Suite(&PackagesSuite{})
func (s *PackagesSuite) TestPackagesGetMaximumVersion(c *C) {
response, err := s.HTTPRequest("GET", "/api/repos/dummy/packages?maximumVersion=1", nil)
c.Assert(err, IsNil)
c.Check(response.Code, Equals, 200)
c.Check(response.Body.String(), Equals, "[]")
}
+1130
View File
File diff suppressed because it is too large Load Diff
+921
View File
@@ -0,0 +1,921 @@
package api
import (
"fmt"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/database"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/aptly-dev/aptly/task"
"github.com/aptly-dev/aptly/utils"
"github.com/gin-gonic/gin"
)
// @Summary Serve HTML Listing
// @Description If ServeInAPIMode is enabled in aptly config,
// @Description this endpoint is enabled which returns an HTML listing of each repo that can be browsed
// @Tags Repos
// @Produce html
// @Success 200 {object} string "HTML"
// @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")
c.Writer.Flush()
_, _ = c.Writer.WriteString("<pre>\n")
if len(localRepos) == 0 {
_, _ = c.Writer.WriteString("<a href=\"-/\">default</a>\n")
}
for publishPrefix := range localRepos {
_, _ = c.Writer.WriteString(fmt.Sprintf("<a href=\"%[1]s/\">%[1]s</a>\n", publishPrefix))
}
_, _ = c.Writer.WriteString("</pre>")
c.Writer.Flush()
}
}
// @Summary Serve Packages
// @Description If ServeInAPIMode is enabled in aptly config,
// @Description this api serves a specified package from storage
// @Tags Repos
// @Param storage path string true "Storage"
// @Param pkgPath path string true "Package Path" allowReserved=true
// @Produce json
// @Success 200 ""
// @Router /repos/{storage}/{pkgPath} [get]
func reposServeInAPIMode(c *gin.Context) {
pkgpath := c.Param("pkgPath")
storage := c.Param("storage")
if storage == "-" {
storage = ""
} else {
storage = "filesystem:" + storage
}
publicPath := context.GetPublishedStorage(storage).(aptly.FileSystemPublishedStorage).PublicPath()
c.FileFromFS(pkgpath, http.Dir(publicPath))
}
// @Summary List Repositories
// @Description **Get list of available repos**
// @Description Each repo is returned as in “show” API.
// @Tags Repos
// @Produce json
// @Success 200 {array} localRepoResponse
// @Router /api/repos [get]
func apiReposList(c *gin.Context) {
result := []localRepoResponse{}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
err := collection.ForEach(func(r *deb.LocalRepo) error {
err := collection.LoadComplete(r)
if err != nil {
return err
}
result = append(result, newLocalRepoResponse(r))
return nil
})
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
c.JSON(200, result)
}
type repoCreateParams struct {
// Name of repository to create
Name string `binding:"required" json:"Name" example:"repo1"`
// Text describing the repository (optional)
Comment string ` json:"Comment" example:"this is a repo"`
// Default distribution when publishing from this local repo
DefaultDistribution string ` json:"DefaultDistribution" example:"stable"`
// Default component when publishing from this local repo
DefaultComponent string ` json:"DefaultComponent" example:"main"`
// Snapshot name to create repository from (optional)
FromSnapshot string ` json:"FromSnapshot" example:""`
}
// @Summary Create Repository
// @Description **Create a local repository**
// @Description
// @Description Distribution and component are used as defaults when publishing repo either directly or via snapshot.
// @Description
// @Description ```
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Name": "aptly-repo"}' http://localhost:8080/api/repos
// @Description {"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}
// @Description ```
// @Tags Repos
// @Consume json
// @Param request body repoCreateParams true "Parameters"
// @Produce json
// @Success 201 {object} deb.LocalRepo
// @Failure 404 {object} Error "Source snapshot not found"
// @Failure 409 {object} Error "Local repo already exists"
// @Failure 500 {object} Error "Internal error"
// @Router /api/repos [post]
func apiReposCreate(c *gin.Context) {
var b repoCreateParams
if c.Bind(&b) != nil {
return
}
repo := deb.NewLocalRepo(b.Name, b.Comment)
repo.DefaultComponent = b.DefaultComponent
repo.DefaultDistribution = b.DefaultDistribution
collectionFactory := context.NewCollectionFactory()
if b.FromSnapshot != "" {
var snapshot *deb.Snapshot
snapshotCollection := collectionFactory.SnapshotCollection()
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
if err != nil {
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err))
return
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to load source snapshot: %s", err))
return
}
repo.UpdateRefList(snapshot.RefList())
}
localRepoCollection := collectionFactory.LocalRepoCollection()
if _, err := localRepoCollection.ByName(b.Name); err == nil {
AbortWithJSONError(c, http.StatusConflict, fmt.Errorf("local repo with name %s already exists", b.Name))
return
}
err := localRepoCollection.Add(repo)
if err != nil {
AbortWithJSONError(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusCreated, repo)
}
type reposEditParams struct {
// Name of repository to modify
Name *string `binding:"required" json:"Name" example:"repo1"`
// Change Comment of repository
Comment *string ` json:"Comment" example:"example repo"`
// Change Default Distribution for publishing
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
// Change Default Component for publishing
DefaultComponent *string ` json:"DefaultComponent" example:""`
}
// @Summary Update Repository
// @Description **Update local repository meta information**
// @Tags Repos
// @Param name path string true "Repository name"
// @Consume json
// @Param request body reposEditParams true "Parameters"
// @Produce json
// @Success 200 {object} deb.LocalRepo "msg"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/repos/{name} [put]
func apiReposEdit(c *gin.Context) {
var b reposEditParams
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
name := c.Params.ByName("name")
repo, err := collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
if b.Name != nil && *b.Name != name {
_, err := collection.ByName(*b.Name)
if err == nil {
// already exists
AbortWithJSONError(c, 404, fmt.Errorf("local repo with name %q already exists", *b.Name))
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
}
c.JSON(200, repo)
}
// GET /api/repos/:name
// @Summary Get Repository Info
// @Description Returns basic information about local repository.
// @Tags Repos
// @Param name path string true "Repository name"
// @Produce json
// @Success 200 {object} deb.LocalRepo
// @Failure 404 {object} Error "Repository not found"
// @Router /api/repos/{name} [get]
func apiReposShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
c.JSON(200, repo)
}
// @Summary Delete Repository
// @Description Drop/delete a repo
// @Description Cannot drop repos that are published.
// @Description Needs force=1 to drop repos used as source by other repos.
// @Tags Repos
// @Param name path string true "Repository name"
// @Param _async query bool false "Run in background and return task object"
// @Param force query int false "force: 1 to enable"
// @Produce json
// @Success 200 {object} task.ProcessReturnValue "Repo object"
// @Failure 404 {object} Error "Not Found"
// @Failure 404 {object} Error "Repo Conflict"
// @Router /api/repos/{name} [delete]
func apiReposDrop(c *gin.Context) {
force := c.Request.URL.Query().Get("force") == "1"
name := c.Params.ByName("name")
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
publishedCollection := collectionFactory.PublishedRepoCollection()
repo, err := collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
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)
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)
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)
})
}
// @Summary List Repo Packages
// @Description **Return a list of packages present in the repo**
// @Description
// @Description If `q` query parameter is missing, return all packages, otherwise return packages that match q
// @Description
// @Description **Example:**
// @Description ```
// @Description $ curl http://localhost:8080/api/repos/aptly-repo/packages
// @Description ["Pi386 aptly 0.8 966561016b44ed80"]
// @Description ```
// @Tags Repos
// @Param name path string true "Repository name"
// @Param q query string true "Package query (e.g Name%20(~%20matlab))"
// @Param withDeps query string true "Set to 1 to include dependencies when evaluating package query"
// @Param format query string true "Set to 'details' to return extra info about each package"
// @Param maximumVersion query string true "Set to 1 to only return the highest version for each package name"
// @Produce json
// @Success 200 {object} string "msg"
// @Failure 404 {object} Error "Not Found"
// @Failure 404 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/packages [get]
func apiReposPackagesShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
err = collection.LoadComplete(repo)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
showPackages(c, repo.RefList(), collectionFactory)
}
type reposPackagesAddDeleteParams struct {
// Package Refs
PackageRefs []string `binding:"required" json:"PackageRefs" example:""`
}
// Handler for both add and delete
func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error) {
var b reposPackagesAddDeleteParams
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
resources := []string{string(repo.Key())}
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.LoadComplete(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
out.Printf("Loading packages...\n")
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// verify package refs and build package list
for _, ref := range b.PackageRefs {
var p *deb.Package
p, err = collectionFactory.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)
}
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = cb(list, p, out)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
}
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = collectionFactory.LocalRepoCollection().Update(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: repo}, nil
})
}
// @Summary Add Packages by Key
// @Description **Add packages to local repository by package keys.**
// @Description
// @Description Any package can be added that is present in the aptly database (from any mirror, snapshot, local repository). This API combined with package list (search) APIs allows one to implement importing, copying, moving packages around.
// @Description
// @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
// @Success 200 {object} string "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 400 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/packages [post]
func apiReposPackagesAdd(c *gin.Context) {
apiReposPackagesAddDelete(c, "Add packages to repo ", func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error {
out.Printf("Adding package %s\n", p.Name)
return list.Add(p)
})
}
// @Summary Delete Packages by Key
// @Description **Remove packages from local repository by package keys.**
// @Description
// @Description Any package(s) can be removed from a local repository. Package references from a local repository can be retrieved with GET /api/repos/:name/packages.
// @Tags Repos
// @Param name path string true "Repository name"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Param request body reposPackagesAddDeleteParams true "Parameters"
// @Produce json
// @Success 200 {object} string "msg"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 400 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/packages [delete]
func apiReposPackagesDelete(c *gin.Context) {
apiReposPackagesAddDelete(c, "Delete packages from repo ", func(list *deb.PackageList, p *deb.Package, out aptly.Progress) error {
out.Printf("Removing package %s\n", p.Name)
list.Remove(p)
return nil
})
}
// @Summary Add Uploaded File
// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically.
// @Description Adding same package to local repository is not an error.
// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import).
// @Tags Repos
// @Param name path string true "Repository name"
// @Param dir path string true "Directory of packages"
// @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"
// @Failure 400 {object} Error "wrong file"
// @Failure 404 {object} Error "Repository not found"
// @Failure 500 {object} Error "Error adding files"
// @Router /api/repos/{name}/file/{dir}/{file} [post]
func apiReposPackageFromFile(c *gin.Context) {
// redirect all work to dir method
apiReposPackageFromDir(c)
}
// @Summary Add Uploaded Directory
// @Description Import packages from files (uploaded using File Upload API) to the local repository. If directory specified, aptly would discover package files automatically.
// @Description Adding same package to local repository is not an error.
// @Description By default aptly would try to remove every successfully processed file and directory `dir` (if it becomes empty after import).
// @Tags Repos
// @Param name path string true "Repository name"
// @Param dir path string true "Directory to add"
// @Consume json
// @Param noRemove query string false "when value is set to 1, dont remove any files"
// @Param forceReplace query string false "when value is set to 1, remove packages conflicting with package being added (in local repository)"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {string} string "OK"
// @Failure 400 {object} Error "wrong file"
// @Failure 404 {object} Error "Repository not found"
// @Failure 500 {object} Error "Error adding files"
// @Router /api/repos/{name}/file/{dir} [post]
func apiReposPackageFromDir(c *gin.Context) {
forceReplace := c.Request.URL.Query().Get("forceReplace") == "1"
noRemove := c.Request.URL.Query().Get("noRemove") == "1"
if !verifyDir(c) {
return
}
dirParam := utils.SanitizePath(c.Params.ByName("dir"))
fileParam := utils.SanitizePath(c.Params.ByName("file"))
if fileParam != "" && !verifyPath(fileParam) {
AbortWithJSONError(c, 400, fmt.Errorf("wrong file"))
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
name := c.Params.ByName("name")
repo, err := collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
var taskName string
var sources []string
if fileParam == "" {
taskName = fmt.Sprintf("Add packages from dir %s to repo %s", dirParam, name)
sources = []string{filepath.Join(context.UploadPath(), dirParam)}
} else {
sources = []string{filepath.Join(context.UploadPath(), dirParam, fileParam)}
taskName = fmt.Sprintf("Add package %s from dir %s to repo %s", fileParam, dirParam, name)
}
resources := []string{string(repo.Key())}
resources = append(resources, sources...)
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collection.LoadComplete(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
verifier := context.GetVerifier()
var (
packageFiles, failedFiles []string
otherFiles []string
processedFiles, failedFiles2 []string
reporter = &aptly.RecordingResultReporter{
Warnings: []string{},
AddedLines: []string{},
RemovedLines: []string{},
}
list *deb.PackageList
)
packageFiles, otherFiles, failedFiles = deb.CollectPackageFiles(sources, reporter)
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.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)
failedFiles = append(failedFiles, failedFiles2...)
processedFiles = append(processedFiles, otherFiles...)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import package files: %s", err)
}
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = collectionFactory.LocalRepoCollection().Update(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
if !noRemove {
processedFiles = utils.StrSliceDeduplicate(processedFiles)
for _, file := range processedFiles {
err := os.Remove(file)
if err != nil {
reporter.Warning("unable to remove file %s: %s", file, err)
}
}
// atempt to remove dir, if it fails, that's fine: probably it's not empty
_ = os.Remove(filepath.Join(context.UploadPath(), dirParam))
}
if failedFiles == nil {
failedFiles = []string{}
}
if len(reporter.AddedLines) > 0 {
out.Printf("Added: %s\n", strings.Join(reporter.AddedLines, ", "))
}
if len(reporter.RemovedLines) > 0 {
out.Printf("Removed: %s\n", strings.Join(reporter.RemovedLines, ", "))
}
if len(reporter.Warnings) > 0 {
out.Printf("Warnings: %s\n", strings.Join(reporter.Warnings, ", "))
}
if len(failedFiles) > 0 {
out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", "))
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{
"Report": reporter,
"FailedFiles": failedFiles,
}}, nil
})
}
type reposCopyPackageParams struct {
// Copy also dependencies
WithDeps bool `json:"with-deps,omitempty"`
// Do not perform operations
DryRun bool `json:"dry-run,omitempty"`
}
// @Summary Copy Package
// @Description Copies a package from a source to destination repository
// @Tags Repos
// @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"
// @Failure 422 {object} Error "Unprocessable Entity"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/repos/{name}/copy/{src}/{file} [post]
func apiReposCopyPackage(c *gin.Context) {
dstRepoName := c.Params.ByName("name")
srcRepoName := c.Params.ByName("src")
fileName := c.Params.ByName("file")
jsonBody := reposCopyPackageParams{
WithDeps: false,
DryRun: false,
}
err := c.Bind(&jsonBody)
if err != nil {
return
}
collectionFactory := context.NewCollectionFactory()
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
if err != nil {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest repo error: %s", err))
return
}
var srcRepo *deb.LocalRepo
srcRepo, err = collectionFactory.LocalRepoCollection().ByName(srcRepoName)
if err != nil {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("src repo error: %s", err))
return
}
if srcRepo.UUID == dstRepo.UUID {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("dest and source are identical"))
return
}
taskName := fmt.Sprintf("Copy packages from repo %s to repo %s", srcRepoName, dstRepoName)
resources := []string{string(dstRepo.Key()), string(srcRepo.Key())}
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
}
err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
}
srcRefList := srcRepo.RefList()
reporter := &aptly.RecordingResultReporter{
Warnings: []string{},
AddedLines: []string{},
RemovedLines: []string{},
}
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.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())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
}
srcList.PrepareIndex()
var architecturesList []string
if jsonBody.WithDeps {
dstList.PrepareIndex()
// Calculate architectures
if len(context.ArchitecturesList()) > 0 {
architecturesList = context.ArchitecturesList()
} else {
architecturesList = dstList.Architectures(false)
}
sort.Strings(architecturesList)
if len(architecturesList) == 0 {
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to determine list of architectures, please specify explicitly")
}
}
// srcList.Filter|FilterWithProgress only accept query list
queries := make([]deb.PackageQuery, 1)
queries[0], err = query.Parse(fileName)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("unable to parse query '%s': %s", fileName, err)
}
toProcess, err := srcList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: jsonBody.WithDeps,
Source: dstList,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("filter error: %s", err)
}
if toProcess.Len() == 0 {
return &task.ProcessReturnValue{Code: http.StatusUnprocessableEntity, Value: nil}, fmt.Errorf("no package found for filter: '%s'", fileName)
}
err = toProcess.ForEach(func(p *deb.Package) error {
err = dstList.Add(p)
if err != nil {
return err
}
name := fmt.Sprintf("added %s-%s(%s)", p.Name, p.Version, p.Architecture)
reporter.AddedLines = append(reporter.AddedLines, name)
return nil
})
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("error processing dest add: %s", err)
}
if jsonBody.DryRun {
reporter.Warning("Changes not saved, as dry run has been requested")
} else {
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
err = collectionFactory.LocalRepoCollection().Update(dstRepo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
}
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{
"Report": reporter,
}}, nil
})
}
// @Summary Include File from Directory
// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api.
// @Tags Repos
// @Param name path string true "Repository name"
// @Param dir path string true "Directory of packages"
// @Param file path string true "File/packages to include"
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
// @Param noRemoveFiles query int false "when value is set to 1, dont remove files that have been imported successfully into repository"
// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files"
// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} string "msg"
// @Failure 404 {object} Error "Not Found"
// @Router /api/repos/{name}/include/{dir}/{file} [post]
func apiReposIncludePackageFromFile(c *gin.Context) {
// redirect all work to dir method
apiReposIncludePackageFromDir(c)
}
type reposIncludePackageFromDirResponse struct {
Report *aptly.RecordingResultReporter
FailedFiles []string
}
// @Summary Include Directory
// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api.
// @Tags Repos
// @Param name path string true "Repository name"
// @Param dir path string true "Directory of packages"
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
// @Param noRemoveFiles query int false "when value is set to 1, dont remove files that have been imported successfully into repository"
// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files"
// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} reposIncludePackageFromDirResponse "Response"
// @Failure 404 {object} Error "Not Found"
// @Router /api/repos/{name}/include/{dir} [post]
func apiReposIncludePackageFromDir(c *gin.Context) {
forceReplace := c.Request.URL.Query().Get("forceReplace") == "1"
noRemoveFiles := c.Request.URL.Query().Get("noRemoveFiles") == "1"
acceptUnsigned := c.Request.URL.Query().Get("acceptUnsigned") == "1"
ignoreSignature := c.Request.URL.Query().Get("ignoreSignature") == "1"
repoTemplateString := c.Params.ByName("name")
collectionFactory := context.NewCollectionFactory()
if !verifyDir(c) {
return
}
var sources []string
var taskName string
dirParam := utils.SanitizePath(c.Params.ByName("dir"))
fileParam := utils.SanitizePath(c.Params.ByName("file"))
if fileParam != "" && !verifyPath(fileParam) {
AbortWithJSONError(c, 400, fmt.Errorf("wrong file"))
return
}
if fileParam == "" {
taskName = fmt.Sprintf("Include packages from changes files in dir %s to repo matching template %s", dirParam, repoTemplateString)
sources = []string{filepath.Join(context.UploadPath(), dirParam)}
} else {
taskName = fmt.Sprintf("Include packages from changes file %s from dir %s to repo matching template %s", fileParam, dirParam, repoTemplateString)
sources = []string{filepath.Join(context.UploadPath(), dirParam, fileParam)}
}
repoTemplate, err := template.New("repo").Parse(repoTemplateString)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("error parsing repo template: %s", err))
return
}
var resources []string
if len(repoTemplate.Root.Nodes) > 1 {
resources = append(resources, task.AllLocalReposResourcesKey)
} else {
// repo template string is simple text so only use resource key of specific repository
repo, err := collectionFactory.LocalRepoCollection().ByName(repoTemplateString)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
resources = append(resources, string(repo.Key()))
}
resources = append(resources, sources...)
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
var (
err error
verifier = context.GetVerifier()
changesFiles []string
failedFiles, failedFiles2 []string
reporter = &aptly.RecordingResultReporter{
Warnings: []string{},
AddedLines: []string{},
RemovedLines: []string{},
}
)
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)
failedFiles = append(failedFiles, failedFiles2...)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import changes files: %s", err)
}
if !noRemoveFiles {
// atempt to remove dir, if it fails, that's fine: probably it's not empty
_ = os.Remove(filepath.Join(context.UploadPath(), dirParam))
}
if failedFiles == nil {
failedFiles = []string{}
}
if len(reporter.AddedLines) > 0 {
out.Printf("Added: %s\n", strings.Join(reporter.AddedLines, ", "))
}
if len(reporter.RemovedLines) > 0 {
out.Printf("Removed: %s\n", strings.Join(reporter.RemovedLines, ", "))
}
if len(reporter.Warnings) > 0 {
out.Printf("Warnings: %s\n", strings.Join(reporter.Warnings, ", "))
}
if len(failedFiles) > 0 {
out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", "))
}
ret := reposIncludePackageFromDirResponse{
Report: reporter,
FailedFiles: failedFiles,
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: ret}, nil
})
}
+63
View File
@@ -0,0 +1,63 @@
package api
import (
"bytes"
"encoding/json"
"github.com/aptly-dev/aptly/deb"
"github.com/gin-gonic/gin"
. "gopkg.in/check.v1"
)
type ReposSuite struct {
APISuite
}
var _ = Suite(&ReposSuite{})
func (s *ReposSuite) TestGetReposIncludesNumPackages(c *C) {
collection := s.context.NewCollectionFactory().LocalRepoCollection()
repo := deb.NewLocalRepo("count-repo-list", "")
repo.UpdateRefList(makePackageRefList(c))
c.Assert(collection.Add(repo), IsNil)
response, err := s.HTTPRequest("GET", "/api/repos", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 200)
var repos []map[string]interface{}
err = json.Unmarshal(response.Body.Bytes(), &repos)
c.Assert(err, IsNil)
found := false
for _, repo := range repos {
if repo["Name"] == "count-repo-list" {
found = true
value, ok := repo["NumPackages"]
c.Assert(ok, Equals, true)
c.Assert(value, Equals, float64(2))
break
}
}
c.Assert(found, Equals, true)
}
func (s *ReposSuite) TestGetReposReturns500OnCorruptRefList(c *C) {
body, err := json.Marshal(gin.H{"Name": "broken-repo-list"})
c.Assert(err, IsNil)
response, err := s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body))
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 201)
collection := s.context.NewCollectionFactory().LocalRepoCollection()
repo, err := collection.ByName("broken-repo-list")
c.Assert(err, IsNil)
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
response, err = s.HTTPRequest("GET", "/api/repos", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 500)
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
}
+242
View File
@@ -0,0 +1,242 @@
package api
import (
"net/http"
"sync/atomic"
"github.com/aptly-dev/aptly/aptly"
ctx "github.com/aptly-dev/aptly/context"
"github.com/aptly-dev/aptly/utils"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log"
// _ "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()
promhttp.Handler().ServeHTTP(c.Writer, c.Request)
}
}
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 {
if aptly.EnableDebug {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
router := gin.New()
context = c
router.UseRawPath = true
if c.Config().LogFormat == "json" {
gin.DefaultWriter = utils.LogWriter{Logger: log.Logger}
router.Use(JSONLogger())
} else {
router.Use(gin.Logger())
}
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().EnableMetricsEndpoint {
MetricsCollectorRegistrar.Register(router)
}
if c.Config().ServeInAPIMode {
router.GET("/repos/", reposListInAPIMode(c.Config().FileSystemPublishRoots))
router.GET("/repos/:storage/*pkgPath", reposServeInAPIMode)
}
api := router.Group("/api")
if context.Flags().Lookup("no-lock").Value.Get().(bool) {
// We use a goroutine to count the number of
// concurrent requests. When no more requests are
// running, we close the database to free the lock.
dbRequests = make(chan dbRequest)
go acquireDatabase()
api.Use(func(c *gin.Context) {
var err error
errCh := make(chan error)
dbRequests <- dbRequest{acquiredb, errCh}
err = <-errCh
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
defer func() {
dbRequests <- dbRequest{releasedb, errCh}
err = <-errCh
if err != nil {
AbortWithJSONError(c, 500, err)
}
}()
c.Next()
})
}
{
if c.Config().EnableMetricsEndpoint {
api.GET("/metrics", apiMetricsGet())
}
api.GET("/version", apiVersion)
api.GET("/storage", apiDiskFree)
isReady := &atomic.Value{}
isReady.Store(false)
defer isReady.Store(true)
api.GET("/ready", apiReady(isReady))
api.GET("/healthy", apiHealthy)
}
{
api.GET("/repos", apiReposList)
api.POST("/repos", apiReposCreate)
api.GET("/repos/:name", apiReposShow)
api.PUT("/repos/:name", apiReposEdit)
api.DELETE("/repos/:name", apiReposDrop)
api.GET("/repos/:name/packages", apiReposPackagesShow)
api.POST("/repos/:name/packages", apiReposPackagesAdd)
api.DELETE("/repos/:name/packages", apiReposPackagesDelete)
api.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
api.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
api.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage)
api.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
api.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)
api.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
}
{
api.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
}
{
api.GET("/mirrors", apiMirrorsList)
api.GET("/mirrors/:name", apiMirrorsShow)
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
api.POST("/mirrors", apiMirrorsCreate)
api.POST("/mirrors/:name", apiMirrorsEdit)
api.PUT("/mirrors/:name", apiMirrorsUpdate)
api.DELETE("/mirrors/:name", apiMirrorsDrop)
}
{
api.GET("/gpg/keys", apiGPGListKeys)
api.POST("/gpg/key", apiGPGAddKey)
api.DELETE("/gpg/key", apiGPGDeleteKey)
}
{
api.GET("/s3", apiS3List)
}
{
api.GET("/files", apiFilesListDirs)
api.POST("/files/:dir", apiFilesUpload)
api.GET("/files/:dir", apiFilesListFiles)
api.DELETE("/files/:dir", apiFilesDeleteDir)
api.DELETE("/files/:dir/:name", apiFilesDeleteFile)
}
{
api.GET("/publish", apiPublishList)
api.GET("/publish/:prefix/:distribution", apiPublishShow)
api.POST("/publish", apiPublishRepoOrSnapshot)
api.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
api.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
api.POST("/publish/:prefix/:distribution/sources", apiPublishAddSource)
api.GET("/publish/:prefix/:distribution/sources", apiPublishListChanges)
api.PUT("/publish/:prefix/:distribution/sources", apiPublishSetSources)
api.DELETE("/publish/:prefix/:distribution/sources", apiPublishDropChanges)
api.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishUpdateSource)
api.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishRemoveSource)
api.POST("/publish/:prefix/:distribution/update", apiPublishUpdate)
}
{
api.GET("/snapshots", apiSnapshotsList)
api.POST("/snapshots", apiSnapshotsCreate)
api.PUT("/snapshots/:name", apiSnapshotsUpdate)
api.GET("/snapshots/:name", apiSnapshotsShow)
api.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
api.DELETE("/snapshots/:name", apiSnapshotsDrop)
api.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
api.POST("/snapshots/:name/merge", apiSnapshotsMerge)
api.POST("/snapshots/:name/pull", apiSnapshotsPull)
}
{
api.GET("/packages/:key", apiPackagesShow)
api.GET("/packages", apiPackages)
}
{
api.GET("/graph.:ext", apiGraph)
}
{
api.POST("/db/cleanup", apiDBCleanup)
}
{
api.GET("/tasks", apiTasksList)
api.POST("/tasks-clear", apiTasksClear)
api.GET("/tasks-wait", apiTasksWait)
api.GET("/tasks/:id/wait", apiTasksWaitForTaskByID)
api.GET("/tasks/:id/output", apiTasksOutputShow)
api.GET("/tasks/:id/detail", apiTasksDetailShow)
api.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
api.GET("/tasks/:id", apiTasksShow)
api.DELETE("/tasks/:id", apiTasksDelete)
}
return router
}
+21
View File
@@ -0,0 +1,21 @@
package api
import (
"github.com/gin-gonic/gin"
)
// @Summary S3 buckets
// @Description **Get list of S3 buckets**
// @Description
// @Description List configured S3 buckets.
// @Tags Status
// @Produce json
// @Success 200 {array} string "List of S3 buckets"
// @Router /api/s3 [get]
func apiS3List(c *gin.Context) {
keys := []string{}
for k := range context.Config().S3PublishRoots {
keys = append(keys, k)
}
c.JSON(200, keys)
}
+825
View File
@@ -0,0 +1,825 @@
package api
import (
"fmt"
"net/http"
"sort"
"strings"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/database"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/aptly-dev/aptly/task"
"github.com/gin-gonic/gin"
)
// @Summary List Snapshots
// @Description **Get list of snapshots**
// @Description
// @Description Each snapshot is returned as in “show” API.
// @Tags Snapshots
// @Produce json
// @Success 200 {array} snapshotResponse
// @Router /api/snapshots [get]
func apiSnapshotsList(c *gin.Context) {
SortMethodString := c.Request.URL.Query().Get("sort")
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
if SortMethodString == "" {
SortMethodString = "name"
}
result := []snapshotResponse{}
err := collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
err := collection.LoadComplete(snapshot)
if err != nil {
return err
}
result = append(result, newSnapshotResponse(snapshot))
return nil
})
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
c.JSON(200, result)
}
type snapshotsCreateFromMirrorParams struct {
// Name of snapshot to create
Name string `binding:"required" json:"Name" example:"snap1"`
// Description of snapshot
Description string ` json:"Description"`
}
// @Summary Snapshot Mirror
// @Description **Create a snapshot of a mirror**
// @Tags Snapshots
// @Produce json
// @Param request body snapshotsCreateFromMirrorParams true "Parameters"
// @Param name path string true "Mirror name"
// @Param _async query bool false "Run in background and return task object"
// @Success 201 {object} deb.Snapshot "Created Snapshot"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Mirror Not Found"
// @Failure 409 {object} Error "Conflicting snapshot"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/mirrors/{name}/snapshots [post]
func apiSnapshotsCreateFromMirror(c *gin.Context) {
var (
err error
repo *deb.RemoteRepo
snapshot *deb.Snapshot
b snapshotsCreateFromMirrorParams
)
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.RemoteRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
name := c.Params.ByName("name")
repo, err = collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
// including snapshot resource key
resources := []string{string(repo.Key()), "S" + b.Name}
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err := repo.CheckLock()
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
}
err = collection.LoadComplete(repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
snapshot, err = deb.NewSnapshotFromRepository(b.Name, repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
if b.Description != "" {
snapshot.Description = b.Description
}
err = snapshotCollection.Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
})
}
type snapshotsCreateParams struct {
// Name of snapshot to create
Name string `binding:"required" json:"Name" example:"snap2"`
// Description of snapshot
Description string ` json:"Description"`
// List of source snapshots
SourceSnapshots []string ` json:"SourceSnapshots" example:"snap1"`
// List of package refs
PackageRefs []string ` json:"PackageRefs" example:""`
}
// @Summary Snapshot Packages
// @Description **Create a snapshot from package refs**
// @Description
// @Description Refs can be obtained from snapshots, local repos, or mirrors
// @Tags Snapshots
// @Param request body snapshotsCreateParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 201 {object} deb.Snapshot "Created snapshot"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Source snapshot or package refs not found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots [post]
func apiSnapshotsCreate(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
b snapshotsCreateParams
)
if c.Bind(&b) != nil {
return
}
if b.Description == "" {
if len(b.SourceSnapshots)+len(b.PackageRefs) == 0 {
b.Description = "Created as empty"
}
}
collectionFactory := context.NewCollectionFactory()
snapshotCollection := collectionFactory.SnapshotCollection()
var resources []string
sources := make([]*deb.Snapshot, len(b.SourceSnapshots))
for i := range b.SourceSnapshots {
sources[i], err = snapshotCollection.ByName(b.SourceSnapshots[i])
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
resources = append(resources, string(sources[i].ResourceKey()))
}
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
for i := range sources {
err = snapshotCollection.LoadComplete(sources[i])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
}
list := deb.NewPackageList()
// verify package refs and build package list
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)
}
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
}
}
snapshot = deb.NewSnapshotFromRefList(b.Name, sources, deb.NewPackageRefListFromPackageList(list), b.Description)
err = snapshotCollection.Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
})
}
type snapshotsCreateFromRepositoryParams struct {
// Name of snapshot to create
Name string `binding:"required" json:"Name" example:"snap1"`
// Description of snapshot
Description string ` json:"Description"`
}
// @Summary Snapshot Repository
// @Description **Create a snapshot of a repository by name**
// @Tags Snapshots
// @Consume json
// @Param request body snapshotsCreateFromRepositoryParams true "Parameters"
// @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"
// @Failure 400 {object} Error "Bad Request"
// @Failure 500 {object} Error "Internal Server Error"
// @Failure 404 {object} Error "Repo Not Found"
// @Router /api/repos/{name}/snapshots [post]
func apiSnapshotsCreateFromRepository(c *gin.Context) {
var (
err error
repo *deb.LocalRepo
snapshot *deb.Snapshot
b snapshotsCreateFromRepositoryParams
)
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.LocalRepoCollection()
snapshotCollection := collectionFactory.SnapshotCollection()
name := c.Params.ByName("name")
repo, err = collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
// including snapshot resource key
resources := []string{string(repo.Key()), "S" + b.Name}
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)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
snapshot, err = deb.NewSnapshotFromLocalRepo(b.Name, repo)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
}
if b.Description != "" {
snapshot.Description = b.Description
}
err = snapshotCollection.Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
})
}
type snapshotsUpdateParams struct {
// Change Name of snapshot
Name string ` json:"Name" example:"snap2"`
// Change Description of snapshot
Description string `json:"Description"`
}
// @Summary Update Snapshot
// @Description **Update snapshot metadata (Name, Description)**
// @Tags Snapshots
// @Param request body snapshotsUpdateParams true "Parameters"
// @Param name path string true "Snapshot name"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 {object} deb.Snapshot "Updated snapshot object"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 409 {object} Error "Conflicting snapshot"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name} [put]
func apiSnapshotsUpdate(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
b snapshotsUpdateParams
)
if c.Bind(&b) != nil {
return
}
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
name := c.Params.ByName("name")
snapshot, err = collection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
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)
if err == nil {
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
}
if b.Name != "" {
snapshot.Name = b.Name
}
if b.Description != "" {
snapshot.Description = b.Description
}
err = collectionFactory.SnapshotCollection().Update(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: snapshot}, nil
})
}
// @Summary Get Snapshot Info
// @Description **Query detailed information about a snapshot by name**
// @Tags Snapshots
// @Param name path string true "Name of the snapshot"
// @Produce json
// @Success 200 {object} deb.Snapshot "msg"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name} [get]
func apiSnapshotsShow(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
snapshot, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
err = collection.LoadComplete(snapshot)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
c.JSON(200, snapshot)
}
// @Summary Delete Snapshot
// @Description **Delete snapshot by name**
// @Description Cannot drop snapshots that are published.
// @Description Needs force=1 to drop snapshots used as source by other snapshots.
// @Tags Snapshots
// @Param name path string true "Snapshot name"
// @Param force query string false "Force operation"
// @Param _async query bool false "Run in background and return task object"
// @Produce json
// @Success 200 ""
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 409 {object} Error "Snapshot in use"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name} [delete]
func apiSnapshotsDrop(c *gin.Context) {
name := c.Params.ByName("name")
force := c.Request.URL.Query().Get("force") == "1"
collectionFactory := context.NewCollectionFactory()
snapshotCollection := collectionFactory.SnapshotCollection()
publishedCollection := collectionFactory.PublishedRepoCollection()
snapshot, err := snapshotCollection.ByName(name)
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
resources := []string{string(snapshot.ResourceKey())}
taskName := fmt.Sprintf("Delete snapshot %s", name)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
published := publishedCollection.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)
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)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, nil
})
}
// @Summary Snapshot diff
// @Description **Return the diff between two snapshots (name & withSnapshot)**
// @Description Provide `onlyMatching=1` to return only packages present in both snapshots.
// @Description Otherwise, returns a `left` and `right` result providing packages only in the first and second snapshots
// @Tags Snapshots
// @Produce json
// @Param name path string true "Snapshot name"
// @Param withSnapshot path string true "Snapshot name to diff against"
// @Param onlyMatching query string false "Only return packages present in both snapshots"
// @Success 200 {array} deb.PackageDiff "Package Diff"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name}/diff/{withSnapshot} [get]
func apiSnapshotsDiff(c *gin.Context) {
onlyMatching := c.Request.URL.Query().Get("onlyMatching") == "1"
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
snapshotA, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
err = collection.LoadComplete(snapshotA)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
err = collection.LoadComplete(snapshotB)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
// Calculate diff
diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), collectionFactory.PackageCollection())
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
result := []deb.PackageDiff{}
for _, pdiff := range diff {
if onlyMatching && (pdiff.Left == nil || pdiff.Right == nil) {
continue
}
result = append(result, pdiff)
}
c.JSON(200, result)
}
// @Summary List Snapshot Packages
// @Description **List all packages in snapshot or perform search on snapshot contents and return results**
// @Description If `q` query parameter is missing, return all packages, otherwise return packages that match q
// @Tags Snapshots
// @Produce json
// @Param name path string true "Snapshot to search"
// @Param q query string false "Package query (e.g Name%20(~%20matlab))"
// @Param withDeps query string false "Set to 1 to include dependencies when evaluating package query"
// @Param format query string false "Set to 'details' to return extra info about each package"
// @Param maximumVersion query string false "Set to 1 to only return the highest version for each package name"
// @Success 200 {array} string "Package info"
// @Failure 404 {object} Error "Snapshot Not Found"
// @Failure 500 {object} Error "Internal Server Error"
// @Router /api/snapshots/{name}/packages [get]
func apiSnapshotsSearchPackages(c *gin.Context) {
collectionFactory := context.NewCollectionFactory()
collection := collectionFactory.SnapshotCollection()
snapshot, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
err = collection.LoadComplete(snapshot)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
showPackages(c, snapshot.RefList(), collectionFactory)
}
type snapshotsMergeParams struct {
// List of snapshot names to be merged
Sources []string `binding:"required" json:"Sources" example:"snapshot1"`
}
// @Summary Snapshot Merge
// @Description **Merge several source snapshots into a new snapshot**
// @Description
// @Description Merge happens from left to right. By default, packages with the same name-architecture pair are replaced during merge (package from latest snapshot on the list wins).
// @Description
// @Description If only one snapshot is specified, merge copies source into destination.
// @Tags Snapshots
// @Consume json
// @Produce json
// @Param name path string true "Name of the snapshot to be created"
// @Param latest query int false "merge only the latest version of each package"
// @Param no-remove query int false "all versions of packages are preserved during merge"
// @Param request body snapshotsMergeParams true "Parameters"
// @Param _async query bool false "Run in background and return task object"
// @Success 201 {object} deb.Snapshot "Resulting snapshot object"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/snapshots/{name}/merge [post]
func apiSnapshotsMerge(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
body snapshotsMergeParams
)
name := c.Params.ByName("name")
if c.Bind(&body) != nil {
return
}
if len(body.Sources) < 1 {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("minimum one source snapshot is required"))
return
}
latest := c.Request.URL.Query().Get("latest") == "1"
noRemove := c.Request.URL.Query().Get("no-remove") == "1"
overrideMatching := !latest && !noRemove
if noRemove && latest {
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("no-remove and latest are mutually exclusive"))
return
}
collectionFactory := context.NewCollectionFactory()
snapshotCollection := collectionFactory.SnapshotCollection()
sources := make([]*deb.Snapshot, len(body.Sources))
resources := make([]string, len(sources))
for i := range body.Sources {
sources[i], err = snapshotCollection.ByName(body.Sources[i])
if err != nil {
AbortWithJSONError(c, http.StatusNotFound, err)
return
}
resources[i] = string(sources[i].ResourceKey())
}
maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = snapshotCollection.LoadComplete(sources[0])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
result := sources[0].RefList()
for i := 1; i < len(sources); i++ {
err = snapshotCollection.LoadComplete(sources[i])
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
result = result.Merge(sources[i].RefList(), overrideMatching, false)
}
if latest {
result.FilterLatestRefs()
}
sourceDescription := make([]string, len(sources))
for i, s := range sources {
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
}
snapshot = deb.NewSnapshotFromRefList(name, sources, result,
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
err = collectionFactory.SnapshotCollection().Add(snapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
}
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: snapshot}, nil
})
}
type snapshotsPullParams struct {
// Source name to be searched for packages and dependencies
Source string `binding:"required" json:"Source" example:"source-snapshot"`
// Name of the snapshot to be created
Destination string `binding:"required" json:"Destination" example:"idestination-snapshot"`
// List of package queries (i.e. name of package to be pulled from `Source`)
Queries []string `binding:"required" json:"Queries" example:"xserver-xorg"`
// List of architectures (optional)
Architectures []string ` json:"Architectures" example:"amd64, armhf"`
}
// @Summary Snapshot Pull
// @Description **Pulls new packages and dependencies from a source snapshot into a new snapshot**
// @Description
// @Description May also upgrade package versions if name snapshot already contains packages being pulled. New snapshot `Destination` is created as result of this process.
// @Description If architectures are limited (with config architectures or parameter `Architectures`, only mentioned architectures are processed, otherwise aptly will process all architectures in the snapshot.
// @Description If following dependencies by source is enabled (using dependencyFollowSource config), pulling binary packages would also pull corresponding source packages as well.
// @Description By default aptly would remove packages matching name and architecture while importing: e.g. when importing software_1.3_amd64, package software_1.2.9_amd64 would be removed.
// @Description
// @Description With flag `no-remove` both package versions would stay in the snapshot.
// @Description
// @Description Aptly pulls first package matching each of package queries, but with flag -all-matches all matching packages would be pulled.
// @Tags Snapshots
// @Param request body snapshotsPullParams true "Parameters"
// @Param name path string true "Name of the snapshot to be created"
// @Param all-matches query int false "pull all the packages that satisfy the dependency version requirements (default is to pull first matching package): 1 to enable"
// @Param dry-run query int false "dont create destination snapshot, just show what would be pulled: 1 to enable"
// @Param no-deps query int false "dont process dependencies, just pull listed packages: 1 to enable"
// @Param no-remove query int false "dont remove other package versions when pulling package: 1 to enable"
// @Param _async query bool false "Run in background and return task object"
// @Consume json
// @Produce json
// @Success 200 {object} deb.Snapshot "Resulting Snapshot object"
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/snapshots/{name}/pull [post]
func apiSnapshotsPull(c *gin.Context) {
var (
err error
destinationSnapshot *deb.Snapshot
body snapshotsPullParams
)
name := c.Params.ByName("name")
if err = c.BindJSON(&body); err != nil {
AbortWithJSONError(c, http.StatusBadRequest, err)
return
}
allMatches := c.Request.URL.Query().Get("all-matches") == "1"
dryRun := c.Request.URL.Query().Get("dry-run") == "1"
noDeps := c.Request.URL.Query().Get("no-deps") == "1"
noRemove := c.Request.URL.Query().Get("no-remove") == "1"
collectionFactory := context.NewCollectionFactory()
// Load <name> snapshot
toSnapshot, err := collectionFactory.SnapshotCollection().ByName(name)
if err != nil {
AbortWithJSONError(c, http.StatusNotFound, err)
return
}
// Load <Source> snapshot
sourceSnapshot, err := collectionFactory.SnapshotCollection().ByName(body.Source)
if err != nil {
AbortWithJSONError(c, http.StatusNotFound, err)
return
}
resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())}
taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination)
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// convert snapshots to package list
toPackageList, err := deb.NewPackageListFromRefList(toSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
sourcePackageList, err := deb.NewPackageListFromRefList(sourceSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
toPackageList.PrepareIndex()
sourcePackageList.PrepareIndex()
var architecturesList []string
if len(context.ArchitecturesList()) > 0 {
architecturesList = context.ArchitecturesList()
} else {
architecturesList = toPackageList.Architectures(false)
}
architecturesList = append(architecturesList, body.Architectures...)
sort.Strings(architecturesList)
if len(architecturesList) == 0 {
err := fmt.Errorf("unable to determine list of architectures, please specify explicitly")
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// Build architecture query: (arch == "i386" | arch == "amd64" | ...)
var archQuery deb.PackageQuery = &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: ""}
for _, arch := range architecturesList {
archQuery = &deb.OrQuery{L: &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: arch}, R: archQuery}
}
queries := make([]deb.PackageQuery, len(body.Queries))
for i, q := range body.Queries {
queries[i], err = query.Parse(q)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
// Add architecture filter
queries[i] = &deb.AndQuery{L: queries[i], R: archQuery}
}
// Filter with dependencies as requested
destinationPackageList, err := sourcePackageList.Filter(deb.FilterOptions{
Queries: queries,
WithDependencies: !noDeps,
Source: toPackageList,
DependencyOptions: context.DependencyOptions(),
Architectures: architecturesList,
Progress: context.Progress(),
})
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
destinationPackageList.PrepareIndex()
removedPackages := []string{}
addedPackages := []string{}
alreadySeen := map[string]bool{}
_ = destinationPackageList.ForEachIndexed(func(pkg *deb.Package) error {
key := pkg.Architecture + "_" + pkg.Name
_, seen := alreadySeen[key]
// If we haven't seen such name-architecture pair and were instructed to remove, remove it
if !noRemove && !seen {
// Remove all packages with the same name and architecture
packageSearchResults := toPackageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true, false)
for _, p := range packageSearchResults {
toPackageList.Remove(p)
removedPackages = append(removedPackages, p.String())
}
}
// If !allMatches, add only first matching name-arch package
if !seen || allMatches {
_ = toPackageList.Add(pkg)
addedPackages = append(addedPackages, pkg.String())
}
alreadySeen[key] = true
return nil
})
alreadySeen = nil
if dryRun {
response := struct {
AddedPackages []string `json:"added_packages"`
RemovedPackages []string `json:"removed_packages"`
}{
AddedPackages: addedPackages,
RemovedPackages: removedPackages,
}
return &task.ProcessReturnValue{Code: http.StatusOK, Value: response}, nil
}
// 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, ", ")))
err = collectionFactory.SnapshotCollection().Add(destinationSnapshot)
if err != nil {
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
}
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: destinationSnapshot}, nil
})
}
+53
View File
@@ -0,0 +1,53 @@
package api
import (
"encoding/json"
"github.com/aptly-dev/aptly/deb"
. "gopkg.in/check.v1"
)
type SnapshotsSuite struct {
APISuite
}
var _ = Suite(&SnapshotsSuite{})
func (s *SnapshotsSuite) TestGetSnapshotsIncludesNumPackages(c *C) {
collection := s.context.NewCollectionFactory().SnapshotCollection()
snapshot := deb.NewSnapshotFromRefList("count-snapshot-list", nil, makePackageRefList(c), "")
c.Assert(collection.Add(snapshot), IsNil)
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 200)
var snapshots []map[string]interface{}
err = json.Unmarshal(response.Body.Bytes(), &snapshots)
c.Assert(err, IsNil)
found := false
for _, snapshot := range snapshots {
if snapshot["Name"] == "count-snapshot-list" {
found = true
value, ok := snapshot["NumPackages"]
c.Assert(ok, Equals, true)
c.Assert(value, Equals, float64(2))
break
}
}
c.Assert(found, Equals, true)
}
func (s *SnapshotsSuite) TestGetSnapshotsReturns500OnCorruptRefList(c *C) {
collection := s.context.NewCollectionFactory().SnapshotCollection()
snapshot := deb.NewSnapshotFromRefList("broken-snapshot-list", nil, makePackageRefList(c), "")
c.Assert(collection.Add(snapshot), IsNil)
putRawDBValue(c, &s.APISuite, snapshot.RefKey(), []byte("not-msgpack"))
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
c.Assert(err, IsNil)
c.Assert(response.Code, Equals, 500)
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
}
+45
View File
@@ -0,0 +1,45 @@
package api
import (
"fmt"
"syscall"
"github.com/gin-gonic/gin"
)
type diskFree struct {
// Storage size [MiB]
Total uint64
// Available Storage [MiB]
Free uint64
// Percentage Full
PercentFull float32
}
// @Summary Get Storage Utilization
// @Description **Get disk free information of aptly storage**
// @Description
// @Description Units in MiB.
// @Tags Status
// @Produce json
// @Success 200 {object} diskFree "Storage information"
// @Failure 400 {object} Error "Internal Error"
// @Router /api/storage [get]
func apiDiskFree(c *gin.Context) {
var df diskFree
fs := context.Config().GetRootDir()
var stat syscall.Statfs_t
err := syscall.Statfs(fs, &stat)
if err != nil {
AbortWithJSONError(c, 400, fmt.Errorf("Error getting storage info on %s: %s", fs, err))
return
}
df.Total = uint64(stat.Blocks) * uint64(stat.Bsize) / 1048576
df.Free = uint64(stat.Bavail) * uint64(stat.Bsize) / 1048576
df.PercentFull = 100.0 - float32(stat.Bavail)/float32(stat.Blocks)*100.0
c.JSON(200, df)
}
+203
View File
@@ -0,0 +1,203 @@
package api
import (
"strconv"
"github.com/aptly-dev/aptly/task"
"github.com/gin-gonic/gin"
)
// @Summary List Tasks
// @Description **Get list of available tasks. Each task is returned as in “show” API**
// @Tags Tasks
// @Produce json
// @Success 200 {array} task.Task
// @Router /api/tasks [get]
func apiTasksList(c *gin.Context) {
list := context.TaskList()
c.JSON(200, list.GetTasks())
}
// @Summary Clear Tasks
// @Description **Removes finished and failed tasks from internal task list**
// @Tags Tasks
// @Produce json
// @Success 200 ""
// @Router /api/tasks-clear [post]
func apiTasksClear(c *gin.Context) {
list := context.TaskList()
list.Clear()
c.JSON(200, gin.H{})
}
// @Summary Wait for all Tasks
// @Description **Waits for and returns when all running tasks are complete**
// @Tags Tasks
// @Produce json
// @Success 200 ""
// @Router /api/tasks-wait [get]
func apiTasksWait(c *gin.Context) {
list := context.TaskList()
list.Wait()
c.JSON(200, gin.H{})
}
// @Summary Wait for Task
// @Description **Waits for and returns when given Task ID is complete**
// @Tags Tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} task.Task
// @Failure 500 {object} Error "invalid syntax, bad id?"
// @Failure 400 {object} Error "Task Not Found"
// @Router /api/tasks/{id}/wait [get]
func apiTasksWaitForTaskByID(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
task, err := list.WaitForTaskByID(int(id))
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
c.JSON(200, task)
}
// @Summary Get Task Info
// @Description **Return task information for a given ID**
// @Tags Tasks
// @Produce plain
// @Param id path int true "Task ID"
// @Success 200 {object} task.Task
// @Failure 500 {object} Error "invalid syntax, bad id?"
// @Failure 404 {object} Error "Task Not Found"
// @Router /api/tasks/{id} [get]
func apiTasksShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
var task task.Task
task, err = list.GetTaskByID(int(id))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
c.JSON(200, task)
}
// @Summary Get Task Output
// @Description **Return task output for a given ID**
// @Tags Tasks
// @Produce plain
// @Param id path int true "Task ID"
// @Success 200 {object} string "Task output"
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 404 {object} Error "Task Not Found"
// @Router /api/tasks/{id}/output [get]
func apiTasksOutputShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
var output string
output, err = list.GetTaskOutputByID(int(id))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
c.JSON(200, output)
}
// @Summary Get Task Details
// @Description **Return task detail for a given ID**
// @Tags Tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} string "Task detail"
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 404 {object} Error "Task Not Found"
// @Router /api/tasks/{id}/detail [get]
func apiTasksDetailShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
var detail interface{}
detail, err = list.GetTaskDetailByID(int(id))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
c.JSON(200, detail)
}
// @Summary Get Task Return Value
// @Description **Return task return value (status code) by given ID**
// @Tags Tasks
// @Produce plain
// @Param id path int true "Task ID"
// @Success 200 {object} string "msg"
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 404 {object} Error "Not Found"
// @Router /api/tasks/{id}/return_value [get]
func apiTasksReturnValueShow(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
output, err := list.GetTaskReturnValueByID(int(id))
if err != nil {
AbortWithJSONError(c, 404, err)
return
}
c.JSON(200, output)
}
// @Summary Delete Task
// @Description **Delete completed task by given ID. Does not stop task execution**
// @Tags Tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} task.Task
// @Failure 500 {object} Error "invalid syntax, bad ID?"
// @Failure 400 {object} Error "Task in progress or not found"
// @Router /api/tasks/{id} [delete]
func apiTasksDelete(c *gin.Context) {
list := context.TaskList()
id, err := strconv.ParseInt(c.Params.ByName("id"), 10, 0)
if err != nil {
AbortWithJSONError(c, 500, err)
return
}
var delTask task.Task
delTask, err = list.DeleteTaskByID(int(id))
if err != nil {
AbortWithJSONError(c, 400, err)
return
}
c.JSON(200, delTask)
}
+4
View File
@@ -0,0 +1,4 @@
package aptly
// AptlyConf holds the default aptly.conf (filled in at link time)
var AptlyConf []byte
+3
View File
@@ -0,0 +1,3 @@
package aptly
var DistributionFocal = "focal"
+115 -24
View File
@@ -3,49 +3,132 @@
package aptly
import (
"github.com/smira/aptly/utils"
"context"
"io"
"os"
"github.com/aptly-dev/aptly/database"
"github.com/aptly-dev/aptly/utils"
)
// ReadSeekerCloser = ReadSeeker + Closer
type ReadSeekerCloser interface {
io.ReadSeeker
io.Closer
}
// PackagePool is asbtraction of package pool storage.
//
// PackagePool stores all the package files, deduplicating them.
type PackagePool interface {
// Path returns full path to package file in pool given any name and hash of file contents
Path(filename string, hashMD5 string) (string, error)
// RelativePath returns path relative to pool's root for package files given MD5 and original filename
RelativePath(filename string, hashMD5 string) (string, error)
// Verify checks whether file exists in the pool and fills back checksum info
//
// if poolPath is empty, poolPath is generated automatically based on checksum info (if available)
// in any case, if function returns true, it also fills back checksums with complete information about the file in the pool
Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage ChecksumStorage) (string, bool, error)
// Import copies file into package pool
//
// - srcPath is full path to source file as it is now
// - basename is desired human-readable name (canonical filename)
// - checksums are used to calculate file placement
// - move indicates whether srcPath can be removed
Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage ChecksumStorage) (path string, err error)
// LegacyPath returns legacy (pre 1.1) path to package file (relative to root)
LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error)
// Size returns the size of the given file in bytes.
Size(path string) (size int64, err error)
// Open returns ReadSeekerCloser to access the file
Open(path string) (ReadSeekerCloser, error)
// FilepathList returns file paths of all the files in the pool
FilepathList(progress Progress) ([]string, error)
// Remove deletes file in package pool returns its size
Remove(path string) (size int64, err error)
// Import copies file into package pool
Import(path string, hashMD5 string) error
}
// LocalPackagePool is implemented by PackagePools residing on the same filesystem
type LocalPackagePool interface {
// Stat returns Unix stat(2) info
Stat(path string) (os.FileInfo, error)
// GenerateTempPath generates temporary path for download (which is fast to import into package pool later on)
GenerateTempPath(filename string) (string, error)
// Link generates hardlink to destination path
Link(path, dstPath string) error
// Symlink generates symlink to destination path
Symlink(path, dstPath string) error
// FullPath generates full path to the file in pool
//
// Please use with care: it's not supposed to be used to access files
FullPath(path string) string
}
// PublishedStorage is abstraction of filesystem storing all published repositories
type PublishedStorage interface {
// PublicPath returns root of public part
PublicPath() string
// MkDir creates directory recursively under public path
MkDir(path string) error
// CreateFile creates file for writing under public path
CreateFile(path string) (*os.File, error)
// PutFile puts file into published storage at specified path
PutFile(path string, sourceFilename string) error
// RemoveDirs removes directory structure under public path
RemoveDirs(path string, progress Progress) error
// Remove removes single file under public path
Remove(path string) error
// LinkFromPool links package file from pool to dist's pool location
LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath string) error
LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool PackagePool, sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error
// Filelist returns list of files under prefix
Filelist(prefix string) ([]string, error)
// ChecksumsForFile proxies requests to utils.ChecksumsForFile, joining public path
ChecksumsForFile(path string) (utils.ChecksumInfo, error)
// RenameFile renames (moves) file
RenameFile(oldName, newName string) error
// SymLink creates a symbolic link, which can be read with ReadLink
SymLink(src string, dst string) error
// HardLink creates a hardlink of a file
HardLink(src string, dst string) error
// FileExists returns true if path exists
FileExists(path string) (bool, error)
// ReadLink returns the symbolic link pointed to by path
ReadLink(path string) (string, error)
}
// FileSystemPublishedStorage is published storage on filesystem
type FileSystemPublishedStorage interface {
// PublicPath returns root of public part
PublicPath() string
}
// PublishedStorageProvider is a thing that returns PublishedStorage by name
type PublishedStorageProvider interface {
// GetPublishedStorage returns PublishedStorage by name
GetPublishedStorage(name string) PublishedStorage
}
// BarType used to differentiate between different progress bars
type BarType int
const (
// BarGeneralBuildPackageList identifies bar for building package list
BarGeneralBuildPackageList BarType = iota
// BarGeneralVerifyDependencies identifies bar for verifying dependencies
BarGeneralVerifyDependencies
// BarGeneralBuildFileList identifies bar for building file list
BarGeneralBuildFileList
// BarCleanupBuildList identifies bar for building list to cleanup
BarCleanupBuildList
// BarCleanupDeleteUnreferencedFiles identifies bar for deleting unreferenced files
BarCleanupDeleteUnreferencedFiles
// BarMirrorUpdateDownloadIndexes identifies bar for downloading index files
BarMirrorUpdateDownloadIndexes
// BarMirrorUpdateDownloadPackages identifies bar for downloading packages
BarMirrorUpdateDownloadPackages
// BarMirrorUpdateBuildPackageList identifies bar for building package list of downloaded files
BarMirrorUpdateBuildPackageList
// BarMirrorUpdateImportFiles identifies bar for importing package files
BarMirrorUpdateImportFiles
// BarMirrorUpdateFinalizeDownload identifies bar for finalizing downloads
BarMirrorUpdateFinalizeDownload
// BarPublishGeneratePackageFiles identifies bar for generating package files to publish
BarPublishGeneratePackageFiles
// BarPublishFinalizeIndexes identifies bar for finalizing index files
BarPublishFinalizeIndexes
)
// Progress is a progress displaying entity, it allows progress bars & simple prints
type Progress interface {
// Writer interface to support progress bar ticking
@@ -57,7 +140,7 @@ type Progress interface {
// Flush returns when all queued messages are sent
Flush()
// InitBar starts progressbar for count bytes or count items
InitBar(count int64, isBytes bool)
InitBar(count int64, isBytes bool, barType BarType)
// ShutdownBar stops progress bar and hides it
ShutdownBar()
// AddBar increments progress for progress bar
@@ -68,21 +151,29 @@ type Progress interface {
Printf(msg string, a ...interface{})
// ColoredPrintf does printf in colored way + newline
ColoredPrintf(msg string, a ...interface{})
// PrintfStdErr does printf but in safe manner to stderr
PrintfStdErr(msg string, a ...interface{})
}
// Downloader is parallel HTTP fetcher
type Downloader interface {
// Download starts new download task
Download(url string, destination string, result chan<- error)
Download(ctx context.Context, url string, destination string) error
// DownloadWithChecksum starts new download task with checksum verification
DownloadWithChecksum(url string, destination string, result chan<- error, expected utils.ChecksumInfo, ignoreMismatch bool)
// Pause pauses task processing
Pause()
// Resume resumes task processing
Resume()
// Shutdown stops downloader after current tasks are finished,
// but doesn't process rest of queue
Shutdown()
DownloadWithChecksum(ctx context.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error
// GetProgress returns Progress object
GetProgress() Progress
// GetLength returns size by heading object with url
GetLength(ctx context.Context, url string) (int64, error)
}
// ChecksumStorageProvider creates ChecksumStorage based on DB
type ChecksumStorageProvider func(db database.ReaderWriter) ChecksumStorage
// ChecksumStorage is stores checksums in some (persistent) storage
type ChecksumStorage interface {
// Get finds checksums in DB by path
Get(path string) (*utils.ChecksumInfo, error)
// Update adds or updates information about checksum in DB
Update(path string, c *utils.ChecksumInfo) error
}
+67
View File
@@ -0,0 +1,67 @@
package aptly
import (
"fmt"
)
// ResultReporter is abstraction for result reporting from complex processing functions
type ResultReporter interface {
// Warning is non-fatal error message
Warning(msg string, a ...interface{})
// Removed is signal that something has been removed
Removed(msg string, a ...interface{})
// Added is signal that something has been added
Added(msg string, a ...interface{})
}
// ConsoleResultReporter is implementation of ResultReporter that prints in colors to console
type ConsoleResultReporter struct {
Progress Progress
}
// Check interface
var (
_ ResultReporter = &ConsoleResultReporter{}
)
// Warning is non-fatal error message (yellow)
func (c *ConsoleResultReporter) Warning(msg string, a ...interface{}) {
c.Progress.ColoredPrintf("@y[!]@| @!"+msg+"@|", a...)
}
// Removed is signal that something has been removed (red)
func (c *ConsoleResultReporter) Removed(msg string, a ...interface{}) {
c.Progress.ColoredPrintf("@r[-]@| "+msg, a...)
}
// Added is signal that something has been added (green)
func (c *ConsoleResultReporter) Added(msg string, a ...interface{}) {
c.Progress.ColoredPrintf("@g[+]@| "+msg, a...)
}
// RecordingResultReporter is implementation of ResultReporter that collects all messages
type RecordingResultReporter struct {
Warnings []string
AddedLines []string `json:"Added"`
RemovedLines []string `json:"Removed"`
}
// Check interface
var (
_ ResultReporter = &RecordingResultReporter{}
)
// Warning is non-fatal error message
func (r *RecordingResultReporter) Warning(msg string, a ...interface{}) {
r.Warnings = append(r.Warnings, fmt.Sprintf(msg, a...))
}
// Removed is signal that something has been removed
func (r *RecordingResultReporter) Removed(msg string, a ...interface{}) {
r.RemovedLines = append(r.RemovedLines, fmt.Sprintf(msg, a...))
}
// Added is signal that something has been added
func (r *RecordingResultReporter) Added(msg string, a ...interface{}) {
r.AddedLines = append(r.AddedLines, fmt.Sprintf(msg, a...))
}
+3 -3
View File
@@ -1,7 +1,7 @@
package aptly
// Version of aptly
const Version = "0.5.1"
// Version of aptly (filled in at link time)
var Version string
// Enable debugging features?
// EnableDebug triggers some debugging features
const EnableDebug = false
+129
View File
@@ -0,0 +1,129 @@
package azure
// Package azure handles publishing to Azure Storage
import (
"context"
"encoding/hex"
"fmt"
"io"
"net/url"
"path/filepath"
"time"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
)
func isBlobNotFound(err error) bool {
storageError, ok := err.(azblob.StorageError)
return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
}
type azContext struct {
container azblob.ContainerURL
prefix string
}
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
if err != nil {
return nil, err
}
if endpoint == "" {
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
}
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{
container: containerURL,
prefix: prefix,
}
return result, nil
}
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)
md5s = make([]string, 0, 1024)
prefix = filepath.Join(az.prefix, prefix)
if prefix != "" {
prefix += delimiter
}
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)
}
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)
}
}
return paths, md5s, nil
}
func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
uploadOptions := azblob.UploadStreamToBlockBlobOptions{
BufferSize: 4 * 1024 * 1024,
MaxBuffers: 8,
}
if len(sourceMD5) > 0 {
decodedMD5, err := hex.DecodeString(sourceMD5)
if err != nil {
return err
}
uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
ContentMD5: decodedMD5,
}
}
_, err := azblob.UploadStreamToBlockBlob(
context.Background(),
source,
blob.ToBlockBlobURL(),
uploadOptions,
)
return err
}
// String
func (az *azContext) String() string {
return fmt.Sprintf("Azure: %s/%s", az.container, az.prefix)
}
+12
View File
@@ -0,0 +1,12 @@
package azure
import (
"testing"
. "gopkg.in/check.v1"
)
// Launch gocheck tests
func Test(t *testing.T) {
TestingT(t)
}
+218
View File
@@ -0,0 +1,218 @@
package azure
import (
"context"
"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"
)
type PackagePool struct {
az *azContext
}
// Check interface
var (
_ aptly.PackagePool = (*PackagePool)(nil)
)
// NewPackagePool creates published storage from Azure storage credentials
func NewPackagePool(accountName, accountKey, container, prefix, endpoint string) (*PackagePool, error) {
azctx, err := newAzContext(accountName, accountKey, container, prefix, endpoint)
if err != nil {
return nil, err
}
return &PackagePool{az: azctx}, nil
}
// String
func (pool *PackagePool) String() string {
return pool.az.String()
}
func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.ChecksumInfo) string {
hash := checksums.SHA256
// Use the same path as the file pool, for compat reasons.
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) {
targetChecksums, err := checksumStorage.Get(poolPath)
if err != nil {
return nil, err
}
if targetChecksums == nil {
// we don't have checksums stored yet for this file
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
}
return nil, errors.Wrapf(err, "error downloading blob at %s", poolPath)
}
targetChecksums = &utils.ChecksumInfo{}
*targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
if err != nil {
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
}
err = checksumStorage.Update(poolPath, targetChecksums)
if err != nil {
return nil, err
}
}
return targetChecksums, nil
}
func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) {
if progress != nil {
progress.InitBar(0, false, aptly.BarGeneralBuildFileList)
defer progress.ShutdownBar()
}
paths, _, err := pool.az.internalFilelist("", progress)
return paths, err
}
func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, error) {
return "", errors.New("Azure package pool does not support legacy paths")
}
func (pool *PackagePool) Size(path string) (int64, error) {
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
}
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.Wrap(err, "error creating temporary file for blob download")
}
defer os.Remove(temp.Name())
err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
if err != nil {
return nil, errors.Wrapf(err, "error downloading blob at %s", path)
}
return temp, nil
}
func (pool *PackagePool) Remove(path string) (int64, error) {
blob := pool.az.blobURL(path)
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
}
_, 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
}
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
if checksums.MD5 == "" || checksums.SHA256 == "" || checksums.SHA512 == "" {
// need to update checksums, MD5 and SHA256 should be always defined
var err error
*checksums, err = utils.ChecksumsForFile(srcPath)
if err != nil {
return "", err
}
}
path := pool.buildPoolPath(basename, checksums)
blob := pool.az.blobURL(path)
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
if err != nil {
return "", err
} else if targetChecksums != nil {
// target already exists
*checksums = *targetChecksums
return path, nil
}
source, err := os.Open(srcPath)
if err != nil {
return "", err
}
defer source.Close()
err = pool.az.putFile(blob, source, checksums.MD5)
if err != nil {
return "", err
}
if !checksums.Complete() {
// need full checksums here
*checksums, err = utils.ChecksumsForFile(srcPath)
if err != nil {
return "", err
}
}
err = checksumStorage.Update(path, checksums)
if err != nil {
return "", err
}
return path, nil
}
func (pool *PackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) {
if poolPath == "" {
if checksums.SHA256 != "" {
poolPath = pool.buildPoolPath(basename, checksums)
} else {
// No checksums or pool path, so no idea what file to look for.
return "", false, nil
}
}
size, err := pool.Size(poolPath)
if err != nil {
return "", false, err
} else if size != checksums.Size {
return "", false, nil
}
targetChecksums, err := pool.ensureChecksums(poolPath, checksumStorage)
if err != nil {
return "", false, err
} else if targetChecksums == nil {
return "", false, nil
}
if checksums.MD5 != "" && targetChecksums.MD5 != checksums.MD5 ||
checksums.SHA256 != "" && targetChecksums.SHA256 != checksums.SHA256 {
// wrong file?
return "", false, nil
}
// fill back checksums
*checksums = *targetChecksums
return poolPath, true, nil
}
+255
View File
@@ -0,0 +1,255 @@
package azure
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"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"
. "gopkg.in/check.v1"
)
type PackagePoolSuite struct {
accountName, accountKey, endpoint string
pool, prefixedPool *PackagePool
debFile string
cs aptly.ChecksumStorage
}
var _ = Suite(&PackagePoolSuite{})
func (s *PackagePoolSuite) SetUpSuite(c *C) {
s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
if s.accountName == "" {
println("Please set the the following two environment variables to run the Azure storage tests.")
println(" 1. AZURE_STORAGE_ACCOUNT")
println(" 2. AZURE_STORAGE_ACCESS_KEY")
c.Skip("AZURE_STORAGE_ACCOUNT not set.")
}
s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
if s.accountKey == "" {
println("Please set the the following two environment variables to run the Azure storage tests.")
println(" 1. AZURE_STORAGE_ACCOUNT")
println(" 2. AZURE_STORAGE_ACCESS_KEY")
c.Skip("AZURE_STORAGE_ACCESS_KEY not set.")
}
s.endpoint = os.Getenv("AZURE_STORAGE_ENDPOINT")
}
func (s *PackagePoolSuite) SetUpTest(c *C) {
container := randContainer()
prefix := "lala"
var err error
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
c.Assert(err, IsNil)
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)
c.Assert(err, IsNil)
_, _File, _, _ := runtime.Caller(0)
s.debFile = filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb")
s.cs = files.NewMockChecksumStorage()
}
func (s *PackagePoolSuite) TestFilepathList(c *C) {
list, err := s.pool.FilepathList(nil)
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{})
s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs)
list, err = s.pool.FilepathList(nil)
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{
"c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb",
"c7/6b/4bd12fd92e4dfe1b55b18a67a669_b.deb",
})
}
func (s *PackagePoolSuite) TestRemove(c *C) {
s.pool.Import(s.debFile, "a.deb", &utils.ChecksumInfo{}, false, s.cs)
s.pool.Import(s.debFile, "b.deb", &utils.ChecksumInfo{}, false, s.cs)
size, err := s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb")
c.Check(err, IsNil)
c.Check(size, Equals, int64(2738))
_, err = s.pool.Remove("c7/6b/4bd12fd92e4dfe1b55b18a67a669_a.deb")
c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*")
list, err := s.pool.FilepathList(nil)
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"c7/6b/4bd12fd92e4dfe1b55b18a67a669_b.deb"})
}
func (s *PackagePoolSuite) TestImportOk(c *C) {
var checksum utils.ChecksumInfo
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
c.Check(err, IsNil)
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
// SHA256 should be automatically calculated
c.Check(checksum.SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12")
// checksum storage is filled with new checksum
c.Check(s.cs.(*files.MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12")
size, err := s.pool.Size(path)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(2738))
// import as different name
checksum = utils.ChecksumInfo{}
path, err = s.pool.Import(s.debFile, "some.deb", &checksum, false, s.cs)
c.Check(err, IsNil)
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_some.deb")
// checksum storage is filled with new checksum
c.Check(s.cs.(*files.MockChecksumStorage).Store[path].SHA256, Equals, "c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12")
// double import, should be ok
checksum = utils.ChecksumInfo{}
path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
c.Check(err, IsNil)
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
// checksum is filled back based on checksum storage
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// clear checksum storage, and do double-import
delete(s.cs.(*files.MockChecksumStorage).Store, path)
checksum = utils.ChecksumInfo{}
path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
c.Check(err, IsNil)
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
// checksum is filled back based on re-calculation of file in the pool
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// import under new name, but with path-relevant checksums already filled in
checksum = utils.ChecksumInfo{SHA256: checksum.SHA256}
path, err = s.pool.Import(s.debFile, "other.deb", &checksum, false, s.cs)
c.Check(err, IsNil)
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_other.deb")
// checksum is filled back based on re-calculation of source file
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
}
func (s *PackagePoolSuite) TestVerify(c *C) {
// file doesn't exist yet
ppath, exists, err := s.pool.Verify("", filepath.Base(s.debFile), &utils.ChecksumInfo{}, s.cs)
c.Check(ppath, Equals, "")
c.Check(err, IsNil)
c.Check(exists, Equals, false)
// import file
checksum := utils.ChecksumInfo{}
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &checksum, false, s.cs)
c.Check(err, IsNil)
c.Check(path, Equals, "c7/6b/4bd12fd92e4dfe1b55b18a67a669_libboost-program-options-dev_1.49.0.1_i386.deb")
// check existence
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs)
c.Check(ppath, Equals, ppath)
c.Check(err, IsNil)
c.Check(exists, Equals, true)
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// check existence with fixed path
checksum = utils.ChecksumInfo{Size: checksum.Size}
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs)
c.Check(ppath, Equals, path)
c.Check(err, IsNil)
c.Check(exists, Equals, true)
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// check existence, but with checksums missing (that aren't needed to find the path)
checksum.SHA512 = ""
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs)
c.Check(ppath, Equals, path)
c.Check(err, IsNil)
c.Check(exists, Equals, true)
// checksum is filled back based on checksum storage
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// check existence, with missing checksum info but correct path and size available
checksum = utils.ChecksumInfo{Size: checksum.Size}
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs)
c.Check(ppath, Equals, path)
c.Check(err, IsNil)
c.Check(exists, Equals, true)
// checksum is filled back based on checksum storage
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// check existence, with wrong checksum info but correct path and size available
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &utils.ChecksumInfo{
SHA256: "abc",
Size: checksum.Size,
}, s.cs)
c.Check(ppath, Equals, "")
c.Check(err, IsNil)
c.Check(exists, Equals, false)
// check existence, with missing checksums (that aren't needed to find the path)
// and no info in checksum storage
delete(s.cs.(*files.MockChecksumStorage).Store, path)
checksum.SHA512 = ""
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &checksum, s.cs)
c.Check(ppath, Equals, path)
c.Check(err, IsNil)
c.Check(exists, Equals, true)
// checksum is filled back based on re-calculation
c.Check(checksum.SHA512, Equals, "d7302241373da972aa9b9e71d2fd769b31a38f71182aa71bc0d69d090d452c69bb74b8612c002ccf8a89c279ced84ac27177c8b92d20f00023b3d268e6cec69c")
// check existence, with wrong size
checksum = utils.ChecksumInfo{Size: 13455}
ppath, exists, err = s.pool.Verify(path, filepath.Base(s.debFile), &checksum, s.cs)
c.Check(ppath, Equals, "")
c.Check(err, IsNil)
c.Check(exists, Equals, false)
// check existence, with empty checksum info
ppath, exists, err = s.pool.Verify("", filepath.Base(s.debFile), &utils.ChecksumInfo{}, s.cs)
c.Check(ppath, Equals, "")
c.Check(err, IsNil)
c.Check(exists, Equals, false)
}
func (s *PackagePoolSuite) TestImportNotExist(c *C) {
_, err := s.pool.Import("no-such-file", "a.deb", &utils.ChecksumInfo{}, false, s.cs)
c.Check(err, ErrorMatches, ".*no such file or directory")
}
func (s *PackagePoolSuite) TestSize(c *C) {
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &utils.ChecksumInfo{}, false, s.cs)
c.Check(err, IsNil)
size, err := s.pool.Size(path)
c.Assert(err, IsNil)
c.Check(size, Equals, int64(2738))
_, err = s.pool.Size("do/es/ntexist")
c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*")
}
func (s *PackagePoolSuite) TestOpen(c *C) {
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &utils.ChecksumInfo{}, false, s.cs)
c.Check(err, IsNil)
f, err := s.pool.Open(path)
c.Assert(err, IsNil)
contents, err := ioutil.ReadAll(f)
c.Assert(err, IsNil)
c.Check(len(contents), Equals, 2738)
c.Check(f.Close(), IsNil)
_, err = s.pool.Open("do/es/ntexist")
c.Check(err, ErrorMatches, "(.|\n)*BlobNotFound(.|\n)*")
}
+278
View File
@@ -0,0 +1,278 @@
package azure
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
"github.com/pkg/errors"
)
// PublishedStorage abstract file system with published files (actually hosted on Azure)
type PublishedStorage struct {
container azblob.ContainerURL
prefix string
az *azContext
pathCache map[string]map[string]string
}
// Check interface
var (
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
)
// NewPublishedStorage creates published storage from Azure storage credentials
func NewPublishedStorage(accountName, accountKey, container, prefix, endpoint string) (*PublishedStorage, error) {
azctx, err := newAzContext(accountName, accountKey, container, prefix, endpoint)
if err != nil {
return nil, err
}
return &PublishedStorage{az: azctx}, nil
}
// String
func (storage *PublishedStorage) String() string {
return storage.az.String()
}
// MkDir creates directory recursively under public path
func (storage *PublishedStorage) MkDir(_ string) error {
// no op for Azure
return nil
}
// PutFile puts file into published storage at specified path
func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error {
var (
source *os.File
err error
)
sourceMD5, err := utils.MD5ChecksumForFile(sourceFilename)
if err != nil {
return err
}
source, err = os.Open(sourceFilename)
if err != nil {
return err
}
defer source.Close()
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))
}
return err
}
// RemoveDirs removes directory structure under public path
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
filelist, err := storage.Filelist(path)
if err != nil {
return err
}
for _, filename := range filelist {
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)
}
}
return nil
}
// Remove removes single file under public path
func (storage *PublishedStorage) Remove(path string) error {
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))
}
return err
}
// LinkFromPool links package file from pool to dist's pool location
//
// publishedPrefix is desired prefix for the location in the pool.
// publishedRelPath is desired location in pool (like pool/component/liba/libav/)
// sourcePool is instance of aptly.PackagePool
// sourcePath is filepath to package file in package pool
//
// LinkFromPool returns relative path for the published file to be included in package index
func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool,
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
relFilePath := filepath.Join(publishedRelPath, fileName)
// 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)
}
pathCache := storage.pathCache[publishedPrefix]
if pathCache == nil {
paths, md5s, err := storage.az.internalFilelist(publishedPrefix, nil)
if err != nil {
return fmt.Errorf("error caching paths under prefix: %s", err)
}
pathCache = make(map[string]string, len(paths))
for i := range paths {
pathCache[paths[i]] = md5s[i]
}
storage.pathCache[publishedPrefix] = pathCache
}
destinationMD5, exists := pathCache[relFilePath]
sourceMD5 := sourceChecksums.MD5
if exists {
if sourceMD5 == "" {
return fmt.Errorf("unable to compare object, MD5 checksum missing")
}
if destinationMD5 == sourceMD5 {
return nil
}
if !force && destinationMD5 != sourceMD5 {
return fmt.Errorf("error putting file to %s: file already exists and is different: %s", poolPath, storage)
}
}
source, err := sourcePool.Open(sourcePath)
if err != nil {
return err
}
defer source.Close()
err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
if err == nil {
pathCache[relFilePath] = sourceMD5
} else {
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s: %s", sourcePath, storage, poolPath))
}
return err
}
// Filelist returns list of files under prefix
func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
paths, _, err := storage.az.internalFilelist(prefix, nil)
return paths, err
}
// Internal copy or move implementation
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
const leaseDuration = 30
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 srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{})
srcBlobLeaseID := leaseResp.LeaseID()
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()
for {
if copyStatus == azblob.CopyStatusSuccess {
if move {
_, err = srcBlobURL.Delete(
context.Background(),
azblob.DeleteSnapshotsOptionNone,
azblob.BlobAccessConditions{
LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
})
return err
}
return nil
} else if copyStatus == azblob.CopyStatusPending {
time.Sleep(1 * time.Second)
blobPropsResp, err := dstBlobURL.GetProperties(
context.Background(),
azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
azblob.ClientProvidedKeyOptions{})
if err != nil {
return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
}
copyStatus = blobPropsResp.CopyStatus()
_, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
if err != nil {
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)
}
}
}
// RenameFile renames (moves) file
func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
return storage.internalCopyOrMoveBlob(oldName, newName, nil, true /* move */)
}
// SymLink creates a copy of src file and adds link information as meta data
func (storage *PublishedStorage) SymLink(src string, dst string) error {
return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
}
// HardLink using symlink functionality as hard links do not exist
func (storage *PublishedStorage) HardLink(src string, dst string) error {
return storage.SymLink(src, dst)
}
// FileExists returns true if path exists
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
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, err
} else if resp.StatusCode() == http.StatusOK {
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) {
blob := storage.az.blobURL(path)
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
if err != nil {
return "", err
} else if resp.StatusCode() != http.StatusOK {
return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
}
return resp.NewMetadata()["SymLink"], nil
}
+371
View File
@@ -0,0 +1,371 @@
package azure
import (
"context"
"crypto/md5"
"crypto/rand"
"io/ioutil"
"os"
"path/filepath"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/aptly-dev/aptly/files"
"github.com/aptly-dev/aptly/utils"
. "gopkg.in/check.v1"
)
type PublishedStorageSuite struct {
accountName, accountKey, endpoint string
storage, prefixedStorage *PublishedStorage
}
var _ = Suite(&PublishedStorageSuite{})
const testContainerPrefix = "aptlytest-"
func randContainer() string {
return testContainerPrefix + randString(32-len(testContainerPrefix))
}
func randString(n int) string {
if n <= 0 {
panic("negative number")
}
const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return string(bytes)
}
func (s *PublishedStorageSuite) SetUpSuite(c *C) {
s.accountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
if s.accountName == "" {
println("Please set the following two environment variables to run the Azure storage tests.")
println(" 1. AZURE_STORAGE_ACCOUNT")
println(" 2. AZURE_STORAGE_ACCESS_KEY")
c.Skip("AZURE_STORAGE_ACCOUNT not set.")
}
s.accountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
if s.accountKey == "" {
println("Please set the following two environment variables to run the Azure storage tests.")
println(" 1. AZURE_STORAGE_ACCOUNT")
println(" 2. AZURE_STORAGE_ACCESS_KEY")
c.Skip("AZURE_STORAGE_ACCESS_KEY not set.")
}
s.endpoint = os.Getenv("AZURE_STORAGE_ENDPOINT")
}
func (s *PublishedStorageSuite) SetUpTest(c *C) {
container := randContainer()
prefix := "lala"
var err error
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
c.Assert(err, IsNil)
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)
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) TearDownTest(c *C) {
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 {
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)
body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
data, err := ioutil.ReadAll(body)
c.Assert(err, IsNil)
return data
}
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
_, err := s.storage.az.container.NewBlobURL(path).GetProperties(
context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
c.Assert(err, NotNil)
storageError, ok := err.(azblob.StorageError)
c.Assert(ok, Equals, true)
c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
}
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
hash := md5.Sum(data)
_, err := azblob.UploadBufferToBlockBlob(
context.Background(),
data,
s.storage.az.container.NewBlockBlobURL(path),
azblob.UploadToBlockBlobOptions{
BlobHTTPHeaders: azblob.BlobHTTPHeaders{
ContentMD5: hash[:],
},
})
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) TestPutFile(c *C) {
content := []byte("Welcome to Azure!")
filename := "a/b.txt"
dir := c.MkDir()
err := ioutil.WriteFile(filepath.Join(dir, "a"), content, 0644)
c.Assert(err, IsNil)
err = s.storage.PutFile(filename, filepath.Join(dir, "a"))
c.Check(err, IsNil)
c.Check(s.GetFile(c, filename), DeepEquals, content)
err = s.prefixedStorage.PutFile(filename, filepath.Join(dir, "a"))
c.Check(err, IsNil)
c.Check(s.GetFile(c, filepath.Join(s.prefixedStorage.az.prefix, filename)), DeepEquals, content)
}
func (s *PublishedStorageSuite) TestPutFilePlus(c *C) {
content := []byte("Welcome to Azure!")
filename := "a/b+c.txt"
dir := c.MkDir()
err := ioutil.WriteFile(filepath.Join(dir, "a"), content, 0644)
c.Assert(err, IsNil)
err = s.storage.PutFile(filename, filepath.Join(dir, "a"))
c.Check(err, IsNil)
c.Check(s.GetFile(c, filename), DeepEquals, content)
s.AssertNoFile(c, "a/b c.txt")
}
func (s *PublishedStorageSuite) TestFilelist(c *C) {
paths := []string{"a", "b", "c", "testa", "test/a", "test/b", "lala/a", "lala/b", "lala/c"}
for _, path := range paths {
s.PutFile(c, path, []byte("test"))
}
list, err := s.storage.Filelist("")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a", "lala/b", "lala/c", "test/a", "test/b", "testa"})
list, err = s.storage.Filelist("test")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a", "b"})
list, err = s.storage.Filelist("test2")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{})
list, err = s.prefixedStorage.Filelist("")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a", "b", "c"})
}
func (s *PublishedStorageSuite) TestFilelistPlus(c *C) {
paths := []string{"a", "b", "c", "testa", "test/a+1", "test/a 1", "lala/a+b", "lala/a b", "lala/c"}
for _, path := range paths {
s.PutFile(c, path, []byte("test"))
}
list, err := s.storage.Filelist("")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a b", "lala/a+b", "lala/c", "test/a 1", "test/a+1", "testa"})
list, err = s.storage.Filelist("test")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a 1", "a+1"})
list, err = s.storage.Filelist("test2")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{})
list, err = s.prefixedStorage.Filelist("")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a b", "a+b", "c"})
}
func (s *PublishedStorageSuite) TestRemove(c *C) {
s.PutFile(c, "a/b", []byte("test"))
err := s.storage.Remove("a/b")
c.Check(err, IsNil)
s.AssertNoFile(c, "a/b")
s.PutFile(c, "lala/xyz", []byte("test"))
err = s.prefixedStorage.Remove("xyz")
c.Check(err, IsNil)
s.AssertNoFile(c, "lala/xyz")
}
func (s *PublishedStorageSuite) TestRemovePlus(c *C) {
s.PutFile(c, "a/b+c", []byte("test"))
s.PutFile(c, "a/b", []byte("test"))
err := s.storage.Remove("a/b+c")
c.Check(err, IsNil)
s.AssertNoFile(c, "a/b+c")
s.AssertNoFile(c, "a/b c")
err = s.storage.Remove("a/b")
c.Check(err, IsNil)
s.AssertNoFile(c, "a/b")
}
func (s *PublishedStorageSuite) TestRemoveDirs(c *C) {
paths := []string{"a", "b", "c", "testa", "test/a", "test/b", "lala/a", "lala/b", "lala/c"}
for _, path := range paths {
s.PutFile(c, path, []byte("test"))
}
err := s.storage.RemoveDirs("test", nil)
c.Check(err, IsNil)
list, err := s.storage.Filelist("")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a", "lala/b", "lala/c", "testa"})
}
func (s *PublishedStorageSuite) TestRemoveDirsPlus(c *C) {
paths := []string{"a", "b", "c", "testa", "test/a+1", "test/a 1", "lala/a+b", "lala/a b", "lala/c"}
for _, path := range paths {
s.PutFile(c, path, []byte("test"))
}
err := s.storage.RemoveDirs("test", nil)
c.Check(err, IsNil)
list, err := s.storage.Filelist("")
c.Check(err, IsNil)
c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a b", "lala/a+b", "lala/c", "testa"})
}
func (s *PublishedStorageSuite) TestRenameFile(c *C) {
dir := c.MkDir()
err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("Welcome to Azure!"), 0644)
c.Assert(err, IsNil)
err = s.storage.PutFile("source.txt", filepath.Join(dir, "a"))
c.Check(err, IsNil)
err = s.storage.RenameFile("source.txt", "dest.txt")
c.Check(err, IsNil)
c.Check(s.GetFile(c, "dest.txt"), DeepEquals, []byte("Welcome to Azure!"))
exists, err := s.storage.FileExists("source.txt")
c.Check(err, IsNil)
c.Check(exists, Equals, false)
}
func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
root := c.MkDir()
pool := files.NewPackagePool(root, false)
cs := files.NewMockChecksumStorage()
tmpFile1 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
err := ioutil.WriteFile(tmpFile1, []byte("Contents"), 0644)
c.Assert(err, IsNil)
cksum1 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}
tmpFile2 := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
err = ioutil.WriteFile(tmpFile2, []byte("Spam"), 0644)
c.Assert(err, IsNil)
cksum2 := utils.ChecksumInfo{MD5: "e9dfd31cc505d51fc26975250750deab"}
tmpFile3 := filepath.Join(c.MkDir(), "netboot/boot.img.gz")
os.MkdirAll(filepath.Dir(tmpFile3), 0777)
err = ioutil.WriteFile(tmpFile3, []byte("Contents"), 0644)
c.Assert(err, IsNil)
cksum3 := utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}
src1, err := pool.Import(tmpFile1, "mars-invaders_1.03.deb", &cksum1, true, cs)
c.Assert(err, IsNil)
src2, err := pool.Import(tmpFile2, "mars-invaders_1.03.deb", &cksum2, true, cs)
c.Assert(err, IsNil)
src3, err := pool.Import(tmpFile3, "netboot/boot.img.gz", &cksum3, true, cs)
c.Assert(err, IsNil)
// first link from pool
err = s.storage.LinkFromPool("", filepath.Join("", "pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
c.Check(err, IsNil)
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
// duplicate link from pool
err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
c.Check(err, IsNil)
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
// link from pool with conflict
err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, false)
c.Check(err, ErrorMatches, ".*file already exists and is different.*")
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
// link from pool with conflict and force
err = s.storage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src2, cksum2, true)
c.Check(err, IsNil)
c.Check(s.GetFile(c, "pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Spam"))
// for prefixed storage:
// first link from pool
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, src1, cksum1, false)
c.Check(err, IsNil)
// 2nd link from pool, providing wrong path for source file
//
// 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)
c.Check(s.GetFile(c, "lala/pool/main/m/mars-invaders/mars-invaders_1.03.deb"), DeepEquals, []byte("Contents"))
// link from pool with nested file name
err = s.storage.LinkFromPool("", "dists/jessie/non-free/installer-i386/current/images", "netboot/boot.img.gz", pool, src3, cksum3, false)
c.Check(err, IsNil)
c.Check(s.GetFile(c, "dists/jessie/non-free/installer-i386/current/images/netboot/boot.img.gz"), DeepEquals, []byte("Contents"))
}
func (s *PublishedStorageSuite) TestSymLink(c *C) {
s.PutFile(c, "a/b", []byte("test"))
err := s.storage.SymLink("a/b", "a/b.link")
c.Check(err, IsNil)
var link string
link, err = s.storage.ReadLink("a/b.link")
c.Check(err, IsNil)
c.Check(link, Equals, "a/b")
c.Skip("copy not available in azure test")
}
func (s *PublishedStorageSuite) TestFileExists(c *C) {
s.PutFile(c, "a/b", []byte("test"))
exists, err := s.storage.FileExists("a/b")
c.Check(err, IsNil)
c.Check(exists, Equals, true)
exists, _ = s.storage.FileExists("a/b.invalid")
c.Check(err, IsNil)
c.Check(exists, Equals, false)
}
+15
View File
@@ -0,0 +1,15 @@
package cmd
import (
"github.com/smira/commander"
)
func makeCmdAPI() *commander.Command {
return &commander.Command{
UsageLine: "api",
Short: "start API server/issue requests",
Subcommands: []*commander.Command{
makeCmdAPIServe(),
},
}
}
+124
View File
@@ -0,0 +1,124 @@
package cmd
import (
stdcontext "context"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"github.com/aptly-dev/aptly/api"
"github.com/aptly-dev/aptly/systemd/activation"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyAPIServe(cmd *commander.Command, args []string) error {
var (
err error
)
if len(args) != 0 {
cmd.Usage()
return commander.ErrCommandError
}
// There are only two working options for aptly's rootDir:
// 1. rootDir does not exist, then we'll create it
// 2. rootDir exists and is writable
// anything else must fail.
// E.g.: Running the service under a different user may lead to a rootDir
// that exists but is not usable due to access permissions.
err = utils.DirIsAccessible(context.Config().GetRootDir())
if err != nil {
return err
}
// Try to recycle systemd fds for listening
listeners, err := activation.Listeners(true)
if len(listeners) > 1 {
panic("Got more than 1 listener from systemd. This is currently not supported!")
}
if err == nil && len(listeners) == 1 {
listener := listeners[0]
defer func() { _ = listener.Close() }()
fmt.Printf("\nTaking over web server at: %s (press Ctrl+C to quit)...\n", listener.Addr().String())
err = http.Serve(listener, api.Router(context))
if err != nil {
return fmt.Errorf("unable to serve: %s", err)
}
return nil
}
// If there are none: use the listen argument.
listen := context.Flags().Lookup("listen").Value.String()
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
server := http.Server{Handler: api.Router(context)}
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
go (func() {
if _, ok := <-sigchan; ok {
fmt.Printf("\nShutdown signal received, waiting for background tasks...\n")
context.TaskList().Wait()
_ = server.Shutdown(stdcontext.Background())
}
})()
defer close(sigchan)
listenURL, err := url.Parse(listen)
if err == nil && listenURL.Scheme == "unix" {
file := listenURL.Path
_ = os.Remove(file)
var listener net.Listener
listener, err = net.Listen("unix", file)
if err != nil {
return fmt.Errorf("failed to listen on: %s\n%s", file, err)
}
defer func() { _ = listener.Close() }()
err = server.Serve(listener)
} else {
server.Addr = listen
err = server.ListenAndServe()
}
if err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("unable to serve: %s", err)
}
return nil
}
func makeCmdAPIServe() *commander.Command {
cmd := &commander.Command{
Run: aptlyAPIServe,
UsageLine: "serve",
Short: "start API HTTP service",
Long: `
Start HTTP server with aptly REST API. The server can listen to either a port
or Unix domain socket. When using a socket, Aptly will fully manage the socket
file. This command also supports taking over from a systemd file descriptors to
enable systemd socket activation.
Example:
$ aptly api serve -listen=:8080
$ aptly api serve -listen=unix:///tmp/aptly.sock
`,
Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError),
}
cmd.Flag.String("listen", ":8080", "host:port for HTTP listening or unix://path to listen on a Unix domain socket")
cmd.Flag.Bool("no-lock", false, "don't lock the database")
return cmd
}
+68 -18
View File
@@ -2,35 +2,78 @@
package cmd
import (
"bytes"
"fmt"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/deb"
"os"
"text/template"
"time"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
"os"
"time"
)
// Various command flags/UI things
const (
Yes = "yes"
No = "no"
)
// ListPackagesRefList shows list of packages in PackageRefList
func ListPackagesRefList(reflist *deb.PackageRefList) (err error) {
func ListPackagesRefList(reflist *deb.PackageRefList, collectionFactory *deb.CollectionFactory) (err error) {
fmt.Printf("Packages:\n")
if reflist == nil {
return
}
err = reflist.ForEach(func(key []byte) error {
p, err2 := context.CollectionFactory().PackageCollection().ByKey(key)
if err2 != nil {
return err2
}
fmt.Printf(" %s\n", p)
return nil
})
list, err := deb.NewPackageListFromRefList(reflist, collectionFactory.PackageCollection(), context.Progress())
if err != nil {
return fmt.Errorf("unable to load packages: %s", err)
}
return PrintPackageList(list, "", " ")
}
// PrintPackageList shows package list with specified format or default representation
func PrintPackageList(result *deb.PackageList, format, prefix string) error {
result.PrepareIndex()
if format == "" {
return result.ForEachIndexed(func(p *deb.Package) error {
context.Progress().Printf(prefix+"%s\n", p)
return nil
})
}
formatTemplate, err := template.New("format").Parse(format)
if err != nil {
return fmt.Errorf("error parsing -format template: %s", err)
}
return result.ForEachIndexed(func(p *deb.Package) error {
b := &bytes.Buffer{}
err = formatTemplate.Execute(b, p.ExtendedStanza())
if err != nil {
return fmt.Errorf("error applying template: %s", err)
}
context.Progress().Printf(prefix+"%s\n", b.String())
return nil
})
}
// LookupOption checks boolean flag with default (usually config) and command-line
// setting
func LookupOption(defaultValue bool, flags *flag.FlagSet, name string) (result bool) {
result = defaultValue
if flags.IsSet(name) {
result = flags.Lookup(name).Value.Get().(bool)
}
return
}
@@ -46,30 +89,37 @@ upgrade individual packages, take snapshots and publish them
back as Debian repositories.
aptly's goal is to establish repeatability and controlled changes
in a package-centric environment. aptly allows to fix a set of packages
in a package-centric environment. aptly allows one to fix a set of packages
in a repository, so that package installation and upgrade becomes
deterministic. At the same time aptly allows to perform controlled,
deterministic. At the same time aptly allows one to perform controlled,
fine-grained changes in repository contents to transition your
package environment to new version.`,
Flag: *flag.NewFlagSet("aptly", flag.ExitOnError),
Subcommands: []*commander.Command{
makeCmdDb(),
makeCmdConfig(),
makeCmdDB(),
makeCmdGraph(),
makeCmdMirror(),
makeCmdRepo(),
makeCmdServe(),
makeCmdSnapshot(),
makeCmdTask(),
makeCmdPublish(),
makeCmdVersion(),
makeCmdPackage(),
makeCmdAPI(),
},
}
cmd.Flag.Int("db-open-attempts", 10, "number of attempts to open DB if it's locked by other instance")
cmd.Flag.Bool("dep-follow-suggests", false, "when processing dependencies, follow Suggests")
cmd.Flag.Bool("dep-follow-source", false, "when processing dependencies, follow from binary to Source packages")
cmd.Flag.Bool("dep-follow-recommends", false, "when processing dependencies, follow Recommends")
cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if depdency is 'a|b'")
cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if dependency is 'a|b'")
cmd.Flag.Bool("dep-verbose-resolve", false, "when processing dependencies, print detailed logs")
cmd.Flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available")
cmd.Flag.String("config", "", "location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf)")
cmd.Flag.String("config", "", "location of configuration file (default locations in order: ~/.aptly.conf, /usr/local/etc/aptly.conf, /etc/aptly.conf)")
cmd.Flag.String("gpg-provider", "", "PGP implementation (\"gpg\", \"gpg1\", \"gpg2\" for external gpg or \"internal\" for Go internal implementation)")
if aptly.EnableDebug {
cmd.Flag.String("cpuprofile", "", "write cpu profile to file")
+15
View File
@@ -0,0 +1,15 @@
package cmd
import (
"github.com/smira/commander"
)
func makeCmdConfig() *commander.Command {
return &commander.Command{
UsageLine: "config",
Short: "manage aptly configuration",
Subcommands: []*commander.Command{
makeCmdConfigShow(),
},
}
}
+51
View File
@@ -0,0 +1,51 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/smira/commander"
yaml "gopkg.in/yaml.v3"
)
func aptlyConfigShow(_ *commander.Command, _ []string) error {
showYaml := context.Flags().Lookup("yaml").Value.Get().(bool)
config := context.Config()
if showYaml {
yamlData, err := yaml.Marshal(&config)
if err != nil {
return fmt.Errorf("error marshaling to YAML: %s", err)
}
fmt.Println(string(yamlData))
} else {
prettyJSON, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("unable to dump the config file: %s", err)
}
fmt.Println(string(prettyJSON))
}
return nil
}
func makeCmdConfigShow() *commander.Command {
cmd := &commander.Command{
Run: aptlyConfigShow,
UsageLine: "show",
Short: "show current aptly's config",
Long: `
Command show displays the current aptly configuration.
Example:
$ aptly config show
`,
}
cmd.Flag.Bool("yaml", false, "show yaml config")
return cmd
}
+18 -263
View File
@@ -1,281 +1,36 @@
package cmd
import (
"fmt"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/console"
"github.com/smira/aptly/database"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/files"
"github.com/smira/aptly/http"
"github.com/smira/aptly/utils"
ctx "github.com/aptly-dev/aptly/context"
"github.com/smira/flag"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"time"
)
// AptlyContext is a common context shared by all commands
type AptlyContext struct {
flags *flag.FlagSet
configLoaded bool
progress aptly.Progress
downloader aptly.Downloader
database database.Storage
packagePool aptly.PackagePool
publishedStorage aptly.PublishedStorage
collectionFactory *deb.CollectionFactory
dependencyOptions int
architecturesList []string
// Debug features
fileCPUProfile *os.File
fileMemProfile *os.File
fileMemStats *os.File
}
var context *AptlyContext
// FatalError is type for panicking to abort execution with non-zero
// exit code and print meaningful explanation
type FatalError struct {
ReturnCode int
Message string
}
// Fatal panics and aborts execution with exit code 1
func Fatal(err error) {
panic(&FatalError{ReturnCode: 1, Message: err.Error()})
}
// Config loads and returns current configuration
func (context *AptlyContext) Config() *utils.ConfigStructure {
if !context.configLoaded {
var err error
configLocation := context.flags.Lookup("config").Value.String()
if configLocation != "" {
err = utils.LoadConfig(configLocation, &utils.Config)
if err != nil {
Fatal(err)
}
} else {
configLocations := []string{
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
"/etc/aptly.conf",
}
for _, configLocation := range configLocations {
err = utils.LoadConfig(configLocation, &utils.Config)
if err == nil {
break
}
if !os.IsNotExist(err) {
Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err))
}
}
if err != nil {
fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0])
utils.SaveConfig(configLocations[0], &utils.Config)
}
}
context.configLoaded = true
}
return &utils.Config
}
// DependencyOptions calculates options related to dependecy handling
func (context *AptlyContext) DependencyOptions() int {
if context.dependencyOptions == -1 {
context.dependencyOptions = 0
if context.Config().DepFollowSuggests || context.flags.Lookup("dep-follow-suggests").Value.Get().(bool) {
context.dependencyOptions |= deb.DepFollowSuggests
}
if context.Config().DepFollowRecommends || context.flags.Lookup("dep-follow-recommends").Value.Get().(bool) {
context.dependencyOptions |= deb.DepFollowRecommends
}
if context.Config().DepFollowAllVariants || context.flags.Lookup("dep-follow-all-variants").Value.Get().(bool) {
context.dependencyOptions |= deb.DepFollowAllVariants
}
if context.Config().DepFollowSource || context.flags.Lookup("dep-follow-source").Value.Get().(bool) {
context.dependencyOptions |= deb.DepFollowSource
}
}
return context.dependencyOptions
}
// ArchitecturesList returns list of architectures fixed via command line or config
func (context *AptlyContext) ArchitecturesList() []string {
if context.architecturesList == nil {
context.architecturesList = context.Config().Architectures
optionArchitectures := context.flags.Lookup("architectures").Value.String()
if optionArchitectures != "" {
context.architecturesList = strings.Split(optionArchitectures, ",")
}
}
return context.architecturesList
}
// Progress creates or returns Progress object
func (context *AptlyContext) Progress() aptly.Progress {
if context.progress == nil {
context.progress = console.NewProgress()
context.progress.Start()
}
return context.progress
}
// Downloader returns instance of current downloader
func (context *AptlyContext) Downloader() aptly.Downloader {
if context.downloader == nil {
context.downloader = http.NewDownloader(context.Config().DownloadConcurrency, context.Progress())
}
return context.downloader
}
// DBPath builds path to database
func (context *AptlyContext) DBPath() string {
return filepath.Join(context.Config().RootDir, "db")
}
// Database opens and returns current instance of database
func (context *AptlyContext) Database() (database.Storage, error) {
if context.database == nil {
var err error
context.database, err = database.OpenDB(context.DBPath())
if err != nil {
return nil, fmt.Errorf("can't open database: %s", err)
}
}
return context.database, nil
}
// CollectionFactory builds factory producing all kinds of collections
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
if context.collectionFactory == nil {
db, err := context.Database()
if err != nil {
Fatal(err)
}
context.collectionFactory = deb.NewCollectionFactory(db)
}
return context.collectionFactory
}
// PackagePool returns instance of PackagePool
func (context *AptlyContext) PackagePool() aptly.PackagePool {
if context.packagePool == nil {
context.packagePool = files.NewPackagePool(context.Config().RootDir)
}
return context.packagePool
}
// PublishedStorage returns instance of PublishedStorage
func (context *AptlyContext) PublishedStorage() aptly.PublishedStorage {
if context.publishedStorage == nil {
context.publishedStorage = files.NewPublishedStorage(context.Config().RootDir)
}
return context.publishedStorage
}
var context *ctx.AptlyContext
// ShutdownContext shuts context down
func ShutdownContext() {
if aptly.EnableDebug {
if context.fileMemProfile != nil {
pprof.WriteHeapProfile(context.fileMemProfile)
context.fileMemProfile.Close()
context.fileMemProfile = nil
}
if context.fileCPUProfile != nil {
pprof.StopCPUProfile()
context.fileCPUProfile.Close()
context.fileCPUProfile = nil
}
if context.fileMemProfile != nil {
context.fileMemProfile.Close()
context.fileMemProfile = nil
}
}
if context.database != nil {
context.database.Close()
}
if context.downloader != nil {
context.downloader.Shutdown()
}
if context.progress != nil {
context.progress.Shutdown()
}
context.Shutdown()
}
// CleanupContext does partial shutdown of context
func CleanupContext() {
context.Cleanup()
}
// InitContext initializes context with default settings
func InitContext(flags *flag.FlagSet) error {
var err error
context = &AptlyContext{flags: flags, dependencyOptions: -1}
if aptly.EnableDebug {
cpuprofile := flags.Lookup("cpuprofile").Value.String()
if cpuprofile != "" {
context.fileCPUProfile, err = os.Create(cpuprofile)
if err != nil {
return err
}
pprof.StartCPUProfile(context.fileCPUProfile)
}
memprofile := flags.Lookup("memprofile").Value.String()
if memprofile != "" {
context.fileMemProfile, err = os.Create(memprofile)
if err != nil {
return err
}
}
memstats := flags.Lookup("memstats").Value.String()
if memstats != "" {
interval := flags.Lookup("meminterval").Value.Get().(time.Duration)
context.fileMemStats, err = os.Create(memstats)
if err != nil {
return err
}
context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
go func() {
var stats runtime.MemStats
start := time.Now().UnixNano()
for {
runtime.ReadMemStats(&stats)
if context.fileMemStats != nil {
context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
time.Sleep(interval)
} else {
break
}
}
}()
}
if context != nil {
panic("context already initialized")
}
return nil
context, err = ctx.NewContext(flags)
return err
}
// GetContext gives access to the context
func GetContext() *ctx.AptlyContext {
return context
}
+3 -3
View File
@@ -4,13 +4,13 @@ import (
"github.com/smira/commander"
)
func makeCmdDb() *commander.Command {
func makeCmdDB() *commander.Command {
return &commander.Command{
UsageLine: "db",
Short: "manage aptly's internal database and package pool",
Subcommands: []*commander.Command{
makeCmdDbCleanup(),
makeCmdDbRecover(),
makeCmdDBCleanup(),
makeCmdDBRecover(),
},
}
}
+220 -63
View File
@@ -2,46 +2,165 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/utils"
"github.com/smira/commander"
"sort"
"strings"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
)
// aptly db cleanup
func aptlyDbCleanup(cmd *commander.Command, args []string) error {
func aptlyDBCleanup(cmd *commander.Command, args []string) error {
var err error
if len(args) != 0 {
cmd.Usage()
return err
return commander.ErrCommandError
}
verbose := context.Flags().Lookup("verbose").Value.Get().(bool)
dryRun := context.Flags().Lookup("dry-run").Value.Get().(bool)
collectionFactory := context.NewCollectionFactory()
// collect information about references packages...
existingPackageRefs := deb.NewPackageRefList()
referencedAppStreamFiles := []string{}
context.Progress().Printf("Loading mirrors, local repos and snapshots...\n")
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
if err != nil {
return err
// used only in verbose mode to report package use source
packageRefSources := map[string][]string{}
context.Progress().ColoredPrintf("@{w!}Loading mirrors, local repos, snapshots and published repos...@|")
if verbose {
context.Progress().ColoredPrintf("@{y}Loading mirrors:@|")
}
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
if verbose {
context.Progress().ColoredPrintf("- @{g}%s@|", repo.Name)
}
e := collectionFactory.RemoteRepoCollection().LoadComplete(repo)
if e != nil {
return e
}
if repo.RefList() != nil {
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false)
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
if verbose {
description := fmt.Sprintf("mirror %s", repo.Name)
_ = repo.RefList().ForEach(func(key []byte) error {
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
return nil
})
}
}
for _, poolPath := range repo.AppStreamFiles {
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
}
return nil
})
if err != nil {
return err
}
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
if err != nil {
return err
collectionFactory.Flush()
if verbose {
context.Progress().ColoredPrintf("@{y}Loading local repos:@|")
}
err = collectionFactory.LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
if verbose {
context.Progress().ColoredPrintf("- @{g}%s@|", repo.Name)
}
e := collectionFactory.LocalRepoCollection().LoadComplete(repo)
if e != nil {
return e
}
if repo.RefList() != nil {
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false)
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
if verbose {
description := fmt.Sprintf("local repo %s", repo.Name)
_ = repo.RefList().ForEach(func(key []byte) error {
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
return nil
})
}
}
return nil
})
if err != nil {
return err
}
collectionFactory.Flush()
if verbose {
context.Progress().ColoredPrintf("@{y}Loading snapshots:@|")
}
err = collectionFactory.SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
if verbose {
context.Progress().ColoredPrintf("- @{g}%s@|", snapshot.Name)
}
e := collectionFactory.SnapshotCollection().LoadComplete(snapshot)
if e != nil {
return e
}
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false, true)
if verbose {
description := fmt.Sprintf("snapshot %s", snapshot.Name)
_ = snapshot.RefList().ForEach(func(key []byte) error {
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
return nil
})
}
for _, poolPath := range snapshot.AppStreamFiles {
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
}
return nil
})
if err != nil {
return err
}
collectionFactory.Flush()
if verbose {
context.Progress().ColoredPrintf("@{y}Loading published repositories:@|")
}
err = collectionFactory.PublishedRepoCollection().ForEach(func(published *deb.PublishedRepo) error {
if verbose {
context.Progress().ColoredPrintf("- @{g}%s:%s/%s{|}", published.Storage, published.Prefix, published.Distribution)
}
if published.SourceKind != deb.SourceLocalRepo {
return nil
}
e := collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if e != nil {
return e
}
for _, component := range published.Components() {
existingPackageRefs = existingPackageRefs.Merge(published.RefList(component), false, true)
if verbose {
description := fmt.Sprintf("published repository %s:%s/%s component %s",
published.Storage, published.Prefix, published.Distribution, component)
_ = published.RefList(component).ForEach(func(key []byte) error {
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
return nil
})
}
}
return nil
})
@@ -49,51 +168,70 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
return err
}
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return err
}
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false)
return nil
})
if err != nil {
return err
}
collectionFactory.Flush()
// ... and compare it to the list of all packages
context.Progress().Printf("Loading list of all packages...\n")
allPackageRefs := context.CollectionFactory().PackageCollection().AllPackageRefs()
context.Progress().ColoredPrintf("@{w!}Loading list of all packages...@|")
allPackageRefs := collectionFactory.PackageCollection().AllPackageRefs()
toDelete := allPackageRefs.Substract(existingPackageRefs)
toDelete := allPackageRefs.Subtract(existingPackageRefs)
// delete packages that are no longer referenced
context.Progress().Printf("Deleting unreferenced packages (%d)...\n", toDelete.Len())
context.Progress().ColoredPrintf("@{r!}Deleting unreferenced packages (%d)...@|", toDelete.Len())
// database can't err as collection factory already constructed
db, _ := context.Database()
db.StartBatch()
err = toDelete.ForEach(func(ref []byte) error {
return context.CollectionFactory().PackageCollection().DeleteByKey(ref)
})
if err != nil {
return err
if toDelete.Len() > 0 {
if verbose {
context.Progress().ColoredPrintf("@{r}List of package keys to delete:@|")
err = toDelete.ForEach(func(ref []byte) error {
context.Progress().ColoredPrintf(" - @{r}%s@|", string(ref))
return nil
})
if err != nil {
return err
}
}
if !dryRun {
batch := db.CreateBatch()
err = toDelete.ForEach(func(ref []byte) error {
return collectionFactory.PackageCollection().DeleteByKey(ref, batch)
})
if err != nil {
return fmt.Errorf("unable to delete by key: %s", err)
}
err = batch.Write()
if err != nil {
return fmt.Errorf("unable to write to DB: %s", err)
}
} else {
context.Progress().ColoredPrintf("@{y!}Skipped deletion, as -dry-run has been requested.@|")
}
}
err = db.FinishBatch()
if err != nil {
return fmt.Errorf("unable to write to DB: %s", err)
}
collectionFactory.Flush()
// now, build a list of files that should be present in Repository (package pool)
context.Progress().Printf("Building list of files referenced by packages...\n")
context.Progress().ColoredPrintf("@{w!}Building list of files referenced by packages...@|")
referencedFiles := make([]string, 0, existingPackageRefs.Len())
context.Progress().InitBar(int64(existingPackageRefs.Len()), false)
context.Progress().InitBar(int64(existingPackageRefs.Len()), false, aptly.BarCleanupBuildList)
err = existingPackageRefs.ForEach(func(key []byte) error {
pkg, err2 := context.CollectionFactory().PackageCollection().ByKey(key)
pkg, err2 := collectionFactory.PackageCollection().ByKey(key)
if err2 != nil {
return err2
tail := ""
if verbose {
tail = fmt.Sprintf(" (sources: %s)", strings.Join(packageRefSources[string(key)], ", "))
}
if dryRun {
context.Progress().ColoredPrintf("@{r!}Unresolvable package reference, skipping (-dry-run): %s: %s%s",
string(key), err2, tail)
return nil
}
return fmt.Errorf("unable to load package %s: %s%s", string(key), err2, tail)
}
paths, err2 := pkg.FilepathList(context.PackagePool())
if err2 != nil {
@@ -108,11 +246,12 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
return err
}
referencedFiles = append(referencedFiles, referencedAppStreamFiles...)
sort.Strings(referencedFiles)
context.Progress().ShutdownBar()
// build a list of files in the package pool
context.Progress().Printf("Building list of files in package pool...\n")
context.Progress().ColoredPrintf("@{w!}Building list of files in package pool...@|")
existingFiles, err := context.PackagePool().FilepathList(context.Progress())
if err != nil {
return fmt.Errorf("unable to collect file paths: %s", err)
@@ -122,35 +261,50 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
filesToDelete := utils.StrSlicesSubstract(existingFiles, referencedFiles)
// delete files that are no longer referenced
context.Progress().Printf("Deleting unreferenced files (%d)...\n", len(filesToDelete))
context.Progress().ColoredPrintf("@{r!}Deleting unreferenced files (%d)...@|", len(filesToDelete))
if len(filesToDelete) > 0 {
context.Progress().InitBar(int64(len(filesToDelete)), false)
var size, totalSize int64
for _, file := range filesToDelete {
size, err = context.PackagePool().Remove(file)
if err != nil {
return err
if verbose {
context.Progress().ColoredPrintf("@{r}List of files to be deleted:@|")
for _, file := range filesToDelete {
context.Progress().ColoredPrintf(" - @{r}%s@|", file)
}
context.Progress().AddBar(1)
totalSize += size
}
context.Progress().ShutdownBar()
context.Progress().Printf("Disk space freed: %s...\n", utils.HumanBytes(totalSize))
if !dryRun {
context.Progress().InitBar(int64(len(filesToDelete)), false, aptly.BarCleanupDeleteUnreferencedFiles)
var size, totalSize int64
for _, file := range filesToDelete {
size, err = context.PackagePool().Remove(file)
if err != nil {
return err
}
context.Progress().AddBar(1)
totalSize += size
}
context.Progress().ShutdownBar()
context.Progress().ColoredPrintf("@{w!}Disk space freed: %s...@|", utils.HumanBytes(totalSize))
} else {
context.Progress().ColoredPrintf("@{y!}Skipped file deletion, as -dry-run has been requested.@|")
}
}
context.Progress().Printf("Compacting database...\n")
err = db.CompactDB()
if !dryRun {
context.Progress().ColoredPrintf("@{w!}Compacting database...@|")
err = db.CompactDB()
} else {
context.Progress().ColoredPrintf("@{y!}Skipped DB compaction, as -dry-run has been requested.@|")
}
return err
}
func makeCmdDbCleanup() *commander.Command {
func makeCmdDBCleanup() *commander.Command {
cmd := &commander.Command{
Run: aptlyDbCleanup,
Run: aptlyDBCleanup,
UsageLine: "cleanup",
Short: "cleanup DB and package pool",
Long: `
@@ -163,5 +317,8 @@ Example:
`,
}
cmd.Flag.Bool("verbose", false, "be verbose when loading objects/removing them")
cmd.Flag.Bool("dry-run", false, "don't delete anything")
return cmd
}
+48 -6
View File
@@ -1,28 +1,37 @@
package cmd
import (
"github.com/smira/aptly/database"
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/aptly-dev/aptly/database/goleveldb"
)
// aptly db recover
func aptlyDbRecover(cmd *commander.Command, args []string) error {
func aptlyDBRecover(cmd *commander.Command, args []string) error {
var err error
if len(args) != 0 {
cmd.Usage()
return err
return commander.ErrCommandError
}
context.Progress().Printf("Recovering database...\n")
err = database.RecoverDB(context.DBPath())
if err = goleveldb.RecoverDB(context.DBPath()); err != nil {
return err
}
context.Progress().Printf("Checking database integrity...\n")
err = checkIntegrity()
return err
}
func makeCmdDbRecover() *commander.Command {
func makeCmdDBRecover() *commander.Command {
cmd := &commander.Command{
Run: aptlyDbRecover,
Run: aptlyDBRecover,
UsageLine: "recover",
Short: "recover DB after crash",
Long: `
@@ -37,3 +46,36 @@ Example:
return cmd
}
func checkIntegrity() error {
return context.NewCollectionFactory().LocalRepoCollection().ForEach(checkRepo)
}
func checkRepo(repo *deb.LocalRepo) error {
collectionFactory := context.NewCollectionFactory()
repos := collectionFactory.LocalRepoCollection()
err := repos.LoadComplete(repo)
if err != nil {
return fmt.Errorf("load complete repo %q: %s", repo.Name, err)
}
dangling, err := deb.FindDanglingReferences(repo.RefList(), collectionFactory.PackageCollection())
if err != nil {
return fmt.Errorf("find dangling references: %w", err)
}
if len(dangling.Refs) > 0 {
for _, ref := range dangling.Refs {
context.Progress().Printf("Removing dangling database reference %q\n", ref)
}
repo.UpdateRefList(repo.RefList().Subtract(dangling))
if err = repos.Update(repo); err != nil {
return fmt.Errorf("update repo: %w", err)
}
}
return nil
}
+60 -125
View File
@@ -2,149 +2,55 @@ package cmd
import (
"bytes"
"code.google.com/p/gographviz"
"fmt"
"github.com/smira/aptly/deb"
"github.com/smira/commander"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
func graphvizEscape(s string) string {
return fmt.Sprintf("\"%s\"", strings.Replace(s, "\"", "\\\"", 0))
}
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
)
func aptlyGraph(cmd *commander.Command, args []string) error {
var err error
graph := gographviz.NewGraph()
graph.SetDir(true)
graph.SetName("aptly")
existingNodes := map[string]bool{}
fmt.Printf("Loading mirrors...\n")
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
if err != nil {
return err
}
graph.AddNode("aptly", graphvizEscape(repo.UUID), map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "darkgoldenrod1",
"label": graphvizEscape(fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}",
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "),
strings.Join(repo.Architectures, ", "), repo.NumPackages())),
})
existingNodes[repo.UUID] = true
return nil
})
if err != nil {
return err
if len(args) != 0 {
cmd.Usage()
return commander.ErrCommandError
}
fmt.Printf("Loading local repos...\n")
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
if err != nil {
return err
}
graph.AddNode("aptly", graphvizEscape(repo.UUID), map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "mediumseagreen",
"label": graphvizEscape(fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
repo.Name, repo.Comment, repo.NumPackages())),
})
existingNodes[repo.UUID] = true
return nil
})
if err != nil {
return err
}
fmt.Printf("Loading snapshots...\n")
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
existingNodes[snapshot.UUID] = true
return nil
})
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return err
}
description := snapshot.Description
if snapshot.SourceKind == "repo" {
description = "Snapshot from repo"
}
graph.AddNode("aptly", graphvizEscape(snapshot.UUID), map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "cadetblue1",
"label": graphvizEscape(fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages())),
})
if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
for _, uuid := range snapshot.SourceIDs {
_, exists := existingNodes[uuid]
if exists {
graph.AddEdge(graphvizEscape(uuid), "", graphvizEscape(snapshot.UUID), "", true, nil)
}
}
}
return nil
})
if err != nil {
return err
}
fmt.Printf("Loading published repos...\n")
context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
graph.AddNode("aptly", graphvizEscape(repo.UUID), map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "darkolivegreen1",
"label": graphvizEscape(fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, repo.Component, strings.Join(repo.Architectures, ", "))),
})
_, exists := existingNodes[repo.SourceUUID]
if exists {
graph.AddEdge(graphvizEscape(repo.SourceUUID), "", graphvizEscape(repo.UUID), "", true, nil)
}
return nil
})
layout := context.Flags().Lookup("layout").Value.String()
fmt.Printf("Generating graph...\n")
collectionFactory := context.NewCollectionFactory()
graph, err := deb.BuildGraph(collectionFactory, layout)
if err != nil {
return err
}
buf := bytes.NewBufferString(graph.String())
tempfile, err := ioutil.TempFile("", "aptly-graph")
tempfile, err := os.CreateTemp("", "aptly-graph")
if err != nil {
return err
}
tempfile.Close()
os.Remove(tempfile.Name())
_ = tempfile.Close()
_ = os.Remove(tempfile.Name())
tempfilename := tempfile.Name() + ".png"
format := context.Flags().Lookup("format").Value.String()
output := context.Flags().Lookup("output").Value.String()
command := exec.Command("dot", "-Tpng", "-o"+tempfilename)
if filepath.Ext(output) != "" {
format = filepath.Ext(output)[1:]
}
tempfilename := tempfile.Name() + "." + format
command := exec.Command("dot", "-T"+format, "-o"+tempfilename)
command.Stderr = os.Stderr
stdin, err := command.StdinPipe()
@@ -172,15 +78,40 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
return err
}
err = exec.Command("open", tempfilename).Run()
if err != nil {
fmt.Printf("Rendered to PNG file: %s\n", tempfilename)
err = nil
if output != "" {
err = utils.CopyFile(tempfilename, output)
if err != nil {
return fmt.Errorf("unable to copy %s -> %s: %s", tempfilename, output, err)
}
fmt.Printf("Output saved to %s\n", output)
_ = os.Remove(tempfilename)
} else {
command := getOpenCommand()
fmt.Printf("Displaying %s file: %s %s\n", format, command, tempfilename)
args := strings.Split(command, " ")
viewer := exec.Command(args[0], append(args[1:], tempfilename)...)
viewer.Stderr = os.Stderr
err = viewer.Start()
}
return err
}
// getOpenCommand tries to guess command to open image for OS
func getOpenCommand() string {
switch runtime.GOOS {
case "darwin":
return "/usr/bin/open"
case "windows":
return "cmd /c start"
default:
return "xdg-open"
}
}
func makeCmdGraph() *commander.Command {
cmd := &commander.Command{
Run: aptlyGraph,
@@ -197,5 +128,9 @@ Example:
`,
}
cmd.Flag.String("format", "png", "render graph to specified format (png, svg, pdf, etc.)")
cmd.Flag.String("output", "", "specify output filename, default is to open result in viewer")
cmd.Flag.String("layout", "horizontal", "create a more 'vertical' or a more 'horizontal' graph layout")
return cmd
}
+13 -9
View File
@@ -1,25 +1,26 @@
package cmd
import (
"github.com/smira/aptly/utils"
"strings"
"github.com/aptly-dev/aptly/pgp"
"github.com/smira/commander"
"github.com/smira/flag"
"strings"
)
func getVerifier(flags *flag.FlagSet) (utils.Verifier, error) {
if context.Config().GpgDisableVerify || flags.Lookup("ignore-signatures").Value.Get().(bool) {
return nil, nil
func getVerifier(flags *flag.FlagSet) (pgp.Verifier, error) {
keyRings := flags.Lookup("keyring").Value.Get().([]string)
ignoreSignatures := context.Config().GpgDisableVerify
if context.Flags().IsSet("ignore-signatures") {
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
}
keyRings := flags.Lookup("keyring").Value.Get().([]string)
verifier := &utils.GpgVerifier{}
verifier := context.GetVerifier()
for _, keyRing := range keyRings {
verifier.AddKeyring(keyRing)
}
err := verifier.InitKeyring()
err := verifier.InitKeyring(!ignoreSignatures) // be verbose only if verifying signatures is requested
if err != nil {
return nil, err
}
@@ -54,6 +55,9 @@ func makeCmdMirror() *commander.Command {
makeCmdMirrorShow(),
makeCmdMirrorDrop(),
makeCmdMirrorUpdate(),
makeCmdMirrorRename(),
makeCmdMirrorEdit(),
makeCmdMirrorSearch(),
},
}
}
+40 -9
View File
@@ -2,20 +2,29 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/smira/commander"
"github.com/smira/flag"
"strings"
)
func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
var err error
if !(len(args) == 2 && strings.HasPrefix(args[1], "ppa:") || len(args) >= 3) {
cmd.Usage()
return err
return commander.ErrCommandError
}
downloadSources := context.Config().DownloadSourcePackages || context.flags.Lookup("with-sources").Value.Get().(bool)
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
downloadAppStream := context.Flags().Lookup("with-appstream").Value.Get().(bool)
ignoreSignatures := context.Config().GpgDisableVerify
if context.Flags().IsSet("ignore-signatures") {
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
}
var (
mirrorName, archiveURL, distribution string
@@ -32,22 +41,36 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
archiveURL, distribution, components = args[1], args[2], args[3:]
}
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(), downloadSources)
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
downloadSources, downloadUdebs, downloadInstaller, downloadAppStream)
if err != nil {
return fmt.Errorf("unable to create mirror: %s", err)
}
verifier, err := getVerifier(context.flags)
repo.Filter = context.Flags().Lookup("filter").Value.String() // allows file/stdin with @
repo.FilterWithDeps = context.Flags().Lookup("filter-with-deps").Value.Get().(bool)
repo.SkipComponentCheck = context.Flags().Lookup("force-components").Value.Get().(bool)
repo.SkipArchitectureCheck = context.Flags().Lookup("force-architectures").Value.Get().(bool)
if repo.Filter != "" {
_, err = query.Parse(repo.Filter)
if err != nil {
return fmt.Errorf("unable to create mirror: %s", err)
}
}
verifier, err := getVerifier(context.Flags())
if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
}
err = repo.Fetch(context.Downloader(), verifier)
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
if err != nil {
return fmt.Errorf("unable to fetch mirror: %s", err)
}
err = context.CollectionFactory().RemoteRepoCollection().Add(repo)
collectionFactory := context.NewCollectionFactory()
err = collectionFactory.RemoteRepoCollection().Add(repo)
if err != nil {
return fmt.Errorf("unable to add mirror: %s", err)
}
@@ -63,7 +86,7 @@ func makeCmdMirrorCreate() *commander.Command {
Short: "create new mirror",
Long: `
Creates mirror <name> of remote repository, aptly supports both regular and flat Debian repositories exported
via HTTP. aptly would try download Release file from remote repository and verify its' signature. Command
via HTTP and FTP. aptly would try download Release file from remote repository and verify its' signature. Command
line format resembles apt utlitily sources.list(5).
PPA urls could specified in short format:
@@ -78,7 +101,15 @@ Example:
}
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin")
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well")
cmd.Flag.Bool("force-components", false, "(only with component list) skip check that requested components are listed in Release file")
cmd.Flag.Bool("force-architectures", false, "(only with architecture list) skip check that requested architectures are listed in Release file")
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
return cmd
+12 -5
View File
@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"github.com/smira/commander"
"github.com/smira/flag"
)
@@ -10,19 +11,25 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return err
return commander.ErrCommandError
}
name := args[0]
collectionFactory := context.NewCollectionFactory()
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
repo, err := collectionFactory.RemoteRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
force := context.flags.Lookup("force").Value.Get().(bool)
err = repo.CheckLock()
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
force := context.Flags().Lookup("force").Value.Get().(bool)
if !force {
snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo)
snapshots := collectionFactory.SnapshotCollection().ByRemoteRepoSource(repo)
if len(snapshots) > 0 {
fmt.Printf("Mirror `%s` was used to create following snapshots:\n", repo.Name)
@@ -34,7 +41,7 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
}
}
err = context.CollectionFactory().RemoteRepoCollection().Drop(repo)
err = collectionFactory.RemoteRepoCollection().Drop(repo)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
+123
View File
@@ -0,0 +1,123 @@
package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/pgp"
"github.com/aptly-dev/aptly/query"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return commander.ErrCommandError
}
collectionFactory := context.NewCollectionFactory()
repo, err := collectionFactory.RemoteRepoCollection().ByName(args[0])
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
err = repo.CheckLock()
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
fetchMirror := false
ignoreSignatures := context.Config().GpgDisableVerify
context.Flags().Visit(func(flag *flag.Flag) {
switch flag.Name {
case "filter":
repo.Filter = flag.Value.String() // allows file/stdin with @
case "filter-with-deps":
repo.FilterWithDeps = flag.Value.Get().(bool)
case "with-appstream":
repo.DownloadAppStream = flag.Value.Get().(bool)
case "with-installer":
repo.DownloadInstaller = flag.Value.Get().(bool)
case "with-sources":
repo.DownloadSources = flag.Value.Get().(bool)
case "with-udebs":
repo.DownloadUdebs = flag.Value.Get().(bool)
case "archive-url":
repo.SetArchiveRoot(flag.Value.String())
fetchMirror = true
case "ignore-signatures":
ignoreSignatures = true
}
})
if repo.IsFlat() && repo.DownloadUdebs {
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
}
if repo.IsFlat() && repo.DownloadAppStream {
return fmt.Errorf("unable to edit: flat mirrors don't support AppStream (DEP-11) metadata")
}
if repo.Filter != "" {
_, err = query.Parse(repo.Filter)
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
}
if context.GlobalFlags().Lookup("architectures").Value.String() != "" {
repo.Architectures = context.ArchitecturesList()
fetchMirror = true
}
if fetchMirror {
var verifier pgp.Verifier
verifier, err = getVerifier(context.Flags())
if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
}
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
}
err = collectionFactory.RemoteRepoCollection().Update(repo)
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
fmt.Printf("Mirror %s successfully updated.\n", repo)
return err
}
func makeCmdMirrorEdit() *commander.Command {
cmd := &commander.Command{
Run: aptlyMirrorEdit,
UsageLine: "edit <name>",
Short: "edit mirror settings",
Long: `
Command edit allows one to change settings of mirror:
filters, list of architectures.
Example:
$ aptly mirror edit -filter=nginx -filter-with-deps some-mirror
`,
Flag: *flag.NewFlagSet("aptly-mirror-edit", flag.ExitOnError),
}
cmd.Flag.String("archive-url", "", "archive url is the root of archive")
AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin")
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well")
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
return cmd
}
+50 -7
View File
@@ -1,24 +1,38 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/smira/aptly/deb"
"github.com/smira/commander"
"sort"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
)
func aptlyMirrorList(cmd *commander.Command, args []string) error {
var err error
if len(args) != 0 {
cmd.Usage()
return err
return commander.ErrCommandError
}
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool)
repos := make([]string, context.CollectionFactory().RemoteRepoCollection().Len())
if jsonFlag {
return aptlyMirrorListJSON(cmd, args)
}
return aptlyMirrorListTxt(cmd, args)
}
func aptlyMirrorListTxt(cmd *commander.Command, _ []string) error {
var err error
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
collectionFactory := context.NewCollectionFactory()
repos := make([]string, collectionFactory.RemoteRepoCollection().Len())
i := 0
context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
_ = collectionFactory.RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
if raw {
repos[i] = repo.Name
} else {
@@ -28,6 +42,8 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
return nil
})
_ = context.CloseDatabase()
sort.Strings(repos)
if raw {
@@ -49,6 +65,32 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
return err
}
func aptlyMirrorListJSON(_ *commander.Command, _ []string) error {
var err error
repos := make([]*deb.RemoteRepo, context.NewCollectionFactory().RemoteRepoCollection().Len())
i := 0
_ = context.NewCollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
repos[i] = repo
i++
return nil
})
_ = context.CloseDatabase()
sort.Slice(repos, func(i, j int) bool {
return repos[i].Name < repos[j].Name
})
if output, e := json.MarshalIndent(repos, "", " "); e == nil {
fmt.Println(string(output))
} else {
err = e
}
return err
}
func makeCmdMirrorList() *commander.Command {
cmd := &commander.Command{
Run: aptlyMirrorList,
@@ -63,6 +105,7 @@ Example:
`,
}
cmd.Flag.Bool("json", false, "display list in JSON format")
cmd.Flag.Bool("raw", false, "display list in machine-readable format")
return cmd
+66
View File
@@ -0,0 +1,66 @@
package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
)
func aptlyMirrorRename(cmd *commander.Command, args []string) error {
var (
err error
repo *deb.RemoteRepo
)
if len(args) != 2 {
cmd.Usage()
return commander.ErrCommandError
}
oldName, newName := args[0], args[1]
collectionFactory := context.NewCollectionFactory()
repo, err = collectionFactory.RemoteRepoCollection().ByName(oldName)
if err != nil {
return fmt.Errorf("unable to rename: %s", err)
}
err = repo.CheckLock()
if err != nil {
return fmt.Errorf("unable to rename: %s", err)
}
_, err = collectionFactory.RemoteRepoCollection().ByName(newName)
if err == nil {
return fmt.Errorf("unable to rename: mirror %s already exists", newName)
}
repo.Name = newName
err = collectionFactory.RemoteRepoCollection().Update(repo)
if err != nil {
return fmt.Errorf("unable to rename: %s", err)
}
fmt.Printf("\nMirror %s -> %s has been successfully renamed.\n", oldName, newName)
return err
}
func makeCmdMirrorRename() *commander.Command {
cmd := &commander.Command{
Run: aptlyMirrorRename,
UsageLine: "rename <old-name> <new-name>",
Short: "renames mirror",
Long: `
Command changes name of the mirror.Mirror name should be unique.
Example:
$ aptly mirror rename wheezy-min wheezy-main
`,
}
return cmd
}
+29
View File
@@ -0,0 +1,29 @@
package cmd
import (
"github.com/smira/commander"
"github.com/smira/flag"
)
func makeCmdMirrorSearch() *commander.Command {
cmd := &commander.Command{
Run: aptlySnapshotMirrorRepoSearch,
UsageLine: "search <name> [<package-query>]",
Short: "search mirror for packages matching query",
Long: `
Command search displays list of packages in mirror that match package query
If query is not specified, all the packages are displayed.
Example:
$ aptly mirror search wheezy-main '$Architecture (i386), Name (% *-dev)'
`,
Flag: *flag.NewFlagSet("aptly-mirror-show", flag.ExitOnError),
}
cmd.Flag.Bool("with-deps", false, "include dependencies into search results")
cmd.Flag.String("format", "", "custom format for result printing")
return cmd
}
+91 -10
View File
@@ -1,42 +1,79 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/smira/aptly/utils"
"sort"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
"strings"
)
func aptlyMirrorShow(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return err
return commander.ErrCommandError
}
jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool)
if jsonFlag {
return aptlyMirrorShowJSON(cmd, args)
}
return aptlyMirrorShowTxt(cmd, args)
}
func aptlyMirrorShowTxt(_ *commander.Command, args []string) error {
var err error
name := args[0]
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
collectionFactory := context.NewCollectionFactory()
repo, err := collectionFactory.RemoteRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
err = collectionFactory.RemoteRepoCollection().LoadComplete(repo)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
fmt.Printf("Name: %s\n", repo.Name)
if repo.Status == deb.MirrorUpdating {
fmt.Printf("Status: In Update (PID %d)\n", repo.WorkerPID)
}
fmt.Printf("Archive Root URL: %s\n", repo.ArchiveRoot)
fmt.Printf("Distribution: %s\n", repo.Distribution)
fmt.Printf("Components: %s\n", strings.Join(repo.Components, ", "))
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, ", "))
downloadSources := "no"
downloadSources := No
if repo.DownloadSources {
downloadSources = "yes"
downloadSources = Yes
}
fmt.Printf("Download Sources: %s\n", downloadSources)
downloadUdebs := No
if repo.DownloadUdebs {
downloadUdebs = Yes
}
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
downloadAppStream := No
if repo.DownloadAppStream {
downloadAppStream = Yes
}
fmt.Printf("Download AppStream: %s\n", downloadAppStream)
if repo.Filter != "" {
fmt.Printf("Filter: %s\n", repo.Filter)
filterWithDeps := No
if repo.FilterWithDeps {
filterWithDeps = Yes
}
fmt.Printf("Filter With Deps: %s\n", filterWithDeps)
}
if repo.LastDownloadDate.IsZero() {
fmt.Printf("Last update: never\n")
} else {
@@ -49,18 +86,61 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
fmt.Printf("%s: %s\n", k, repo.Meta[k])
}
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool)
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages {
if repo.LastDownloadDate.IsZero() {
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
} else {
ListPackagesRefList(repo.RefList())
_ = ListPackagesRefList(repo.RefList(), collectionFactory)
}
}
return err
}
func aptlyMirrorShowJSON(_ *commander.Command, args []string) error {
var err error
name := args[0]
repo, err := context.NewCollectionFactory().RemoteRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
err = context.NewCollectionFactory().RemoteRepoCollection().LoadComplete(repo)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
// include packages if requested
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages {
if repo.RefList() != nil {
var list *deb.PackageList
list, err = deb.NewPackageListFromRefList(repo.RefList(), context.NewCollectionFactory().PackageCollection(), context.Progress())
if err != nil {
return fmt.Errorf("unable to get package list: %s", err)
}
list.PrepareIndex()
_ = list.ForEachIndexed(func(p *deb.Package) error {
repo.Packages = append(repo.Packages, p.GetFullName())
return nil
})
sort.Strings(repo.Packages)
}
}
var output []byte
if output, err = json.MarshalIndent(repo, "", " "); err == nil {
fmt.Println(string(output))
}
return err
}
func makeCmdMirrorShow() *commander.Command {
cmd := &commander.Command{
Run: aptlyMirrorShow,
@@ -76,6 +156,7 @@ Example:
Flag: *flag.NewFlagSet("aptly-mirror-show", flag.ExitOnError),
}
cmd.Flag.Bool("json", false, "display record in JSON format")
cmd.Flag.Bool("with-packages", false, "show detailed list of packages and versions stored in the mirror")
return cmd
+245 -9
View File
@@ -2,6 +2,14 @@ package cmd
import (
"fmt"
"os"
"strings"
"sync"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
)
@@ -10,44 +18,266 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return err
return commander.ErrCommandError
}
name := args[0]
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
collectionFactory := context.NewCollectionFactory()
repo, err := collectionFactory.RemoteRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
err = collectionFactory.RemoteRepoCollection().LoadComplete(repo)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
ignoreMismatch := context.flags.Lookup("ignore-checksums").Value.Get().(bool)
force := context.Flags().Lookup("force").Value.Get().(bool)
if !force {
err = repo.CheckLock()
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
}
verifier, err := getVerifier(context.flags)
ignoreSignatures := context.Config().GpgDisableVerify
if context.Flags().IsSet("ignore-signatures") {
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
}
ignoreChecksums := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
verifier, err := getVerifier(context.Flags())
if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
}
err = repo.Fetch(context.Downloader(), verifier)
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
err = repo.Download(context.Progress(), context.Downloader(), context.CollectionFactory(), context.PackagePool(), ignoreMismatch)
context.Progress().Printf("Downloading & parsing package files...\n")
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), verifier, collectionFactory, ignoreSignatures, ignoreChecksums)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
err = context.CollectionFactory().RemoteRepoCollection().Update(repo)
if repo.DownloadAppStream && !repo.IsFlat() {
context.Progress().Printf("Downloading AppStream metadata...\n")
err = repo.DownloadAppStreamFiles(context.Progress(), context.Downloader(),
context.PackagePool(), collectionFactory.ChecksumCollection(nil), ignoreChecksums)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
}
if repo.Filter != "" {
context.Progress().Printf("Applying filter...\n")
var filterQuery deb.PackageQuery
filterQuery, err = query.Parse(repo.Filter)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
var oldLen, newLen int
oldLen, newLen, err = repo.ApplyFilter(context.DependencyOptions(), filterQuery, context.Progress())
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
context.Progress().Printf("Packages filtered: %d -> %d.\n", oldLen, newLen)
}
var (
downloadSize int64
queue []deb.PackageDownloadTask
)
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
latestOnly := context.Flags().Lookup("latest").Value.Get().(bool)
context.Progress().Printf("Building download queue...\n")
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
collectionFactory.ChecksumCollection(nil), skipExistingPackages, latestOnly)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
context.Progress().Printf("\nMirror `%s` has been successfully updated.\n", repo.Name)
defer func() {
// on any interruption, unlock the mirror
err = context.ReOpenDatabase()
if err == nil {
repo.MarkAsIdle()
_ = collectionFactory.RemoteRepoCollection().Update(repo)
}
}()
repo.MarkAsUpdating()
err = collectionFactory.RemoteRepoCollection().Update(repo)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
err = context.CloseDatabase()
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
context.GoContextHandleSignals()
count := len(queue)
context.Progress().Printf("Download queue: %d items (%s)\n", count, utils.HumanBytes(downloadSize))
// Download from the queue
context.Progress().InitBar(downloadSize, true, aptly.BarMirrorUpdateDownloadPackages)
downloadQueue := make(chan int)
var (
errors []string
errLock sync.Mutex
)
pushError := func(err error) {
errLock.Lock()
errors = append(errors, err.Error())
errLock.Unlock()
}
go func() {
for idx := range queue {
select {
case downloadQueue <- idx:
case <-context.Done():
return
}
}
close(downloadQueue)
}()
var wg sync.WaitGroup
for i := 0; i < context.Config().DownloadConcurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case idx, ok := <-downloadQueue:
if !ok {
return
}
task := &queue[idx]
var e error
// provision download location
if pp, ok := context.PackagePool().(aptly.LocalPackagePool); ok {
task.TempDownPath, e = pp.GenerateTempPath(task.File.Filename)
} else {
var file *os.File
file, e = os.CreateTemp("", task.File.Filename)
if e == nil {
task.TempDownPath = file.Name()
_ = file.Close()
}
}
if e != nil {
pushError(e)
continue
}
// download file...
e = context.Downloader().DownloadWithChecksum(
context,
repo.PackageURL(task.File.DownloadURL()).String(),
task.TempDownPath,
&task.File.Checksums,
ignoreChecksums)
if e != nil {
pushError(e)
continue
}
task.Done = true
case <-context.Done():
return
}
}
}()
}
// Wait for all download goroutines to finish
wg.Wait()
context.Progress().ShutdownBar()
err = context.ReOpenDatabase()
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
defer func() {
for _, task := range queue {
if task.TempDownPath == "" {
continue
}
if err := os.Remove(task.TempDownPath); err != nil && !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Failed to delete %s: %v\n", task.TempDownPath, err)
}
}
}()
// Import downloaded files
context.Progress().InitBar(int64(len(queue)), false, aptly.BarMirrorUpdateImportFiles)
for idx := range queue {
context.Progress().AddBar(1)
task := &queue[idx]
if !task.Done {
// download not finished yet
continue
}
// 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))
if err != nil {
return fmt.Errorf("unable to import file: %s", err)
}
// update "attached" files if any
for _, additionalTask := range task.Additional {
additionalTask.File.PoolPath = task.File.PoolPath
additionalTask.File.Checksums = task.File.Checksums
}
}
context.Progress().ShutdownBar()
select {
case <-context.Done():
return fmt.Errorf("unable to update: interrupted")
default:
}
if len(errors) > 0 {
return fmt.Errorf("unable to update: download errors:\n %s", strings.Join(errors, "\n "))
}
_ = repo.FinalizeDownload(collectionFactory, context.Progress())
err = collectionFactory.RemoteRepoCollection().Update(repo)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
context.Progress().Printf("\nMirror `%s` has been updated successfully.\n", repo.Name)
return err
}
@@ -68,8 +298,14 @@ Example:
Flag: *flag.NewFlagSet("aptly-mirror-update", flag.ExitOnError),
}
cmd.Flag.Bool("force", false, "force update mirror even if it is locked by another process")
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
cmd.Flag.Bool("latest", false, "download only latest version of each package (per architecture)")
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
return cmd
+16
View File
@@ -0,0 +1,16 @@
package cmd
import (
"github.com/smira/commander"
)
func makeCmdPackage() *commander.Command {
return &commander.Command{
UsageLine: "package",
Short: "operations on packages",
Subcommands: []*commander.Command{
makeCmdPackageSearch(),
makeCmdPackageShow(),
},
}
}
+69
View File
@@ -0,0 +1,69 @@
package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPackageSearch(cmd *commander.Command, args []string) error {
var (
err error
q deb.PackageQuery
)
if len(args) > 1 {
cmd.Usage()
return commander.ErrCommandError
}
if len(args) == 1 {
value, err := GetStringOrFileContent(args[0])
if err != nil {
return fmt.Errorf("unable to read package query from file %s: %w", args[0], err)
}
q, err = query.Parse(value)
if err != nil {
return fmt.Errorf("unable to search: %s", err)
}
} else {
q = &deb.MatchAllQuery{}
}
collectionFactory := context.NewCollectionFactory()
result := q.Query(collectionFactory.PackageCollection())
if result.Len() == 0 {
return fmt.Errorf("no results")
}
format := context.Flags().Lookup("format").Value.String()
_ = PrintPackageList(result, format, "")
return err
}
func makeCmdPackageSearch() *commander.Command {
cmd := &commander.Command{
Run: aptlyPackageSearch,
UsageLine: "search [<package-query>]",
Short: "search for packages matching query",
Long: `
Command search displays list of packages in whole DB that match package query.
Use '@file' to read query from file or '@-' for stdin.
If query is not specified, all the packages are displayed.
Example:
$ aptly package search '$Architecture (i386), Name (% *-dev)'
`,
Flag: *flag.NewFlagSet("aptly-package-search", flag.ExitOnError),
}
cmd.Flag.String("format", "", "custom format for result printing")
return cmd
}
+150
View File
@@ -0,0 +1,150 @@
package cmd
import (
"bufio"
"fmt"
"os"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/query"
"github.com/smira/commander"
"github.com/smira/flag"
)
func printReferencesTo(p *deb.Package, collectionFactory *deb.CollectionFactory) (err error) {
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
e := collectionFactory.RemoteRepoCollection().LoadComplete(repo)
if e != nil {
return e
}
if repo.RefList() != nil {
if repo.RefList().Has(p) {
fmt.Printf(" mirror %s\n", repo)
}
}
return nil
})
if err != nil {
return err
}
err = collectionFactory.LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
e := collectionFactory.LocalRepoCollection().LoadComplete(repo)
if e != nil {
return e
}
if repo.RefList() != nil {
if repo.RefList().Has(p) {
fmt.Printf(" local repo %s\n", repo)
}
}
return nil
})
if err != nil {
return err
}
err = collectionFactory.SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
e := collectionFactory.SnapshotCollection().LoadComplete(snapshot)
if e != nil {
return e
}
if snapshot.RefList().Has(p) {
fmt.Printf(" snapshot %s\n", snapshot)
}
return nil
})
return err
}
func aptlyPackageShow(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return commander.ErrCommandError
}
value, err := GetStringOrFileContent(args[0])
if err != nil {
return fmt.Errorf("unable to read package query from file %s: %w", args[0], err)
}
q, err := query.Parse(value)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
withFiles := context.Flags().Lookup("with-files").Value.Get().(bool)
withReferences := context.Flags().Lookup("with-references").Value.Get().(bool)
w := bufio.NewWriter(os.Stdout)
collectionFactory := context.NewCollectionFactory()
result := q.Query(collectionFactory.PackageCollection())
err = result.ForEach(func(p *deb.Package) error {
_ = p.Stanza().WriteTo(w, p.IsSource, false, false)
_ = w.Flush()
fmt.Printf("\n")
if withFiles {
fmt.Printf("Files in the pool:\n")
packagePool := context.PackagePool()
for _, f := range p.Files() {
var path string
path, err = f.GetPoolPath(packagePool)
if err != nil {
return err
}
if pp, ok := packagePool.(aptly.LocalPackagePool); ok {
path = pp.FullPath(path)
}
fmt.Printf(" %s\n", path)
}
fmt.Printf("\n")
}
if withReferences {
fmt.Printf("References to package:\n")
_ = printReferencesTo(p, collectionFactory)
fmt.Printf("\n")
}
return nil
})
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
return err
}
func makeCmdPackageShow() *commander.Command {
cmd := &commander.Command{
Run: aptlyPackageShow,
UsageLine: "show <package-query>",
Short: "show details about packages matching query",
Long: `
Command shows displays detailed meta-information about packages
matching query. Information from Debian control file is displayed.
Optionally information about package files and
inclusion into mirrors/snapshots/local repos is shown.
Use '@file' to read query from file or '@-' for stdin.
Example:
$ aptly package show 'nginx-light_1.2.1-2.2+wheezy2_i386'
`,
Flag: *flag.NewFlagSet("aptly-package-show", flag.ExitOnError),
}
cmd.Flag.Bool("with-files", false, "display information about files from package pool")
cmd.Flag.Bool("with-references", false, "display information about mirrors, snapshots and local repos referencing this package")
return cmd
}
+56 -5
View File
@@ -1,19 +1,36 @@
package cmd
import (
"github.com/smira/aptly/utils"
"strings"
"github.com/aptly-dev/aptly/pgp"
"github.com/smira/commander"
"github.com/smira/flag"
)
func getSigner(flags *flag.FlagSet) (utils.Signer, error) {
if flags.Lookup("skip-signing").Value.Get().(bool) || context.Config().GpgDisableSign {
func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
if LookupOption(context.Config().GpgDisableSign, flags, "skip-signing") {
return nil, nil
}
signer := &utils.GpgSigner{}
signer.SetKey(flags.Lookup("gpg-key").Value.String())
signer := context.GetSigner()
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))
err := signer.Init()
if err != nil {
@@ -24,6 +41,23 @@ func getSigner(flags *flag.FlagSet) (utils.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",
@@ -32,9 +66,26 @@ func makeCmdPublish() *commander.Command {
makeCmdPublishDrop(),
makeCmdPublishList(),
makeCmdPublishRepo(),
makeCmdPublishShow(),
makeCmdPublishSnapshot(),
makeCmdPublishSource(),
makeCmdPublishSwitch(),
makeCmdPublishUpdate(),
},
}
}
func makeCmdPublishSource() *commander.Command {
return &commander.Command{
UsageLine: "source",
Short: "manage sources of published repository",
Subcommands: []*commander.Command{
makeCmdPublishSourceAdd(),
makeCmdPublishSourceDrop(),
makeCmdPublishSourceList(),
makeCmdPublishSourceRemove(),
makeCmdPublishSourceReplace(),
makeCmdPublishSourceUpdate(),
},
}
}
+18 -8
View File
@@ -2,6 +2,8 @@ package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
)
@@ -9,18 +11,23 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error {
var err error
if len(args) < 1 || len(args) > 2 {
cmd.Usage()
return err
return commander.ErrCommandError
}
distribution := args[0]
prefix := "."
param := "."
if len(args) == 2 {
prefix = args[1]
param = args[1]
}
err = context.CollectionFactory().PublishedRepoCollection().Remove(context.PublishedStorage(), prefix, distribution,
context.CollectionFactory(), context.Progress())
storage, prefix := deb.ParsePrefix(param)
collectionFactory := context.NewCollectionFactory()
err = collectionFactory.PublishedRepoCollection().Remove(context, storage, prefix, distribution,
collectionFactory, context.Progress(),
context.Flags().Lookup("force-drop").Value.Get().(bool),
context.Flags().Lookup("skip-cleanup").Value.Get().(bool))
if err != nil {
return fmt.Errorf("unable to remove: %s", err)
}
@@ -33,11 +40,11 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error {
func makeCmdPublishDrop() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishDrop,
UsageLine: "drop <distribution> [<prefix>]",
UsageLine: "drop <distribution> [[<endpoint>:]<prefix>]",
Short: "remove published repository",
Long: `
Command removes whatever has been published under specified <prefix> and
<distribution> name.
Command removes whatever has been published under specified <prefix>,
publishing <endpoint> and <distribution> name.
Example:
@@ -45,5 +52,8 @@ Example:
`,
}
cmd.Flag.Bool("force-drop", false, "remove published repository even if some files could not be cleaned up")
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
return cmd
}
+66 -10
View File
@@ -1,31 +1,48 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/smira/aptly/deb"
"github.com/smira/commander"
"os"
"sort"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
)
func aptlyPublishList(cmd *commander.Command, args []string) error {
var err error
if len(args) != 0 {
cmd.Usage()
return err
return commander.ErrCommandError
}
jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool)
if jsonFlag {
return aptlyPublishListJSON(cmd, args)
}
return aptlyPublishListTxt(cmd, args)
}
func aptlyPublishListTxt(cmd *commander.Command, _ []string) error {
var err error
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
published := make([]string, 0, context.CollectionFactory().PublishedRepoCollection().Len())
collectionFactory := context.NewCollectionFactory()
published := make([]string, 0, collectionFactory.PublishedRepoCollection().Len())
err = context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
err := context.CollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.CollectionFactory())
if err != nil {
return err
err = collectionFactory.PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
e := collectionFactory.PublishedRepoCollection().LoadShallow(repo, collectionFactory)
if e != nil {
fmt.Fprintf(os.Stderr, "Error found on one publish (prefix:%s / distribution:%s / component:%s\n)",
repo.StoragePrefix(), repo.Distribution, repo.Components())
return e
}
if raw {
published = append(published, fmt.Sprintf("%s %s", repo.Prefix, repo.Distribution))
published = append(published, fmt.Sprintf("%s %s", repo.StoragePrefix(), repo.Distribution))
} else {
published = append(published, repo.String())
}
@@ -36,6 +53,8 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to load list of repos: %s", err)
}
_ = context.CloseDatabase()
sort.Strings(published)
if raw {
@@ -58,6 +77,42 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
return err
}
func aptlyPublishListJSON(_ *commander.Command, _ []string) error {
var err error
repos := make([]*deb.PublishedRepo, 0, context.NewCollectionFactory().PublishedRepoCollection().Len())
err = context.NewCollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
e := context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.NewCollectionFactory())
if e != nil {
fmt.Fprintf(os.Stderr, "Error found on one publish (prefix:%s / distribution:%s / component:%s\n)",
repo.StoragePrefix(), repo.Distribution, repo.Components())
return e
}
repos = append(repos, repo)
return nil
})
if err != nil {
return fmt.Errorf("unable to load list of repos: %s", err)
}
_ = context.CloseDatabase()
sort.Slice(repos, func(i, j int) bool {
return repos[i].GetPath() < repos[j].GetPath()
})
if output, e := json.MarshalIndent(repos, "", " "); e == nil {
fmt.Println(string(output))
} else {
err = e
}
return err
}
func makeCmdPublishList() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishList,
@@ -72,6 +127,7 @@ Example:
`,
}
cmd.Flag.Bool("json", false, "display list in JSON format")
cmd.Flag.Bool("raw", false, "display list in machine-readable format")
return cmd
+23 -3
View File
@@ -8,13 +8,19 @@ import (
func makeCmdPublishRepo() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSnapshotOrRepo,
UsageLine: "repo <name> [<prefix>]",
UsageLine: "repo <name> [[<endpoint>:]<prefix>]",
Short: "publish local repository",
Long: `
Command publishes current state of local repository ready to be consumed
by apt tools. Published repostiories appear under rootDir/public directory.
Valid GPG key is required for publishing.
Multiple component repository could be published by specifying several
components split by commas via -component flag and multiple local
repositories as the arguments:
aptly publish repo -component=main,contrib repo-main repo-contrib
It is not recommended to publish local repositories directly unless the
repository is for testing purposes and changes happen frequently. For
production usage please take snapshot of repository and publish it
@@ -27,13 +33,27 @@ Example:
Flag: *flag.NewFlagSet("aptly-publish-repo", flag.ExitOnError),
}
cmd.Flag.String("distribution", "", "distribution name to publish")
cmd.Flag.String("component", "", "component name to publish")
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
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)")
cmd.Flag.String("passphrase-file", "", "GPG passphrase-file for the key (warning: could be insecure)")
cmd.Flag.Bool("batch", false, "run GPG with detached tty")
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
cmd.Flag.String("origin", "", "origin name to publish")
cmd.Flag.String("notautomatic", "", "set value for NotAutomatic field")
cmd.Flag.String("butautomaticupgrades", "", "set value for ButAutomaticUpgrades field")
cmd.Flag.String("label", "", "label to publish")
cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)")
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
cmd.Flag.String("version", "", "version of the release")
return cmd
}
+127
View File
@@ -0,0 +1,127 @@
package cmd
import (
"encoding/json"
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
)
func aptlyPublishShow(cmd *commander.Command, args []string) error {
if len(args) < 1 || len(args) > 2 {
cmd.Usage()
return commander.ErrCommandError
}
jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool)
if jsonFlag {
return aptlyPublishShowJSON(cmd, args)
}
return aptlyPublishShowTxt(cmd, args)
}
func aptlyPublishShowTxt(_ *commander.Command, args []string) error {
var err error
distribution := args[0]
param := "."
if len(args) == 2 {
param = args[1]
}
storage, prefix := deb.ParsePrefix(param)
collectionFactory := context.NewCollectionFactory()
repo, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
if repo.Storage != "" {
fmt.Printf("Storage: %s\n", repo.Storage)
}
fmt.Printf("Prefix: %s\n", repo.Prefix)
if repo.Distribution != "" {
fmt.Printf("Distribution: %s\n", repo.Distribution)
}
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, " "))
fmt.Printf("Sources:\n")
for _, component := range repo.Components() {
sourceID := repo.Sources[component]
var name string
if repo.SourceKind == deb.SourceSnapshot {
source, e := collectionFactory.SnapshotCollection().ByUUID(sourceID)
if e != nil {
continue
}
name = source.Name
} else if repo.SourceKind == deb.SourceLocalRepo {
source, e := collectionFactory.LocalRepoCollection().ByUUID(sourceID)
if e != nil {
continue
}
name = source.Name
}
if name != "" {
fmt.Printf(" %s: %s [%s]\n", component, name, repo.SourceKind)
}
}
return err
}
func aptlyPublishShowJSON(_ *commander.Command, args []string) error {
var err error
distribution := args[0]
param := "."
if len(args) == 2 {
param = args[1]
}
storage, prefix := deb.ParsePrefix(param)
repo, err := context.NewCollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to show: %s", err)
}
err = context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.NewCollectionFactory())
if err != nil {
return err
}
var output []byte
if output, err = json.MarshalIndent(repo, "", " "); err == nil {
fmt.Println(string(output))
}
return err
}
func makeCmdPublishShow() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishShow,
UsageLine: "show <distribution> [[<endpoint>:]<prefix>]",
Short: "shows details of published repository",
Long: `
Command show displays full information of a published repository.
Example:
$ aptly publish show wheezy
`,
}
cmd.Flag.Bool("json", false, "display record in JSON format")
return cmd
}
+173 -52
View File
@@ -2,110 +2,211 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/utils"
"strings"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
"strings"
)
func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
var err error
if len(args) < 1 || len(args) > 2 {
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
collectionFactory := context.NewCollectionFactory()
if len(args) < len(components) || len(args) > len(components)+1 {
cmd.Usage()
return err
return commander.ErrCommandError
}
name := args[0]
var prefix string
if len(args) == 2 {
prefix = args[1]
var param string
if len(args) == len(components)+1 {
param = args[len(components)]
args = args[0 : len(args)-1]
} else {
prefix = ""
param = ""
}
storage, prefix := deb.ParsePrefix(param)
var (
source interface{}
sources = []interface{}{}
message string
)
if cmd.Name() == "snapshot" {
var snapshot *deb.Snapshot
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
if cmd.Name() == "snapshot" { // nolint: goconst
var (
snapshot *deb.Snapshot
emptyWarning = false
parts = []string{}
)
for _, name := range args {
snapshot, err = collectionFactory.SnapshotCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
err = collectionFactory.SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
sources = append(sources, snapshot)
parts = append(parts, snapshot.Name)
if snapshot.NumPackages() == 0 {
emptyWarning = true
}
}
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
if len(parts) == 1 {
message = fmt.Sprintf("Snapshot %s has", parts[0])
} else {
message = fmt.Sprintf("Snapshots %s have", strings.Join(parts, ", "))
}
source = snapshot
message = fmt.Sprintf("Snapshot %s", snapshot.Name)
} else if cmd.Name() == "repo" {
var localRepo *deb.LocalRepo
localRepo, err = context.CollectionFactory().LocalRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
if emptyWarning {
context.Progress().Printf("Warning: publishing from empty source, architectures list should be complete, it can't be changed after publishing (use -architectures flag)\n")
}
} else if cmd.Name() == "repo" { // nolint: goconst
var (
localRepo *deb.LocalRepo
emptyWarning = false
parts = []string{}
)
for _, name := range args {
localRepo, err = collectionFactory.LocalRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
err = collectionFactory.LocalRepoCollection().LoadComplete(localRepo)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
sources = append(sources, localRepo)
parts = append(parts, localRepo.Name)
if localRepo.NumPackages() == 0 {
emptyWarning = true
}
}
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(localRepo)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
if len(parts) == 1 {
message = fmt.Sprintf("Local repo %s has", parts[0])
} else {
message = fmt.Sprintf("Local repos %s have", strings.Join(parts, ", "))
}
source = localRepo
message = fmt.Sprintf("Local repo %s", localRepo.Name)
if emptyWarning {
context.Progress().Printf("Warning: publishing from empty source, architectures list should be complete, it can't be changed after publishing (use -architectures flag)\n")
}
} else {
panic("unknown command")
}
component := context.flags.Lookup("component").Value.String()
distribution := context.flags.Lookup("distribution").Value.String()
distribution := context.Flags().Lookup("distribution").Value.String()
origin := context.Flags().Lookup("origin").Value.String()
notAutomatic := context.Flags().Lookup("notautomatic").Value.String()
butAutomaticUpgrades := context.Flags().Lookup("butautomaticupgrades").Value.String()
multiDist := context.Flags().Lookup("multi-dist").Value.Get().(bool)
published, err := deb.NewPublishedRepo(prefix, distribution, component, context.ArchitecturesList(), source, context.CollectionFactory())
published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, collectionFactory, multiDist)
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
published.Origin = cmd.Flag.Lookup("origin").Value.String()
published.Label = cmd.Flag.Lookup("label").Value.String()
if origin != "" {
published.Origin = origin
}
if notAutomatic != "" {
published.NotAutomatic = notAutomatic
}
if butAutomaticUpgrades != "" {
published.ButAutomaticUpgrades = butAutomaticUpgrades
}
published.Label = context.Flags().Lookup("label").Value.String()
published.Suite = context.Flags().Lookup("suite").Value.String()
published.Codename = context.Flags().Lookup("codename").Value.String()
duplicate := context.CollectionFactory().PublishedRepoCollection().CheckDuplicate(published)
published.SkipContents = context.Config().SkipContentsPublishing
if context.Flags().IsSet("skip-contents") {
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
}
published.SkipBz2 = context.Config().SkipBz2Publishing
if context.Flags().IsSet("skip-bz2") {
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
}
if context.Flags().IsSet("acquire-by-hash") {
published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool)
}
if context.Flags().IsSet("signed-by") {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
if duplicate != nil {
context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory())
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
}
signer, err := getSigner(context.flags)
signer, err := getSigner(context.Flags())
if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err)
}
err = published.Publish(context.PackagePool(), context.PublishedStorage(), context.CollectionFactory(), signer, context.Progress())
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing the same package pool.\n")
}
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
err = context.CollectionFactory().PublishedRepoCollection().Add(published)
err = collectionFactory.PublishedRepoCollection().Add(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
prefix, component, distribution = published.Prefix, published.Component, published.Distribution
var repoComponents string
prefix, repoComponents, distribution = published.Prefix, strings.Join(published.Components(), " "), published.Distribution
if prefix == "." {
prefix = ""
} else if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
context.Progress().Printf("\n%s has been successfully published.\nPlease setup your webserver to serve directory '%s' with autoindexing.\n",
message, context.PublishedStorage().PublicPath())
context.Progress().Printf("\n%s been successfully published.\n", message)
if localStorage, ok := context.GetPublishedStorage(storage).(aptly.FileSystemPublishedStorage); ok {
context.Progress().Printf("Please setup your webserver to serve directory '%s' with autoindexing.\n",
localStorage.PublicPath())
}
context.Progress().Printf("Now you can add following line to apt sources:\n")
context.Progress().Printf(" deb http://your-server/%s %s %s\n", prefix, distribution, component)
if utils.StrSliceHasItem(published.Architectures, "source") {
context.Progress().Printf(" deb-src http://your-server/%s %s %s\n", prefix, distribution, component)
context.Progress().Printf(" deb http://your-server/%s %s %s\n", prefix, distribution, repoComponents)
if utils.StrSliceHasItem(published.Architectures, deb.ArchitectureSource) {
context.Progress().Printf(" deb-src http://your-server/%s %s %s\n", prefix, distribution, repoComponents)
}
context.Progress().Printf("Don't forget to add your GPG key to apt with apt-key.\n")
context.Progress().Printf("\nYou can also use `aptly serve` to publish your repositories over HTTP quickly.\n")
@@ -116,13 +217,19 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
func makeCmdPublishSnapshot() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSnapshotOrRepo,
UsageLine: "snapshot <name> [<prefix>]",
UsageLine: "snapshot <name> [[<endpoint>:]<prefix>]",
Short: "publish snapshot",
Long: `
Command publishes snapshot as Debian repository ready to be consumed
by apt tools. Published repostiories appear under rootDir/public directory.
Valid GPG key is required for publishing.
Multiple component repository could be published by specifying several
components split by commas via -component flag and multiple snapshots
as the arguments:
aptly publish snapshot -component=main,contrib snap-main snap-contrib
Example:
$ aptly publish snapshot wheezy-main
@@ -130,13 +237,27 @@ Example:
Flag: *flag.NewFlagSet("aptly-publish-snapshot", flag.ExitOnError),
}
cmd.Flag.String("distribution", "", "distribution name to publish")
cmd.Flag.String("component", "", "component name to publish")
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
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)")
cmd.Flag.String("passphrase-file", "", "GPG passphrase-file for the key (warning: could be insecure)")
cmd.Flag.Bool("batch", false, "run GPG with detached tty")
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
cmd.Flag.String("origin", "", "origin name to publish")
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
cmd.Flag.String("origin", "", "overwrite origin name to publish")
cmd.Flag.String("notautomatic", "", "overwrite value for NotAutomatic field")
cmd.Flag.String("butautomaticupgrades", "", "overwrite value for ButAutomaticUpgrades field")
cmd.Flag.String("label", "", "label to publish")
cmd.Flag.String("suite", "", "suite to publish (defaults to distribution)")
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.String("version", "", "version of the release")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
return cmd
}
+94
View File
@@ -0,0 +1,94 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceAdd(cmd *commander.Command, args []string) error {
if len(args) < 2 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
names := args[1:]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(names) != len(components) {
return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names))
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
for i, component := range components {
name := names[i]
_, exists := sources[component]
if exists {
return fmt.Errorf("unable to add: component '%s' has already been added", component)
}
context.Progress().Printf("Adding component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind)
sources[component] = name
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n",
distribution, published.StoragePrefix())
return err
}
func makeCmdPublishSourceAdd() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceAdd,
UsageLine: "add <distribution> <source>",
Short: "add source components to a published repo",
Long: `
The command adds components of a snapshot or local repository to be published.
This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be modified. The number of given components must be
equal to the number of given sources, e.g.:
aptly publish source add -component=main,contrib wheezy wheezy-main wheezy-contrib
Example:
$ aptly publish source add -component=contrib wheezy ppa wheezy-contrib
This command assigns the snapshot wheezy-contrib to the component contrib and
adds it to published repository revision of ppa/wheezy.
`,
Flag: *flag.NewFlagSet("aptly-publish-source-add", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}
+62
View File
@@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceDrop(cmd *commander.Command, args []string) error {
if len(args) != 1 {
cmd.Usage()
return commander.ErrCommandError
}
prefix := context.Flags().Lookup("prefix").Value.String()
distribution := args[0]
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
published.DropRevision()
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
context.Progress().Printf("Source changes have been removed successfully.\n")
return err
}
func makeCmdPublishSourceDrop() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceDrop,
UsageLine: "drop <distribution>",
Short: "drop pending source component changes of a published repository",
Long: `
Remove all pending changes what would be applied with a subsequent 'aptly publish update'.
Example:
$ aptly publish source drop wheezy
`,
Flag: *flag.NewFlagSet("aptly-publish-source-drop", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}
+89
View File
@@ -0,0 +1,89 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceList(cmd *commander.Command, args []string) error {
if len(args) != 1 {
cmd.Usage()
return commander.ErrCommandError
}
prefix := context.Flags().Lookup("prefix").Value.String()
distribution := args[0]
storage, prefix := deb.ParsePrefix(prefix)
published, err := context.NewCollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to list: %s", err)
}
err = context.NewCollectionFactory().PublishedRepoCollection().LoadComplete(published, context.NewCollectionFactory())
if err != nil {
return err
}
if published.Revision == nil {
return fmt.Errorf("unable to list: no source changes exist")
}
jsonFlag := cmd.Flag.Lookup("json").Value.Get().(bool)
if jsonFlag {
return aptlyPublishSourceListJSON(published)
}
return aptlyPublishSourceListTxt(published)
}
func aptlyPublishSourceListTxt(published *deb.PublishedRepo) error {
revision := published.Revision
fmt.Printf("Sources:\n")
for _, component := range revision.Components() {
name := revision.Sources[component]
fmt.Printf(" %s: %s [%s]\n", component, name, published.SourceKind)
}
return nil
}
func aptlyPublishSourceListJSON(published *deb.PublishedRepo) error {
revision := published.Revision
output, err := json.MarshalIndent(revision.SourceList(), "", " ")
if err != nil {
return fmt.Errorf("unable to list: %s", err)
}
fmt.Println(string(output))
return nil
}
func makeCmdPublishSourceList() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceList,
UsageLine: "list <distribution>",
Short: "lists revision of published repository",
Long: `
Command lists sources of a published repository.
Example:
$ aptly publish source list wheezy
`,
Flag: *flag.NewFlagSet("aptly-publish-source-list", flag.ExitOnError),
}
cmd.Flag.Bool("json", false, "display record in JSON format")
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}
+86
View File
@@ -0,0 +1,86 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceRemove(cmd *commander.Command, args []string) error {
if len(args) < 1 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(components) == 0 {
return fmt.Errorf("unable to remove: missing components, specify at least one component")
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to remove: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to remove: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
for _, component := range components {
name, exists := sources[component]
if !exists {
return fmt.Errorf("unable to remove: component '%s' does not exist", component)
}
context.Progress().Printf("Removing component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind)
delete(sources, component)
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n",
distribution, published.StoragePrefix())
return err
}
func makeCmdPublishSourceRemove() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceRemove,
UsageLine: "remove <distribution> [[<endpoint>:]<prefix>] <source>",
Short: "remove source components from a published repo",
Long: `
The command removes source components (snapshot / local repo) from a published repository.
This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be removed, e.g.:
Example:
$ aptly publish source remove -component=contrib,non-free wheezy filesystem:symlink:debian
`,
Flag: *flag.NewFlagSet("aptly-publish-source-remove", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to remove (for multi-component publishing, separate components with commas)")
return cmd
}
+89
View File
@@ -0,0 +1,89 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceReplace(cmd *commander.Command, args []string) error {
if len(args) < 2 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
names := args[1:]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(names) != len(components) {
return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names))
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
context.Progress().Printf("Replacing source list...\n")
clear(sources)
for i, component := range components {
name := names[i]
context.Progress().Printf("Adding component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind)
sources[component] = name
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n",
distribution, published.StoragePrefix())
return err
}
func makeCmdPublishSourceReplace() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceReplace,
UsageLine: "replace <distribution> <source>",
Short: "replace the source components of a published repository",
Long: `
The command replaces the source components of a snapshot or local repository to be published.
This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be modified. The number of given components must be
equal to the number of given sources, e.g.:
aptly publish source replace -component=main,contrib wheezy wheezy-main wheezy-contrib
Example:
$ aptly publish source replace -component=contrib wheezy ppa wheezy-contrib
`,
Flag: *flag.NewFlagSet("aptly-publish-source-add", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}
+91
View File
@@ -0,0 +1,91 @@
package cmd
import (
"fmt"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSourceUpdate(cmd *commander.Command, args []string) error {
if len(args) < 2 {
cmd.Usage()
return commander.ErrCommandError
}
distribution := args[0]
names := args[1:]
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(names) != len(components) {
return fmt.Errorf("mismatch in number of components (%d) and sources (%d)", len(components), len(names))
}
prefix := context.Flags().Lookup("prefix").Value.String()
storage, prefix := deb.ParsePrefix(prefix)
collectionFactory := context.NewCollectionFactory()
published, err := collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
revision := published.ObtainRevision()
sources := revision.Sources
for i, component := range components {
name := names[i]
_, exists := sources[component]
if !exists {
return fmt.Errorf("unable to update: component '%s' does not exist", component)
}
context.Progress().Printf("Updating component '%s' with source '%s' [%s]...\n", component, name, published.SourceKind)
sources[component] = name
}
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
context.Progress().Printf("\nYou can run 'aptly publish update %s %s' to update the content of the published repository.\n",
distribution, published.StoragePrefix())
return err
}
func makeCmdPublishSourceUpdate() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSourceUpdate,
UsageLine: "update <distribution> <source>",
Short: "update the source components of a published repository",
Long: `
The command updates the source components of a snapshot or local repository to be published.
This does not publish the changes directly, but rather schedules them for a subsequent 'aptly publish update'.
The flag -component is mandatory. Use a comma-separated list of components, if
multiple components should be modified. The number of given components must be
equal to the number of given sources, e.g.:
aptly publish source update -component=main,contrib wheezy wheezy-main wheezy-contrib
Example:
$ aptly publish source update -component=contrib wheezy ppa wheezy-contrib
`,
Flag: *flag.NewFlagSet("aptly-publish-source-update", flag.ExitOnError),
}
cmd.Flag.String("prefix", ".", "publishing prefix in the form of [<endpoint>:]<prefix>")
cmd.Flag.String("component", "", "component names to add (for multi-component publishing, separate components with commas)")
return cmd
}
+116 -44
View File
@@ -2,83 +2,134 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"strings"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
var err error
if len(args) < 2 || len(args) > 3 {
var (
err error
names []string
)
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(args) < len(components)+1 || len(args) > len(components)+2 {
cmd.Usage()
return err
return commander.ErrCommandError
}
distribution := args[0]
prefix := "."
param := "."
var (
name string
snapshot *deb.Snapshot
)
if len(args) == 3 {
prefix = args[1]
name = args[2]
if len(args) == len(components)+2 {
param = args[1]
names = args[2:]
} else {
name = args[1]
names = args[1:]
}
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
storage, prefix := deb.ParsePrefix(param)
var published *deb.PublishedRepo
published, err = context.CollectionFactory().PublishedRepoCollection().ByPrefixDistribution(prefix, distribution)
collectionFactory := context.NewCollectionFactory()
published, err = collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
return fmt.Errorf("unable to switch: %s", err)
}
if published.SourceKind != "snapshot" {
return fmt.Errorf("unable to update: not a snapshot publish")
if published.SourceKind != deb.SourceSnapshot {
return fmt.Errorf("unable to switch: not a published snapshot repository")
}
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory())
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
return fmt.Errorf("unable to switch: %s", err)
}
published.UpdateSnapshot(snapshot)
publishedComponents := published.Components()
if len(components) == 1 && len(publishedComponents) == 1 && components[0] == "" {
components = publishedComponents
}
signer, err := getSigner(context.flags)
if len(names) != len(components) {
return fmt.Errorf("mismatch in number of components (%d) and snapshots (%d)", len(components), len(names))
}
snapshotCollection := collectionFactory.SnapshotCollection()
for i, component := range components {
if !utils.StrSliceHasItem(publishedComponents, component) {
return fmt.Errorf("unable to switch: component %s does not exist in published repository", component)
}
snapshot, err := snapshotCollection.ByName(names[i])
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
published.UpdateSnapshot(component, snapshot)
}
signer, err := getSigner(context.Flags())
if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err)
}
err = published.Publish(context.PackagePool(), context.PublishedStorage(), context.CollectionFactory(), signer, context.Progress())
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n")
}
if context.Flags().IsSet("skip-contents") {
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
}
if context.Flags().IsSet("skip-bz2") {
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
}
if context.Flags().IsSet("signed-by") {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
err = context.CollectionFactory().PublishedRepoCollection().Update(published)
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
err = context.CollectionFactory().PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, published.Component,
context.PublishedStorage(), context.CollectionFactory(), context.Progress())
if err != nil {
return fmt.Errorf("unable to update: %s", err)
skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool)
if !skipCleanup {
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(context, published, components, collectionFactory, context.Progress())
if err != nil {
return fmt.Errorf("unable to switch: %s", err)
}
}
context.Progress().Printf("\nPublish for snapshot %s has been successfully switched to new snapshot.\n", published.String())
context.Progress().Printf("\nPublished %s repository %s has been successfully switched to new source.\n", published.SourceKind, published.String())
return err
}
@@ -86,22 +137,43 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
func makeCmdPublishSwitch() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishSwitch,
UsageLine: "switch <distribution> [<prefix>] <new-snapshot>",
Short: "update published repository by switching to new snapshot",
UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-source>",
Short: "update published repository by switching to new source",
Long: `
Command switches in-place published repository with new snapshot contents. All
publishing parameters are preserved (architecture list, distribution, component).
Command switches in-place published snapshots with new source contents. All
publishing parameters are preserved (architecture list, distribution,
component).
For multiple component repositories, flag -component should be given with
list of components to update. Corresponding sources should be given in the
same order, e.g.:
aptly publish switch -component=main,contrib wheezy wh-main wh-contrib
Example:
$ aptly publish update wheezy ppa wheezy-7.5
$ aptly publish switch wheezy ppa wheezy-7.5
This command would switch published repository (with one component) named ppa/wheezy
(prefix ppa, dsitribution wheezy to new snapshot wheezy-7.5).
`,
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)")
cmd.Flag.String("passphrase-file", "", "GPG passphrase-file for the key (warning: could be insecure)")
cmd.Flag.Bool("batch", false, "run GPG with detached tty")
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.String("version", "", "version of the release")
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
return cmd
}
+87 -26
View File
@@ -2,7 +2,8 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
@@ -11,56 +12,95 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
var err error
if len(args) < 1 || len(args) > 2 {
cmd.Usage()
return err
return commander.ErrCommandError
}
distribution := args[0]
prefix := "."
param := "."
if len(args) == 2 {
prefix = args[1]
param = args[1]
}
storage, prefix := deb.ParsePrefix(param)
var published *deb.PublishedRepo
published, err = context.CollectionFactory().PublishedRepoCollection().ByPrefixDistribution(prefix, distribution)
collectionFactory := context.NewCollectionFactory()
published, err = collectionFactory.PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
if published.SourceKind != "local" {
return fmt.Errorf("unable to update: not a local repository publish")
}
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory())
err = collectionFactory.PublishedRepoCollection().LoadComplete(published, collectionFactory)
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
published.UpdateLocalRepo()
result, err := published.Update(collectionFactory, context.Progress())
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
signer, err := getSigner(context.flags)
signer, err := getSigner(context.Flags())
if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err)
}
err = published.Publish(context.PackagePool(), context.PublishedStorage(), context.CollectionFactory(), signer, context.Progress())
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n")
}
if context.Flags().IsSet("skip-contents") {
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
}
if context.Flags().IsSet("skip-bz2") {
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
}
if context.Flags().IsSet("signed-by") {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("origin") {
published.Origin = context.Flags().Lookup("origin").Value.String()
}
if context.Flags().IsSet("label") {
published.Label = context.Flags().Lookup("label").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
}
err = context.CollectionFactory().PublishedRepoCollection().Update(published)
err = collectionFactory.PublishedRepoCollection().Update(published)
if err != nil {
return fmt.Errorf("unable to save to DB: %s", err)
}
err = context.CollectionFactory().PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, published.Component,
context.PublishedStorage(), context.CollectionFactory(), context.Progress())
if err != nil {
return fmt.Errorf("unable to update: %s", err)
skipCleanup := context.Flags().Lookup("skip-cleanup").Value.Get().(bool)
if !skipCleanup {
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
err = collectionFactory.PublishedRepoCollection().CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, context.Progress())
if err != nil {
return fmt.Errorf("unable to update: %s", err)
}
}
context.Progress().Printf("\nPublish for local repo %s has been successfully updated.\n", published.String())
context.Progress().Printf("\nPublished %s repository %s has been updated successfully.\n", published.SourceKind, published.String())
return err
}
@@ -68,13 +108,22 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
func makeCmdPublishUpdate() *commander.Command {
cmd := &commander.Command{
Run: aptlyPublishUpdate,
UsageLine: "update <distribution> [<prefix>]",
Short: "update published local repository",
UsageLine: "update <distribution> [[<endpoint>:]<prefix>]",
Short: "update published repository",
Long: `
Command re-publishes (updates) published local repository. <distribution>
and <prefix> should be occupied with local repository published
using command aptly publish repo. Update happens in-place with
minimum possible downtime for published repository.
The command updates updates a published repository after applying pending changes to the sources.
For published local repositories:
* update to match local repository contents
For published snapshots:
* switch components to new snapshot
The update happens in-place with minimum possible downtime for published repository.
For multiple component published repositories, all local repositories are updated.
Example:
@@ -82,10 +131,22 @@ 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)")
cmd.Flag.String("passphrase-file", "", "GPG passphrase-file for the key (warning: could be insecure)")
cmd.Flag.Bool("batch", false, "run GPG with detached tty")
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
cmd.Flag.String("origin", "", "overwrite origin name to publish")
cmd.Flag.String("label", "", "overwrite label to publish")
cmd.Flag.String("version", "", "version of the release")
return cmd
}
+3
View File
@@ -19,6 +19,9 @@ func makeCmdRepo() *commander.Command {
makeCmdRepoMove(),
makeCmdRepoRemove(),
makeCmdRepoShow(),
makeCmdRepoRename(),
makeCmdRepoSearch(),
makeCmdRepoInclude(),
},
}
}
+39 -129
View File
@@ -2,191 +2,100 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/utils"
"os"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/deb"
"github.com/aptly-dev/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
"os"
"path/filepath"
"sort"
"strings"
)
func aptlyRepoAdd(cmd *commander.Command, args []string) error {
var err error
if len(args) < 2 {
cmd.Usage()
return err
return commander.ErrCommandError
}
name := args[0]
verifier := &utils.GpgVerifier{}
verifier := context.GetVerifier()
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
collectionFactory := context.NewCollectionFactory()
repo, err := collectionFactory.LocalRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
err = collectionFactory.LocalRepoCollection().LoadComplete(repo)
if err != nil {
return fmt.Errorf("unable to add: %s", err)
}
context.Progress().Printf("Loading packages...\n")
list, err := deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), context.Progress())
if err != nil {
return fmt.Errorf("unable to load packages: %s", err)
}
packageFiles := []string{}
forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
for _, location := range args[1:] {
info, err2 := os.Stat(location)
if err2 != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to process %s: %s@|", location, err2)
continue
}
if info.IsDir() {
err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error {
if err3 != nil {
return err3
}
if info.IsDir() {
return nil
}
var packageFiles, otherFiles, failedFiles []string
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
packageFiles = append(packageFiles, path)
}
packageFiles, otherFiles, failedFiles = deb.CollectPackageFiles(args[1:], &aptly.ConsoleResultReporter{Progress: context.Progress()})
return nil
})
} else {
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
packageFiles = append(packageFiles, location)
} else {
context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location)
continue
}
}
var processedFiles, failedFiles2 []string
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
collectionFactory.PackageCollection(), &aptly.ConsoleResultReporter{Progress: context.Progress()}, nil,
collectionFactory.ChecksumCollection)
failedFiles = append(failedFiles, failedFiles2...)
if err != nil {
return fmt.Errorf("unable to import package files: %s", err)
}
processedFiles := []string{}
sort.Strings(packageFiles)
for _, file := range packageFiles {
var (
stanza deb.Stanza
p *deb.Package
)
candidateProcessedFiles := []string{}
isSourcePackage := strings.HasSuffix(file, ".dsc")
if isSourcePackage {
stanza, err = deb.GetControlFileFromDsc(file, verifier)
if err == nil {
stanza["Package"] = stanza["Source"]
delete(stanza, "Source")
p, err = deb.NewSourcePackageFromControlFile(stanza)
}
} else {
stanza, err = deb.GetControlFileFromDeb(file)
p = deb.NewPackageFromControlFile(stanza)
}
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to read file %s: %s@|", file, err)
continue
}
var checksums utils.ChecksumInfo
checksums, err = utils.ChecksumsForFile(file)
if err != nil {
return err
}
if isSourcePackage {
p.UpdateFiles(append(p.Files(), deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}))
} else {
p.UpdateFiles([]deb.PackageFile{deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}})
}
err = context.PackagePool().Import(file, checksums.MD5)
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", file, err)
continue
}
candidateProcessedFiles = append(candidateProcessedFiles, file)
// go over all files, except for the last one (.dsc/.deb itself)
for _, f := range p.Files() {
if filepath.Base(f.Filename) == filepath.Base(file) {
continue
}
sourceFile := filepath.Join(filepath.Dir(file), filepath.Base(f.Filename))
err = context.PackagePool().Import(sourceFile, f.Checksums.MD5)
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", sourceFile, err)
break
}
candidateProcessedFiles = append(candidateProcessedFiles, sourceFile)
}
if err != nil {
// some files haven't been imported
continue
}
err = context.CollectionFactory().PackageCollection().Update(p)
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to save package %s: %s@|", p, err)
continue
}
err = list.Add(p)
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to add package to repo %s: %s@|", p, err)
continue
}
context.Progress().ColoredPrintf("@g[+]@| %s added@|", p)
processedFiles = append(processedFiles, candidateProcessedFiles...)
}
processedFiles = append(processedFiles, otherFiles...)
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
err = collectionFactory.LocalRepoCollection().Update(repo)
if err != nil {
return fmt.Errorf("unable to save: %s", err)
}
if context.flags.Lookup("remove-files").Value.Get().(bool) {
if context.Flags().Lookup("remove-files").Value.Get().(bool) {
processedFiles = utils.StrSliceDeduplicate(processedFiles)
for _, file := range processedFiles {
err := os.Remove(file)
err = os.Remove(file)
if err != nil {
return fmt.Errorf("unable to remove file: %s", err)
}
}
}
if len(failedFiles) > 0 {
context.Progress().ColoredPrintf("@y[!]@| @!Some files were skipped due to errors:@|")
for _, file := range failedFiles {
context.Progress().ColoredPrintf(" %s", file)
}
return fmt.Errorf("some files failed to be added")
}
return err
}
func makeCmdRepoAdd() *commander.Command {
cmd := &commander.Command{
Run: aptlyRepoAdd,
UsageLine: "add <name> <package file.deb>|<directory> ...",
UsageLine: "add <name> (<package file.deb>|<directory>)...",
Short: "add packages to local repository",
Long: `
Command adds packages to local repository from .deb (binary packages) and .dsc (source packages) files.
When importing from directory aptly would do recursive scan looking for all files matching *.deb or *.dsc
Command adds packages to local repository from .deb, .udeb (binary packages) and .dsc (source packages) files.
When importing from directory aptly would do recursive scan looking for all files matching *.[u]deb or *.dsc
patterns. Every file discovered would be analyzed to extract metadata, package would then be created and added
to the database. Files would be imported to internal package pool. For source packages, all required files are
added automatically as well. Extra files for source package should be in the same directory as *.dsc file.
@@ -199,6 +108,7 @@ Example:
}
cmd.Flag.Bool("remove-files", false, "remove files that have been imported successfully into repository")
cmd.Flag.Bool("force-replace", false, "when adding package that conflicts with existing package, remove existing package")
return cmd
}
+2 -2
View File
@@ -8,10 +8,10 @@ import (
func makeCmdRepoCopy() *commander.Command {
cmd := &commander.Command{
Run: aptlyRepoMoveCopyImport,
UsageLine: "copy <src-name> <dst-name> <package-spec> ...",
UsageLine: "copy <src-name> <dst-name> <package-query> ...",
Short: "copy packages between local repositories",
Long: `
Command copy copies packages matching <package-spec> from local repo
Command copy copies packages matching <package-query> from local repo
<src-name> to local repo <dst-name>.
Example:
+40 -8
View File
@@ -2,23 +2,49 @@ package cmd
import (
"fmt"
"github.com/smira/aptly/deb"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
func aptlyRepoCreate(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
if !(len(args) == 1 || (len(args) == 4 && args[1] == "from" && args[2] == "snapshot")) { // nolint: goconst
cmd.Usage()
return err
return commander.ErrCommandError
}
repo := deb.NewLocalRepo(args[0], context.flags.Lookup("comment").Value.String())
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String())
repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
err = context.CollectionFactory().LocalRepoCollection().Add(repo)
uploadersFile := context.Flags().Lookup("uploaders-file").Value.Get().(string)
if uploadersFile != "" {
repo.Uploaders, err = deb.NewUploadersFromFile(uploadersFile)
if err != nil {
return err
}
}
collectionFactory := context.NewCollectionFactory()
if len(args) == 4 {
var snapshot *deb.Snapshot
snapshot, err = collectionFactory.SnapshotCollection().ByName(args[3])
if err != nil {
return fmt.Errorf("unable to load source snapshot: %s", err)
}
err = collectionFactory.SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return fmt.Errorf("unable to load source snapshot: %s", err)
}
repo.UpdateRefList(snapshot.RefList())
}
err = collectionFactory.LocalRepoCollection().Add(repo)
if err != nil {
return fmt.Errorf("unable to add local repo: %s", err)
}
@@ -30,16 +56,21 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error {
func makeCmdRepoCreate() *commander.Command {
cmd := &commander.Command{
Run: aptlyRepoCreate,
UsageLine: "create <name>",
UsageLine: "create <name> [ from snapshot <snapshot> ]",
Short: "create local repository",
Long: `
Create local package repository. Repository would be empty when
created, packages could be added from files, copied or moved from
another local repository or imported from the mirror.
If local package repository is created from snapshot, repo initial
contents are copied from snapsot contents.
Example:
$ aptly repo create testing
$ aptly repo create mysql35 from snapshot mysql-35-2017
`,
Flag: *flag.NewFlagSet("aptly-repo-create", flag.ExitOnError),
}
@@ -47,6 +78,7 @@ Example:
cmd.Flag.String("comment", "", "any text that would be used to described local repository")
cmd.Flag.String("distribution", "", "default distribution when publishing")
cmd.Flag.String("component", "main", "default component when publishing")
cmd.Flag.String("uploaders-file", "", "uploaders.json to be used when including .changes into this repository")
return cmd
}
+9 -7
View File
@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"github.com/smira/commander"
"github.com/smira/flag"
)
@@ -10,21 +11,22 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return err
return commander.ErrCommandError
}
name := args[0]
collectionFactory := context.NewCollectionFactory()
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
repo, err := collectionFactory.LocalRepoCollection().ByName(name)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
published := context.CollectionFactory().PublishedRepoCollection().ByLocalRepo(repo)
published := collectionFactory.PublishedRepoCollection().ByLocalRepo(repo)
if len(published) > 0 {
fmt.Printf("Local repo `%s` is published currently:\n", repo.Name)
for _, repo := range published {
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.CollectionFactory())
err = collectionFactory.PublishedRepoCollection().LoadComplete(repo, collectionFactory)
if err != nil {
return fmt.Errorf("unable to load published: %s", err)
}
@@ -34,9 +36,9 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: local repo is published")
}
force := context.flags.Lookup("force").Value.Get().(bool)
force := context.Flags().Lookup("force").Value.Get().(bool)
if !force {
snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo)
snapshots := collectionFactory.SnapshotCollection().ByLocalRepoSource(repo)
if len(snapshots) > 0 {
fmt.Printf("Local repo `%s` was used to create following snapshots:\n", repo.Name)
@@ -48,7 +50,7 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
}
}
err = context.CollectionFactory().LocalRepoCollection().Drop(repo)
err = collectionFactory.LocalRepoCollection().Drop(repo)
if err != nil {
return fmt.Errorf("unable to drop: %s", err)
}
+34 -15
View File
@@ -2,6 +2,9 @@ package cmd
import (
"fmt"
"github.com/AlekSi/pointer"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/smira/flag"
)
@@ -10,32 +13,47 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error {
var err error
if len(args) != 1 {
cmd.Usage()
return err
return commander.ErrCommandError
}
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(args[0])
collectionFactory := context.NewCollectionFactory()
repo, err := collectionFactory.LocalRepoCollection().ByName(args[0])
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
err = collectionFactory.LocalRepoCollection().LoadComplete(repo)
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
if context.flags.Lookup("comment").Value.String() != "" {
repo.Comment = context.flags.Lookup("comment").Value.String()
var uploadersFile *string
context.Flags().Visit(func(flag *flag.Flag) {
switch flag.Name {
case "comment":
repo.Comment = flag.Value.String()
case "distribution":
repo.DefaultDistribution = flag.Value.String()
case "component":
repo.DefaultComponent = flag.Value.String()
case "uploaders-file":
uploadersFile = pointer.ToString(flag.Value.String())
}
})
if uploadersFile != nil {
if *uploadersFile != "" {
repo.Uploaders, err = deb.NewUploadersFromFile(*uploadersFile)
if err != nil {
return err
}
} else {
repo.Uploaders = nil
}
}
if context.flags.Lookup("distribution").Value.String() != "" {
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
}
if context.flags.Lookup("component").Value.String() != "" {
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
}
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
err = collectionFactory.LocalRepoCollection().Update(repo)
if err != nil {
return fmt.Errorf("unable to edit: %s", err)
}
@@ -50,7 +68,7 @@ func makeCmdRepoEdit() *commander.Command {
UsageLine: "edit <name>",
Short: "edit properties of local repository",
Long: `
Command edit allows to change metadata of local repository:
Command edit allows one to change metadata of local repository:
comment, default distribution and component.
Example:
@@ -63,6 +81,7 @@ Example:
cmd.Flag.String("comment", "", "any text that would be used to described local repository")
cmd.Flag.String("distribution", "", "default distribution when publishing")
cmd.Flag.String("component", "", "default component when publishing")
cmd.Flag.String("uploaders-file", "", "uploaders.json to be used when including .changes into this repository")
return cmd
}

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