Compare commits

...

248 Commits

Author SHA1 Message Date
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
225 changed files with 6410 additions and 1441 deletions
+15 -3
View File
@@ -1,17 +1,20 @@
language: go language: go
go: go:
- 1.2.1 - 1.3.3
- 1.3.1
- tip - tip
env: env:
global: global:
- secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc=" - secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc="
- secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E=" - secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E="
- secure: "OxiVNmre2JzUszwPNNilKDgIqtfX2gnRSsVz6nuySB1uO2yQsOQmKWJ9cVYgH2IB5H8eWXKOhexcSE28kz6TPLRuEcU9fnqKY3uEkdwm7rJfz9lf+7C4bJEUdA1OIzJppjnWUiXxD7CEPL1DlnMZM24eDQYqa/4WKACAgkK53gE="
before_install: before_install:
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -y python-boto - sudo apt-get install -y python-virtualenv graphviz
- virtualenv env
- . env/bin/activate
- pip install boto requests python-swiftclient
install: install:
- make prepare - make prepare
@@ -21,3 +24,12 @@ script: make travis
matrix: matrix:
allow_failures: allow_failures:
- go: tip - go: tip
notifications:
webhooks:
urls:
- "https://webhooks.gitter.im/e/c691da114a41eed6ec45"
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false
+12 -2
View File
@@ -3,5 +3,15 @@ List of contributors, in chronological order:
* Andrey Smirnov (https://github.com/smira) * Andrey Smirnov (https://github.com/smira)
* Sebastien Binet (https://github.com/sbinet) * Sebastien Binet (https://github.com/sbinet)
* Ryan Uber (https://github.com/ryanuber) * Ryan Uber (https://github.com/ryanuber)
* Simon Aquino (https://github.com/simonaquino) * Simon Aquino (https://github.com/queeno)
* Vincent Batoufflet (https://github.com/vbatoufflet) * 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)
+12 -6
View File
@@ -1,27 +1,33 @@
gom 'code.google.com/p/go-uuid/uuid', :commit => '5fac954758f5' 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 => '454bc64fdfa2' gom 'code.google.com/p/gographviz', :commit => '454bc64fdfa2'
gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556' gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556'
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793' gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827' gom 'github.com/AlekSi/pointer', :commit => '5f6d527dae3d678b46fbb20331ddf44e2b841943'
gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1' gom 'github.com/cheggaaa/pb', :commit => '2c1b74620cc58a81ac152ee2d322e28c806d81ed'
gom 'github.com/gin-gonic/gin', :commit => 'b1758d3bfa09e61ddbc1c9a627e936eec6a170de'
gom 'github.com/jlaffaye/ftp', :commit => 'fec71e62e457557fbe85cefc847a048d57815d76'
gom 'github.com/julienschmidt/httprouter', :commit => '46807412fe50aaceb73bb57061c2230fd26a1640'
gom 'github.com/mattn/go-shellwords', :commit => 'c7ca6f94add751566a61cf2199e1de78d4c3eee4' gom 'github.com/mattn/go-shellwords', :commit => 'c7ca6f94add751566a61cf2199e1de78d4c3eee4'
gom 'github.com/mitchellh/goamz/s3', :commit => 'e7664b32019f31fd1bdf33f9e85f28722f700405' gom 'github.com/mitchellh/goamz/s3', :commit => 'e7664b32019f31fd1bdf33f9e85f28722f700405'
gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b' gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b'
gom 'github.com/ncw/swift', :commit => '384ef27c70645e285f8bb9d02276bf654d06027e'
gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47' gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47'
gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107' gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107'
gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa' gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa'
gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'e2fa4e6ac1cc41a73bc9fd467878ecbf65df5cc3' gom 'github.com/syndtr/gosnappy/snappy', :commit => 'ce8acff4829e0c2458a67ead32390ac0a381c862'
gom 'github.com/syndtr/goleveldb/leveldb', :commit => '97e257099d2ab9578151ba85e2641e2cd14d3ca8'
gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b' gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b'
gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1'
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08' gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
gom 'golang.org/x/crypto/ssh/terminal', :commit => 'a7ead6ddf06233883deca151dffaef2effbf498f'
group :test do group :test do
gom 'launchpad.net/gocheck' gom 'gopkg.in/check.v1'
end end
group :development do group :development do
gom 'github.com/golang/lint/golint' gom 'github.com/golang/lint/golint'
gom 'github.com/mattn/goveralls' gom 'github.com/mattn/goveralls'
gom 'github.com/axw/gocov/gocov' gom 'github.com/axw/gocov/gocov'
gom 'code.google.com/p/go.tools/cmd/cover' gom 'golang.org/x/tools/cmd/cover'
end end
+6 -4
View File
@@ -1,6 +1,6 @@
GOVERSION=$(shell go version | awk '{print $$3;}') GOVERSION=$(shell go version | awk '{print $$3;}')
PACKAGES=database deb files http query s3 utils PACKAGES=context database deb files http query swift s3 utils
ALL_PACKAGES=aptly cmd console database deb files http query s3 utils ALL_PACKAGES=api aptly context cmd console database deb files http query swift s3 utils
BINPATH=$(abspath ./_vendor/bin) BINPATH=$(abspath ./_vendor/bin)
GOM_ENVIRONMENT=-test GOM_ENVIRONMENT=-test
PYTHON?=python PYTHON?=python
@@ -36,7 +36,7 @@ coverage: coverage.out
rm -f coverage.out rm -f coverage.out
check: check:
$(GOM) exec go tool vet -all=true -shadow=true $(ALL_PACKAGES:%=./%) $(GOM) exec go tool vet -all=true $(ALL_PACKAGES:%=./%)
$(GOM) exec golint $(ALL_PACKAGES:%=./%) $(GOM) exec golint $(ALL_PACKAGES:%=./%)
install: install:
@@ -67,7 +67,7 @@ package:
(cd root/etc/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/master/aptly) (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 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>" \ 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" --deb-recommends bzip2 -C root/ . -f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" --deb-recommends bzip2 --deb-recommends graphviz -C root/ .
mv aptly_$(VERSION)_*.deb ~ mv aptly_$(VERSION)_*.deb ~
src-package: src-package:
@@ -77,6 +77,8 @@ src-package:
cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin} rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin}
mkdir -p aptly-$(VERSION)/bash_completion.d
(cd aptly-$(VERSION)/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/$(VERSION)/aptly)
tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION) tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
rm -rf aptly-$(VERSION) rm -rf aptly-$(VERSION)
curl -T aptly-$(VERSION)-src.tar.bz2 -usmira:$(BINTRAY_KEY) https://api.bintray.com/content/smira/aptly/aptly/$(VERSION)/$(VERSION)/aptly-$(VERSION)-src.tar.bz2 curl -T aptly-$(VERSION)-src.tar.bz2 -usmira:$(BINTRAY_KEY) https://api.bintray.com/content/smira/aptly/aptly/$(VERSION)/$(VERSION)/aptly-$(VERSION)-src.tar.bz2
+12 -5
View File
@@ -8,8 +8,11 @@ aptly
.. image:: https://coveralls.io/repos/smira/aptly/badge.png?branch=HEAD .. image:: https://coveralls.io/repos/smira/aptly/badge.png?branch=HEAD
:target: https://coveralls.io/r/smira/aptly?branch=HEAD :target: https://coveralls.io/r/smira/aptly?branch=HEAD
.. image:: http://gobuild.io/badge/github.com/smira/aptly/download.png .. image:: https://badges.gitter.im/Join Chat.svg
:target: http://gobuild.io/github.com/smira/aptly :target: https://gitter.im/smira/aptly?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. image:: http://goreportcard.com/badge/gojp/goreportcard
:target: http://goreportcard.com/report/gojp/goreportcard
Aptly is a swiss army knife for Debian repository management. Aptly is a swiss army knife for Debian repository management.
@@ -28,6 +31,7 @@ Aptly features: ("+" means planned features)
* merge two or more snapshots into one * merge two or more snapshots into one
* filter repository by search query, pulling dependencies when required * filter repository by search query, pulling dependencies when required
* publish self-made packages as Debian repositories * publish self-made packages as Debian repositories
* REST API for remote access
* mirror repositories "as-is" (without resigning with user's key) (+) * mirror repositories "as-is" (without resigning with user's key) (+)
* support for yum repositories (+) * support for yum repositories (+)
@@ -44,8 +48,7 @@ To install aptly on Debian/Ubuntu, add new repository to /etc/apt/sources.list::
And import key that is used to sign the release:: And import key that is used to sign the release::
$ gpg --keyserver keys.gnupg.net --recv-keys 2A194991 $ apt-key adv --keyserver keys.gnupg.net --recv-keys E083A3782A194991
$ gpg -a --export 2A194991 | sudo apt-key add -
After that you can install aptly as any other software package:: After that you can install aptly as any other software package::
@@ -55,9 +58,13 @@ After that you can install aptly as any other software package::
Don't worry about squeeze part in repo name: aptly package should work on Debian squeeze+, 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. Ubuntu 10.0+. Package contains aptly binary, man page and bash completion.
If you would like to use nightly builds (unstable), please use following repository::
deb http://repo.aptly.info/ nightly main
Binary executables (depends almost only on libc) are available for download from `Bintray <http://dl.bintray.com/smira/aptly/>`_. Binary executables (depends almost only on libc) are available for download from `Bintray <http://dl.bintray.com/smira/aptly/>`_.
If you have Go environment set up, you can build aptly from source by running (go 1.2+ required):: If you have Go environment set up, you can build aptly from source by running (go 1.3+ required)::
go get -u github.com/mattn/gom go get -u github.com/mattn/gom
mkdir -p $GOPATH/src/github.com/smira/aptly mkdir -p $GOPATH/src/github.com/smira/aptly
+113
View File
@@ -0,0 +1,113 @@
// Package api provides implementation of aptly REST API
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/query"
"sort"
"time"
)
// Lock order acquisition (canonical):
// 1. RemoteRepoCollection
// 2. LocalRepoCollection
// 3. SnapshotCollection
// 4. PublishedRepoCollection
// GET /api/version
func apiVersion(c *gin.Context) {
c.JSON(200, gin.H{"Version": aptly.Version})
}
// Periodically flushes CollectionFactory to free up memory used by collections,
// flushing caches.
//
// Should be run in goroutine!
func cacheFlusher() {
ticker := time.Tick(15 * time.Minute)
for {
<-ticker
// lock everything to eliminate in-progress calls
r := context.CollectionFactory().RemoteRepoCollection()
r.Lock()
defer r.Unlock()
l := context.CollectionFactory().LocalRepoCollection()
l.Lock()
defer l.Unlock()
s := context.CollectionFactory().SnapshotCollection()
s.Lock()
defer s.Unlock()
p := context.CollectionFactory().PublishedRepoCollection()
p.Lock()
defer p.Unlock()
// all collections locked, flush them
context.CollectionFactory().Flush()
}
}
// Common piece of code to show list of packages,
// with searching & details if requested
func showPackages(c *gin.Context, reflist *deb.PackageRefList) {
result := []*deb.Package{}
list, err := deb.NewPackageListFromRefList(reflist, context.CollectionFactory().PackageCollection(), nil)
if err != nil {
c.Fail(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 {
c.Fail(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 {
c.Fail(400, fmt.Errorf("unable to determine list of architectures, please specify explicitly"))
return
}
}
list.PrepareIndex()
list, err = list.Filter([]deb.PackageQuery{q}, withDeps,
nil, context.DependencyOptions(), architecturesList)
if err != nil {
c.Fail(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())
}
}
+185
View File
@@ -0,0 +1,185 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"os"
"path/filepath"
"strings"
)
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")) {
c.Fail(400, fmt.Errorf("wrong dir"))
return false
}
return true
}
// GET /files
func apiFilesListDirs(c *gin.Context) {
list := []string{}
err := filepath.Walk(context.UploadPath(), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == context.UploadPath() {
return nil
}
if info.IsDir() {
list = append(list, filepath.Base(path))
return filepath.SkipDir
}
return nil
})
if err != nil && !os.IsNotExist(err) {
c.Fail(400, err)
return
}
c.JSON(200, list)
}
// POST /files/:dir/
func apiFilesUpload(c *gin.Context) {
if !verifyDir(c) {
return
}
path := filepath.Join(context.UploadPath(), c.Params.ByName("dir"))
err := os.MkdirAll(path, 0777)
if err != nil {
c.Fail(500, err)
return
}
err = c.Request.ParseMultipartForm(10 * 1024 * 1024)
if err != nil {
c.Fail(400, err)
return
}
stored := []string{}
for _, files := range c.Request.MultipartForm.File {
for _, file := range files {
src, err := file.Open()
if err != nil {
c.Fail(500, err)
return
}
defer src.Close()
destPath := filepath.Join(path, filepath.Base(file.Filename))
dst, err := os.Create(destPath)
if err != nil {
c.Fail(500, err)
return
}
defer dst.Close()
_, err = io.Copy(dst, src)
if err != nil {
c.Fail(500, err)
return
}
stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename)))
}
}
c.JSON(200, stored)
}
// GET /files/:dir
func apiFilesListFiles(c *gin.Context) {
if !verifyDir(c) {
return
}
list := []string{}
root := filepath.Join(context.UploadPath(), c.Params.ByName("dir"))
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == root {
return nil
}
list = append(list, filepath.Base(path))
return nil
})
if err != nil {
if os.IsNotExist(err) {
c.Fail(404, err)
} else {
c.Fail(500, err)
}
return
}
c.JSON(200, list)
}
// DELETE /files/:dir
func apiFilesDeleteDir(c *gin.Context) {
if !verifyDir(c) {
return
}
err := os.RemoveAll(filepath.Join(context.UploadPath(), c.Params.ByName("dir")))
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, gin.H{})
}
// DELETE /files/:dir/:name
func apiFilesDeleteFile(c *gin.Context) {
if !verifyDir(c) {
return
}
if !verifyPath(c.Params.ByName("name")) {
c.Fail(400, fmt.Errorf("wrong file"))
return
}
err := os.Remove(filepath.Join(context.UploadPath(), c.Params.ByName("dir"), c.Params.ByName("name")))
if err != nil {
if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) {
c.Fail(500, err)
return
}
}
c.JSON(200, gin.H{})
}
+75
View File
@@ -0,0 +1,75 @@
package api
import (
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"github.com/smira/aptly/deb"
"io"
"mime"
"os"
"os/exec"
)
// GET /api/graph.:ext
func apiGraph(c *gin.Context) {
var (
err error
output []byte
)
ext := c.Params.ByName("ext")
factory := context.CollectionFactory()
factory.RemoteRepoCollection().RLock()
defer factory.RemoteRepoCollection().RUnlock()
factory.LocalRepoCollection().RLock()
defer factory.LocalRepoCollection().RUnlock()
factory.SnapshotCollection().RLock()
defer factory.SnapshotCollection().RUnlock()
factory.PublishedRepoCollection().RLock()
defer factory.PublishedRepoCollection().RUnlock()
graph, err := deb.BuildGraph(factory)
if err != nil {
c.JSON(500, err)
return
}
buf := bytes.NewBufferString(graph.String())
command := exec.Command("dot", "-T"+ext)
command.Stderr = os.Stderr
stdin, err := command.StdinPipe()
if err != nil {
c.Fail(500, err)
return
}
_, err = io.Copy(stdin, buf)
if err != nil {
c.Fail(500, err)
return
}
err = stdin.Close()
if err != nil {
c.Fail(500, err)
return
}
output, err = command.Output()
if err != nil {
c.Fail(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)
}
+16
View File
@@ -0,0 +1,16 @@
package api
import (
"github.com/gin-gonic/gin"
)
// GET /api/packages/:key
func apiPackagesShow(c *gin.Context) {
p, err := context.CollectionFactory().PackageCollection().ByKey([]byte(c.Params.ByName("key")))
if err != nil {
c.Fail(404, err)
return
}
c.JSON(200, p)
}
+336
View File
@@ -0,0 +1,336 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/utils"
"strings"
)
// SigningOptions is a shared between publish API GPG options structure
type SigningOptions struct {
Skip bool
Batch bool
GpgKey string
Keyring string
SecretKeyring string
Passphrase string
PassphraseFile string
}
func getSigner(options *SigningOptions) (utils.Signer, error) {
if options.Skip {
return nil, nil
}
signer := &utils.GpgSigner{}
signer.SetKey(options.GpgKey)
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
signer.SetBatch(options.Batch)
err := signer.Init()
if err != nil {
return nil, err
}
return signer, nil
}
// Replace '_' with '/' and double '__' with single '_'
func parseEscapedPath(path string) string {
result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1)
if result == "" {
result = "."
}
return result
}
// GET /publish
func apiPublishList(c *gin.Context) {
localCollection := context.CollectionFactory().LocalRepoCollection()
localCollection.RLock()
defer localCollection.RUnlock()
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.RLock()
defer snapshotCollection.RUnlock()
collection := context.CollectionFactory().PublishedRepoCollection()
collection.RLock()
defer collection.RUnlock()
result := make([]*deb.PublishedRepo, 0, collection.Len())
err := collection.ForEach(func(repo *deb.PublishedRepo) error {
err := collection.LoadComplete(repo, context.CollectionFactory())
if err != nil {
return err
}
result = append(result, repo)
return nil
})
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, result)
}
// POST /publish/:prefix
func apiPublishRepoOrSnapshot(c *gin.Context) {
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
var b struct {
SourceKind string `binding:"required"`
Sources []struct {
Component string
Name string `binding:"required"`
} `binding:"required"`
Distribution string
Label string
Origin string
ForceOverwrite bool
Architectures []string
Signing SigningOptions
}
if !c.Bind(&b) {
return
}
signer, err := getSigner(&b.Signing)
if err != nil {
c.Fail(500, fmt.Errorf("unable to initialize GPG signer: %s", err))
return
}
if len(b.Sources) == 0 {
c.Fail(400, fmt.Errorf("unable to publish: soures are empty"))
return
}
var components []string
var sources []interface{}
if b.SourceKind == "snapshot" {
var snapshot *deb.Snapshot
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.RLock()
defer snapshotCollection.RUnlock()
for _, source := range b.Sources {
components = append(components, source.Component)
snapshot, err = snapshotCollection.ByName(source.Name)
if err != nil {
c.Fail(404, fmt.Errorf("unable to publish: %s", err))
return
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
c.Fail(500, fmt.Errorf("unable to publish: %s", err))
return
}
sources = append(sources, snapshot)
}
} else if b.SourceKind == "local" {
var localRepo *deb.LocalRepo
localCollection := context.CollectionFactory().LocalRepoCollection()
localCollection.RLock()
defer localCollection.RUnlock()
for _, source := range b.Sources {
components = append(components, source.Component)
localRepo, err = localCollection.ByName(source.Name)
if err != nil {
c.Fail(404, fmt.Errorf("unable to publish: %s", err))
return
}
err = localCollection.LoadComplete(localRepo)
if err != nil {
c.Fail(500, fmt.Errorf("unable to publish: %s", err))
}
sources = append(sources, localRepo)
}
} else {
c.Fail(400, fmt.Errorf("unknown SourceKind"))
return
}
collection := context.CollectionFactory().PublishedRepoCollection()
collection.Lock()
defer collection.Unlock()
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, context.CollectionFactory())
if err != nil {
c.Fail(500, fmt.Errorf("unable to publish: %s", err))
return
}
published.Origin = b.Origin
published.Label = b.Label
duplicate := collection.CheckDuplicate(published)
if duplicate != nil {
context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory())
c.Fail(400, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate))
return
}
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite)
if err != nil {
c.Fail(500, fmt.Errorf("unable to publish: %s", err))
return
}
err = collection.Add(published)
if err != nil {
c.Fail(500, fmt.Errorf("unable to save to DB: %s", err))
}
c.JSON(201, published)
}
// PUT /publish/:prefix/:distribution
func apiPublishUpdateSwitch(c *gin.Context) {
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := c.Params.ByName("distribution")
var b struct {
ForceOverwrite bool
Signing SigningOptions
Snapshots []struct {
Component string `binding:"required"`
Name string `binding:"required"`
}
}
if !c.Bind(&b) {
return
}
signer, err := getSigner(&b.Signing)
if err != nil {
c.Fail(500, fmt.Errorf("unable to initialize GPG signer: %s", err))
return
}
// published.LoadComplete would touch local repo collection
localRepoCollection := context.CollectionFactory().LocalRepoCollection()
localRepoCollection.RLock()
defer localRepoCollection.RUnlock()
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.RLock()
defer snapshotCollection.RUnlock()
collection := context.CollectionFactory().PublishedRepoCollection()
collection.Lock()
defer collection.Unlock()
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
if err != nil {
c.Fail(404, fmt.Errorf("unable to update: %s", err))
return
}
err = collection.LoadComplete(published, context.CollectionFactory())
if err != nil {
c.Fail(500, fmt.Errorf("unable to update: %s", err))
return
}
var updatedComponents []string
if published.SourceKind == "local" {
if len(b.Snapshots) > 0 {
c.Fail(400, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
return
}
updatedComponents = published.Components()
for _, component := range updatedComponents {
published.UpdateLocalRepo(component)
}
} else if published.SourceKind == "snapshot" {
publishedComponents := published.Components()
for _, snapshotInfo := range b.Snapshots {
if !utils.StrSliceHasItem(publishedComponents, snapshotInfo.Component) {
c.Fail(404, fmt.Errorf("component %s is not in published repository", snapshotInfo.Component))
return
}
snapshot, err := snapshotCollection.ByName(snapshotInfo.Name)
if err != nil {
c.Fail(404, err)
return
}
err = snapshotCollection.LoadComplete(snapshot)
if err != nil {
c.Fail(500, err)
return
}
published.UpdateSnapshot(snapshotInfo.Component, snapshot)
updatedComponents = append(updatedComponents, snapshotInfo.Component)
}
} else {
c.Fail(500, fmt.Errorf("unknown published repository type"))
}
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite)
if err != nil {
c.Fail(500, fmt.Errorf("unable to update: %s", err))
}
err = collection.Update(published)
if err != nil {
c.Fail(500, fmt.Errorf("unable to save to DB: %s", err))
}
err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents,
context.GetPublishedStorage(storage), context.CollectionFactory(), nil)
if err != nil {
c.Fail(500, fmt.Errorf("unable to update: %s", err))
}
c.JSON(200, published)
}
// DELETE /publish/:prefix/:distribution
func apiPublishDrop(c *gin.Context) {
param := parseEscapedPath(c.Params.ByName("prefix"))
storage, prefix := deb.ParsePrefix(param)
distribution := c.Params.ByName("distribution")
// published.LoadComplete would touch local repo collection
localRepoCollection := context.CollectionFactory().LocalRepoCollection()
localRepoCollection.RLock()
defer localRepoCollection.RUnlock()
collection := context.CollectionFactory().PublishedRepoCollection()
collection.Lock()
defer collection.Unlock()
err := collection.Remove(context, storage, prefix, distribution,
context.CollectionFactory(), context.Progress())
if err != nil {
c.Fail(500, fmt.Errorf("unable to drop: %s", err))
return
}
c.JSON(200, gin.H{})
}
+370
View File
@@ -0,0 +1,370 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/database"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/utils"
"os"
"path/filepath"
)
// GET /api/repos
func apiReposList(c *gin.Context) {
result := []*deb.LocalRepo{}
collection := context.CollectionFactory().LocalRepoCollection()
collection.RLock()
defer collection.RUnlock()
context.CollectionFactory().LocalRepoCollection().ForEach(func(r *deb.LocalRepo) error {
result = append(result, r)
return nil
})
c.JSON(200, result)
}
// POST /api/repos
func apiReposCreate(c *gin.Context) {
var b struct {
Name string `binding:"required"`
Comment string
DefaultDistribution string
DefaultComponent string
}
if !c.Bind(&b) {
return
}
repo := deb.NewLocalRepo(b.Name, b.Comment)
repo.DefaultComponent = b.DefaultComponent
repo.DefaultDistribution = b.DefaultDistribution
collection := context.CollectionFactory().LocalRepoCollection()
collection.Lock()
defer collection.Unlock()
err := context.CollectionFactory().LocalRepoCollection().Add(repo)
if err != nil {
c.Fail(400, err)
return
}
c.JSON(201, repo)
}
// PUT /api/repos/:name
func apiReposEdit(c *gin.Context) {
var b struct {
Comment string
DefaultDistribution string
DefaultComponent string
}
if !c.Bind(&b) {
return
}
collection := context.CollectionFactory().LocalRepoCollection()
collection.Lock()
defer collection.Unlock()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
if b.Comment != "" {
repo.Comment = b.Comment
}
if b.DefaultDistribution != "" {
repo.DefaultDistribution = b.DefaultDistribution
}
if b.DefaultComponent != "" {
repo.DefaultComponent = b.DefaultComponent
}
err = collection.Update(repo)
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, repo)
}
// GET /api/repos/:name
func apiReposShow(c *gin.Context) {
collection := context.CollectionFactory().LocalRepoCollection()
collection.RLock()
defer collection.RUnlock()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
c.JSON(200, repo)
}
// DELETE /api/repos/:name
func apiReposDrop(c *gin.Context) {
force := c.Request.URL.Query().Get("force") == "1"
collection := context.CollectionFactory().LocalRepoCollection()
collection.Lock()
defer collection.Unlock()
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.RLock()
defer snapshotCollection.RUnlock()
publishedCollection := context.CollectionFactory().PublishedRepoCollection()
publishedCollection.RLock()
defer publishedCollection.RUnlock()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
published := publishedCollection.ByLocalRepo(repo)
if len(published) > 0 {
c.Fail(409, fmt.Errorf("unable to drop, local repo is published"))
return
}
if !force {
snapshots := snapshotCollection.ByLocalRepoSource(repo)
if len(snapshots) > 0 {
c.Fail(409, fmt.Errorf("unable to drop, local repo has snapshots, use ?force=1 to override"))
return
}
}
err = collection.Drop(repo)
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, gin.H{})
}
// GET /api/repos/:name/packages
func apiReposPackagesShow(c *gin.Context) {
collection := context.CollectionFactory().LocalRepoCollection()
collection.RLock()
defer collection.RUnlock()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(repo)
if err != nil {
c.Fail(500, err)
return
}
showPackages(c, repo.RefList())
}
// Handler for both add and delete
func apiReposPackagesAddDelete(c *gin.Context, cb func(list *deb.PackageList, p *deb.Package) error) {
var b struct {
PackageRefs []string
}
if !c.Bind(&b) {
return
}
collection := context.CollectionFactory().LocalRepoCollection()
collection.Lock()
defer collection.Unlock()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(repo)
if err != nil {
c.Fail(500, err)
return
}
list, err := deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), nil)
if err != nil {
c.Fail(500, err)
return
}
// verify package refs and build package list
for _, ref := range b.PackageRefs {
var p *deb.Package
p, err = context.CollectionFactory().PackageCollection().ByKey([]byte(ref))
if err != nil {
if err == database.ErrNotFound {
c.Fail(404, fmt.Errorf("package %s: %s", ref, err))
} else {
c.Fail(500, err)
}
return
}
err = cb(list, p)
if err != nil {
c.Fail(400, err)
return
}
}
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
if err != nil {
c.Fail(500, fmt.Errorf("unable to save: %s", err))
return
}
c.JSON(200, repo)
}
// POST /repos/:name/packages
func apiReposPackagesAdd(c *gin.Context) {
apiReposPackagesAddDelete(c, func(list *deb.PackageList, p *deb.Package) error {
return list.Add(p)
})
}
// DELETE /repos/:name/packages
func apiReposPackagesDelete(c *gin.Context) {
apiReposPackagesAddDelete(c, func(list *deb.PackageList, p *deb.Package) error {
list.Remove(p)
return nil
})
}
// POST /repos/:name/file/:dir/:file
func apiReposPackageFromFile(c *gin.Context) {
// redirect all work to dir method
apiReposPackageFromDir(c)
}
// POST /repos/:name/file/:dir
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
}
fileParam := c.Params.ByName("file")
if fileParam != "" && !verifyPath(fileParam) {
c.Fail(400, fmt.Errorf("wrong file"))
return
}
collection := context.CollectionFactory().LocalRepoCollection()
collection.Lock()
defer collection.Unlock()
repo, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(repo)
if err != nil {
c.Fail(500, err)
return
}
verifier := &utils.GpgVerifier{}
var (
sources []string
packageFiles, failedFiles []string
processedFiles, failedFiles2 []string
reporter = &aptly.RecordingResultReporter{
Warnings: []string{},
AddedLines: []string{},
RemovedLines: []string{},
}
list *deb.PackageList
)
if fileParam == "" {
sources = []string{filepath.Join(context.UploadPath(), c.Params.ByName("dir"))}
} else {
sources = []string{filepath.Join(context.UploadPath(), c.Params.ByName("dir"), c.Params.ByName("file"))}
}
packageFiles, failedFiles, err = deb.CollectPackageFiles(sources, reporter)
if err != nil {
c.Fail(500, fmt.Errorf("unable to collect package files: %s", err))
return
}
list, err = deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), nil)
if err != nil {
c.Fail(500, fmt.Errorf("unable to load packages: %s", err))
return
}
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
context.CollectionFactory().PackageCollection(), reporter)
failedFiles = append(failedFiles, failedFiles2...)
if err != nil {
c.Fail(500, fmt.Errorf("unable to import package files: %s", err))
return
}
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
if err != nil {
c.Fail(500, fmt.Errorf("unable to save: %s", err))
return
}
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(), c.Params.ByName("dir")))
}
if failedFiles == nil {
failedFiles = []string{}
}
c.JSON(200, gin.H{
"Report": reporter,
"FailedFiles": failedFiles,
})
}
+82
View File
@@ -0,0 +1,82 @@
package api
import (
"github.com/gin-gonic/gin"
ctx "github.com/smira/aptly/context"
"net/http"
)
var context *ctx.AptlyContext
// Router returns prebuilt with routes http.Handler
func Router(c *ctx.AptlyContext) http.Handler {
context = c
go cacheFlusher()
router := gin.Default()
router.Use(gin.ErrorLogger())
root := router.Group("/api")
{
root.GET("/version", apiVersion)
}
{
root.GET("/repos", apiReposList)
root.POST("/repos", apiReposCreate)
root.GET("/repos/:name", apiReposShow)
root.PUT("/repos/:name", apiReposEdit)
root.DELETE("/repos/:name", apiReposDrop)
root.GET("/repos/:name/packages", apiReposPackagesShow)
root.POST("/repos/:name/packages", apiReposPackagesAdd)
root.DELETE("/repos/:name/packages", apiReposPackagesDelete)
root.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
root.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
root.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
}
{
root.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
}
{
root.GET("/files", apiFilesListDirs)
root.POST("/files/:dir", apiFilesUpload)
root.GET("/files/:dir", apiFilesListFiles)
root.DELETE("/files/:dir", apiFilesDeleteDir)
root.DELETE("/files/:dir/:name", apiFilesDeleteFile)
}
{
root.GET("/publish", apiPublishList)
root.POST("/publish", apiPublishRepoOrSnapshot)
root.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
root.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
root.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
}
{
root.GET("/snapshots", apiSnapshotsList)
root.POST("/snapshots", apiSnapshotsCreate)
root.PUT("/snapshots/:name", apiSnapshotsUpdate)
root.GET("/snapshots/:name", apiSnapshotsShow)
root.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
root.DELETE("/snapshots/:name", apiSnapshotsDrop)
root.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
}
{
root.GET("/packages/:key", apiPackagesShow)
}
{
root.GET("/graph.:ext", apiGraph)
}
return router
}
+410
View File
@@ -0,0 +1,410 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/smira/aptly/database"
"github.com/smira/aptly/deb"
)
// GET /api/snapshots
func apiSnapshotsList(c *gin.Context) {
SortMethodString := c.Request.URL.Query().Get("sort")
collection := context.CollectionFactory().SnapshotCollection()
collection.RLock()
defer collection.RUnlock()
if SortMethodString == "" {
SortMethodString = "name"
}
result := []*deb.Snapshot{}
collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
result = append(result, snapshot)
return nil
})
c.JSON(200, result)
}
// POST /api/mirrors/:name/snapshots/
func apiSnapshotsCreateFromMirror(c *gin.Context) {
var (
err error
repo *deb.RemoteRepo
snapshot *deb.Snapshot
)
var b struct {
Name string `binding:"required"`
Description string
}
if !c.Bind(&b) {
return
}
collection := context.CollectionFactory().RemoteRepoCollection()
collection.RLock()
defer collection.RUnlock()
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.Lock()
defer snapshotCollection.Unlock()
repo, err = collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = repo.CheckLock()
if err != nil {
c.Fail(409, err)
return
}
err = collection.LoadComplete(repo)
if err != nil {
c.Fail(500, err)
return
}
snapshot, err = deb.NewSnapshotFromRepository(b.Name, repo)
if err != nil {
c.Fail(400, err)
return
}
if b.Description != "" {
snapshot.Description = b.Description
}
err = snapshotCollection.Add(snapshot)
if err != nil {
c.Fail(400, err)
return
}
c.JSON(201, snapshot)
}
// POST /api/snapshots
func apiSnapshotsCreate(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
)
var b struct {
Name string `binding:"required"`
Description string
SourceSnapshots []string
PackageRefs []string
}
if !c.Bind(&b) {
return
}
if b.Description == "" {
if len(b.SourceSnapshots)+len(b.PackageRefs) == 0 {
b.Description = "Created as empty"
}
}
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.Lock()
defer snapshotCollection.Unlock()
sources := make([]*deb.Snapshot, len(b.SourceSnapshots))
for i := range b.SourceSnapshots {
sources[i], err = snapshotCollection.ByName(b.SourceSnapshots[i])
if err != nil {
c.Fail(404, err)
return
}
err = snapshotCollection.LoadComplete(sources[i])
if err != nil {
c.Fail(500, err)
return
}
}
list := deb.NewPackageList()
// verify package refs and build package list
for _, ref := range b.PackageRefs {
var p *deb.Package
p, err = context.CollectionFactory().PackageCollection().ByKey([]byte(ref))
if err != nil {
if err == database.ErrNotFound {
c.Fail(404, fmt.Errorf("package %s: %s", ref, err))
} else {
c.Fail(500, err)
}
return
}
err = list.Add(p)
if err != nil {
c.Fail(400, err)
return
}
}
snapshot = deb.NewSnapshotFromRefList(b.Name, sources, deb.NewPackageRefListFromPackageList(list), b.Description)
err = snapshotCollection.Add(snapshot)
if err != nil {
c.Fail(400, err)
return
}
c.JSON(201, snapshot)
}
// POST /api/repos/:name/snapshots
func apiSnapshotsCreateFromRepository(c *gin.Context) {
var (
err error
repo *deb.LocalRepo
snapshot *deb.Snapshot
)
var b struct {
Name string `binding:"required"`
Description string
}
if !c.Bind(&b) {
return
}
collection := context.CollectionFactory().LocalRepoCollection()
collection.RLock()
defer collection.RUnlock()
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.Lock()
defer snapshotCollection.Unlock()
repo, err = collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(repo)
if err != nil {
c.Fail(500, err)
return
}
snapshot, err = deb.NewSnapshotFromLocalRepo(b.Name, repo)
if err != nil {
c.Fail(400, err)
return
}
if b.Description != "" {
snapshot.Description = b.Description
}
err = snapshotCollection.Add(snapshot)
if err != nil {
c.Fail(400, err)
return
}
c.JSON(201, snapshot)
}
// PUT /api/snapshots/:name
func apiSnapshotsUpdate(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
)
var b struct {
Name string
Description string
}
if !c.Bind(&b) {
return
}
collection := context.CollectionFactory().SnapshotCollection()
collection.Lock()
defer collection.Unlock()
snapshot, err = collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
_, err = collection.ByName(b.Name)
if err == nil {
c.Fail(409, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name))
return
}
if b.Name != "" {
snapshot.Name = b.Name
}
if b.Description != "" {
snapshot.Description = b.Description
}
err = context.CollectionFactory().SnapshotCollection().Update(snapshot)
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, snapshot)
}
// GET /api/snapshots/:name
func apiSnapshotsShow(c *gin.Context) {
collection := context.CollectionFactory().SnapshotCollection()
collection.RLock()
defer collection.RUnlock()
snapshot, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(snapshot)
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, snapshot)
}
// DELETE /api/snapshots/:name
func apiSnapshotsDrop(c *gin.Context) {
name := c.Params.ByName("name")
force := c.Request.URL.Query().Get("force") == "1"
snapshotCollection := context.CollectionFactory().SnapshotCollection()
snapshotCollection.Lock()
defer snapshotCollection.Unlock()
publishedCollection := context.CollectionFactory().PublishedRepoCollection()
publishedCollection.RLock()
defer publishedCollection.RUnlock()
snapshot, err := snapshotCollection.ByName(name)
if err != nil {
c.Fail(404, err)
return
}
published := publishedCollection.BySnapshot(snapshot)
if len(published) > 0 {
c.Fail(409, fmt.Errorf("unable to drop: snapshot is published"))
return
}
if !force {
snapshots := snapshotCollection.BySnapshotSource(snapshot)
if len(snapshots) > 0 {
c.Fail(409, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override"))
return
}
}
err = snapshotCollection.Drop(snapshot)
if err != nil {
c.Fail(500, err)
return
}
c.JSON(200, gin.H{})
}
// GET /api/snapshots/:name/diff/:withSnapshot
func apiSnapshotsDiff(c *gin.Context) {
onlyMatching := c.Request.URL.Query().Get("onlyMatching") == "1"
collection := context.CollectionFactory().SnapshotCollection()
collection.RLock()
defer collection.RUnlock()
snapshotA, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(snapshotA)
if err != nil {
c.Fail(500, err)
return
}
err = collection.LoadComplete(snapshotB)
if err != nil {
c.Fail(500, err)
return
}
// Calculate diff
diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), context.CollectionFactory().PackageCollection())
if err != nil {
c.Fail(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)
}
// GET /api/snapshots/:name/packages
func apiSnapshotsSearchPackages(c *gin.Context) {
collection := context.CollectionFactory().SnapshotCollection()
collection.RLock()
defer collection.RUnlock()
snapshot, err := collection.ByName(c.Params.ByName("name"))
if err != nil {
c.Fail(404, err)
return
}
err = collection.LoadComplete(snapshot)
if err != nil {
c.Fail(500, err)
return
}
showPackages(c, snapshot.RefList())
}
+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...))
}
+1 -1
View File
@@ -1,7 +1,7 @@
package aptly package aptly
// Version of aptly // Version of aptly
const Version = "0.8" const Version = "0.9.1"
// Enable debugging features? // Enable debugging features?
const EnableDebug = false const EnableDebug = 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(),
},
}
}
+52
View File
@@ -0,0 +1,52 @@
package cmd
import (
"fmt"
"github.com/smira/aptly/api"
"github.com/smira/commander"
"github.com/smira/flag"
"net/http"
)
func aptlyAPIServe(cmd *commander.Command, args []string) error {
var (
err error
)
if len(args) != 0 {
cmd.Usage()
return commander.ErrCommandError
}
listen := context.Flags().Lookup("listen").Value.String()
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
err = http.ListenAndServe(listen, api.Router(context))
if err != nil {
return fmt.Errorf("unable to serve: %s", err)
}
return err
}
func makeCmdAPIServe() *commander.Command {
cmd := &commander.Command{
Run: aptlyAPIServe,
UsageLine: "serve",
Short: "start API HTTP service",
Long: `
Stat HTTP server with aptly REST API.
Example:
$ aptly api serve -listen=:8080
`,
Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError),
}
cmd.Flag.String("listen", ":8080", "host:port for HTTP listening")
return cmd
}
+3 -2
View File
@@ -65,17 +65,18 @@ fine-grained changes in repository contents to transition your
package environment to new version.`, package environment to new version.`,
Flag: *flag.NewFlagSet("aptly", flag.ExitOnError), Flag: *flag.NewFlagSet("aptly", flag.ExitOnError),
Subcommands: []*commander.Command{ Subcommands: []*commander.Command{
makeCmdConfig(),
makeCmdDb(), makeCmdDb(),
makeCmdGraph(), makeCmdGraph(),
makeCmdMirror(), makeCmdMirror(),
makeCmdRepo(), makeCmdRepo(),
makeCmdServe(), makeCmdServe(),
makeCmdSnapshot(), makeCmdSnapshot(),
// Disabled on no docs makeCmdTask(),
//makeCmdTask(),
makeCmdPublish(), makeCmdPublish(),
makeCmdVersion(), makeCmdVersion(),
makeCmdPackage(), makeCmdPackage(),
makeCmdAPI(),
}, },
} }
+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(),
},
}
}
+38
View File
@@ -0,0 +1,38 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/smira/commander"
)
func aptlyConfigShow(cmd *commander.Command, args []string) error {
config := context.Config()
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
`,
}
return cmd
}
+6 -352
View File
@@ -1,313 +1,20 @@
package cmd package cmd
import ( import (
"fmt" ctx "github.com/smira/aptly/context"
"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/s3"
"github.com/smira/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"time"
) )
// AptlyContext is a common context shared by all commands var context *ctx.AptlyContext
type AptlyContext struct {
flags, globalFlags *flag.FlagSet
configLoaded bool
progress aptly.Progress
downloader aptly.Downloader
database database.Storage
packagePool aptly.PackagePool
publishedStorages map[string]aptly.PublishedStorage
collectionFactory *deb.CollectionFactory
dependencyOptions int
architecturesList []string
// Debug features
fileCPUProfile *os.File
fileMemProfile *os.File
fileMemStats *os.File
}
var context *AptlyContext
// Check interface
var _ aptly.PublishedStorageProvider = &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) {
returnCode := 1
if err == commander.ErrFlagError || err == commander.ErrCommandError {
returnCode = 2
}
panic(&FatalError{ReturnCode: returnCode, Message: err.Error()})
}
// Config loads and returns current configuration
func (context *AptlyContext) Config() *utils.ConfigStructure {
if !context.configLoaded {
var err error
configLocation := context.globalFlags.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 LookupOption(context.Config().DepFollowSuggests, context.globalFlags, "dep-follow-suggests") {
context.dependencyOptions |= deb.DepFollowSuggests
}
if LookupOption(context.Config().DepFollowRecommends, context.globalFlags, "dep-follow-recommends") {
context.dependencyOptions |= deb.DepFollowRecommends
}
if LookupOption(context.Config().DepFollowAllVariants, context.globalFlags, "dep-follow-all-variants") {
context.dependencyOptions |= deb.DepFollowAllVariants
}
if LookupOption(context.Config().DepFollowSource, context.globalFlags, "dep-follow-source") {
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.globalFlags.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 {
var downloadLimit int64
limitFlag := context.flags.Lookup("download-limit")
if limitFlag != nil {
downloadLimit = limitFlag.Value.Get().(int64)
}
if downloadLimit == 0 {
downloadLimit = context.Config().DownloadLimit
}
context.downloader = http.NewDownloader(context.Config().DownloadConcurrency,
downloadLimit*1024, 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
}
// CloseDatabase closes the db temporarily
func (context *AptlyContext) CloseDatabase() error {
if context.database == nil {
return nil
}
return context.database.Close()
}
// ReOpenDatabase reopens the db after close
func (context *AptlyContext) ReOpenDatabase() error {
if context.database == nil {
return nil
}
const MaxTries = 10
const Delay = 10 * time.Second
for try := 0; try < MaxTries; try++ {
err := context.database.ReOpen()
if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 {
return err
}
context.Progress().Printf("Unable to reopen database, sleeping %s\n", Delay)
<-time.After(Delay)
}
return fmt.Errorf("unable to reopen the DB, maximum number of retries reached")
}
// 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
}
// GetPublishedStorage returns instance of PublishedStorage
func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage {
publishedStorage, ok := context.publishedStorages[name]
if !ok {
if name == "" {
publishedStorage = files.NewPublishedStorage(context.Config().RootDir)
} else if strings.HasPrefix(name, "s3:") {
params, ok := context.Config().S3PublishRoots[name[3:]]
if !ok {
Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:]))
}
var err error
publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey,
params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
params.EncryptionMethod, params.PlusWorkaround)
if err != nil {
Fatal(err)
}
} else {
Fatal(fmt.Errorf("unknown published storage format: %v", name))
}
context.publishedStorages[name] = publishedStorage
}
return publishedStorage
}
// UpdateFlags sets internal copy of flags in the context
func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) {
context.flags = flags
}
// ShutdownContext shuts context down // ShutdownContext shuts context down
func ShutdownContext() { func ShutdownContext() {
if aptly.EnableDebug { context.Shutdown()
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()
context.database = nil
}
if context.downloader != nil {
context.downloader.Abort()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
} }
// CleanupContext does partial shutdown of context // CleanupContext does partial shutdown of context
func CleanupContext() { func CleanupContext() {
if context.downloader != nil { context.Cleanup()
context.downloader.Shutdown()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
} }
// InitContext initializes context with default settings // InitContext initializes context with default settings
@@ -318,60 +25,7 @@ func InitContext(flags *flag.FlagSet) error {
panic("context already initialized") panic("context already initialized")
} }
context = &AptlyContext{ context, err = ctx.NewContext(flags)
flags: flags,
globalFlags: flags,
dependencyOptions: -1,
publishedStorages: map[string]aptly.PublishedStorage{},
}
if aptly.EnableDebug { return err
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
}
}
}()
}
}
return nil
} }
+22 -4
View File
@@ -20,14 +20,14 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
// collect information about references packages... // collect information about references packages...
existingPackageRefs := deb.NewPackageRefList() existingPackageRefs := deb.NewPackageRefList()
context.Progress().Printf("Loading mirrors, local repos and snapshots...\n") context.Progress().Printf("Loading mirrors, local repos, snapshots and published repos...\n")
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error { err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo) err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
if err != nil { if err != nil {
return err return err
} }
if repo.RefList() != nil { if repo.RefList() != nil {
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false) existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
} }
return nil return nil
}) })
@@ -41,7 +41,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
return err return err
} }
if repo.RefList() != nil { if repo.RefList() != nil {
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false) existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
} }
return nil return nil
}) })
@@ -54,7 +54,25 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false) existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false, true)
return nil
})
if err != nil {
return err
}
err = context.CollectionFactory().PublishedRepoCollection().ForEach(func(published *deb.PublishedRepo) error {
if published.SourceKind != "local" {
return nil
}
err := context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory())
if err != nil {
return err
}
for _, component := range published.Components() {
existingPackageRefs = existingPackageRefs.Merge(published.RefList(component), false, true)
}
return nil return nil
}) })
if err != nil { if err != nil {
+4 -116
View File
@@ -2,7 +2,6 @@ package cmd
import ( import (
"bytes" "bytes"
"code.google.com/p/gographviz"
"fmt" "fmt"
"github.com/smira/aptly/deb" "github.com/smira/aptly/deb"
"github.com/smira/commander" "github.com/smira/commander"
@@ -10,7 +9,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"strings"
) )
func aptlyGraph(cmd *commander.Command, args []string) error { func aptlyGraph(cmd *commander.Command, args []string) error {
@@ -21,121 +19,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
graph := gographviz.NewEscape()
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", repo.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "darkgoldenrod1",
"label": 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
}
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", repo.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "mediumseagreen",
"label": 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", snapshot.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "cadetblue1",
"label": 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(uuid, 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", repo.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "darkolivegreen1",
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution,
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")),
})
for _, uuid := range repo.Sources {
_, exists := existingNodes[uuid]
if exists {
graph.AddEdge(uuid, repo.UUID, true, nil)
}
}
return nil
})
fmt.Printf("Generating graph...\n") fmt.Printf("Generating graph...\n")
graph, err := deb.BuildGraph(context.CollectionFactory())
if err != nil {
return err
}
buf := bytes.NewBufferString(graph.String()) buf := bytes.NewBufferString(graph.String())
+7 -5
View File
@@ -16,8 +16,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.flags, "with-sources") downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
downloadUdebs := context.flags.Lookup("with-udebs").Value.Get().(bool) downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
var ( var (
mirrorName, archiveURL, distribution string mirrorName, archiveURL, distribution string
@@ -40,8 +40,9 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to create mirror: %s", err) return fmt.Errorf("unable to create mirror: %s", err)
} }
repo.Filter = context.flags.Lookup("filter").Value.String() repo.Filter = context.Flags().Lookup("filter").Value.String()
repo.FilterWithDeps = context.flags.Lookup("filter-with-deps").Value.Get().(bool) repo.FilterWithDeps = context.Flags().Lookup("filter-with-deps").Value.Get().(bool)
repo.SkipComponentCheck = context.Flags().Lookup("force-components").Value.Get().(bool)
if repo.Filter != "" { if repo.Filter != "" {
_, err = query.Parse(repo.Filter) _, err = query.Parse(repo.Filter)
@@ -50,7 +51,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
} }
} }
verifier, err := getVerifier(context.flags) verifier, err := getVerifier(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err) return fmt.Errorf("unable to initialize GPG verifier: %s", err)
} }
@@ -95,6 +96,7 @@ Example:
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)") cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
cmd.Flag.String("filter", "", "filter packages in mirror") cmd.Flag.String("filter", "", "filter packages in mirror")
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well") 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.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
return cmd return cmd
+1 -1
View File
@@ -25,7 +25,7 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: %s", err) return fmt.Errorf("unable to drop: %s", err)
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo) snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo)
+2 -2
View File
@@ -24,7 +24,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to edit: %s", err) return fmt.Errorf("unable to edit: %s", err)
} }
context.flags.Visit(func(flag *flag.Flag) { context.Flags().Visit(func(flag *flag.Flag) {
switch flag.Name { switch flag.Name {
case "filter": case "filter":
repo.Filter = flag.Value.String() repo.Filter = flag.Value.String()
@@ -48,7 +48,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
} }
} }
if context.globalFlags.Lookup("architectures").Value.String() != "" { if context.GlobalFlags().Lookup("architectures").Value.String() != "" {
repo.Architectures = context.ArchitecturesList() repo.Architectures = context.ArchitecturesList()
err = repo.Fetch(context.Downloader(), nil) err = repo.Fetch(context.Downloader(), nil)
+2
View File
@@ -28,6 +28,8 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
return nil return nil
}) })
context.CloseDatabase()
sort.Strings(repos) sort.Strings(repos)
if raw { if raw {
+1 -1
View File
@@ -66,7 +66,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
fmt.Printf("%s: %s\n", k, repo.Meta[k]) 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 withPackages {
if repo.LastDownloadDate.IsZero() { if repo.LastDownloadDate.IsZero() {
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n") fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
+3 -3
View File
@@ -31,7 +31,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to update: %s", err)
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
err = repo.CheckLock() err = repo.CheckLock()
if err != nil { if err != nil {
@@ -39,9 +39,9 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
} }
} }
ignoreMismatch := context.flags.Lookup("ignore-checksums").Value.Get().(bool) ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
verifier, err := getVerifier(context.flags) verifier, err := getVerifier(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err) return fmt.Errorf("unable to initialize GPG verifier: %s", err)
} }
+4
View File
@@ -21,6 +21,10 @@ func aptlyPackageSearch(cmd *commander.Command, args []string) error {
} }
result := q.Query(context.CollectionFactory().PackageCollection()) result := q.Query(context.CollectionFactory().PackageCollection())
if result.Len() == 0 {
return fmt.Errorf("no results")
}
result.ForEach(func(p *deb.Package) error { result.ForEach(func(p *deb.Package) error {
context.Progress().Printf("%s\n", p) context.Progress().Printf("%s\n", p)
return nil return nil
+4 -4
View File
@@ -72,15 +72,15 @@ func aptlyPackageShow(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to show: %s", err) return fmt.Errorf("unable to show: %s", err)
} }
withFiles := context.flags.Lookup("with-files").Value.Get().(bool) withFiles := context.Flags().Lookup("with-files").Value.Get().(bool)
withReferences := context.flags.Lookup("with-references").Value.Get().(bool) withReferences := context.Flags().Lookup("with-references").Value.Get().(bool)
w := bufio.NewWriter(os.Stdout) w := bufio.NewWriter(os.Stdout)
result := q.Query(context.CollectionFactory().PackageCollection()) result := q.Query(context.CollectionFactory().PackageCollection())
err = result.ForEach(func(p *deb.Package) error { err = result.ForEach(func(p *deb.Package) error {
p.Stanza().WriteTo(w) p.Stanza().WriteTo(w, p.IsSource, false)
w.Flush() w.Flush()
fmt.Printf("\n") fmt.Printf("\n")
@@ -116,7 +116,7 @@ func makeCmdPackageShow() *commander.Command {
cmd := &commander.Command{ cmd := &commander.Command{
Run: aptlyPackageShow, Run: aptlyPackageShow,
UsageLine: "show <package-query>", UsageLine: "show <package-query>",
Short: "show details about packages matcing query", Short: "show details about packages matching query",
Long: ` Long: `
Command shows displays detailed meta-information about packages Command shows displays detailed meta-information about packages
matching query. Information from Debian control file is displayed. matching query. Information from Debian control file is displayed.
+1 -15
View File
@@ -4,7 +4,6 @@ import (
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"strings"
) )
func getSigner(flags *flag.FlagSet) (utils.Signer, error) { func getSigner(flags *flag.FlagSet) (utils.Signer, error) {
@@ -16,6 +15,7 @@ func getSigner(flags *flag.FlagSet) (utils.Signer, error) {
signer.SetKey(flags.Lookup("gpg-key").Value.String()) signer.SetKey(flags.Lookup("gpg-key").Value.String())
signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String()) 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.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
signer.SetBatch(flags.Lookup("batch").Value.Get().(bool))
err := signer.Init() err := signer.Init()
if err != nil { if err != nil {
@@ -26,20 +26,6 @@ func getSigner(flags *flag.FlagSet) (utils.Signer, error) {
} }
func parsePrefix(param string) (storage, prefix string) {
i := strings.LastIndex(param, ":")
if i != -1 {
storage = param[:i]
prefix = param[i+1:]
if prefix == "" {
prefix = "."
}
} else {
prefix = param
}
return
}
func makeCmdPublish() *commander.Command { func makeCmdPublish() *commander.Command {
return &commander.Command{ return &commander.Command{
UsageLine: "publish", UsageLine: "publish",
+2 -1
View File
@@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/smira/aptly/deb"
"github.com/smira/commander" "github.com/smira/commander"
) )
@@ -19,7 +20,7 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error {
param = args[1] param = args[1]
} }
storage, prefix := parsePrefix(param) storage, prefix := deb.ParsePrefix(param)
err = context.CollectionFactory().PublishedRepoCollection().Remove(context, storage, prefix, distribution, err = context.CollectionFactory().PublishedRepoCollection().Remove(context, storage, prefix, distribution,
context.CollectionFactory(), context.Progress()) context.CollectionFactory(), context.Progress())
+2
View File
@@ -36,6 +36,8 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to load list of repos: %s", err) return fmt.Errorf("unable to load list of repos: %s", err)
} }
context.CloseDatabase()
sort.Strings(published) sort.Strings(published)
if raw { if raw {
+1
View File
@@ -39,6 +39,7 @@ Example:
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)")
cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-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-signing", false, "don't sign Release files with GPG")
cmd.Flag.String("origin", "", "origin name to publish") cmd.Flag.String("origin", "", "origin name to publish")
cmd.Flag.String("label", "", "label to publish") cmd.Flag.String("label", "", "label to publish")
+6 -5
View File
@@ -13,7 +13,7 @@ import (
func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
var err error var err error
components := strings.Split(context.flags.Lookup("component").Value.String(), ",") components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(args) < len(components) || len(args) > len(components)+1 { if len(args) < len(components) || len(args) > len(components)+1 {
cmd.Usage() cmd.Usage()
@@ -27,7 +27,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
} else { } else {
param = "" param = ""
} }
storage, prefix := parsePrefix(param) storage, prefix := deb.ParsePrefix(param)
var ( var (
sources = []interface{}{} sources = []interface{}{}
@@ -110,7 +110,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
panic("unknown command") panic("unknown command")
} }
distribution := context.flags.Lookup("distribution").Value.String() distribution := context.Flags().Lookup("distribution").Value.String()
published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, context.CollectionFactory()) published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, context.CollectionFactory())
if err != nil { if err != nil {
@@ -125,12 +125,12 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate) 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 { if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err) return fmt.Errorf("unable to initialize GPG signer: %s", err)
} }
forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite { if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n") "the same package pool.\n")
@@ -201,6 +201,7 @@ Example:
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)")
cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-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-signing", false, "don't sign Release files with GPG")
cmd.Flag.String("origin", "", "origin name to publish") cmd.Flag.String("origin", "", "origin name to publish")
cmd.Flag.String("label", "", "label to publish") cmd.Flag.String("label", "", "label to publish")
+16 -7
View File
@@ -3,6 +3,7 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/smira/aptly/deb" "github.com/smira/aptly/deb"
"github.com/smira/aptly/utils"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"strings" "strings"
@@ -11,7 +12,7 @@ import (
func aptlyPublishSwitch(cmd *commander.Command, args []string) error { func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
var err error var err error
components := strings.Split(context.flags.Lookup("component").Value.String(), ",") components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(args) < len(components)+1 || len(args) > len(components)+2 { if len(args) < len(components)+1 || len(args) > len(components)+2 {
cmd.Usage() cmd.Usage()
@@ -33,7 +34,7 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
names = args[1:] names = args[1:]
} }
storage, prefix := parsePrefix(param) storage, prefix := deb.ParsePrefix(param)
var published *deb.PublishedRepo var published *deb.PublishedRepo
@@ -61,6 +62,10 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
} }
for i, component := range components { for i, component := range components {
if !utils.StrSliceHasItem(publishedComponents, component) {
return fmt.Errorf("unable to switch: component %s is not in published repository", component)
}
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(names[i]) snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(names[i])
if err != nil { if err != nil {
return fmt.Errorf("unable to switch: %s", err) return fmt.Errorf("unable to switch: %s", err)
@@ -74,12 +79,12 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
published.UpdateSnapshot(component, snapshot) published.UpdateSnapshot(component, snapshot)
} }
signer, err := getSigner(context.flags) signer, err := getSigner(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err) return fmt.Errorf("unable to initialize GPG signer: %s", err)
} }
forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite { if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n") "the same package pool.\n")
@@ -112,7 +117,7 @@ func makeCmdPublishSwitch() *commander.Command {
UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-snapshot>", UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-snapshot>",
Short: "update published repository by switching to new snapshot", Short: "update published repository by switching to new snapshot",
Long: ` Long: `
Command switches in-place published repository with new snapshot contents. All Command switches in-place published snapshots with new snapshot contents. All
publishing parameters are preserved (architecture list, distribution, publishing parameters are preserved (architecture list, distribution,
component). component).
@@ -120,11 +125,14 @@ For multiple component repositories, flag -component should be given with
list of components to update. Corresponding snapshots should be given in the list of components to update. Corresponding snapshots should be given in the
same order, e.g.: same order, e.g.:
aptly publish update -component=main,contrib wheezy wh-main wh-contrib aptly publish switch -component=main,contrib wheezy wh-main wh-contrib
Example: 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), Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
} }
@@ -133,6 +141,7 @@ Example:
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)")
cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-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-signing", false, "don't sign Release files with GPG")
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)") 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.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
+4 -3
View File
@@ -20,7 +20,7 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
if len(args) == 2 { if len(args) == 2 {
param = args[1] param = args[1]
} }
storage, prefix := parsePrefix(param) storage, prefix := deb.ParsePrefix(param)
var published *deb.PublishedRepo var published *deb.PublishedRepo
@@ -43,12 +43,12 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
published.UpdateLocalRepo(component) published.UpdateLocalRepo(component)
} }
signer, err := getSigner(context.flags) signer, err := getSigner(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err) return fmt.Errorf("unable to initialize GPG signer: %s", err)
} }
forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite { if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n") "the same package pool.\n")
@@ -100,6 +100,7 @@ Example:
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)")
cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-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-signing", false, "don't sign Release files with GPG")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
+13 -144
View File
@@ -2,14 +2,12 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/deb" "github.com/smira/aptly/deb"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
"github.com/smira/commander" "github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"os" "os"
"path/filepath"
"sort"
"strings"
) )
func aptlyRepoAdd(cmd *commander.Command, args []string) error { func aptlyRepoAdd(cmd *commander.Command, args []string) error {
@@ -40,151 +38,22 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to load packages: %s", err) return fmt.Errorf("unable to load packages: %s", err)
} }
forceReplace := context.flags.Lookup("force-replace").Value.Get().(bool) forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
packageFiles := []string{} var packageFiles, failedFiles []string
failedFiles := []string{}
for _, location := range args[1:] { packageFiles, failedFiles, err = deb.CollectPackageFiles(args[1:], &aptly.ConsoleResultReporter{Progress: context.Progress()})
info, err2 := os.Stat(location) if err != nil {
if err2 != nil { return fmt.Errorf("unable to collect package files: %s", err)
context.Progress().ColoredPrintf("@y[!]@| @!Unable to process %s: %s@|", location, err2)
failedFiles = append(failedFiles, location)
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
}
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
strings.HasSuffix(info.Name(), ".dsc") {
packageFiles = append(packageFiles, path)
}
return nil
})
} else {
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
strings.HasSuffix(info.Name(), ".dsc") {
packageFiles = append(packageFiles, location)
} else {
context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location)
failedFiles = append(failedFiles, location)
continue
}
}
} }
processedFiles := []string{} var processedFiles, failedFiles2 []string
sort.Strings(packageFiles)
if forceReplace { processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
list.PrepareIndex() context.CollectionFactory().PackageCollection(), &aptly.ConsoleResultReporter{Progress: context.Progress()})
} failedFiles = append(failedFiles, failedFiles2...)
if err != nil {
for _, file := range packageFiles { return fmt.Errorf("unable to import package files: %s", err)
var (
stanza deb.Stanza
p *deb.Package
)
candidateProcessedFiles := []string{}
isSourcePackage := strings.HasSuffix(file, ".dsc")
isUdebPackage := strings.HasSuffix(file, ".udeb")
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)
if isUdebPackage {
p = deb.NewUdebPackageFromControlFile(stanza)
} else {
p = deb.NewPackageFromControlFile(stanza)
}
}
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to read file %s: %s@|", file, err)
failedFiles = append(failedFiles, file)
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)
failedFiles = append(failedFiles, file)
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)
failedFiles = append(failedFiles, file)
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)
failedFiles = append(failedFiles, file)
continue
}
if forceReplace {
conflictingPackages := list.Search(deb.Dependency{Pkg: p.Name, Version: p.Version, Architecture: p.Architecture}, true)
for _, cp := range conflictingPackages {
context.Progress().ColoredPrintf("@r[-]@| %s removed due to conflict with package being added", cp)
list.Remove(cp)
}
}
err = list.Add(p)
if err != nil {
context.Progress().ColoredPrintf("@y[!]@| @!Unable to add package to repo %s: %s@|", p, err)
failedFiles = append(failedFiles, file)
continue
}
context.Progress().ColoredPrintf("@g[+]@| %s added@|", p)
processedFiles = append(processedFiles, candidateProcessedFiles...)
} }
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
@@ -194,7 +63,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to save: %s", err) 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) processedFiles = utils.StrSliceDeduplicate(processedFiles)
for _, file := range processedFiles { for _, file := range processedFiles {
+3 -3
View File
@@ -14,9 +14,9 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
repo := deb.NewLocalRepo(args[0], context.flags.Lookup("comment").Value.String()) repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String())
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String() repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
repo.DefaultComponent = context.flags.Lookup("component").Value.String() repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
err = context.CollectionFactory().LocalRepoCollection().Add(repo) err = context.CollectionFactory().LocalRepoCollection().Add(repo)
if err != nil { if err != nil {
+1 -1
View File
@@ -34,7 +34,7 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: local repo is published") 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 { if !force {
snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo) snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo)
+6 -6
View File
@@ -23,16 +23,16 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to edit: %s", err) return fmt.Errorf("unable to edit: %s", err)
} }
if context.flags.Lookup("comment").Value.String() != "" { if context.Flags().Lookup("comment").Value.String() != "" {
repo.Comment = context.flags.Lookup("comment").Value.String() repo.Comment = context.Flags().Lookup("comment").Value.String()
} }
if context.flags.Lookup("distribution").Value.String() != "" { if context.Flags().Lookup("distribution").Value.String() != "" {
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String() repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
} }
if context.flags.Lookup("component").Value.String() != "" { if context.Flags().Lookup("component").Value.String() != "" {
repo.DefaultComponent = context.flags.Lookup("component").Value.String() repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
} }
err = context.CollectionFactory().LocalRepoCollection().Update(repo) err = context.CollectionFactory().LocalRepoCollection().Update(repo)
+2
View File
@@ -33,6 +33,8 @@ func aptlyRepoList(cmd *commander.Command, args []string) error {
return nil return nil
}) })
context.CloseDatabase()
sort.Strings(repos) sort.Strings(repos)
if raw { if raw {
+2 -2
View File
@@ -87,7 +87,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
var architecturesList []string var architecturesList []string
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
if withDeps { if withDeps {
dstList.PrepareIndex() dstList.PrepareIndex()
@@ -145,7 +145,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to %s: %s", command, err) return fmt.Errorf("unable to %s: %s", command, err)
} }
if context.flags.Lookup("dry-run").Value.Get().(bool) { if context.Flags().Lookup("dry-run").Value.Get().(bool) {
context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n") context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n")
} else { } else {
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList)) dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
+1 -1
View File
@@ -54,7 +54,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
return nil return nil
}) })
if context.flags.Lookup("dry-run").Value.Get().(bool) { if context.Flags().Lookup("dry-run").Value.Get().(bool) {
context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n") context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n")
} else { } else {
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
+1 -1
View File
@@ -31,7 +31,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error {
fmt.Printf("Default Component: %s\n", repo.DefaultComponent) fmt.Printf("Default Component: %s\n", repo.DefaultComponent)
fmt.Printf("Number of packages: %d\n", repo.NumPackages()) fmt.Printf("Number of packages: %d\n", repo.NumPackages())
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages { if withPackages {
ListPackagesRefList(repo.RefList()) ListPackagesRefList(repo.RefList())
} }
+5 -4
View File
@@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
ctx "github.com/smira/aptly/context"
"github.com/smira/commander" "github.com/smira/commander"
) )
@@ -9,7 +10,7 @@ import (
func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) { func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fatal, ok := r.(*FatalError) fatal, ok := r.(*ctx.FatalError)
if !ok { if !ok {
panic(r) panic(r)
} }
@@ -22,13 +23,13 @@ func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode
flags, args, err := cmd.ParseFlags(cmdArgs) flags, args, err := cmd.ParseFlags(cmdArgs)
if err != nil { if err != nil {
Fatal(err) ctx.Fatal(err)
} }
if initContext { if initContext {
err = InitContext(flags) err = InitContext(flags)
if err != nil { if err != nil {
Fatal(err) ctx.Fatal(err)
} }
defer ShutdownContext() defer ShutdownContext()
} }
@@ -37,7 +38,7 @@ func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode
err = cmd.Dispatch(args) err = cmd.Dispatch(args)
if err != nil { if err != nil {
Fatal(err) ctx.Fatal(err)
} }
return return
+1 -1
View File
@@ -27,7 +27,7 @@ func aptlyServe(cmd *commander.Command, args []string) error {
return nil return nil
} }
listen := context.flags.Lookup("listen").Value.String() listen := context.Flags().Lookup("listen").Value.String()
listenHost, listenPort, err := net.SplitHostPort(listen) listenHost, listenPort, err := net.SplitHostPort(listen)
+1 -1
View File
@@ -13,7 +13,7 @@ func aptlySnapshotDiff(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
onlyMatching := context.flags.Lookup("only-matching").Value.Get().(bool) onlyMatching := context.Flags().Lookup("only-matching").Value.Get().(bool)
// Load <name-a> snapshot // Load <name-a> snapshot
snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
+1 -1
View File
@@ -35,7 +35,7 @@ func aptlySnapshotDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: snapshot is published") return fmt.Errorf("unable to drop: snapshot is published")
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot) snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot)
if len(snapshots) > 0 { if len(snapshots) > 0 {
+1 -1
View File
@@ -17,7 +17,7 @@ func aptlySnapshotFilter(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
// Load <source> snapshot // Load <source> snapshot
source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
+12 -62
View File
@@ -4,49 +4,8 @@ import (
"fmt" "fmt"
"github.com/smira/aptly/deb" "github.com/smira/aptly/deb"
"github.com/smira/commander" "github.com/smira/commander"
"sort"
) )
// Snapshot sorting methods
const (
SortName = iota
SortTime
)
type snapshotListToSort struct {
list []*deb.Snapshot
sortMethod int
}
func parseSortMethod(sortMethod string) (int, error) {
switch sortMethod {
case "time", "Time":
return SortTime, nil
case "name", "Name":
return SortName, nil
}
return -1, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
}
func (s snapshotListToSort) Swap(i, j int) {
s.list[i], s.list[j] = s.list[j], s.list[i]
}
func (s snapshotListToSort) Less(i, j int) bool {
switch s.sortMethod {
case SortName:
return s.list[i].Name < s.list[j].Name
case SortTime:
return s.list[i].CreatedAt.Before(s.list[j].CreatedAt)
}
panic("unknown sort method")
}
func (s snapshotListToSort) Len() int {
return len(s.list)
}
func aptlySnapshotList(cmd *commander.Command, args []string) error { func aptlySnapshotList(cmd *commander.Command, args []string) error {
var err error var err error
if len(args) != 0 { if len(args) != 0 {
@@ -57,33 +16,24 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error {
raw := cmd.Flag.Lookup("raw").Value.Get().(bool) raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string) sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string)
snapshotsToSort := &snapshotListToSort{} collection := context.CollectionFactory().SnapshotCollection()
snapshotsToSort.list = make([]*deb.Snapshot, context.CollectionFactory().SnapshotCollection().Len())
snapshotsToSort.sortMethod, err = parseSortMethod(sortMethodString)
if err != nil {
return err
}
i := 0
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
snapshotsToSort.list[i] = snapshot
i++
return nil
})
sort.Sort(snapshotsToSort)
if raw { if raw {
for _, snapshot := range snapshotsToSort.list { collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
fmt.Printf("%s\n", snapshot.Name) fmt.Printf("%s\n", snapshot.Name)
} return nil
})
} else { } else {
if len(snapshotsToSort.list) > 0 { if collection.Len() > 0 {
fmt.Printf("List of snapshots:\n") fmt.Printf("List of snapshots:\n")
for _, snapshot := range snapshotsToSort.list { err = collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
fmt.Printf(" * %s\n", snapshot.String()) fmt.Printf(" * %s\n", snapshot.String())
return nil
})
if err != nil {
return err
} }
fmt.Printf("\nTo get more information about snapshot, run `aptly snapshot show <name>`.\n") fmt.Printf("\nTo get more information about snapshot, run `aptly snapshot show <name>`.\n")
@@ -91,8 +41,8 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error {
fmt.Printf("\nNo snapshots found, create one with `aptly snapshot create...`.\n") fmt.Printf("\nNo snapshots found, create one with `aptly snapshot create...`.\n")
} }
} }
return err
return err
} }
func makeCmdSnapshotList() *commander.Command { func makeCmdSnapshotList() *commander.Command {
+8 -8
View File
@@ -28,22 +28,22 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
} }
} }
latest := context.flags.Lookup("latest").Value.Get().(bool) latest := context.Flags().Lookup("latest").Value.Get().(bool)
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool)
if noRemove && latest { if noRemove && latest {
return fmt.Errorf("-no-remove and -latest can't be specified together") return fmt.Errorf("-no-remove and -latest can't be specified together")
} }
overrideMatching := !latest && !noRemove overrideMatching := !latest && !noRemove
result := sources[0].RefList() result := sources[0].RefList()
for i := 1; i < len(sources); i++ { for i := 1; i < len(sources); i++ {
result = result.Merge(sources[i].RefList(), overrideMatching) result = result.Merge(sources[i].RefList(), overrideMatching, false)
} }
if latest { if latest {
deb.FilterLatestRefs(result) result.FilterLatestRefs()
} }
sourceDescription := make([]string, len(sources)) sourceDescription := make([]string, len(sources))
@@ -84,7 +84,7 @@ Example:
} }
cmd.Flag.Bool("latest", false, "use only the latest version of each package") cmd.Flag.Bool("latest", false, "use only the latest version of each package")
cmd.Flag.Bool("no-remove", false, "don't remove duplicate arch/name packages") cmd.Flag.Bool("no-remove", false, "don't remove duplicate arch/name packages")
return cmd return cmd
} }
+5 -5
View File
@@ -17,9 +17,9 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
noDeps := context.flags.Lookup("no-deps").Value.Get().(bool) noDeps := context.Flags().Lookup("no-deps").Value.Get().(bool)
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool)
allMatches := context.flags.Lookup("all-matches").Value.Get().(bool) allMatches := context.Flags().Lookup("all-matches").Value.Get().(bool)
// Load <name> snapshot // Load <name> snapshot
snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
@@ -91,7 +91,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to parse query: %s", err) return fmt.Errorf("unable to parse query: %s", err)
} }
// Add architecture filter // Add architecture filter
queries[i] = &deb.AndQuery{queries[i], archQuery} queries[i] = &deb.AndQuery{L: queries[i], R: archQuery}
} }
// Filter with dependencies as requested // Filter with dependencies as requested
@@ -129,7 +129,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
}) })
alreadySeen = nil alreadySeen = nil
if context.flags.Lookup("dry-run").Value.Get().(bool) { if context.Flags().Lookup("dry-run").Value.Get().(bool) {
context.Progress().Printf("\nNot creating snapshot, as dry run was requested.\n") context.Progress().Printf("\nNot creating snapshot, as dry run was requested.\n")
} else { } else {
// Create <destination> snapshot // Create <destination> snapshot
+1 -1
View File
@@ -73,7 +73,7 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
return fmt.Errorf("unable to search: %s", err) return fmt.Errorf("unable to search: %s", err)
} }
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
architecturesList := []string{} architecturesList := []string{}
if withDeps { if withDeps {
+1 -1
View File
@@ -30,7 +30,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error {
fmt.Printf("Description: %s\n", snapshot.Description) fmt.Printf("Description: %s\n", snapshot.Description)
fmt.Printf("Number of packages: %d\n", snapshot.NumPackages()) fmt.Printf("Number of packages: %d\n", snapshot.NumPackages())
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages { if withPackages {
ListPackagesRefList(snapshot.RefList()) ListPackagesRefList(snapshot.RefList())
} }
+16 -10
View File
@@ -18,13 +18,15 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error {
var text string var text string
cmdArgs := []string{} cmdArgs := []string{}
if finfo, err := os.Stat(filename); os.IsNotExist(err) || finfo.IsDir() { var finfo os.FileInfo
if finfo, err = os.Stat(filename); os.IsNotExist(err) || finfo.IsDir() {
return fmt.Errorf("no such file, %s\n", filename) return fmt.Errorf("no such file, %s\n", filename)
} }
fmt.Println("Reading file...\n") fmt.Print("Reading file...\n\n")
file, err := os.Open(filename) var file *os.File
file, err = os.Open(filename)
if err != nil { if err != nil {
return err return err
@@ -80,6 +82,10 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error {
for i, command := range cmdList { for i, command := range cmdList {
if !commandErrored { if !commandErrored {
err := context.ReOpenDatabase()
if err != nil {
return fmt.Errorf("failed to reopen DB: %s", err)
}
context.Progress().ColoredPrintf("@g%d) [Running]: %s@!", (i + 1), strings.Join(command, " ")) context.Progress().ColoredPrintf("@g%d) [Running]: %s@!", (i + 1), strings.Join(command, " "))
context.Progress().ColoredPrintf("\n@yBegin command output: ----------------------------@!") context.Progress().ColoredPrintf("\n@yBegin command output: ----------------------------@!")
context.Progress().Flush() context.Progress().Flush()
@@ -129,16 +135,16 @@ func makeCmdTaskRun() *commander.Command {
UsageLine: "run -filename=<filename> | <command1>, <command2>, ...", UsageLine: "run -filename=<filename> | <command1>, <command2>, ...",
Short: "run aptly tasks", Short: "run aptly tasks",
Long: ` Long: `
Command helps origanise multiple aptly commands in one single aptly task, running as single thread. Command helps organise multiple aptly commands in one single aptly task, running as single thread.
Example: Example:
$ aptly task run $ aptly task run
> repo create local > repo create local
> repo add local pkg1 > repo add local pkg1
> publish repo local > publish repo local
> serve > serve
> >
`, `,
} }
+12 -1
View File
@@ -14,6 +14,8 @@ const (
codeHideProgress codeHideProgress
codeStop codeStop
codeFlush codeFlush
codeBarEnabled
codeBarDisabled
) )
type printTask struct { type printTask struct {
@@ -81,6 +83,8 @@ func (p *Progress) InitBar(count int64, isBytes bool) {
p.bar.SetUnits(pb.U_BYTES) p.bar.SetUnits(pb.U_BYTES)
p.bar.ShowSpeed = true p.bar.ShowSpeed = true
} }
p.queue <- printTask{code: codeBarEnabled}
p.bar.Start() p.bar.Start()
} }
} }
@@ -91,6 +95,7 @@ func (p *Progress) ShutdownBar() {
return return
} }
p.bar.Finish() p.bar.Finish()
p.queue <- printTask{code: codeBarDisabled}
p.bar = nil p.bar = nil
p.queue <- printTask{code: codeHideProgress} p.queue <- printTask{code: codeHideProgress}
} }
@@ -151,9 +156,15 @@ func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
} }
func (p *Progress) worker() { func (p *Progress) worker() {
hasBar := false
for { for {
task := <-p.queue task := <-p.queue
switch task.code { switch task.code {
case codeBarEnabled:
hasBar = true
case codeBarDisabled:
hasBar = false
case codePrint: case codePrint:
if p.barShown { if p.barShown {
fmt.Print("\r\033[2K") fmt.Print("\r\033[2K")
@@ -161,7 +172,7 @@ func (p *Progress) worker() {
} }
fmt.Print(task.message) fmt.Print(task.message)
case codeProgress: case codeProgress:
if p.bar != nil { if hasBar {
fmt.Print("\r" + task.message) fmt.Print("\r" + task.message)
p.barShown = true p.barShown = true
} }
+1 -3
View File
@@ -1,9 +1,7 @@
// +build !freebsd
package console package console
import ( import (
"code.google.com/p/go.crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
"syscall" "syscall"
) )
-10
View File
@@ -1,10 +0,0 @@
// +build freebsd
package console
// RunningOnTerminal checks whether stdout is terminal
//
// Stub for FreeBSD, until in go1.3 terminal.IsTerminal would start working for FreeBSD
func RunningOnTerminal() bool {
return false
}
+490
View File
@@ -0,0 +1,490 @@
// Package context provides single entry to all resources
package context
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/s3"
"github.com/smira/aptly/swift"
"github.com/smira/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"sync"
"time"
)
// AptlyContext is a common context shared by all commands
type AptlyContext struct {
sync.Mutex
flags, globalFlags *flag.FlagSet
configLoaded bool
progress aptly.Progress
downloader aptly.Downloader
database database.Storage
packagePool aptly.PackagePool
publishedStorages map[string]aptly.PublishedStorage
collectionFactory *deb.CollectionFactory
dependencyOptions int
architecturesList []string
// Debug features
fileCPUProfile *os.File
fileMemProfile *os.File
fileMemStats *os.File
}
// Check interface
var _ aptly.PublishedStorageProvider = &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) {
returnCode := 1
if err == commander.ErrFlagError || err == commander.ErrCommandError {
returnCode = 2
}
panic(&FatalError{ReturnCode: returnCode, Message: err.Error()})
}
// Config loads and returns current configuration
func (context *AptlyContext) Config() *utils.ConfigStructure {
context.Lock()
defer context.Unlock()
return context.config()
}
func (context *AptlyContext) config() *utils.ConfigStructure {
if !context.configLoaded {
var err error
configLocation := context.globalFlags.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
}
// LookupOption checks boolean flag with default (usually config) and command-line
// setting
func (context *AptlyContext) LookupOption(defaultValue bool, name string) (result bool) {
context.Lock()
defer context.Unlock()
return context.lookupOption(defaultValue, name)
}
func (context *AptlyContext) lookupOption(defaultValue bool, name string) (result bool) {
result = defaultValue
if context.globalFlags.IsSet(name) {
result = context.globalFlags.Lookup(name).Value.Get().(bool)
}
return
}
// DependencyOptions calculates options related to dependecy handling
func (context *AptlyContext) DependencyOptions() int {
context.Lock()
defer context.Unlock()
if context.dependencyOptions == -1 {
context.dependencyOptions = 0
if context.lookupOption(context.config().DepFollowSuggests, "dep-follow-suggests") {
context.dependencyOptions |= deb.DepFollowSuggests
}
if context.lookupOption(context.config().DepFollowRecommends, "dep-follow-recommends") {
context.dependencyOptions |= deb.DepFollowRecommends
}
if context.lookupOption(context.config().DepFollowAllVariants, "dep-follow-all-variants") {
context.dependencyOptions |= deb.DepFollowAllVariants
}
if context.lookupOption(context.config().DepFollowSource, "dep-follow-source") {
context.dependencyOptions |= deb.DepFollowSource
}
}
return context.dependencyOptions
}
// ArchitecturesList returns list of architectures fixed via command line or config
func (context *AptlyContext) ArchitecturesList() []string {
context.Lock()
defer context.Unlock()
if context.architecturesList == nil {
context.architecturesList = context.config().Architectures
optionArchitectures := context.globalFlags.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 {
context.Lock()
defer context.Unlock()
return context._progress()
}
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 {
context.Lock()
defer context.Unlock()
if context.downloader == nil {
var downloadLimit int64
limitFlag := context.flags.Lookup("download-limit")
if limitFlag != nil {
downloadLimit = limitFlag.Value.Get().(int64)
}
if downloadLimit == 0 {
downloadLimit = context.config().DownloadLimit
}
context.downloader = http.NewDownloader(context.config().DownloadConcurrency,
downloadLimit*1024, context._progress())
}
return context.downloader
}
// DBPath builds path to database
func (context *AptlyContext) DBPath() string {
context.Lock()
defer context.Unlock()
return context.dbPath()
}
// 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) {
context.Lock()
defer context.Unlock()
return context._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
}
// CloseDatabase closes the db temporarily
func (context *AptlyContext) CloseDatabase() error {
context.Lock()
defer context.Unlock()
if context.database == nil {
return nil
}
return context.database.Close()
}
// ReOpenDatabase reopens the db after close
func (context *AptlyContext) ReOpenDatabase() error {
context.Lock()
defer context.Unlock()
if context.database == nil {
return nil
}
const MaxTries = 10
const Delay = 10 * time.Second
for try := 0; try < MaxTries; try++ {
err := context.database.ReOpen()
if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 {
return err
}
context._progress().Printf("Unable to reopen database, sleeping %s\n", Delay)
<-time.After(Delay)
}
return fmt.Errorf("unable to reopen the DB, maximum number of retries reached")
}
// CollectionFactory builds factory producing all kinds of collections
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
context.Lock()
defer context.Unlock()
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 {
context.Lock()
defer context.Unlock()
if context.packagePool == nil {
context.packagePool = files.NewPackagePool(context.config().RootDir)
}
return context.packagePool
}
// GetPublishedStorage returns instance of PublishedStorage
func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage {
context.Lock()
defer context.Unlock()
publishedStorage, ok := context.publishedStorages[name]
if !ok {
if name == "" {
publishedStorage = files.NewPublishedStorage(context.config().RootDir)
} else if strings.HasPrefix(name, "s3:") {
params, ok := context.config().S3PublishRoots[name[3:]]
if !ok {
Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:]))
}
var err error
publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey,
params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
params.EncryptionMethod, params.PlusWorkaround)
if err != nil {
Fatal(err)
}
} else if strings.HasPrefix(name, "swift:") {
params, ok := context.config().SwiftPublishRoots[name[6:]]
if !ok {
Fatal(fmt.Errorf("published Swift storage %v not configured", name[6:]))
}
var err error
publishedStorage, err = swift.NewPublishedStorage(params.UserName, params.Password,
params.AuthURL, params.Tenant, params.TenantID, params.Container, params.Prefix)
if err != nil {
Fatal(err)
}
} else {
Fatal(fmt.Errorf("unknown published storage format: %v", name))
}
context.publishedStorages[name] = publishedStorage
}
return publishedStorage
}
// UploadPath builds path to upload storage
func (context *AptlyContext) UploadPath() string {
return filepath.Join(context.Config().RootDir, "upload")
}
// UpdateFlags sets internal copy of flags in the context
func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) {
context.Lock()
defer context.Unlock()
context.flags = flags
}
// Flags returns current command flags
func (context *AptlyContext) Flags() *flag.FlagSet {
context.Lock()
defer context.Unlock()
return context.flags
}
// GlobalFlags returns flags passed to all commands
func (context *AptlyContext) GlobalFlags() *flag.FlagSet {
context.Lock()
defer context.Unlock()
return context.globalFlags
}
// Shutdown shuts context down
func (context *AptlyContext) Shutdown() {
context.Lock()
defer context.Unlock()
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()
context.database = nil
}
if context.downloader != nil {
context.downloader.Abort()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
}
// Cleanup does partial shutdown of context
func (context *AptlyContext) Cleanup() {
context.Lock()
defer context.Unlock()
if context.downloader != nil {
context.downloader.Shutdown()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
}
// NewContext initializes context with default settings
func NewContext(flags *flag.FlagSet) (*AptlyContext, error) {
var err error
context := &AptlyContext{
flags: flags,
globalFlags: flags,
dependencyOptions: -1,
publishedStorages: map[string]aptly.PublishedStorage{},
}
if aptly.EnableDebug {
cpuprofile := flags.Lookup("cpuprofile").Value.String()
if cpuprofile != "" {
context.fileCPUProfile, err = os.Create(cpuprofile)
if err != nil {
return nil, err
}
pprof.StartCPUProfile(context.fileCPUProfile)
}
memprofile := flags.Lookup("memprofile").Value.String()
if memprofile != "" {
context.fileMemProfile, err = os.Create(memprofile)
if err != nil {
return nil, 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 nil, 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
}
}
}()
}
}
return context, nil
}
+1 -1
View File
@@ -101,7 +101,7 @@ func (l *levelDB) Put(key []byte, value []byte) error {
return err return err
} }
} else { } else {
if bytes.Compare(old, value) == 0 { if bytes.Equal(old, value) {
return nil return nil
} }
} }
+2 -1
View File
@@ -1,8 +1,9 @@
package database package database
import ( import (
. "launchpad.net/gocheck"
"testing" "testing"
. "gopkg.in/check.v1"
) )
// Launch gocheck tests // Launch gocheck tests
+30 -1
View File
@@ -2,10 +2,12 @@ package deb
import ( import (
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
"sync"
) )
// CollectionFactory is a single place to generate all desired collections // CollectionFactory is a single place to generate all desired collections
type CollectionFactory struct { type CollectionFactory struct {
*sync.Mutex
db database.Storage db database.Storage
packages *PackageCollection packages *PackageCollection
remoteRepos *RemoteRepoCollection remoteRepos *RemoteRepoCollection
@@ -16,11 +18,14 @@ type CollectionFactory struct {
// NewCollectionFactory creates new factory // NewCollectionFactory creates new factory
func NewCollectionFactory(db database.Storage) *CollectionFactory { func NewCollectionFactory(db database.Storage) *CollectionFactory {
return &CollectionFactory{db: db} return &CollectionFactory{Mutex: &sync.Mutex{}, db: db}
} }
// PackageCollection returns (or creates) new PackageCollection // PackageCollection returns (or creates) new PackageCollection
func (factory *CollectionFactory) PackageCollection() *PackageCollection { func (factory *CollectionFactory) PackageCollection() *PackageCollection {
factory.Lock()
defer factory.Unlock()
if factory.packages == nil { if factory.packages == nil {
factory.packages = NewPackageCollection(factory.db) factory.packages = NewPackageCollection(factory.db)
} }
@@ -30,6 +35,9 @@ func (factory *CollectionFactory) PackageCollection() *PackageCollection {
// RemoteRepoCollection returns (or creates) new RemoteRepoCollection // RemoteRepoCollection returns (or creates) new RemoteRepoCollection
func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection { func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection {
factory.Lock()
defer factory.Unlock()
if factory.remoteRepos == nil { if factory.remoteRepos == nil {
factory.remoteRepos = NewRemoteRepoCollection(factory.db) factory.remoteRepos = NewRemoteRepoCollection(factory.db)
} }
@@ -39,6 +47,9 @@ func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection {
// SnapshotCollection returns (or creates) new SnapshotCollection // SnapshotCollection returns (or creates) new SnapshotCollection
func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection { func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection {
factory.Lock()
defer factory.Unlock()
if factory.snapshots == nil { if factory.snapshots == nil {
factory.snapshots = NewSnapshotCollection(factory.db) factory.snapshots = NewSnapshotCollection(factory.db)
} }
@@ -48,6 +59,9 @@ func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection {
// LocalRepoCollection returns (or creates) new LocalRepoCollection // LocalRepoCollection returns (or creates) new LocalRepoCollection
func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection { func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection {
factory.Lock()
defer factory.Unlock()
if factory.localRepos == nil { if factory.localRepos == nil {
factory.localRepos = NewLocalRepoCollection(factory.db) factory.localRepos = NewLocalRepoCollection(factory.db)
} }
@@ -57,9 +71,24 @@ func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection {
// PublishedRepoCollection returns (or creates) new PublishedRepoCollection // PublishedRepoCollection returns (or creates) new PublishedRepoCollection
func (factory *CollectionFactory) PublishedRepoCollection() *PublishedRepoCollection { func (factory *CollectionFactory) PublishedRepoCollection() *PublishedRepoCollection {
factory.Lock()
defer factory.Unlock()
if factory.publishedRepos == nil { if factory.publishedRepos == nil {
factory.publishedRepos = NewPublishedRepoCollection(factory.db) factory.publishedRepos = NewPublishedRepoCollection(factory.db)
} }
return factory.publishedRepos return factory.publishedRepos
} }
// Flush removes all references to collections, so that memory could be reclaimed
func (factory *CollectionFactory) Flush() {
factory.Lock()
defer factory.Unlock()
factory.localRepos = nil
factory.snapshots = nil
factory.remoteRepos = nil
factory.publishedRepos = nil
factory.packages = nil
}
+1 -1
View File
@@ -47,7 +47,7 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) {
return nil, fmt.Errorf("unable to read .tar archive: %s", err) return nil, fmt.Errorf("unable to read .tar archive: %s", err)
} }
if tarHeader.Name == "./control" { if tarHeader.Name == "./control" || tarHeader.Name == "control" {
reader := NewControlFileReader(untar) reader := NewControlFileReader(untar)
stanza, err := reader.ReadStanza() stanza, err := reader.ReadStanza()
if err != nil { if err != nil {
+2 -1
View File
@@ -2,9 +2,10 @@ package deb
import ( import (
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
. "launchpad.net/gocheck"
"path/filepath" "path/filepath"
"runtime" "runtime"
. "gopkg.in/check.v1"
) )
type DebSuite struct { type DebSuite struct {
+2 -1
View File
@@ -1,8 +1,9 @@
package deb package deb
import ( import (
. "launchpad.net/gocheck"
"testing" "testing"
. "gopkg.in/check.v1"
) )
// Launch gocheck tests // Launch gocheck tests
+80 -5
View File
@@ -11,9 +11,76 @@ import (
type Stanza map[string]string type Stanza map[string]string
// Canonical order of fields in stanza // Canonical order of fields in stanza
var canocialOrder = []string{"Package", "Origin", "Label", "Suite", "Version", "Installed-Size", "Priority", "Section", "Maintainer", // Taken from: http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/vivid/apt/vivid/view/head:/apt-pkg/tagfile.cc#L504
"Architecture", "Codename", "Date", "Architectures", "Components", "Description", "MD5sum", "MD5Sum", "SHA1", "SHA256", var (
"Archive", "Component"} canonicalOrderRelease = []string{
"Origin",
"Label",
"Archive",
"Suite",
"Version",
"Codename",
"Date",
"Architectures",
"Architecture",
"Components",
"Component",
"Description",
"MD5Sum",
"SHA1",
"SHA256",
}
canonicalOrderBinary = []string{
"Package",
"Essential",
"Status",
"Priority",
"Section",
"Installed-Size",
"Maintainer",
"Original-Maintainer",
"Architecture",
"Source",
"Version",
"Replaces",
"Provides",
"Depends",
"Pre-Depends",
"Recommends",
"Suggests",
"Conflicts",
"Breaks",
"Conffiles",
"Filename",
"Size",
"MD5Sum",
"MD5sum",
"SHA1",
"SHA256",
"Description",
}
canonicalOrderSource = []string{
"Package",
"Source",
"Binary",
"Version",
"Priority",
"Section",
"Maintainer",
"Original-Maintainer",
"Build-Depends",
"Build-Depends-Indep",
"Build-Conflicts",
"Build-Conflicts-Indep",
"Architecture",
"Standards-Version",
"Format",
"Directory",
"Files",
}
)
// Copy returns copy of Stanza // Copy returns copy of Stanza
func (s Stanza) Copy() (result Stanza) { func (s Stanza) Copy() (result Stanza) {
@@ -41,8 +108,16 @@ func writeField(w *bufio.Writer, field, value string) (err error) {
} }
// WriteTo saves stanza back to stream, modifying itself on the fly // WriteTo saves stanza back to stream, modifying itself on the fly
func (s Stanza) WriteTo(w *bufio.Writer) error { func (s Stanza) WriteTo(w *bufio.Writer, isSource, isRelease bool) error {
for _, field := range canocialOrder { canonicalOrder := canonicalOrderBinary
if isSource {
canonicalOrder = canonicalOrderSource
}
if isRelease {
canonicalOrder = canonicalOrderRelease
}
for _, field := range canonicalOrder {
value, ok := s[field] value, ok := s[field]
if ok { if ok {
delete(s, field) delete(s, field)
+3 -2
View File
@@ -3,8 +3,9 @@ package deb
import ( import (
"bufio" "bufio"
"bytes" "bytes"
. "launchpad.net/gocheck"
"strings" "strings"
. "gopkg.in/check.v1"
) )
type ControlFileSuite struct { type ControlFileSuite struct {
@@ -107,7 +108,7 @@ func (s *ControlFileSuite) TestReadWriteStanza(c *C) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
w := bufio.NewWriter(buf) w := bufio.NewWriter(buf)
err = stanza.Copy().WriteTo(w) err = stanza.Copy().WriteTo(w, false, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = w.Flush() err = w.Flush()
c.Assert(err, IsNil) c.Assert(err, IsNil)
+120
View File
@@ -0,0 +1,120 @@
package deb
import (
"code.google.com/p/gographviz"
"fmt"
"strings"
)
// BuildGraph generates graph contents from aptly object database
func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, error) {
var err error
graph := gographviz.NewEscape()
graph.SetDir(true)
graph.SetName("aptly")
existingNodes := map[string]bool{}
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error {
err := collectionFactory.RemoteRepoCollection().LoadComplete(repo)
if err != nil {
return err
}
graph.AddNode("aptly", repo.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "darkgoldenrod1",
"label": 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 nil, err
}
err = collectionFactory.LocalRepoCollection().ForEach(func(repo *LocalRepo) error {
err := collectionFactory.LocalRepoCollection().LoadComplete(repo)
if err != nil {
return err
}
graph.AddNode("aptly", repo.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "mediumseagreen",
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
repo.Name, repo.Comment, repo.NumPackages()),
})
existingNodes[repo.UUID] = true
return nil
})
if err != nil {
return nil, err
}
collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error {
existingNodes[snapshot.UUID] = true
return nil
})
err = collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error {
err := collectionFactory.SnapshotCollection().LoadComplete(snapshot)
if err != nil {
return err
}
description := snapshot.Description
if snapshot.SourceKind == "repo" {
description = "Snapshot from repo"
}
graph.AddNode("aptly", snapshot.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "cadetblue1",
"label": 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(uuid, snapshot.UUID, true, nil)
}
}
}
return nil
})
if err != nil {
return nil, err
}
collectionFactory.PublishedRepoCollection().ForEach(func(repo *PublishedRepo) error {
graph.AddNode("aptly", repo.UUID, map[string]string{
"shape": "Mrecord",
"style": "filled",
"fillcolor": "darkolivegreen1",
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution,
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")),
})
for _, uuid := range repo.Sources {
_, exists := existingNodes[uuid]
if exists {
graph.AddEdge(uuid, repo.UUID, true, nil)
}
}
return nil
})
return graph, nil
}
+163
View File
@@ -0,0 +1,163 @@
package deb
import (
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/utils"
"os"
"path/filepath"
"sort"
"strings"
)
// CollectPackageFiles walks filesystem collecting all candidates for package files
func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (packageFiles, failedFiles []string, err error) {
for _, location := range locations {
info, err2 := os.Stat(location)
if err2 != nil {
reporter.Warning("Unable to process %s: %s", location, err2)
failedFiles = append(failedFiles, location)
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
}
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
strings.HasSuffix(info.Name(), ".dsc") {
packageFiles = append(packageFiles, path)
}
return nil
})
} else {
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
strings.HasSuffix(info.Name(), ".dsc") {
packageFiles = append(packageFiles, location)
} else {
reporter.Warning("Unknown file extension: %s", location)
failedFiles = append(failedFiles, location)
continue
}
}
}
sort.Strings(packageFiles)
return
}
// ImportPackageFiles imports files into local repository
func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace bool, verifier utils.Verifier,
pool aptly.PackagePool, collection *PackageCollection, reporter aptly.ResultReporter) (processedFiles []string, failedFiles []string, err error) {
if forceReplace {
list.PrepareIndex()
}
for _, file := range packageFiles {
var (
stanza Stanza
p *Package
)
candidateProcessedFiles := []string{}
isSourcePackage := strings.HasSuffix(file, ".dsc")
isUdebPackage := strings.HasSuffix(file, ".udeb")
if isSourcePackage {
stanza, err = GetControlFileFromDsc(file, verifier)
if err == nil {
stanza["Package"] = stanza["Source"]
delete(stanza, "Source")
p, err = NewSourcePackageFromControlFile(stanza)
}
} else {
stanza, err = GetControlFileFromDeb(file)
if isUdebPackage {
p = NewUdebPackageFromControlFile(stanza)
} else {
p = NewPackageFromControlFile(stanza)
}
}
if err != nil {
reporter.Warning("Unable to read file %s: %s", file, err)
failedFiles = append(failedFiles, file)
continue
}
var checksums utils.ChecksumInfo
checksums, err = utils.ChecksumsForFile(file)
if err != nil {
return nil, nil, err
}
if isSourcePackage {
p.UpdateFiles(append(p.Files(), PackageFile{Filename: filepath.Base(file), Checksums: checksums}))
} else {
p.UpdateFiles([]PackageFile{{Filename: filepath.Base(file), Checksums: checksums}})
}
err = pool.Import(file, checksums.MD5)
if err != nil {
reporter.Warning("Unable to import file %s into pool: %s", file, err)
failedFiles = append(failedFiles, file)
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 = pool.Import(sourceFile, f.Checksums.MD5)
if err != nil {
reporter.Warning("Unable to import file %s into pool: %s", sourceFile, err)
failedFiles = append(failedFiles, file)
break
}
candidateProcessedFiles = append(candidateProcessedFiles, sourceFile)
}
if err != nil {
// some files haven't been imported
continue
}
err = collection.Update(p)
if err != nil {
reporter.Warning("Unable to save package %s: %s", p, err)
failedFiles = append(failedFiles, file)
continue
}
if forceReplace {
conflictingPackages := list.Search(Dependency{Pkg: p.Name, Version: p.Version, Relation: VersionEqual, Architecture: p.Architecture}, true)
for _, cp := range conflictingPackages {
reporter.Removed("%s removed due to conflict with package being added", cp)
list.Remove(cp)
}
}
err = list.Add(p)
if err != nil {
reporter.Warning("Unable to add package to repo %s: %s", p, err)
failedFiles = append(failedFiles, file)
continue
}
reporter.Added("%s added", p)
processedFiles = append(processedFiles, candidateProcessedFiles...)
}
err = nil
return
}
+8 -2
View File
@@ -150,7 +150,10 @@ func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, s
} }
func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexFile { func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexFile {
key := fmt.Sprintf("pi-%s-%s-%s", component, arch, udeb) if arch == "source" {
udeb = false
}
key := fmt.Sprintf("pi-%s-%s-%v", component, arch, udeb)
file, ok := files.indexes[key] file, ok := files.indexes[key]
if !ok { if !ok {
var relativePath string var relativePath string
@@ -180,7 +183,10 @@ func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexF
} }
func (files *indexFiles) ReleaseIndex(component, arch string, udeb bool) *indexFile { func (files *indexFiles) ReleaseIndex(component, arch string, udeb bool) *indexFile {
key := fmt.Sprintf("ri-%s-%s-%s", component, arch, udeb) if arch == "source" {
udeb = false
}
key := fmt.Sprintf("ri-%s-%s-%v", component, arch, udeb)
file, ok := files.indexes[key] file, ok := files.indexes[key]
if !ok { if !ok {
var relativePath string var relativePath string
+19 -1
View File
@@ -38,6 +38,11 @@ type PackageList struct {
providesIndex map[string][]*Package providesIndex map[string][]*Package
} }
// PackageConflictError means that package can't be added to the list due to error
type PackageConflictError struct {
error
}
// Verify interface // Verify interface
var ( var (
_ sort.Interface = &PackageList{} _ sort.Interface = &PackageList{}
@@ -90,7 +95,7 @@ func (l *PackageList) Add(p *Package) error {
existing, ok := l.packages[key] existing, ok := l.packages[key]
if ok { if ok {
if !existing.Equals(p) { if !existing.Equals(p) {
return fmt.Errorf("conflict in package %s", p) return &PackageConflictError{fmt.Errorf("conflict in package %s", p)}
} }
return nil return nil
} }
@@ -205,6 +210,19 @@ func (l *PackageList) Architectures(includeSource bool) (result []string) {
return return
} }
// Strings builds list of strings with package keys
func (l *PackageList) Strings() []string {
result := make([]string, l.Len())
i := 0
for _, p := range l.packages {
result[i] = string(p.Key(""))
i += 1
}
return result
}
// depSliceDeduplicate removes dups in slice of Dependencies // depSliceDeduplicate removes dups in slice of Dependencies
func depSliceDeduplicate(s []Dependency) []Dependency { func depSliceDeduplicate(s []Dependency) []Dependency {
l := len(s) l := len(s)
+31 -30
View File
@@ -2,10 +2,11 @@ package deb
import ( import (
"errors" "errors"
. "launchpad.net/gocheck"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
. "gopkg.in/check.v1"
) )
type containsChecker struct { type containsChecker struct {
@@ -79,20 +80,20 @@ func (s *PackageListSuite) SetUpTest(c *C) {
s.il = NewPackageList() s.il = NewPackageList()
s.packages = []*Package{ s.packages = []*Package{
&Package{Name: "lib", Version: "1.0", Architecture: "i386", Source: "lib (0.9)", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"mail-agent"}}}, {Name: "lib", Version: "1.0", Architecture: "i386", Source: "lib (0.9)", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"mail-agent"}}},
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, {Name: "dpkg", Version: "1.7", Architecture: "i386", Provides: []string{"package-installer"}, deps: &PackageDependencies{}},
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all", Source: "app", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, {Name: "data", Version: "1.1~bp1", Architecture: "all", Source: "app", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}},
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, {Name: "app", Version: "1.1~bp1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
&Package{Name: "mailer", Version: "3.5.8", Architecture: "i386", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, {Name: "mailer", Version: "3.5.8", Architecture: "i386", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}},
&Package{Name: "app", Version: "1.1~bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, {Name: "app", Version: "1.1~bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
&Package{Name: "app", Version: "1.1~bp1", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, {Name: "app", Version: "1.1~bp1", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}},
&Package{Name: "app", Version: "1.0", Architecture: "s390", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, {Name: "app", Version: "1.0", Architecture: "s390", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
&Package{Name: "aa", Version: "2.0-1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, {Name: "aa", Version: "2.0-1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}},
&Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "amd64", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, {Name: "dpkg", Version: "1.6.1-3", Architecture: "amd64", Provides: []string{"package-installer"}, deps: &PackageDependencies{}},
&Package{Name: "libx", Version: "1.5", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, {Name: "libx", Version: "1.5", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}},
&Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "arm", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, {Name: "dpkg", Version: "1.6.1-3", Architecture: "arm", Provides: []string{"package-installer"}, deps: &PackageDependencies{}},
&Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, {Name: "dpkg", Version: "1.6.1-3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
&Package{Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, {Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
} }
for _, p := range s.packages { for _, p := range s.packages {
s.il.Add(p) s.il.Add(p)
@@ -101,12 +102,12 @@ func (s *PackageListSuite) SetUpTest(c *C) {
s.il2 = NewPackageList() s.il2 = NewPackageList()
s.packages2 = []*Package{ s.packages2 = []*Package{
&Package{Name: "mailer", Version: "3.5.8", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, {Name: "mailer", Version: "3.5.8", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}},
&Package{Name: "sendmail", Version: "1.0", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, {Name: "sendmail", Version: "1.0", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}},
&Package{Name: "app", Version: "1.1-bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, {Name: "app", Version: "1.1-bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
&Package{Name: "app", Version: "1.1-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, {Name: "app", Version: "1.1-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
&Package{Name: "app", Version: "1.2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, {Name: "app", Version: "1.2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}},
&Package{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, {Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
} }
for _, p := range s.packages2 { for _, p := range s.packages2 {
s.il2.Add(p) s.il2.Add(p)
@@ -114,10 +115,10 @@ func (s *PackageListSuite) SetUpTest(c *C) {
s.il2.PrepareIndex() s.il2.PrepareIndex()
s.sourcePackages = []*Package{ s.sourcePackages = []*Package{
&Package{Name: "postfix", Version: "1.3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, {Name: "postfix", Version: "1.3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
&Package{Name: "app", Version: "1.1~bp1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, {Name: "app", Version: "1.1~bp1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
&Package{Name: "aa", Version: "2.0-1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, {Name: "aa", Version: "2.0-1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
&Package{Name: "lib", Version: "0.9", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, {Name: "lib", Version: "0.9", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
} }
} }
@@ -387,7 +388,7 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) {
missing, err = s.il.VerifyDependencies(0, []string{"i386", "amd64"}, s.il, nil) missing, err = s.il.VerifyDependencies(0, []string{"i386", "amd64"}, s.il, nil)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}})
missing, err = s.il.VerifyDependencies(0, []string{"arm"}, s.il, nil) missing, err = s.il.VerifyDependencies(0, []string{"arm"}, s.il, nil)
c.Check(err, IsNil) c.Check(err, IsNil)
@@ -395,8 +396,8 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) {
missing, err = s.il.VerifyDependencies(DepFollowAllVariants, []string{"arm"}, s.il, nil) missing, err = s.il.VerifyDependencies(DepFollowAllVariants, []string{"arm"}, s.il, nil)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "arm"}, c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "arm"},
Dependency{Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}}) {Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}})
for _, p := range s.sourcePackages { for _, p := range s.sourcePackages {
s.il.Add(p) s.il.Add(p)
@@ -404,11 +405,11 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) {
missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"i386", "amd64"}, s.il, nil) missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"i386", "amd64"}, s.il, nil)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}})
missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"arm"}, s.il, nil) missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"arm"}, s.il, nil)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "libx", Relation: VersionEqual, Version: "1.5", Architecture: "source"}}) c.Check(missing, DeepEquals, []Dependency{{Pkg: "libx", Relation: VersionEqual, Version: "1.5", Architecture: "source"}})
_, err = s.il.VerifyDependencies(0, []string{"i386", "amd64", "s390"}, s.il, nil) _, err = s.il.VerifyDependencies(0, []string{"i386", "amd64", "s390"}, s.il, nil)
c.Check(err, ErrorMatches, "unable to process package app_1.0_s390:.*") c.Check(err, ErrorMatches, "unable to process package app_1.0_s390:.*")
+6 -3
View File
@@ -7,12 +7,13 @@ import (
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
"log" "log"
"sync"
) )
// LocalRepo is a collection of packages created locally // LocalRepo is a collection of packages created locally
type LocalRepo struct { type LocalRepo struct {
// Permanent internal ID // Permanent internal ID
UUID string UUID string `json:"-"`
// User-assigned name // User-assigned name
Name string Name string
// Comment // Comment
@@ -88,6 +89,7 @@ func (repo *LocalRepo) RefKey() []byte {
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos // LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
type LocalRepoCollection struct { type LocalRepoCollection struct {
*sync.RWMutex
db database.Storage db database.Storage
list []*LocalRepo list []*LocalRepo
} }
@@ -95,7 +97,8 @@ type LocalRepoCollection struct {
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection // NewLocalRepoCollection loads LocalRepos from DB and makes up collection
func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection { func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
result := &LocalRepoCollection{ result := &LocalRepoCollection{
db: db, RWMutex: &sync.RWMutex{},
db: db,
} }
blobs := db.FetchByPrefix([]byte("L")) blobs := db.FetchByPrefix([]byte("L"))
@@ -104,7 +107,7 @@ func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
for _, blob := range blobs { for _, blob := range blobs {
r := &LocalRepo{} r := &LocalRepo{}
if err := r.Decode(blob); err != nil { if err := r.Decode(blob); err != nil {
log.Printf("Error decoding mirror: %s\n", err) log.Printf("Error decoding repo: %s\n", err)
} else { } else {
result.list = append(result.list, r) result.list = append(result.list, r)
} }
+2 -1
View File
@@ -3,7 +3,8 @@ package deb
import ( import (
"errors" "errors"
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
. "launchpad.net/gocheck"
. "gopkg.in/check.v1"
) )
type LocalRepoSuite struct { type LocalRepoSuite struct {
+27 -3
View File
@@ -1,6 +1,7 @@
package deb package deb
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/smira/aptly/aptly" "github.com/smira/aptly/aptly"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
@@ -38,6 +39,11 @@ type Package struct {
collection *PackageCollection collection *PackageCollection
} }
// Check interface
var (
_ json.Marshaler = &Package{}
)
// NewPackageFromControlFile creates Package from parsed Debian control file // NewPackageFromControlFile creates Package from parsed Debian control file
func NewPackageFromControlFile(input Stanza) *Package { func NewPackageFromControlFile(input Stanza) *Package {
result := &Package{ result := &Package{
@@ -55,12 +61,18 @@ func NewPackageFromControlFile(input Stanza) *Package {
filesize, _ := strconv.ParseInt(input["Size"], 10, 64) filesize, _ := strconv.ParseInt(input["Size"], 10, 64)
md5, ok := input["MD5sum"]
if !ok {
// there are some broken repos out there with MD5 in wrong field
md5 = input["MD5Sum"]
}
result.UpdateFiles(PackageFiles{PackageFile{ result.UpdateFiles(PackageFiles{PackageFile{
Filename: filepath.Base(input["Filename"]), Filename: filepath.Base(input["Filename"]),
downloadPath: filepath.Dir(input["Filename"]), downloadPath: filepath.Dir(input["Filename"]),
Checksums: utils.ChecksumInfo{ Checksums: utils.ChecksumInfo{
Size: filesize, Size: filesize,
MD5: strings.TrimSpace(input["MD5sum"]), MD5: strings.TrimSpace(md5),
SHA1: strings.TrimSpace(input["SHA1"]), SHA1: strings.TrimSpace(input["SHA1"]),
SHA256: strings.TrimSpace(input["SHA256"]), SHA256: strings.TrimSpace(input["SHA256"]),
}, },
@@ -68,6 +80,7 @@ func NewPackageFromControlFile(input Stanza) *Package {
delete(input, "Filename") delete(input, "Filename")
delete(input, "MD5sum") delete(input, "MD5sum")
delete(input, "MD5Sum")
delete(input, "SHA1") delete(input, "SHA1")
delete(input, "SHA256") delete(input, "SHA256")
delete(input, "Size") delete(input, "Size")
@@ -198,6 +211,16 @@ func (p *Package) String() string {
return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture) return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture)
} }
// MarshalJSON implements json.Marshaller interface
func (p *Package) MarshalJSON() ([]byte, error) {
stanza := p.Stanza()
stanza["FilesHash"] = fmt.Sprintf("%08x", p.FilesHash)
stanza["Key"] = string(p.Key(""))
stanza["ShortKey"] = string(p.ShortKey(""))
return json.Marshal(stanza)
}
// GetField returns fields from package // GetField returns fields from package
func (p *Package) GetField(name string) string { func (p *Package) GetField(name string) string {
switch name { switch name {
@@ -262,7 +285,6 @@ func (p *Package) GetField(name string) string {
default: default:
return p.Extra()[name] return p.Extra()[name]
} }
return ""
} }
// MatchesArchitecture checks whether packages matches specified architecture // MatchesArchitecture checks whether packages matches specified architecture
@@ -404,7 +426,9 @@ func (p *Package) Stanza() (result Stanza) {
result["Architecture"] = p.SourceArchitecture result["Architecture"] = p.SourceArchitecture
} else { } else {
result["Architecture"] = p.Architecture result["Architecture"] = p.Architecture
result["Source"] = p.Source if p.Source != "" {
result["Source"] = p.Source
}
} }
if p.IsSource { if p.IsSource {
+2 -1
View File
@@ -3,7 +3,8 @@ package deb
import ( import (
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
. "launchpad.net/gocheck"
. "gopkg.in/check.v1"
) )
type PackageCollectionSuite struct { type PackageCollectionSuite struct {
+2 -1
View File
@@ -3,9 +3,10 @@ package deb
import ( import (
"github.com/smira/aptly/files" "github.com/smira/aptly/files"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
. "launchpad.net/gocheck"
"os" "os"
"path/filepath" "path/filepath"
. "gopkg.in/check.v1"
) )
type PackageFilesSuite struct { type PackageFilesSuite struct {
+3 -2
View File
@@ -4,10 +4,11 @@ import (
"bytes" "bytes"
"github.com/smira/aptly/files" "github.com/smira/aptly/files"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
. "launchpad.net/gocheck"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
. "gopkg.in/check.v1"
) )
type PackageSuite struct { type PackageSuite struct {
@@ -398,7 +399,7 @@ func (s *PackageSuite) TestDownloadList(c *C) {
list, err := p.DownloadList(packagePool) list, err := p.DownloadList(packagePool)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(list, DeepEquals, []PackageDownloadTask{ c.Check(list, DeepEquals, []PackageDownloadTask{
PackageDownloadTask{ {
RepoURI: "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb", RepoURI: "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb",
DestinationPath: poolPath, DestinationPath: poolPath,
Checksums: utils.ChecksumInfo{Size: 5, Checksums: utils.ChecksumInfo{Size: 5,
+2 -1
View File
@@ -2,7 +2,8 @@ package deb
import ( import (
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
. "launchpad.net/gocheck"
. "gopkg.in/check.v1"
) )
type PpaSuite struct { type PpaSuite struct {
+67 -7
View File
@@ -1,8 +1,10 @@
package deb package deb
import ( import (
"bufio"
"bytes" "bytes"
"code.google.com/p/go-uuid/uuid" "code.google.com/p/go-uuid/uuid"
"encoding/json"
"fmt" "fmt"
"github.com/smira/aptly/aptly" "github.com/smira/aptly/aptly"
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
@@ -14,6 +16,7 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
) )
@@ -56,6 +59,21 @@ type PublishedRepo struct {
rePublishing bool rePublishing bool
} }
// ParsePrefix splits [storage:]prefix into components
func ParsePrefix(param string) (storage, prefix string) {
i := strings.LastIndex(param, ":")
if i != -1 {
storage = param[:i]
prefix = param[i+1:]
if prefix == "" {
prefix = "."
}
} else {
prefix = param
}
return
}
// walkUpTree goes from source in the tree of source snapshots/mirrors/local repos // walkUpTree goes from source in the tree of source snapshots/mirrors/local repos
// gathering information about declared components and distributions // gathering information about declared components and distributions
func walkUpTree(source interface{}, collectionFactory *CollectionFactory) (rootDistributions []string, rootComponents []string) { func walkUpTree(source interface{}, collectionFactory *CollectionFactory) (rootDistributions []string, rootComponents []string) {
@@ -239,6 +257,40 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
return result, nil return result, nil
} }
// MarshalJSON requires object to be "loeaded completely"
func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
type sourceInfo struct {
Component, Name string
}
sources := []sourceInfo{}
for component, item := range p.sourceItems {
name := ""
if item.snapshot != nil {
name = item.snapshot.Name
} else if item.localRepo != nil {
name = item.localRepo.Name
} else {
panic("no snapshot/local repo")
}
sources = append(sources, sourceInfo{
Component: component,
Name: name,
})
}
return json.Marshal(map[string]interface{}{
"Architectures": p.Architectures,
"Distribution": p.Distribution,
"Label": p.Label,
"Origin": p.Origin,
"Prefix": p.Prefix,
"SourceKind": p.SourceKind,
"Sources": sources,
"Storage": p.Storage,
})
}
// String returns human-readable represenation of PublishedRepo // String returns human-readable represenation of PublishedRepo
func (p *PublishedRepo) String() string { func (p *PublishedRepo) String() string {
var sources = []string{} var sources = []string{}
@@ -471,7 +523,9 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
progress.InitBar(int64(list.Len()), false) progress.InitBar(int64(list.Len()), false)
} }
err = list.ForEach(func(pkg *Package) error { list.PrepareIndex()
err = list.ForEachIndexed(func(pkg *Package) error {
if progress != nil { if progress != nil {
progress.AddBar(1) progress.AddBar(1)
} }
@@ -494,12 +548,14 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
for _, arch := range p.Architectures { for _, arch := range p.Architectures {
if pkg.MatchesArchitecture(arch) { if pkg.MatchesArchitecture(arch) {
bufWriter, err := indexes.PackageIndex(component, arch, pkg.IsUdeb).BufWriter() var bufWriter *bufio.Writer
bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb).BufWriter()
if err != nil { if err != nil {
return err return err
} }
err = pkg.Stanza().WriteTo(bufWriter) err = pkg.Stanza().WriteTo(bufWriter, pkg.IsSource, false)
if err != nil { if err != nil {
return err return err
} }
@@ -545,9 +601,10 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
release["Origin"] = p.GetOrigin() release["Origin"] = p.GetOrigin()
release["Label"] = p.GetLabel() release["Label"] = p.GetLabel()
bufWriter, err := indexes.ReleaseIndex(component, arch, udeb).BufWriter() var bufWriter *bufio.Writer
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
err = release.WriteTo(bufWriter) err = release.WriteTo(bufWriter, false, true)
if err != nil { if err != nil {
return fmt.Errorf("unable to create Release file: %s", err) return fmt.Errorf("unable to create Release file: %s", err)
} }
@@ -567,6 +624,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
release := make(Stanza) release := make(Stanza)
release["Origin"] = p.GetOrigin() release["Origin"] = p.GetOrigin()
release["Label"] = p.GetLabel() release["Label"] = p.GetLabel()
release["Suite"] = p.Distribution
release["Codename"] = p.Distribution release["Codename"] = p.Distribution
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST") release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{"source"}), " ") release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{"source"}), " ")
@@ -589,7 +647,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
return err return err
} }
err = release.WriteTo(bufWriter) err = release.WriteTo(bufWriter, false, true)
if err != nil { if err != nil {
return fmt.Errorf("unable to create Release file: %s", err) return fmt.Errorf("unable to create Release file: %s", err)
} }
@@ -648,6 +706,7 @@ func (p *PublishedRepo) RemoveFiles(publishedStorageProvider aptly.PublishedStor
// PublishedRepoCollection does listing, updating/adding/deleting of PublishedRepos // PublishedRepoCollection does listing, updating/adding/deleting of PublishedRepos
type PublishedRepoCollection struct { type PublishedRepoCollection struct {
*sync.RWMutex
db database.Storage db database.Storage
list []*PublishedRepo list []*PublishedRepo
} }
@@ -655,7 +714,8 @@ type PublishedRepoCollection struct {
// NewPublishedRepoCollection loads PublishedRepos from DB and makes up collection // NewPublishedRepoCollection loads PublishedRepos from DB and makes up collection
func NewPublishedRepoCollection(db database.Storage) *PublishedRepoCollection { func NewPublishedRepoCollection(db database.Storage) *PublishedRepoCollection {
result := &PublishedRepoCollection{ result := &PublishedRepoCollection{
db: db, RWMutex: &sync.RWMutex{},
db: db,
} }
blobs := db.FetchByPrefix([]byte("U")) blobs := db.FetchByPrefix([]byte("U"))
+5 -1
View File
@@ -9,9 +9,10 @@ import (
"github.com/smira/aptly/files" "github.com/smira/aptly/files"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
"io/ioutil" "io/ioutil"
. "launchpad.net/gocheck"
"os" "os"
"path/filepath" "path/filepath"
. "gopkg.in/check.v1"
) )
type pathExistsChecker struct { type pathExistsChecker struct {
@@ -36,6 +37,9 @@ func (n *NullSigner) Init() error {
func (n *NullSigner) SetKey(keyRef string) { func (n *NullSigner) SetKey(keyRef string) {
} }
func (n *NullSigner) SetBatch(batch bool) {
}
func (n *NullSigner) SetKeyRing(keyring, secretKeyring string) { func (n *NullSigner) SetKeyRing(keyring, secretKeyring string) {
} }
+62 -13
View File
@@ -2,6 +2,8 @@ package deb
import ( import (
"bytes" "bytes"
"encoding/json"
"github.com/AlekSi/pointer"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
"sort" "sort"
) )
@@ -92,6 +94,21 @@ func (l *PackageRefList) Has(p *Package) bool {
return i < len(l.Refs) && bytes.Compare(l.Refs[i], key) == 0 return i < len(l.Refs) && bytes.Compare(l.Refs[i], key) == 0
} }
// Strings builds list of strings with package keys
func (l *PackageRefList) Strings() []string {
if l == nil {
return []string{}
}
result := make([]string, l.Len())
for i := 0; i < l.Len(); i++ {
result[i] = string(l.Refs[i])
}
return result
}
// Substract returns all packages in l that are not in r // Substract returns all packages in l that are not in r
func (l *PackageRefList) Substract(r *PackageRefList) *PackageRefList { func (l *PackageRefList) Substract(r *PackageRefList) *PackageRefList {
result := &PackageRefList{Refs: make([][]byte, 0, 128)} result := &PackageRefList{Refs: make([][]byte, 0, 128)}
@@ -139,6 +156,27 @@ type PackageDiff struct {
Left, Right *Package Left, Right *Package
} }
// Check interface
var (
_ json.Marshaler = PackageDiff{}
)
// MarshalJSON implements json.Marshaler interface
func (d PackageDiff) MarshalJSON() ([]byte, error) {
serialized := struct {
Left, Right *string
}{}
if d.Left != nil {
serialized.Left = pointer.ToString(string(d.Left.Key("")))
}
if d.Right != nil {
serialized.Right = pointer.ToString(string(d.Right.Key("")))
}
return json.Marshal(serialized)
}
// PackageDiffs is a list of PackageDiff records // PackageDiffs is a list of PackageDiff records
type PackageDiffs []PackageDiff type PackageDiffs []PackageDiff
@@ -232,8 +270,9 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle
// Merge merges reflist r into current reflist. If overrideMatching, merge // Merge merges reflist r into current reflist. If overrideMatching, merge
// replaces matching packages (by architecture/name) with reference from r. // replaces matching packages (by architecture/name) with reference from r.
// Otherwise, all packages are saved. // If ignoreConflicting is set, all packages are preserved, otherwise conflciting
func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result *PackageRefList) { // packages are overwritten with packages from "right" snapshot.
func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching, ignoreConflicting bool) (result *PackageRefList) {
var overriddenArch, overridenName []byte var overriddenArch, overridenName []byte
// pointer to left and right reflists // pointer to left and right reflists
@@ -270,13 +309,23 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result
overridenName = nil overridenName = nil
overriddenArch = nil overriddenArch = nil
} else { } else {
partsL := bytes.Split(rl, []byte(" "))
archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2]
partsR := bytes.Split(rr, []byte(" "))
archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2]
if !ignoreConflicting && bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) {
// conflicting duplicates with same arch, name, version, but different file hash
result.Refs = append(result.Refs, r.Refs[ir])
il++
ir++
overridenName = nil
overriddenArch = nil
continue
}
if overrideMatching { if overrideMatching {
partsL := bytes.Split(rl, []byte(" "))
archL, nameL := partsL[0][1:], partsL[1]
partsR := bytes.Split(rr, []byte(" "))
archR, nameR := partsR[0][1:], partsR[1]
if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) { if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) {
// this package has already been overriden on the right // this package has already been overriden on the right
il++ il++
@@ -314,15 +363,15 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result
// packages and reduces it to only the latest of each package. The operations // packages and reduces it to only the latest of each package. The operations
// are done in-place. This implements a "latest wins" approach which can be used // are done in-place. This implements a "latest wins" approach which can be used
// while merging two or more snapshots together. // while merging two or more snapshots together.
func FilterLatestRefs(r *PackageRefList) { func (l *PackageRefList) FilterLatestRefs() {
var ( var (
lastArch, lastName, lastVer []byte lastArch, lastName, lastVer []byte
arch, name, ver []byte arch, name, ver []byte
parts [][]byte parts [][]byte
) )
for i := 0; i < len(r.Refs); i++ { for i := 0; i < len(l.Refs); i++ {
parts = bytes.Split(r.Refs[i][1:], []byte(" ")) parts = bytes.Split(l.Refs[i][1:], []byte(" "))
arch, name, ver = parts[0], parts[1], parts[2] arch, name, ver = parts[0], parts[1], parts[2]
if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) { if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) {
@@ -332,10 +381,10 @@ func FilterLatestRefs(r *PackageRefList) {
// Remove the older refs from the result // Remove the older refs from the result
if vres > 0 { if vres > 0 {
// ver[i] > ver[i-1], remove element i-1 // ver[i] > ver[i-1], remove element i-1
r.Refs = append(r.Refs[:i-1], r.Refs[i:]...) l.Refs = append(l.Refs[:i-1], l.Refs[i:]...)
} else { } else {
// ver[i] < ver[i-1], remove element i // ver[i] < ver[i-1], remove element i
r.Refs = append(r.Refs[:i], r.Refs[i+1:]...) l.Refs = append(l.Refs[:i], l.Refs[i+1:]...)
arch, name, ver = lastArch, lastName, lastVer arch, name, ver = lastArch, lastName, lastVer
} }
+68 -32
View File
@@ -3,7 +3,8 @@ package deb
import ( import (
"errors" "errors"
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
. "launchpad.net/gocheck"
. "gopkg.in/check.v1"
) )
type PackageRefListSuite struct { type PackageRefListSuite struct {
@@ -168,13 +169,13 @@ func (s *PackageRefListSuite) TestDiff(c *C) {
coll := NewPackageCollection(db) coll := NewPackageCollection(db)
packages := []*Package{ packages := []*Package{
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0 {Name: "lib", Version: "1.0", Architecture: "i386"}, //0
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1 {Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2 {Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3 {Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
&Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4 {Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
&Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5 {Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
&Package{Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6 {Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6
} }
for _, p := range packages { for _, p := range packages {
@@ -240,17 +241,20 @@ func (s *PackageRefListSuite) TestMerge(c *C) {
coll := NewPackageCollection(db) coll := NewPackageCollection(db)
packages := []*Package{ packages := []*Package{
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0 {Name: "lib", Version: "1.0", Architecture: "i386"}, //0
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1 {Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2 {Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3 {Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
&Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4 {Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
&Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5 {Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
&Package{Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6 {Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6
&Package{Name: "xyz", Version: "1.0", Architecture: "sparc"}, //7 {Name: "xyz", Version: "1.0", Architecture: "sparc"}, //7
{Name: "dpkg", Version: "1.0", Architecture: "i386", FilesHash: 0x34445}, //8
{Name: "app", Version: "1.1~bp2", Architecture: "i386", FilesHash: 0x44}, //9
} }
for _, p := range packages { for _, p := range packages {
p.V06Plus = true
coll.Update(p) coll.Update(p)
} }
@@ -268,35 +272,67 @@ func (s *PackageRefListSuite) TestMerge(c *C) {
listB.Add(packages[5]) listB.Add(packages[5])
listB.Add(packages[6]) listB.Add(packages[6])
listC := NewPackageList()
listC.Add(packages[0])
listC.Add(packages[8])
listC.Add(packages[9])
reflistA := NewPackageRefListFromPackageList(listA) reflistA := NewPackageRefListFromPackageList(listA)
reflistB := NewPackageRefListFromPackageList(listB) reflistB := NewPackageRefListFromPackageList(listB)
reflistC := NewPackageRefListFromPackageList(listC)
mergeAB := reflistA.Merge(reflistB, true) mergeAB := reflistA.Merge(reflistB, true, false)
mergeBA := reflistB.Merge(reflistA, true) mergeBA := reflistB.Merge(reflistA, true, false)
mergeAC := reflistA.Merge(reflistC, true, false)
mergeBC := reflistB.Merge(reflistC, true, false)
mergeCB := reflistC.Merge(reflistB, true, false)
c.Check(toStrSlice(mergeAB), DeepEquals, c.Check(toStrSlice(mergeAB), DeepEquals,
[]string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp2", "Pi386 dpkg 1.0", "Pi386 lib 1.0", "Psparc xyz 1.0"}) []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000000", "Pi386 dpkg 1.0 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"})
c.Check(toStrSlice(mergeBA), DeepEquals, c.Check(toStrSlice(mergeBA), DeepEquals,
[]string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp1", "Pi386 dpkg 1.7", "Pi386 lib 1.0", "Psparc xyz 1.0"}) []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp1 00000000", "Pi386 dpkg 1.7 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"})
c.Check(toStrSlice(mergeAC), DeepEquals,
[]string{"Pall data 1.1~bp1 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"})
c.Check(toStrSlice(mergeBC), DeepEquals,
[]string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445", "Pi386 lib 1.0 00000000"})
c.Check(toStrSlice(mergeCB), DeepEquals,
[]string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000000", "Pi386 dpkg 1.0 00000000", "Pi386 lib 1.0 00000000"})
mergeABall := reflistA.Merge(reflistB, false) mergeABall := reflistA.Merge(reflistB, false, false)
mergeBAall := reflistB.Merge(reflistA, false) mergeBAall := reflistB.Merge(reflistA, false, false)
mergeACall := reflistA.Merge(reflistC, false, false)
mergeBCall := reflistB.Merge(reflistC, false, false)
mergeCBall := reflistC.Merge(reflistB, false, false)
c.Check(mergeABall, DeepEquals, mergeBAall) c.Check(mergeABall, DeepEquals, mergeBAall)
c.Check(toStrSlice(mergeBAall), DeepEquals, c.Check(toStrSlice(mergeBAall), DeepEquals,
[]string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp1", "Pi386 app 1.1~bp2", "Pi386 dpkg 1.0", "Pi386 dpkg 1.7", "Pi386 lib 1.0", "Psparc xyz 1.0"}) []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp1 00000000", "Pi386 app 1.1~bp2 00000000",
"Pi386 dpkg 1.0 00000000", "Pi386 dpkg 1.7 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"})
c.Check(mergeBCall, Not(DeepEquals), mergeCBall)
c.Check(toStrSlice(mergeACall), DeepEquals,
[]string{"Pall data 1.1~bp1 00000000", "Pi386 app 1.1~bp1 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445",
"Pi386 dpkg 1.7 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"})
c.Check(toStrSlice(mergeBCall), DeepEquals,
[]string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445",
"Pi386 lib 1.0 00000000"})
mergeBCwithConflicts := reflistB.Merge(reflistC, false, true)
c.Check(toStrSlice(mergeBCwithConflicts), DeepEquals,
[]string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000044",
"Pi386 dpkg 1.0 00000000", "Pi386 dpkg 1.0 00034445", "Pi386 lib 1.0 00000000"})
} }
func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) { func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) {
packages := []*Package{ packages := []*Package{
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, {Name: "lib", Version: "1.0", Architecture: "i386"},
&Package{Name: "lib", Version: "1.2~bp1", Architecture: "i386"}, {Name: "lib", Version: "1.2~bp1", Architecture: "i386"},
&Package{Name: "lib", Version: "1.2", Architecture: "i386"}, {Name: "lib", Version: "1.2", Architecture: "i386"},
&Package{Name: "dpkg", Version: "1.2", Architecture: "i386"}, {Name: "dpkg", Version: "1.2", Architecture: "i386"},
&Package{Name: "dpkg", Version: "1.3", Architecture: "i386"}, {Name: "dpkg", Version: "1.3", Architecture: "i386"},
&Package{Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"}, {Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"},
&Package{Name: "dpkg", Version: "1.5", Architecture: "i386"}, {Name: "dpkg", Version: "1.5", Architecture: "i386"},
&Package{Name: "dpkg", Version: "1.6", Architecture: "i386"}, {Name: "dpkg", Version: "1.6", Architecture: "i386"},
} }
rl := NewPackageList() rl := NewPackageList()
@@ -310,7 +346,7 @@ func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) {
rl.Add(packages[7]) rl.Add(packages[7])
result := NewPackageRefListFromPackageList(rl) result := NewPackageRefListFromPackageList(rl)
FilterLatestRefs(result) result.FilterLatestRefs()
c.Check(toStrSlice(result), DeepEquals, c.Check(toStrSlice(result), DeepEquals,
[]string{"Pi386 dpkg 1.6", "Pi386 lib 1.2"}) []string{"Pi386 dpkg 1.6", "Pi386 lib 1.2"})
+26 -6
View File
@@ -14,8 +14,10 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
) )
@@ -56,6 +58,8 @@ type RemoteRepo struct {
Filter string Filter string
// FilterWithDeps to include dependencies from filter query // FilterWithDeps to include dependencies from filter query
FilterWithDeps bool FilterWithDeps bool
// SkipComponentCheck skips component list verification
SkipComponentCheck bool
// Status marks state of repository (being updated, no action) // Status marks state of repository (being updated, no action)
Status int Status int
// WorkerPID is PID of the process modifying the mirror (if any) // WorkerPID is PID of the process modifying the mirror (if any)
@@ -307,6 +311,9 @@ ok:
if !repo.IsFlat() { if !repo.IsFlat() {
architectures := strings.Split(stanza["Architectures"], " ") architectures := strings.Split(stanza["Architectures"], " ")
sort.Strings(architectures)
// "source" architecture is never present, despite Release file claims
architectures = utils.StrSlicesSubstract(architectures, []string{"source"})
if len(repo.Architectures) == 0 { if len(repo.Architectures) == 0 {
repo.Architectures = architectures repo.Architectures = architectures
} else { } else {
@@ -318,14 +325,19 @@ ok:
} }
components := strings.Split(stanza["Components"], " ") components := strings.Split(stanza["Components"], " ")
for i := range components { if strings.Contains(repo.Distribution, "/") {
components[i] = path.Base(components[i]) distributionLast := path.Base(repo.Distribution) + "/"
for i := range components {
if strings.HasPrefix(components[i], distributionLast) {
components[i] = components[i][len(distributionLast):]
}
}
} }
if len(repo.Components) == 0 { if len(repo.Components) == 0 {
repo.Components = components repo.Components = components
} else { } else if !repo.SkipComponentCheck {
err = utils.StringsIsSubset(repo.Components, components, err = utils.StringsIsSubset(repo.Components, components,
fmt.Sprintf("component %%s not available in repo %s", repo)) fmt.Sprintf("component %%s not available in repo %s, use -force-components to override", repo))
if err != nil { if err != nil {
return err return err
} }
@@ -380,6 +392,8 @@ ok:
return err return err
} }
delete(stanza, "SHA512")
repo.Meta = stanza repo.Meta = stanza
return nil return nil
@@ -454,7 +468,11 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.
} }
err = repo.packageList.Add(p) err = repo.packageList.Add(p)
if err != nil { if err != nil {
return err if _, ok := err.(*PackageConflictError); ok {
progress.ColoredPrintf("@y[!]@| @!skipping package %s: duplicate in packages index@|", p)
} else {
return err
}
} }
err = collectionFactory.PackageCollection().Update(p) err = collectionFactory.PackageCollection().Update(p)
@@ -593,6 +611,7 @@ func (repo *RemoteRepo) RefKey() []byte {
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos // RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
type RemoteRepoCollection struct { type RemoteRepoCollection struct {
*sync.RWMutex
db database.Storage db database.Storage
list []*RemoteRepo list []*RemoteRepo
} }
@@ -600,7 +619,8 @@ type RemoteRepoCollection struct {
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection // NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection { func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
result := &RemoteRepoCollection{ result := &RemoteRepoCollection{
db: db, RWMutex: &sync.RWMutex{},
db: db,
} }
blobs := db.FetchByPrefix([]byte("R")) blobs := db.FetchByPrefix([]byte("R"))
+15 -14
View File
@@ -10,9 +10,10 @@ import (
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
"io" "io"
"io/ioutil" "io/ioutil"
. "launchpad.net/gocheck"
"os" "os"
"sort" "sort"
. "gopkg.in/check.v1"
) )
type NullVerifier struct { type NullVerifier struct {
@@ -192,7 +193,7 @@ func (s *RemoteRepoSuite) TestFetch(c *C) {
func (s *RemoteRepoSuite) TestFetchNullVerifier1(c *C) { func (s *RemoteRepoSuite) TestFetchNullVerifier1(c *C) {
downloader := http.NewFakeDownloader() downloader := http.NewFakeDownloader()
downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", errors.New("404")) downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", &http.HTTPError{Code: 404})
downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release.gpg", "GPG") downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release.gpg", "GPG")
@@ -252,8 +253,8 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
err := s.repo.Fetch(s.downloader, nil) err := s.repo.Fetch(s.downloader, nil)
c.Assert(err, IsNil) c.Assert(err, IsNil)
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.HTTPError{Code: 404})
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.HTTPError{Code: 404})
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false) err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false)
@@ -281,11 +282,11 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
err := s.repo.Fetch(s.downloader, nil) err := s.repo.Fetch(s.downloader, nil)
c.Assert(err, IsNil) c.Assert(err, IsNil)
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.HTTPError{Code: 404})
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.HTTPError{Code: 404})
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.bz2", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.bz2", &http.HTTPError{Code: 404})
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", &http.HTTPError{Code: 404})
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile)
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false) err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false)
@@ -322,8 +323,8 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
func (s *RemoteRepoSuite) TestDownloadFlat(c *C) { func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
downloader := http.NewFakeDownloader() downloader := http.NewFakeDownloader()
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile)
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", &http.HTTPError{Code: 404})
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", &http.HTTPError{Code: 404})
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile)
err := s.flat.Fetch(downloader, nil) err := s.flat.Fetch(downloader, nil)
@@ -352,11 +353,11 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
downloader := http.NewFakeDownloader() downloader := http.NewFakeDownloader()
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile)
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", &http.HTTPError{Code: 404})
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", &http.HTTPError{Code: 404})
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile)
downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.bz2", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.bz2", &http.HTTPError{Code: 404})
downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.gz", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.gz", &http.HTTPError{Code: 404})
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile)
err := s.flat.Fetch(downloader, nil) err := s.flat.Fetch(downloader, nil)
+81 -8
View File
@@ -9,22 +9,24 @@ import (
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
"log" "log"
"sort"
"strings" "strings"
"sync"
"time" "time"
) )
// Snapshot is immutable state of repository: list of packages // Snapshot is immutable state of repository: list of packages
type Snapshot struct { type Snapshot struct {
// Persisten internal ID // Persisten internal ID
UUID string UUID string `json:"-"`
// Human-readable name // Human-readable name
Name string Name string
// Date of creation // Date of creation
CreatedAt time.Time CreatedAt time.Time
// Source: kind + ID // Source: kind + ID
SourceKind string SourceKind string `json:"-"`
SourceIDs []string SourceIDs []string `json:"-"`
// Description of how snapshot was created // Description of how snapshot was created
Description string Description string
@@ -131,12 +133,12 @@ func (s *Snapshot) Decode(input []byte) error {
if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") { if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") {
// probably it is broken DB from go < 1.2, try decoding w/o time.Time // probably it is broken DB from go < 1.2, try decoding w/o time.Time
var snapshot11 struct { var snapshot11 struct {
UUID string UUID string
Name string Name string
CreatedAt []byte CreatedAt []byte
SourceKind string SourceKind string
SourceIDs []string SourceIDs []string
Description string Description string
} }
@@ -160,6 +162,7 @@ func (s *Snapshot) Decode(input []byte) error {
// SnapshotCollection does listing, updating/adding/deleting of Snapshots // SnapshotCollection does listing, updating/adding/deleting of Snapshots
type SnapshotCollection struct { type SnapshotCollection struct {
*sync.RWMutex
db database.Storage db database.Storage
list []*Snapshot list []*Snapshot
} }
@@ -167,7 +170,8 @@ type SnapshotCollection struct {
// NewSnapshotCollection loads Snapshots from DB and makes up collection // NewSnapshotCollection loads Snapshots from DB and makes up collection
func NewSnapshotCollection(db database.Storage) *SnapshotCollection { func NewSnapshotCollection(db database.Storage) *SnapshotCollection {
result := &SnapshotCollection{ result := &SnapshotCollection{
db: db, RWMutex: &sync.RWMutex{},
db: db,
} }
blobs := db.FetchByPrefix([]byte("S")) blobs := db.FetchByPrefix([]byte("S"))
@@ -293,6 +297,23 @@ func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) err
return err return err
} }
// ForEachSorted runs method for each snapshot following some sort order
func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error {
sorter, err := newSnapshotSorter(sortMethod, collection)
if err != nil {
return err
}
for _, i := range sorter.list {
err = handler(collection.list[i])
if err != nil {
return err
}
}
return nil
}
// Len returns number of snapshots in collection // Len returns number of snapshots in collection
// ForEach runs method for each snapshot // ForEach runs method for each snapshot
func (collection *SnapshotCollection) Len() int { func (collection *SnapshotCollection) Len() int {
@@ -324,3 +345,55 @@ func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
return collection.db.Delete(snapshot.RefKey()) return collection.db.Delete(snapshot.RefKey())
} }
// Snapshot sorting methods
const (
SortName = iota
SortTime
)
type snapshotSorter struct {
list []int
collection *SnapshotCollection
sortMethod int
}
func newSnapshotSorter(sortMethod string, collection *SnapshotCollection) (*snapshotSorter, error) {
s := &snapshotSorter{collection: collection}
switch sortMethod {
case "time", "Time":
s.sortMethod = SortTime
case "name", "Name":
s.sortMethod = SortName
default:
return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
}
s.list = make([]int, len(collection.list))
for i := range s.list {
s.list[i] = i
}
sort.Sort(s)
return s, nil
}
func (s *snapshotSorter) Swap(i, j int) {
s.list[i], s.list[j] = s.list[j], s.list[i]
}
func (s *snapshotSorter) Less(i, j int) bool {
switch s.sortMethod {
case SortName:
return s.collection.list[s.list[i]].Name < s.collection.list[s.list[j]].Name
case SortTime:
return s.collection.list[s.list[i]].CreatedAt.Before(s.collection.list[s.list[j]].CreatedAt)
}
panic("unknown sort method")
}
func (s *snapshotSorter) Len() int {
return len(s.list)
}
+2 -1
View File
@@ -3,7 +3,8 @@ package deb
import ( import (
"errors" "errors"
"github.com/smira/aptly/database" "github.com/smira/aptly/database"
. "launchpad.net/gocheck"
. "gopkg.in/check.v1"
) )
type SnapshotSuite struct { type SnapshotSuite struct {
+1 -1
View File
@@ -1,7 +1,7 @@
package deb package deb
import ( import (
. "launchpad.net/gocheck" . "gopkg.in/check.v1"
) )
type VersionSuite struct { type VersionSuite struct {
+2 -1
View File
@@ -1,8 +1,9 @@
package files package files
import ( import (
. "launchpad.net/gocheck"
"testing" "testing"
. "gopkg.in/check.v1"
) )
// Launch gocheck tests // Launch gocheck tests
+15 -1
View File
@@ -7,10 +7,12 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sync"
) )
// PackagePool is deduplicated storage of package files on filesystem // PackagePool is deduplicated storage of package files on filesystem
type PackagePool struct { type PackagePool struct {
sync.Mutex
rootPath string rootPath string
} }
@@ -50,6 +52,9 @@ func (pool *PackagePool) Path(filename string, hashMD5 string) (string, error) {
// FilepathList returns file paths of all the files in the pool // FilepathList returns file paths of all the files in the pool
func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) { func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) {
pool.Lock()
defer pool.Unlock()
dirs, err := ioutil.ReadDir(pool.rootPath) dirs, err := ioutil.ReadDir(pool.rootPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -93,6 +98,9 @@ func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error)
// Remove deletes file in package pool returns its size // Remove deletes file in package pool returns its size
func (pool *PackagePool) Remove(path string) (size int64, err error) { func (pool *PackagePool) Remove(path string) (size int64, err error) {
pool.Lock()
defer pool.Unlock()
path = filepath.Join(pool.rootPath, path) path = filepath.Join(pool.rootPath, path)
info, err := os.Stat(path) info, err := os.Stat(path)
@@ -106,6 +114,9 @@ func (pool *PackagePool) Remove(path string) (size int64, err error) {
// Import copies file into package pool // Import copies file into package pool
func (pool *PackagePool) Import(path string, hashMD5 string) error { func (pool *PackagePool) Import(path string, hashMD5 string) error {
pool.Lock()
defer pool.Unlock()
source, err := os.Open(path) source, err := os.Open(path)
if err != nil { if err != nil {
return err return err
@@ -128,12 +139,15 @@ func (pool *PackagePool) Import(path string, hashMD5 string) error {
// unable to stat target location? // unable to stat target location?
return err return err
} }
// file doesn't exist, that's ok
} else { } else {
// target already exists
if targetInfo.Size() != sourceInfo.Size() { if targetInfo.Size() != sourceInfo.Size() {
// trying to overwrite file? // trying to overwrite file?
return fmt.Errorf("unable to import into pool: file %s already exists", poolPath) return fmt.Errorf("unable to import into pool: file %s already exists", poolPath)
} }
// assume that target is already there
return nil
} }
// create subdirs as necessary // create subdirs as necessary
+2 -1
View File
@@ -2,10 +2,11 @@ package files
import ( import (
"io/ioutil" "io/ioutil"
. "launchpad.net/gocheck"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
. "gopkg.in/check.v1"
) )
type PackagePoolSuite struct { type PackagePoolSuite struct {
+2 -1
View File
@@ -2,10 +2,11 @@ package files
import ( import (
"io/ioutil" "io/ioutil"
. "launchpad.net/gocheck"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
. "gopkg.in/check.v1"
) )
type PublishedStorageSuite struct { type PublishedStorageSuite struct {
+30 -4
View File
@@ -16,6 +16,17 @@ import (
"strings" "strings"
) )
// HTTPError is download error connected to HTTP code
type HTTPError struct {
Code int
URL string
}
// Error
func (e *HTTPError) Error() string {
return fmt.Sprintf("HTTP code %d while fetching %s", e.Code, e.URL)
}
// Check interface // Check interface
var ( var (
_ aptly.Downloader = (*downloaderImpl)(nil) _ aptly.Downloader = (*downloaderImpl)(nil)
@@ -129,7 +140,19 @@ func (downloader *downloaderImpl) DownloadWithChecksum(url string, destination s
func (downloader *downloaderImpl) handleTask(task *downloadTask) { func (downloader *downloaderImpl) handleTask(task *downloadTask) {
downloader.progress.Printf("Downloading %s...\n", task.url) downloader.progress.Printf("Downloading %s...\n", task.url)
resp, err := downloader.client.Get(task.url) req, err := http.NewRequest("GET", task.url, nil)
if err != nil {
task.result <- fmt.Errorf("%s: %s", task.url, err)
return
}
proxyURL, _ := downloader.client.Transport.(*http.Transport).Proxy(req)
if proxyURL == nil && (req.URL.Scheme == "http" || req.URL.Scheme == "https") {
req.URL.Opaque = strings.Replace(req.URL.RequestURI(), "+", "%2b", -1)
req.URL.RawQuery = ""
}
resp, err := downloader.client.Do(req)
if err != nil { if err != nil {
task.result <- fmt.Errorf("%s: %s", task.url, err) task.result <- fmt.Errorf("%s: %s", task.url, err)
return return
@@ -139,7 +162,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) {
} }
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode < 200 || resp.StatusCode > 299 {
task.result <- fmt.Errorf("HTTP code %d while fetching %s", resp.StatusCode, task.url) task.result <- &HTTPError{Code: resp.StatusCode, URL: task.url}
return return
} }
@@ -307,13 +330,16 @@ func DownloadTryCompression(downloader aptly.Downloader, url string, expectedChe
} }
if err != nil { if err != nil {
continue if err1, ok := err.(*HTTPError); ok && (err1.Code == 404 || err1.Code == 403) {
continue
}
return nil, nil, err
} }
var uncompressed io.Reader var uncompressed io.Reader
uncompressed, err = method.transformation(file) uncompressed, err = method.transformation(file)
if err != nil { if err != nil {
continue return nil, nil, err
} }
return uncompressed, file, err return uncompressed, file, err
+15 -18
View File
@@ -8,12 +8,13 @@ import (
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
"io" "io"
"io/ioutil" "io/ioutil"
. "launchpad.net/gocheck"
"net" "net"
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"time" "time"
. "gopkg.in/check.v1"
) )
type DownloaderSuite struct { type DownloaderSuite struct {
@@ -210,9 +211,9 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
var buf []byte var buf []byte
expectedChecksums := map[string]utils.ChecksumInfo{ expectedChecksums := map[string]utils.ChecksumInfo{
"file.bz2": utils.ChecksumInfo{Size: int64(len(bzipData))}, "file.bz2": {Size: int64(len(bzipData))},
"file.gz": utils.ChecksumInfo{Size: int64(len(gzipData))}, "file.gz": {Size: int64(len(gzipData))},
"file": utils.ChecksumInfo{Size: int64(len(rawData))}, "file": {Size: int64(len(rawData))},
} }
// bzip2 only available // bzip2 only available
@@ -229,7 +230,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
// bzip2 not available, but gz is // bzip2 not available, but gz is
buf = make([]byte, 4) buf = make([]byte, 4)
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", errors.New("404")) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file.gz", gzipData) d.ExpectResponse("http://example.com/file.gz", gzipData)
r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false) r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
@@ -241,8 +242,8 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
// bzip2 & gzip not available, but raw is // bzip2 & gzip not available, but raw is
buf = make([]byte, 4) buf = make([]byte, 4)
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", errors.New("404")) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectError("http://example.com/file.gz", errors.New("404")) d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file", rawData) d.ExpectResponse("http://example.com/file", rawData)
r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false) r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
@@ -254,14 +255,10 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) {
// gzip available, but broken // gzip available, but broken
buf = make([]byte, 4) buf = make([]byte, 4)
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", errors.New("404")) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file.gz", "x") d.ExpectResponse("http://example.com/file.gz", "x")
d.ExpectResponse("http://example.com/file", "recovered")
r, file, err = DownloadTryCompression(d, "http://example.com/file", nil, false) r, file, err = DownloadTryCompression(d, "http://example.com/file", nil, false)
c.Assert(err, IsNil) c.Assert(err, ErrorMatches, "unexpected EOF")
defer file.Close()
io.ReadFull(r, buf)
c.Assert(string(buf), Equals, "reco")
c.Assert(d.Empty(), Equals, true) c.Assert(d.Empty(), Equals, true)
} }
@@ -271,16 +268,16 @@ func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) {
c.Assert(err, ErrorMatches, "unexpected request.*") c.Assert(err, ErrorMatches, "unexpected request.*")
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", errors.New("404")) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectError("http://example.com/file.gz", errors.New("404")) d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404})
d.ExpectError("http://example.com/file", errors.New("403")) d.ExpectError("http://example.com/file", errors.New("403"))
_, _, err = DownloadTryCompression(d, "http://example.com/file", nil, false) _, _, err = DownloadTryCompression(d, "http://example.com/file", nil, false)
c.Assert(err, ErrorMatches, "403") c.Assert(err, ErrorMatches, "403")
d = NewFakeDownloader() d = NewFakeDownloader()
d.ExpectError("http://example.com/file.bz2", errors.New("404")) d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404})
d.ExpectError("http://example.com/file.gz", errors.New("404")) d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404})
d.ExpectResponse("http://example.com/file", rawData) d.ExpectResponse("http://example.com/file", rawData)
_, _, err = DownloadTryCompression(d, "http://example.com/file", map[string]utils.ChecksumInfo{"file": utils.ChecksumInfo{Size: 7}}, false) _, _, err = DownloadTryCompression(d, "http://example.com/file", map[string]utils.ChecksumInfo{"file": {Size: 7}}, false)
c.Assert(err, ErrorMatches, "checksums don't match.*") c.Assert(err, ErrorMatches, "checksums don't match.*")
} }
+2 -1
View File
@@ -1,8 +1,9 @@
package http package http
import ( import (
. "launchpad.net/gocheck"
"testing" "testing"
. "gopkg.in/check.v1"
) )
// Launch gocheck tests // Launch gocheck tests
+113 -6
View File
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "APTLY" "1" "October 2014" "" "" .TH "APTLY" "1" "February 2015" "" ""
. .
.SH "NAME" .SH "NAME"
\fBaptly\fR \- Debian repository management tool \fBaptly\fR \- Debian repository management tool
@@ -52,7 +52,7 @@ Configuration file is stored in JSON format (default values shown below):
"test": { "test": {
"region": "us\-east\-1", "region": "us\-east\-1",
"bucket": "repo", "bucket": "repo",
"awsAccessKeyID": "" "awsAccessKeyID": "",
"awsSecretAccessKey": "", "awsSecretAccessKey": "",
"prefix": "", "prefix": "",
"acl": "public\-read", "acl": "public\-read",
@@ -60,6 +60,18 @@ Configuration file is stored in JSON format (default values shown below):
"encryptionMethod": "", "encryptionMethod": "",
"plusWorkaround": false "plusWorkaround": false
} }
},
"SwiftPublishEndpoints": {
"test": {
"container": "repo",
"osname": "",
"password": "",
"prefix": "",
"authurl": "",
"tenant": "",
"tenantid": ""
}
}
} }
. .
.fi .fi
@@ -162,6 +174,35 @@ In order to publish to S3, specify endpoint as \fBs3:endpoint\-name:\fR before p
.P .P
\fBaptly publish snapshot wheezy\-main s3:test:\fR \fBaptly publish snapshot wheezy\-main s3:test:\fR
. .
.SH "OPENSTACK SWIFT PUBLISHING ENDPOINTS"
aptly could be configured to publish repository directly to OpenStack Swift\. First, publishing endpoints should be described in aptly configuration file\. Each endpoint has name and associated settings:
.
.TP
\fBcontainer\fR
container name
.
.TP
\fBprefix\fR
(optional) do publishing under specified prefix in the container, defaults to no prefix (container root)
.
.TP
\fBosname\fR, \fBpassword\fR
(optional) OpenStack credentials to access Keystone\. If not supplied, environment variables \fBOS_USERNAME\fR and \fBOS_PASSWORD\fR are used\.
.
.TP
\fBtenant\fR, \fBtenantid\fR
(optional) OpenStack tenant name and id (in order to use v2 authentication)\.
.
.TP
\fBauthurl\fR
(optional) the full url of Keystone server (including port, and version)\. example \fBhttp://identity\.example\.com:5000/v2\.0\fR
.
.P
In order to publish to Swift, specify endpoint as \fBswift:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.:
.
.P
\fBaptly publish snapshot jessie\-main swift:test:\fR
.
.SH "PACKAGE QUERY" .SH "PACKAGE QUERY"
Some commands accept package queries to identify list of packages to process\. Package query syntax almost matches \fBreprepro\fR query language\. Query consists of the following simple terms: Some commands accept package queries to identify list of packages to process\. Package query syntax almost matches \fBreprepro\fR query language\. Query consists of the following simple terms:
. .
@@ -315,6 +356,10 @@ filter packages in mirror
when filtering, include dependencies of matching packages as well when filtering, include dependencies of matching packages as well
. .
.TP .TP
\-\fBforce\-components\fR=false
(only with component list) skip check that requested components are listed in Release file
.
.TP
\-\fBignore\-signatures\fR=false \-\fBignore\-signatures\fR=false
disable verification of Release file signatures disable verification of Release file signatures
. .
@@ -1102,6 +1147,10 @@ $ aptly publish repo testing
Options: Options:
. .
.TP .TP
\-\fBbatch\fR=false
run GPG with detached tty
.
.TP
\-\fBcomponent\fR= \-\fBcomponent\fR=
component name to publish (for multi\-component publishing, separate components with commas) component name to publish (for multi\-component publishing, separate components with commas)
. .
@@ -1181,6 +1230,10 @@ $ aptly publish snapshot wheezy\-main
Options: Options:
. .
.TP .TP
\-\fBbatch\fR=false
run GPG with detached tty
.
.TP
\-\fBcomponent\fR= \-\fBcomponent\fR=
component name to publish (for multi\-component publishing, separate components with commas) component name to publish (for multi\-component publishing, separate components with commas)
. .
@@ -1228,7 +1281,7 @@ don\(cqt sign Release files with GPG
\fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-snapshot\fR \fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-snapshot\fR
. .
.P .P
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 snapshot contents\. All publishing parameters are preserved (architecture list, distribution, component)\.
. .
.P .P
For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding snapshots should be given in the same order, e\.g\.: For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding snapshots should be given in the same order, e\.g\.:
@@ -1237,7 +1290,7 @@ For multiple component repositories, flag \-component should be given with list
. .
.nf .nf
aptly publish update \-component=main,contrib wheezy wh\-main wh\-contrib aptly publish switch \-component=main,contrib wheezy wh\-main wh\-contrib
. .
.fi .fi
. .
@@ -1250,16 +1303,23 @@ Example:
. .
.nf .nf
$ aptly publish update wheezy ppa wheezy\-7\.5 $ aptly publish switch wheezy ppa wheezy\-7\.5
. .
.fi .fi
. .
.IP "" 0 .IP "" 0
. .
.P .P
This command would switch published repository (with one component) named ppa/wheezy (prefix ppa, dsitribution wheezy to new snapshot wheezy\-7\.5)\.
.
.P
Options: Options:
. .
.TP .TP
\-\fBbatch\fR=false
run GPG with detached tty
.
.TP
\-\fBcomponent\fR= \-\fBcomponent\fR=
component names to update (for multi\-component publishing, separate components with commas) component names to update (for multi\-component publishing, separate components with commas)
. .
@@ -1317,6 +1377,10 @@ $ aptly publish update wheezy ppa
Options: Options:
. .
.TP .TP
\-\fBbatch\fR=false
run GPG with detached tty
.
.TP
\-\fBforce\-overwrite\fR=false \-\fBforce\-overwrite\fR=false
overwrite files in package pool in case of mismatch overwrite files in package pool in case of mismatch
. .
@@ -1363,7 +1427,7 @@ $ aptly package search \(cq$Architecture (i386), Name (% *\-dev)\(cq
. .
.IP "" 0 .IP "" 0
. .
.SH "SHOW DETAILS ABOUT PACKAGES MATCING QUERY" .SH "SHOW DETAILS ABOUT PACKAGES MATCHING QUERY"
\fBaptly\fR \fBpackage\fR \fBshow\fR \fIpackage\-query\fR \fBaptly\fR \fBpackage\fR \fBshow\fR \fIpackage\-query\fR
. .
.P .P
@@ -1448,6 +1512,49 @@ Example:
.P .P
$ aptly graph $ aptly graph
. .
.SH "SHOW CURRENT APTLY\(cqS CONFIG"
\fBaptly\fR \fBconfig\fR \fBshow\fR
.
.P
Command show displays the current aptly configuration\.
.
.P
Example:
.
.P
$ aptly config show
.
.SH "RUN APTLY TASKS"
\fBaptly\fR \fBtask\fR \fBrun\fR \-filename=\fIfilename\fR \fB|\fR \fIcommand1\fR, \fIcommand2\fR, \fB\|\.\|\.\|\.\fR
.
.P
Command helps organise multiple aptly commands in one single aptly task, running as single thread\.
.
.P
Example:
.
.IP "" 4
.
.nf
$ aptly task run
> repo create local
> repo add local pkg1
> publish repo local
> serve
>
.
.fi
.
.IP "" 0
.
.P
Options:
.
.TP
\-\fBfilename\fR=
specifies the filename that contains the commands to run
.
.SH "ENVIRONMENT" .SH "ENVIRONMENT"
If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\. If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\.
. .
+46 -2
View File
@@ -44,7 +44,7 @@ Configuration file is stored in JSON format (default values shown below):
"test": { "test": {
"region": "us-east-1", "region": "us-east-1",
"bucket": "repo", "bucket": "repo",
"awsAccessKeyID": "" "awsAccessKeyID": "",
"awsSecretAccessKey": "", "awsSecretAccessKey": "",
"prefix": "", "prefix": "",
"acl": "public-read", "acl": "public-read",
@@ -52,6 +52,18 @@ Configuration file is stored in JSON format (default values shown below):
"encryptionMethod": "", "encryptionMethod": "",
"plusWorkaround": false "plusWorkaround": false
} }
},
"SwiftPublishEndpoints": {
"test": {
"container": "repo",
"osname": "",
"password": "",
"prefix": "",
"authurl": "",
"tenant": "",
"tenantid": ""
}
}
} }
Options: Options:
@@ -101,6 +113,9 @@ Options:
* `S3PublishEndpoints`: * `S3PublishEndpoints`:
configuration of Amazon S3 publishing endpoints (see below) configuration of Amazon S3 publishing endpoints (see below)
* `SwiftPublishEndpoints`:
configuration of OpenStack Swift publishing endpoints (see below)
## S3 PUBLISHING ENDPOINTS ## S3 PUBLISHING ENDPOINTS
aptly could be configured to publish repository directly to Amazon S3. First, publishing aptly could be configured to publish repository directly to Amazon S3. First, publishing
@@ -144,6 +159,31 @@ publishing prefix on the command line, e.g.:
`aptly publish snapshot wheezy-main s3:test:` `aptly publish snapshot wheezy-main s3:test:`
## OPENSTACK SWIFT PUBLISHING ENDPOINTS
aptly could be configured to publish repository directly to OpenStack Swift. First,
publishing endpoints should be described in aptly configuration file. Each endpoint
has name and associated settings:
* `container`:
container name
* `prefix`:
(optional) do publishing under specified prefix in the container, defaults to
no prefix (container root)
* `osname`, `password`:
(optional) OpenStack credentials to access Keystone. If not supplied,
environment variables `OS_USERNAME` and `OS_PASSWORD` are used.
* `tenant`, `tenantid`:
(optional) OpenStack tenant name and id (in order to use v2 authentication).
* `authurl`:
(optional) the full url of Keystone server (including port, and version).
example `http://identity.example.com:5000/v2.0`
In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before
publishing prefix on the command line, e.g.:
`aptly publish snapshot jessie-main swift:test:`
## PACKAGE QUERY ## PACKAGE QUERY
Some commands accept package queries to identify list of packages to process. Some commands accept package queries to identify list of packages to process.
@@ -245,6 +285,10 @@ When specified on command line, query may have to be quoted according to shell r
{{template "command" findCommand . "graph"}} {{template "command" findCommand . "graph"}}
{{template "command" findCommand . "config"}}
{{template "command" findCommand . "task"}}
## ENVIRONMENT ## ENVIRONMENT
If environment variable `HTTP_PROXY` is set `aptly` would use its value If environment variable `HTTP_PROXY` is set `aptly` would use its value
@@ -295,4 +339,4 @@ Options:
* -`{{.Name}}`={{.DefValue}}: * -`{{.Name}}`={{.DefValue}}:
{{.Usage}} {{.Usage}}
{{end}} {{end}}
{{end}} {{end}}
+2 -1
View File
@@ -2,7 +2,8 @@ package query
import ( import (
"fmt" "fmt"
. "launchpad.net/gocheck"
. "gopkg.in/check.v1"
) )
type LexerSuite struct { type LexerSuite struct {

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