mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-04 05:10:40 +00:00
Compare commits
381 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66e73782e5 | |||
| 68f332628d | |||
| 01c0d19243 | |||
| eb0443ed51 | |||
| 4b974b038c | |||
| 2d9ee81c95 | |||
| 5c9d4d2844 | |||
| 49a9ad79dd | |||
| 7e60466c7b | |||
| 233ad2528f | |||
| 2f1afa54c2 | |||
| 6bf910ea56 | |||
| 8fcfedf708 | |||
| 26b46ee2a0 | |||
| e33a2a6f96 | |||
| 06dc1ef9a4 | |||
| 4c57c358b7 | |||
| 65532b3dbf | |||
| fb25dec58e | |||
| e320499f84 | |||
| 4715b12f16 | |||
| c6a30a30de | |||
| 618d06678c | |||
| 903d4cefba | |||
| 79292dc6c8 | |||
| 43414be2ee | |||
| 3c34ae6071 | |||
| 642957e3a3 | |||
| e5d646c007 | |||
| e0f811dab1 | |||
| 48b8311150 | |||
| 8111460e36 | |||
| 0490d0c928 | |||
| b323e315d1 | |||
| 77f928db69 | |||
| b67f3dd6f7 | |||
| 88ff4493b0 | |||
| 6e8fd6e907 | |||
| 9c3095e42c | |||
| c737b8c544 | |||
| 87cecac4ea | |||
| 76ee53e9f8 | |||
| f153c7c3ea | |||
| 36792bba29 | |||
| 0b05964faa | |||
| ff00a5a026 | |||
| fc0310f468 | |||
| 63bf30b890 | |||
| 3004473bbb | |||
| 4356fe5cbe | |||
| d6271b6542 | |||
| 26a65b2336 | |||
| 20adfd49a7 | |||
| 355a98b51f | |||
| 1f73a34a54 | |||
| 7925af9fd6 | |||
| fb03a3baf9 | |||
| f097cd20c1 | |||
| 0489ba9d16 | |||
| 46b3f8fbaf | |||
| cacd0cf103 | |||
| c933668c16 | |||
| 24418ab0a4 | |||
| 4963d0a1d7 | |||
| ea8bfeb8a7 | |||
| a582493a6e | |||
| 930f76887b | |||
| a4201a40d2 | |||
| 4990bb98e5 | |||
| 00d4674aa5 | |||
| 06b4016338 | |||
| c1b2e4fabb | |||
| f438637a98 | |||
| ce208f347e | |||
| 06502584cf | |||
| 0f22dc590a | |||
| 11716f06f0 | |||
| 1ba06e828d | |||
| bc357a19a1 | |||
| 9004f8578c | |||
| 7f038be1cb | |||
| 13fc1122f0 | |||
| cb99cbec58 | |||
| 5d16cf06cf | |||
| b0489117c8 | |||
| fa2eef564c | |||
| d20300b152 | |||
| 398303235a | |||
| 25d048fe49 | |||
| 8c15a0ca95 | |||
| 8e8ff8ba65 | |||
| 1b0eb9d45a | |||
| 403c7272cd | |||
| 0412646151 | |||
| 0725003107 | |||
| 7a1553dc55 | |||
| 8375a2c30f | |||
| 5bbbdb3c19 | |||
| 1fd80c40d0 | |||
| ae5ab2d138 | |||
| eb087fd291 | |||
| 3f6491b8a3 | |||
| 9250479846 | |||
| 9c60421bd6 | |||
| ebea4f10a0 | |||
| d828732307 | |||
| 7c3629337c | |||
| a29034caa5 | |||
| c1fd633ed7 | |||
| bd2cc45524 | |||
| 0665f2231a | |||
| 836abdc81e | |||
| 876eeedb14 | |||
| fd502264a9 | |||
| b155eaa91c | |||
| a0d7ae28bf | |||
| 2816647809 | |||
| 67ce828eeb | |||
| 427c42f4b8 | |||
| b50cb70a0e | |||
| c832a5cdc4 | |||
| 1bd625f17f | |||
| a0fa0becc2 | |||
| d489694ea9 | |||
| 982b5dc886 | |||
| 1ddaecfb94 | |||
| 129c34806c | |||
| 6c7f3b3bbd | |||
| 38cb6bd133 | |||
| 98ca0cdf33 | |||
| 6e32e3dcf4 | |||
| 6a1a871dda | |||
| 6bc7048166 | |||
| 87fbd5201b | |||
| dcf5798229 | |||
| 382ad10cf7 | |||
| ddb2dd7eb6 | |||
| 9b1b43c8b4 | |||
| ee7d84205b | |||
| 93e8e18ca6 | |||
| d586f31247 | |||
| dd9fc8e40e | |||
| d847cba870 | |||
| d983e10d08 | |||
| a6fc65ff4e | |||
| 85f38cd739 | |||
| acde6ff2b2 | |||
| c733129de9 | |||
| e138212593 | |||
| 64ef342121 | |||
| 66c9bb86f5 | |||
| 923e2e1e50 | |||
| 0e552eda55 | |||
| ba32d16c8a | |||
| 4320144024 | |||
| 2564564601 | |||
| f228ad811b | |||
| 5fe442f191 | |||
| 50c4aba9ab | |||
| b3627738c2 | |||
| eec6743fe4 | |||
| 42bf2f5e98 | |||
| 35b9a8ea91 | |||
| 26c0502307 | |||
| 5fa487e2dc | |||
| d9c62780c2 | |||
| 8c54e15a11 | |||
| cc2cc16004 | |||
| 726f12c537 | |||
| f1c235f5c5 | |||
| 9072ba5981 | |||
| 7beb90d4fc | |||
| 61e22743af | |||
| 036baa2264 | |||
| ccd8c2551f | |||
| 74f9787884 | |||
| 83af66a8f6 | |||
| daf887e54f | |||
| 552b11e28d | |||
| 80a88a2248 | |||
| 4c1d6d1463 | |||
| 140a11c04a | |||
| 7be2ef8b85 | |||
| d2d21c3df7 | |||
| f81a91bde9 | |||
| 7efd0de67c | |||
| 6a9db17460 | |||
| b1053826e3 | |||
| 18953c1c90 | |||
| aeb85a1b3c | |||
| 0a6d57ea1a | |||
| aa4dee3c60 | |||
| 9fbe33b356 | |||
| 2a9871e2e9 | |||
| 951b6e9004 | |||
| 2173d3ab65 | |||
| 9c834f410c | |||
| eef44f5cd5 | |||
| aa77ea2835 | |||
| 119bb0195b | |||
| 017dca57ed | |||
| 88351503b0 | |||
| 37b2d49aea | |||
| 0afb1f4306 | |||
| 6ac0658478 | |||
| 50c8e35a90 | |||
| d6c3389d7c | |||
| 6b83213cf4 | |||
| 24927f9a29 | |||
| ecbb9ad20c | |||
| 81e9189853 | |||
| 8efb7903b2 | |||
| c1995beff1 | |||
| 192152b215 | |||
| 972e8c1373 | |||
| c501fc63f8 | |||
| bf08ad800f | |||
| ebc223a895 | |||
| 01b1f23d6b | |||
| 6b08b64d62 | |||
| 89bb20388f | |||
| 9857789204 | |||
| 6d1efe0200 | |||
| a85aa11ecd | |||
| 27ea769ad3 | |||
| 523d0d0945 | |||
| 53f7fef4cf | |||
| b590efa45f | |||
| 59055d7fbd | |||
| 22bcacf143 | |||
| 877109b3b7 | |||
| 10056b8571 | |||
| ac983ff65d | |||
| 2ed76f1e4c | |||
| cb6b18acfe | |||
| 3cd8c5adab | |||
| dd7b7b5f20 | |||
| 1f6880fcad | |||
| c8d9bef686 | |||
| 8a787d2c35 | |||
| 159608cef3 | |||
| 52c5934eb6 | |||
| e4b9e974d2 | |||
| d541b4f137 | |||
| eece643ea5 | |||
| 14bd443d4d | |||
| 9109c60c43 | |||
| 445ecbe8f3 | |||
| 27de979733 | |||
| ad11053412 | |||
| a356f3dff9 | |||
| 1042894123 | |||
| 43eb993160 | |||
| d190ffd39a | |||
| 93c1c7aaab | |||
| 3e5ba27cb7 | |||
| cd3b24799a | |||
| a0870f6726 | |||
| d45b456334 | |||
| 91c753ad2f | |||
| 40509f73b3 | |||
| 1daa076d65 | |||
| aeae6009c4 | |||
| 8049d69793 | |||
| 8aa1954ba7 | |||
| a02a90a3d8 | |||
| f303aabf26 | |||
| 735cbac60d | |||
| 5d69871ca4 | |||
| 1afbae8f7c | |||
| 1ed647e1b0 | |||
| 01b8e9eda5 | |||
| f43d514804 | |||
| 7e8f692b2c | |||
| 4b50f817d7 | |||
| e123e4dfac | |||
| 4fb09d9e85 | |||
| d9b23167bc | |||
| 2c84faaf8d | |||
| 6514b87e3e | |||
| bd34ba4088 | |||
| fae6e977c3 | |||
| 2ae34cd873 | |||
| b365e5e0b2 | |||
| e171f90fd5 | |||
| db499f872d | |||
| 8f9944117c | |||
| ea399a335a | |||
| 976ddb5ff9 | |||
| 7d8600b840 | |||
| 7ad1bb387b | |||
| 2fbf465fbf | |||
| fa786332de | |||
| 5e1bd0ff0e | |||
| 9c92b81706 | |||
| 144ccbf809 | |||
| a11805efb4 | |||
| 5b6cea2d62 | |||
| d4699a3b24 | |||
| 09a695a128 | |||
| ec4d2bcefe | |||
| 3040aceb7f | |||
| 61d8639a8a | |||
| b47754a106 | |||
| 1b08b7311f | |||
| 0130fc0392 | |||
| de32595d29 | |||
| 95e5fdd34a | |||
| a05f00d9f1 | |||
| 97158ef37b | |||
| f01ac06d97 | |||
| a549778754 | |||
| 47d952f712 | |||
| 166f31c34d | |||
| 4940fdc951 | |||
| 7ae785f5a3 | |||
| 09c8421648 | |||
| 6a2059150f | |||
| 9b3dfe920d | |||
| 72f8e4ab61 | |||
| 755944652f | |||
| b29d42d023 | |||
| f19ece776d | |||
| 839763c0b9 | |||
| c56ecab06f | |||
| 02d86422a8 | |||
| 65efe0cd2a | |||
| 468b1f11b9 | |||
| 608870265c | |||
| ed03a7c69e | |||
| 5a42c60af4 | |||
| f66302ef31 | |||
| 346a7bcce9 | |||
| 9bee7cdd08 | |||
| 74eee3496c | |||
| 3ef5429212 | |||
| 3030e66d4c | |||
| 9ae5a5ffb2 | |||
| 833d37d22c | |||
| 03ec1f97a7 | |||
| a2df51b40e | |||
| ae906f525e | |||
| b4a5a55cac | |||
| 6003764ff5 | |||
| 099a82c816 | |||
| ef992e2b44 | |||
| 68e600974d | |||
| 39a1f0ec2d | |||
| 318fc5b7f4 | |||
| 72e54aa3d1 | |||
| 91ff904ac4 | |||
| b59471ad35 | |||
| 6ff601f4a2 | |||
| 0c09bdedaa | |||
| dfc1f27d4c | |||
| 005cee572e | |||
| 18e3ed5d64 | |||
| 3c7696ef7e | |||
| b2779d7a88 | |||
| cdd34b4759 | |||
| 1f2ddca32b | |||
| df06dc356b | |||
| b6c82f073f | |||
| 9a03b5f696 | |||
| 047270540a | |||
| eff3823edf | |||
| 9d02f057c6 | |||
| 8387586cc8 | |||
| b433e7dad5 | |||
| dec4bdee71 | |||
| bb6593d21e | |||
| fe879acf9c | |||
| 5b8390c644 | |||
| d558791070 | |||
| 38ea595c9a | |||
| c03b7929d4 | |||
| d122ab6013 | |||
| a7b594d076 | |||
| e07bcf8e51 | |||
| da6d5b7cf8 | |||
| 15ef5c63c5 |
+18
-4
@@ -1,15 +1,20 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2.1
|
||||
- 1.3
|
||||
- 1.3.3
|
||||
- tip
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc="
|
||||
|
||||
- secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E="
|
||||
- secure: "OxiVNmre2JzUszwPNNilKDgIqtfX2gnRSsVz6nuySB1uO2yQsOQmKWJ9cVYgH2IB5H8eWXKOhexcSE28kz6TPLRuEcU9fnqKY3uEkdwm7rJfz9lf+7C4bJEUdA1OIzJppjnWUiXxD7CEPL1DlnMZM24eDQYqa/4WKACAgkK53gE="
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y python-virtualenv graphviz
|
||||
- virtualenv env
|
||||
- . env/bin/activate
|
||||
- pip install boto requests python-swiftclient
|
||||
install:
|
||||
- make prepare
|
||||
|
||||
@@ -19,3 +24,12 @@ script: make travis
|
||||
matrix:
|
||||
allow_failures:
|
||||
- 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
|
||||
|
||||
@@ -3,4 +3,15 @@ List of contributors, in chronological order:
|
||||
* Andrey Smirnov (https://github.com/smira)
|
||||
* Sebastien Binet (https://github.com/sbinet)
|
||||
* Ryan Uber (https://github.com/ryanuber)
|
||||
* Simon Aquino (https://github.com/simonaquino)
|
||||
* Simon Aquino (https://github.com/queeno)
|
||||
* Vincent Batoufflet (https://github.com/vbatoufflet)
|
||||
* Ivan Kurnosov (https://github.com/zerkms)
|
||||
* Dmitrii Kashin (https://github.com/freehck)
|
||||
* Chris Read (https://github.com/cread)
|
||||
* Rohan Garg (https://github.com/shadeslayer)
|
||||
* Russ Allbery (https://github.com/rra)
|
||||
* Sylvain Baubeau (https://github.com/lebauce)
|
||||
* Andrea Bernardo Ciddio (https://github.com/bcandrea)
|
||||
* Michael Koval (https://github.com/mkoval)
|
||||
* Alexander Guy (https://github.com/alexanderguy)
|
||||
* Sebastien Badia (https://github.com/sbadia)
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
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/mxk/go1/flowcontrol', :commit => '5ff2502e2556'
|
||||
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
|
||||
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827'
|
||||
gom 'github.com/mitchellh/goamz/s3', :commit => '55f224c07975fddef9d2116600c664e30df3d594'
|
||||
gom 'github.com/AlekSi/pointer', :commit => '5f6d527dae3d678b46fbb20331ddf44e2b841943'
|
||||
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/mitchellh/goamz/s3', :commit => 'e7664b32019f31fd1bdf33f9e85f28722f700405'
|
||||
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/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841'
|
||||
gom 'github.com/syndtr/goleveldb/leveldb', :commit => '9888007'
|
||||
gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107'
|
||||
gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa'
|
||||
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/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1'
|
||||
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
|
||||
gom 'golang.org/x/crypto/ssh/terminal', :commit => 'a7ead6ddf06233883deca151dffaef2effbf498f'
|
||||
|
||||
group :test do
|
||||
gom 'launchpad.net/gocheck'
|
||||
gom 'gopkg.in/check.v1'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gom 'github.com/golang/lint/golint'
|
||||
gom 'github.com/mattn/goveralls'
|
||||
gom 'github.com/axw/gocov/gocov'
|
||||
gom 'code.google.com/p/go.tools/cmd/cover'
|
||||
gom 'golang.org/x/tools/cmd/cover'
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
GOVERSION=$(shell go version | awk '{print $$3;}')
|
||||
PACKAGES=database deb files http query s3 utils
|
||||
ALL_PACKAGES=aptly cmd console database deb files http query s3 utils
|
||||
PACKAGES=context database deb files http query swift s3 utils
|
||||
ALL_PACKAGES=api aptly context cmd console database deb files http query swift s3 utils
|
||||
BINPATH=$(abspath ./_vendor/bin)
|
||||
GOM_ENVIRONMENT=-test
|
||||
PYTHON?=python
|
||||
@@ -36,16 +36,14 @@ coverage: coverage.out
|
||||
rm -f coverage.out
|
||||
|
||||
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:%=./%)
|
||||
|
||||
install:
|
||||
$(GOM) build -o $(BINPATH)/aptly
|
||||
|
||||
system-test: install
|
||||
ifeq ($(GOVERSION),$(filter $(GOVERSION),go1.2 go1.2.1 go1.3 devel))
|
||||
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
|
||||
endif
|
||||
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
|
||||
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long
|
||||
|
||||
@@ -79,7 +77,10 @@ src-package:
|
||||
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
|
||||
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)
|
||||
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
|
||||
|
||||
.PHONY: coverage.out
|
||||
|
||||
+19
-6
@@ -8,8 +8,17 @@ aptly
|
||||
.. image:: https://coveralls.io/repos/smira/aptly/badge.png?branch=HEAD
|
||||
:target: https://coveralls.io/r/smira/aptly?branch=HEAD
|
||||
|
||||
.. image:: https://badges.gitter.im/Join Chat.svg
|
||||
: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.
|
||||
|
||||
.. image:: http://www.aptly.info/img/aptly_logo.png
|
||||
:target: http://www.aptly.info/
|
||||
|
||||
Documentation is available at `http://www.aptly.info/ <http://www.aptly.info/>`_. For support use
|
||||
mailing list `aptly-discuss <https://groups.google.com/forum/#!forum/aptly-discuss>`_.
|
||||
|
||||
@@ -20,14 +29,15 @@ Aptly features: ("+" means planned features)
|
||||
* publish snapshot as Debian repository, ready to be consumed by apt
|
||||
* controlled update of one or more packages in snapshot from upstream mirror, tracking dependencies
|
||||
* merge two or more snapshots into one
|
||||
* filter repository by search query, pulling dependencies when required (+)
|
||||
* publish self-made packages as Debian repositories (+)
|
||||
* filter repository by search query, pulling dependencies when required
|
||||
* publish self-made packages as Debian repositories
|
||||
* REST API for remote access
|
||||
* mirror repositories "as-is" (without resigning with user's key) (+)
|
||||
* support for yum repositories (+)
|
||||
|
||||
Current limitations:
|
||||
|
||||
* debian-installer and translations not supported yet
|
||||
* translations are not supported yet
|
||||
|
||||
Download
|
||||
--------
|
||||
@@ -38,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::
|
||||
|
||||
$ gpg --keyserver keys.gnupg.net --recv-keys 2A194991
|
||||
$ gpg -a --export 2A194991 | sudo apt-key add -
|
||||
$ apt-key adv --keyserver keys.gnupg.net --recv-keys E083A3782A194991
|
||||
|
||||
After that you can install aptly as any other software package::
|
||||
|
||||
@@ -49,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+,
|
||||
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/>`_.
|
||||
|
||||
If you have Go environment set up, you can build aptly from source by running (go 1.1+ 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
|
||||
mkdir -p $GOPATH/src/github.com/smira/aptly
|
||||
|
||||
+113
@@ -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
@@ -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{})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
+3
-1
@@ -34,7 +34,7 @@ type PublishedStorage interface {
|
||||
// Remove removes single file under public path
|
||||
Remove(path string) error
|
||||
// LinkFromPool links package file from pool to dist's pool location
|
||||
LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath, sourceMD5 string) error
|
||||
LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath, sourceMD5 string, force bool) error
|
||||
// Filelist returns list of files under prefix
|
||||
Filelist(prefix string) ([]string, error)
|
||||
// RenameFile renames (moves) file
|
||||
@@ -90,6 +90,8 @@ type Downloader interface {
|
||||
// Shutdown stops downloader after current tasks are finished,
|
||||
// but doesn't process rest of queue
|
||||
Shutdown()
|
||||
// Abort stops downloader without waiting for shutdown
|
||||
Abort()
|
||||
// GetProgress returns Progress object
|
||||
GetProgress() Progress
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
package aptly
|
||||
|
||||
// Version of aptly
|
||||
const Version = "0.7"
|
||||
const Version = "0.9"
|
||||
|
||||
// Enable debugging features?
|
||||
const EnableDebug = false
|
||||
|
||||
+15
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
+18
-2
@@ -34,6 +34,18 @@ func ListPackagesRefList(reflist *deb.PackageRefList) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// LookupOption checks boolean flag with default (usually config) and command-line
|
||||
// setting
|
||||
func LookupOption(defaultValue bool, flags *flag.FlagSet, name string) (result bool) {
|
||||
result = defaultValue
|
||||
|
||||
if flags.IsSet(name) {
|
||||
result = flags.Lookup(name).Value.Get().(bool)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RootCommand creates root command in command tree
|
||||
func RootCommand() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
@@ -46,21 +58,25 @@ upgrade individual packages, take snapshots and publish them
|
||||
back as Debian repositories.
|
||||
|
||||
aptly's goal is to establish repeatability and controlled changes
|
||||
in a package-centric environment. aptly allows to fix a set of packages
|
||||
in a package-centric environment. aptly allows one to fix a set of packages
|
||||
in a repository, so that package installation and upgrade becomes
|
||||
deterministic. At the same time aptly allows to perform controlled,
|
||||
deterministic. At the same time aptly allows one to perform controlled,
|
||||
fine-grained changes in repository contents to transition your
|
||||
package environment to new version.`,
|
||||
Flag: *flag.NewFlagSet("aptly", flag.ExitOnError),
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdConfig(),
|
||||
makeCmdDb(),
|
||||
makeCmdGraph(),
|
||||
makeCmdMirror(),
|
||||
makeCmdRepo(),
|
||||
makeCmdServe(),
|
||||
makeCmdSnapshot(),
|
||||
makeCmdTask(),
|
||||
makeCmdPublish(),
|
||||
makeCmdVersion(),
|
||||
makeCmdPackage(),
|
||||
makeCmdAPI(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+12
-302
@@ -1,321 +1,31 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/console"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/http"
|
||||
"github.com/smira/aptly/s3"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
ctx "github.com/smira/aptly/context"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AptlyContext is a common context shared by all commands
|
||||
type AptlyContext struct {
|
||||
flags *flag.FlagSet
|
||||
configLoaded bool
|
||||
|
||||
progress aptly.Progress
|
||||
downloader aptly.Downloader
|
||||
database database.Storage
|
||||
packagePool aptly.PackagePool
|
||||
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.flags.Lookup("config").Value.String()
|
||||
if configLocation != "" {
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
} else {
|
||||
configLocations := []string{
|
||||
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
|
||||
"/etc/aptly.conf",
|
||||
}
|
||||
|
||||
for _, configLocation := range configLocations {
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0])
|
||||
utils.SaveConfig(configLocations[0], &utils.Config)
|
||||
}
|
||||
}
|
||||
|
||||
context.configLoaded = true
|
||||
|
||||
}
|
||||
return &utils.Config
|
||||
}
|
||||
|
||||
// DependencyOptions calculates options related to dependecy handling
|
||||
func (context *AptlyContext) DependencyOptions() int {
|
||||
if context.dependencyOptions == -1 {
|
||||
context.dependencyOptions = 0
|
||||
if context.Config().DepFollowSuggests || context.flags.Lookup("dep-follow-suggests").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowSuggests
|
||||
}
|
||||
if context.Config().DepFollowRecommends || context.flags.Lookup("dep-follow-recommends").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowRecommends
|
||||
}
|
||||
if context.Config().DepFollowAllVariants || context.flags.Lookup("dep-follow-all-variants").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowAllVariants
|
||||
}
|
||||
if context.Config().DepFollowSource || context.flags.Lookup("dep-follow-source").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowSource
|
||||
}
|
||||
}
|
||||
|
||||
return context.dependencyOptions
|
||||
}
|
||||
|
||||
// ArchitecturesList returns list of architectures fixed via command line or config
|
||||
func (context *AptlyContext) ArchitecturesList() []string {
|
||||
if context.architecturesList == nil {
|
||||
context.architecturesList = context.Config().Architectures
|
||||
optionArchitectures := context.flags.Lookup("architectures").Value.String()
|
||||
if optionArchitectures != "" {
|
||||
context.architecturesList = strings.Split(optionArchitectures, ",")
|
||||
}
|
||||
}
|
||||
|
||||
return context.architecturesList
|
||||
}
|
||||
|
||||
// Progress creates or returns Progress object
|
||||
func (context *AptlyContext) Progress() aptly.Progress {
|
||||
if context.progress == nil {
|
||||
context.progress = console.NewProgress()
|
||||
context.progress.Start()
|
||||
}
|
||||
|
||||
return context.progress
|
||||
}
|
||||
|
||||
// Downloader returns instance of current downloader
|
||||
func (context *AptlyContext) Downloader() aptly.Downloader {
|
||||
if context.downloader == nil {
|
||||
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
|
||||
}
|
||||
|
||||
// CollectionFactory builds factory producing all kinds of collections
|
||||
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
|
||||
if context.collectionFactory == nil {
|
||||
db, err := context.Database()
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
context.collectionFactory = deb.NewCollectionFactory(db)
|
||||
}
|
||||
|
||||
return context.collectionFactory
|
||||
}
|
||||
|
||||
// PackagePool returns instance of PackagePool
|
||||
func (context *AptlyContext) PackagePool() aptly.PackagePool {
|
||||
if context.packagePool == nil {
|
||||
context.packagePool = files.NewPackagePool(context.Config().RootDir)
|
||||
}
|
||||
|
||||
return context.packagePool
|
||||
}
|
||||
|
||||
// PublishedStorage returns instance of PublishedStorage
|
||||
func (context *AptlyContext) 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)
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
} else {
|
||||
Fatal(fmt.Errorf("unknown published storage format: %v", name))
|
||||
}
|
||||
context.publishedStorages[name] = publishedStorage
|
||||
}
|
||||
|
||||
return publishedStorage
|
||||
}
|
||||
var context *ctx.AptlyContext
|
||||
|
||||
// ShutdownContext shuts context down
|
||||
func ShutdownContext() {
|
||||
if aptly.EnableDebug {
|
||||
if context.fileMemProfile != nil {
|
||||
pprof.WriteHeapProfile(context.fileMemProfile)
|
||||
context.fileMemProfile.Close()
|
||||
context.fileMemProfile = nil
|
||||
}
|
||||
if context.fileCPUProfile != nil {
|
||||
pprof.StopCPUProfile()
|
||||
context.fileCPUProfile.Close()
|
||||
context.fileCPUProfile = nil
|
||||
}
|
||||
if context.fileMemProfile != nil {
|
||||
context.fileMemProfile.Close()
|
||||
context.fileMemProfile = nil
|
||||
}
|
||||
}
|
||||
if context.database != nil {
|
||||
context.database.Close()
|
||||
}
|
||||
if context.downloader != nil {
|
||||
context.downloader.Shutdown()
|
||||
}
|
||||
if context.progress != nil {
|
||||
context.progress.Shutdown()
|
||||
}
|
||||
context.Shutdown()
|
||||
}
|
||||
|
||||
// CleanupContext does partial shutdown of context
|
||||
func CleanupContext() {
|
||||
context.Cleanup()
|
||||
}
|
||||
|
||||
// InitContext initializes context with default settings
|
||||
func InitContext(flags *flag.FlagSet) error {
|
||||
var err error
|
||||
|
||||
context = &AptlyContext{
|
||||
flags: flags,
|
||||
dependencyOptions: -1,
|
||||
publishedStorages: map[string]aptly.PublishedStorage{},
|
||||
if context != nil {
|
||||
panic("context already initialized")
|
||||
}
|
||||
|
||||
if aptly.EnableDebug {
|
||||
cpuprofile := flags.Lookup("cpuprofile").Value.String()
|
||||
if cpuprofile != "" {
|
||||
context.fileCPUProfile, err = os.Create(cpuprofile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pprof.StartCPUProfile(context.fileCPUProfile)
|
||||
}
|
||||
context, err = ctx.NewContext(flags)
|
||||
|
||||
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
|
||||
return err
|
||||
}
|
||||
|
||||
+19
-1
@@ -20,7 +20,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
// collect information about references packages...
|
||||
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().LoadComplete(repo)
|
||||
if err != nil {
|
||||
@@ -61,6 +61,24 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ... and compare it to the list of all packages
|
||||
context.Progress().Printf("Loading list of all packages...\n")
|
||||
allPackageRefs := context.CollectionFactory().PackageCollection().AllPackageRefs()
|
||||
|
||||
+4
-116
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/gographviz"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
@@ -21,121 +19,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
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")
|
||||
graph, err := deb.BuildGraph(context.CollectionFactory())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(graph.String())
|
||||
|
||||
|
||||
+2
-1
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func getVerifier(flags *flag.FlagSet) (utils.Verifier, error) {
|
||||
if context.Config().GpgDisableVerify || flags.Lookup("ignore-signatures").Value.Get().(bool) {
|
||||
if LookupOption(context.Config().GpgDisableVerify, flags, "ignore-signatures") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func makeCmdMirror() *commander.Command {
|
||||
makeCmdMirrorUpdate(),
|
||||
makeCmdMirrorRename(),
|
||||
makeCmdMirrorEdit(),
|
||||
makeCmdMirrorSearch(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -16,7 +16,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
downloadSources := context.Config().DownloadSourcePackages || context.flags.Lookup("with-sources").Value.Get().(bool)
|
||||
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
||||
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
||||
|
||||
var (
|
||||
mirrorName, archiveURL, distribution string
|
||||
@@ -33,13 +34,15 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
||||
archiveURL, distribution, components = args[1], args[2], args[3:]
|
||||
}
|
||||
|
||||
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(), downloadSources)
|
||||
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
|
||||
downloadSources, downloadUdebs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create mirror: %s", err)
|
||||
}
|
||||
|
||||
repo.Filter = context.flags.Lookup("filter").Value.String()
|
||||
repo.FilterWithDeps = context.flags.Lookup("filter-with-deps").Value.Get().(bool)
|
||||
repo.Filter = context.Flags().Lookup("filter").Value.String()
|
||||
repo.FilterWithDeps = context.Flags().Lookup("filter-with-deps").Value.Get().(bool)
|
||||
repo.SkipComponentCheck = context.Flags().Lookup("force-components").Value.Get().(bool)
|
||||
|
||||
if repo.Filter != "" {
|
||||
_, err = query.Parse(repo.Filter)
|
||||
@@ -48,7 +51,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(context.flags)
|
||||
verifier, err := getVerifier(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||
}
|
||||
@@ -74,7 +77,7 @@ func makeCmdMirrorCreate() *commander.Command {
|
||||
Short: "create new mirror",
|
||||
Long: `
|
||||
Creates mirror <name> of remote repository, aptly supports both regular and flat Debian repositories exported
|
||||
via HTTP. aptly would try download Release file from remote repository and verify its' signature. Command
|
||||
via HTTP and FTP. aptly would try download Release file from remote repository and verify its' signature. Command
|
||||
line format resembles apt utlitily sources.list(5).
|
||||
|
||||
PPA urls could specified in short format:
|
||||
@@ -90,8 +93,10 @@ Example:
|
||||
|
||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
|
||||
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
||||
cmd.Flag.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("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)")
|
||||
|
||||
return cmd
|
||||
|
||||
+6
-1
@@ -20,7 +20,12 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
force := context.flags.Lookup("force").Value.Get().(bool)
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
force := context.Flags().Lookup("force").Value.Get().(bool)
|
||||
if !force {
|
||||
snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo)
|
||||
|
||||
|
||||
+28
-4
@@ -19,15 +19,28 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
context.flags.Visit(func(flag *flag.Flag) {
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
context.Flags().Visit(func(flag *flag.Flag) {
|
||||
switch flag.Name {
|
||||
case "filter":
|
||||
repo.Filter = flag.Value.String()
|
||||
case "filter-with-deps":
|
||||
repo.FilterWithDeps = flag.Value.Get().(bool)
|
||||
case "with-sources":
|
||||
repo.DownloadSources = flag.Value.Get().(bool)
|
||||
case "with-udebs":
|
||||
repo.DownloadUdebs = flag.Value.Get().(bool)
|
||||
}
|
||||
})
|
||||
|
||||
if repo.IsFlat() && repo.DownloadUdebs {
|
||||
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
|
||||
}
|
||||
|
||||
if repo.Filter != "" {
|
||||
_, err = query.Parse(repo.Filter)
|
||||
if err != nil {
|
||||
@@ -35,6 +48,15 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if context.GlobalFlags().Lookup("architectures").Value.String() != "" {
|
||||
repo.Architectures = context.ArchitecturesList()
|
||||
|
||||
err = repo.Fetch(context.Downloader(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
@@ -48,10 +70,10 @@ func makeCmdMirrorEdit() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorEdit,
|
||||
UsageLine: "edit <name>",
|
||||
Short: "edit properties of mirorr",
|
||||
Short: "edit mirror settings",
|
||||
Long: `
|
||||
Command edit allows to change settings of mirror:
|
||||
filters.
|
||||
Command edit allows one to change settings of mirror:
|
||||
filters, list of architectures.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -62,6 +84,8 @@ Example:
|
||||
|
||||
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("with-sources", false, "download source packages in addition to binary packages")
|
||||
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
context.CloseDatabase()
|
||||
|
||||
sort.Strings(repos)
|
||||
|
||||
if raw {
|
||||
|
||||
@@ -24,6 +24,11 @@ func aptlyMirrorRename(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
_, err = context.CollectionFactory().RemoteRepoCollection().ByName(newName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("unable to rename: mirror %s already exists", newName)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func makeCmdMirrorSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMirrorRepoSearch,
|
||||
UsageLine: "search <name> <package-query>",
|
||||
Short: "search mirror for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in mirror that match package query
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror search wheezy-main '$Architecture (i386), Name (% *-dev)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-deps", false, "include dependencies into search results")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+10
-1
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
@@ -28,6 +29,9 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s\n", repo.Name)
|
||||
if repo.Status == deb.MirrorUpdating {
|
||||
fmt.Printf("Status: In Update (PID %d)\n", repo.WorkerPID)
|
||||
}
|
||||
fmt.Printf("Archive Root URL: %s\n", repo.ArchiveRoot)
|
||||
fmt.Printf("Distribution: %s\n", repo.Distribution)
|
||||
fmt.Printf("Components: %s\n", strings.Join(repo.Components, ", "))
|
||||
@@ -37,6 +41,11 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
||||
downloadSources = "yes"
|
||||
}
|
||||
fmt.Printf("Download Sources: %s\n", downloadSources)
|
||||
downloadUdebs := "no"
|
||||
if repo.DownloadUdebs {
|
||||
downloadUdebs = "yes"
|
||||
}
|
||||
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
|
||||
if repo.Filter != "" {
|
||||
fmt.Printf("Filter: %s\n", repo.Filter)
|
||||
filterWithDeps := "no"
|
||||
@@ -57,7 +66,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
||||
fmt.Printf("%s: %s\n", k, repo.Meta[k])
|
||||
}
|
||||
|
||||
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool)
|
||||
withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
|
||||
if withPackages {
|
||||
if repo.LastDownloadDate.IsZero() {
|
||||
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
|
||||
|
||||
+117
-13
@@ -4,8 +4,12 @@ import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
@@ -27,9 +31,17 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
ignoreMismatch := context.flags.Lookup("ignore-checksums").Value.Get().(bool)
|
||||
force := context.Flags().Lookup("force").Value.Get().(bool)
|
||||
if !force {
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(context.flags)
|
||||
ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
|
||||
|
||||
verifier, err := getVerifier(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||
}
|
||||
@@ -39,21 +51,112 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
var filterQuery deb.PackageQuery
|
||||
|
||||
if repo.Filter != "" {
|
||||
filterQuery, err = query.Parse(repo.Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = repo.Download(context.Progress(), context.Downloader(), context.CollectionFactory(), context.PackagePool(), ignoreMismatch,
|
||||
context.DependencyOptions(), filterQuery)
|
||||
context.Progress().Printf("Downloading & parsing package files...\n")
|
||||
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), context.CollectionFactory(), ignoreMismatch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
if repo.Filter != "" {
|
||||
context.Progress().Printf("Applying filter...\n")
|
||||
var filterQuery deb.PackageQuery
|
||||
|
||||
filterQuery, err = query.Parse(repo.Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
var oldLen, newLen int
|
||||
oldLen, newLen, err = repo.ApplyFilter(context.DependencyOptions(), filterQuery)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
context.Progress().Printf("Packages filtered: %d -> %d.\n", oldLen, newLen)
|
||||
}
|
||||
|
||||
var (
|
||||
downloadSize int64
|
||||
queue []deb.PackageDownloadTask
|
||||
)
|
||||
|
||||
context.Progress().Printf("Building download queue...\n")
|
||||
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// on any interruption, unlock the mirror
|
||||
err := context.ReOpenDatabase()
|
||||
if err == nil {
|
||||
repo.MarkAsIdle()
|
||||
context.CollectionFactory().RemoteRepoCollection().Update(repo)
|
||||
}
|
||||
}()
|
||||
|
||||
repo.MarkAsUpdating()
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = context.CloseDatabase()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
// Catch ^C
|
||||
sigch := make(chan os.Signal)
|
||||
signal.Notify(sigch, os.Interrupt)
|
||||
|
||||
count := len(queue)
|
||||
context.Progress().Printf("Download queue: %d items (%s)\n", count, utils.HumanBytes(downloadSize))
|
||||
|
||||
// Download from the queue
|
||||
context.Progress().InitBar(downloadSize, true)
|
||||
|
||||
// Download all package files
|
||||
ch := make(chan error, count)
|
||||
|
||||
// In separate goroutine (to avoid blocking main), push queue to downloader
|
||||
go func() {
|
||||
for _, task := range queue {
|
||||
context.Downloader().DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch)
|
||||
}
|
||||
|
||||
// We don't need queue after this point
|
||||
queue = nil
|
||||
}()
|
||||
|
||||
// Wait for all downloads to finish
|
||||
errors := make([]string, 0)
|
||||
|
||||
for count > 0 {
|
||||
select {
|
||||
case <-sigch:
|
||||
signal.Stop(sigch)
|
||||
return fmt.Errorf("unable to update: interrupted")
|
||||
case err = <-ch:
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
count--
|
||||
}
|
||||
}
|
||||
|
||||
context.Progress().ShutdownBar()
|
||||
signal.Stop(sigch)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("unable to update: download errors:\n %s\n", strings.Join(errors, "\n "))
|
||||
}
|
||||
|
||||
err = context.ReOpenDatabase()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
repo.FinalizeDownload()
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
@@ -80,6 +183,7 @@ Example:
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-update", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("force", false, "force update mirror even if it is locked by another process")
|
||||
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func makeCmdPackage() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "package",
|
||||
Short: "operations on packages",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdPackageSearch(),
|
||||
makeCmdPackageShow(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyPackageSearch(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
q, err := query.Parse(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
result := q.Query(context.CollectionFactory().PackageCollection())
|
||||
if result.Len() == 0 {
|
||||
return fmt.Errorf("no results")
|
||||
}
|
||||
|
||||
result.ForEach(func(p *deb.Package) error {
|
||||
context.Progress().Printf("%s\n", p)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPackageSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPackageSearch,
|
||||
UsageLine: "search <package-query>",
|
||||
Short: "search for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in whole DB that match package query
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly package search '$Architecture (i386), Name (% *-dev)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-package-search", flag.ExitOnError),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
)
|
||||
|
||||
func printReferencesTo(p *deb.Package) (err error) {
|
||||
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.RefList() != nil {
|
||||
if repo.RefList().Has(p) {
|
||||
fmt.Printf(" mirror %s\n", repo)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.RefList() != nil {
|
||||
if repo.RefList().Has(p) {
|
||||
fmt.Printf(" local repo %s\n", repo)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if snapshot.RefList().Has(p) {
|
||||
fmt.Printf(" snapshot %s\n", snapshot)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func aptlyPackageShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
q, err := query.Parse(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
withFiles := context.Flags().Lookup("with-files").Value.Get().(bool)
|
||||
withReferences := context.Flags().Lookup("with-references").Value.Get().(bool)
|
||||
|
||||
w := bufio.NewWriter(os.Stdout)
|
||||
|
||||
result := q.Query(context.CollectionFactory().PackageCollection())
|
||||
|
||||
err = result.ForEach(func(p *deb.Package) error {
|
||||
p.Stanza().WriteTo(w, p.IsSource, false)
|
||||
w.Flush()
|
||||
fmt.Printf("\n")
|
||||
|
||||
if withFiles {
|
||||
fmt.Printf("Files in the pool:\n")
|
||||
for _, f := range p.Files() {
|
||||
path, err := context.PackagePool().Path(f.Filename, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", path)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
if withReferences {
|
||||
fmt.Printf("References to package:\n")
|
||||
printReferencesTo(p)
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPackageShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPackageShow,
|
||||
UsageLine: "show <package-query>",
|
||||
Short: "show details about packages matching query",
|
||||
Long: `
|
||||
Command shows displays detailed meta-information about packages
|
||||
matching query. Information from Debian control file is displayed.
|
||||
Optionally information about package files and
|
||||
inclusion into mirrors/snapshots/local repos is shown.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly package show nginx-light_1.2.1-2.2+wheezy2_i386'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-package-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-files", false, "display information about files from package pool")
|
||||
cmd.Flag.Bool("with-references", false, "display information about mirrors, snapshots and local repos referencing this package")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+3
-16
@@ -4,17 +4,18 @@ import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getSigner(flags *flag.FlagSet) (utils.Signer, error) {
|
||||
if flags.Lookup("skip-signing").Value.Get().(bool) || context.Config().GpgDisableSign {
|
||||
if LookupOption(context.Config().GpgDisableSign, flags, "skip-signing") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
signer := &utils.GpgSigner{}
|
||||
signer.SetKey(flags.Lookup("gpg-key").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.SetBatch(flags.Lookup("batch").Value.Get().(bool))
|
||||
|
||||
err := signer.Init()
|
||||
if err != nil {
|
||||
@@ -25,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 {
|
||||
return &commander.Command{
|
||||
UsageLine: "publish",
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,7 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error {
|
||||
param = args[1]
|
||||
}
|
||||
|
||||
storage, prefix := parsePrefix(param)
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Remove(context, storage, prefix, distribution,
|
||||
context.CollectionFactory(), context.Progress())
|
||||
|
||||
+3
-1
@@ -25,7 +25,7 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
if raw {
|
||||
published = append(published, fmt.Sprintf("%s %s", repo.Prefix, repo.Distribution))
|
||||
published = append(published, fmt.Sprintf("%s %s", repo.StoragePrefix(), repo.Distribution))
|
||||
} else {
|
||||
published = append(published, repo.String())
|
||||
}
|
||||
@@ -36,6 +36,8 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to load list of repos: %s", err)
|
||||
}
|
||||
|
||||
context.CloseDatabase()
|
||||
|
||||
sort.Strings(published)
|
||||
|
||||
if raw {
|
||||
|
||||
@@ -37,9 +37,13 @@ Example:
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG 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.Bool("batch", false, "run GPG with detached tty")
|
||||
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
|
||||
cmd.Flag.String("origin", "", "origin name to publish")
|
||||
cmd.Flag.String("label", "", "label to publish")
|
||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+15
-5
@@ -13,7 +13,7 @@ import (
|
||||
func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) 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 {
|
||||
cmd.Usage()
|
||||
@@ -27,7 +27,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
} else {
|
||||
param = ""
|
||||
}
|
||||
storage, prefix := parsePrefix(param)
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
var (
|
||||
sources = []interface{}{}
|
||||
@@ -110,7 +110,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
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())
|
||||
if err != nil {
|
||||
@@ -125,12 +125,18 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.flags)
|
||||
signer, err := getSigner(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG signer: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress())
|
||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||
if forceOverwrite {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
||||
"the same package pool.\n")
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress(), forceOverwrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
@@ -193,9 +199,13 @@ Example:
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG 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.Bool("batch", false, "run GPG with detached tty")
|
||||
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
|
||||
cmd.Flag.String("origin", "", "origin name to publish")
|
||||
cmd.Flag.String("label", "", "label to publish")
|
||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+25
-7
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
func aptlyPublishSwitch(cmd *commander.Command, args []string) 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 {
|
||||
cmd.Usage()
|
||||
@@ -33,7 +34,7 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
names = args[1:]
|
||||
}
|
||||
|
||||
storage, prefix := parsePrefix(param)
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
var published *deb.PublishedRepo
|
||||
|
||||
@@ -61,6 +62,10 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
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])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
@@ -74,12 +79,18 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
published.UpdateSnapshot(component, snapshot)
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.flags)
|
||||
signer, err := getSigner(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG signer: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress())
|
||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||
if forceOverwrite {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
||||
"the same package pool.\n")
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress(), forceOverwrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
@@ -106,7 +117,7 @@ func makeCmdPublishSwitch() *commander.Command {
|
||||
UsageLine: "switch <distribution> [[<endpoint>:]<prefix>] <new-snapshot>",
|
||||
Short: "update published repository by switching to new snapshot",
|
||||
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,
|
||||
component).
|
||||
|
||||
@@ -114,19 +125,26 @@ 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.:
|
||||
|
||||
aptly publish update -component=main,contrib wheezy wh-main wh-contrib
|
||||
aptly publish switch -component=main,contrib wheezy wh-main wh-contrib
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish update wheezy ppa wheezy-7.5
|
||||
$ aptly publish switch wheezy ppa wheezy-7.5
|
||||
|
||||
This command would switch published repository (with one component) named ppa/wheezy
|
||||
(prefix ppa, dsitribution wheezy to new snapshot wheezy-7.5).
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG 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.Bool("batch", false, "run GPG with detached tty")
|
||||
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.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+13
-3
@@ -20,7 +20,7 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
if len(args) == 2 {
|
||||
param = args[1]
|
||||
}
|
||||
storage, prefix := parsePrefix(param)
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
var published *deb.PublishedRepo
|
||||
|
||||
@@ -43,12 +43,18 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
published.UpdateLocalRepo(component)
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.flags)
|
||||
signer, err := getSigner(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG signer: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress())
|
||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||
if forceOverwrite {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
||||
"the same package pool.\n")
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress(), forceOverwrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
@@ -92,7 +98,11 @@ Example:
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.String("passphrase", "", "GPG 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.Bool("batch", false, "run GPG with detached tty")
|
||||
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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ func makeCmdRepo() *commander.Command {
|
||||
makeCmdRepoRemove(),
|
||||
makeCmdRepoShow(),
|
||||
makeCmdRepoRename(),
|
||||
makeCmdRepoSearch(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+16
-125
@@ -2,14 +2,12 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyRepoAdd(cmd *commander.Command, args []string) error {
|
||||
@@ -40,130 +38,22 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
packageFiles := []string{}
|
||||
failedFiles := []string{}
|
||||
forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
|
||||
|
||||
for _, location := range args[1:] {
|
||||
info, err2 := os.Stat(location)
|
||||
if err2 != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to process %s: %s@|", location, err2)
|
||||
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
|
||||
}
|
||||
var packageFiles, failedFiles []string
|
||||
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
|
||||
packageFiles = append(packageFiles, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
|
||||
packageFiles = append(packageFiles, location)
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location)
|
||||
failedFiles = append(failedFiles, location)
|
||||
continue
|
||||
}
|
||||
}
|
||||
packageFiles, failedFiles, err = deb.CollectPackageFiles(args[1:], &aptly.ConsoleResultReporter{Progress: context.Progress()})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect package files: %s", err)
|
||||
}
|
||||
|
||||
processedFiles := []string{}
|
||||
sort.Strings(packageFiles)
|
||||
var processedFiles, failedFiles2 []string
|
||||
|
||||
for _, file := range packageFiles {
|
||||
var (
|
||||
stanza deb.Stanza
|
||||
p *deb.Package
|
||||
)
|
||||
|
||||
candidateProcessedFiles := []string{}
|
||||
isSourcePackage := strings.HasSuffix(file, ".dsc")
|
||||
|
||||
if isSourcePackage {
|
||||
stanza, err = deb.GetControlFileFromDsc(file, verifier)
|
||||
|
||||
if err == nil {
|
||||
stanza["Package"] = stanza["Source"]
|
||||
delete(stanza, "Source")
|
||||
|
||||
p, err = deb.NewSourcePackageFromControlFile(stanza)
|
||||
}
|
||||
} else {
|
||||
stanza, err = deb.GetControlFileFromDeb(file)
|
||||
p = deb.NewPackageFromControlFile(stanza)
|
||||
}
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to read file %s: %s@|", file, err)
|
||||
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
|
||||
}
|
||||
|
||||
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...)
|
||||
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
|
||||
context.CollectionFactory().PackageCollection(), &aptly.ConsoleResultReporter{Progress: context.Progress()})
|
||||
failedFiles = append(failedFiles, failedFiles2...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to import package files: %s", err)
|
||||
}
|
||||
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
@@ -173,7 +63,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
|
||||
if context.flags.Lookup("remove-files").Value.Get().(bool) {
|
||||
if context.Flags().Lookup("remove-files").Value.Get().(bool) {
|
||||
processedFiles = utils.StrSliceDeduplicate(processedFiles)
|
||||
|
||||
for _, file := range processedFiles {
|
||||
@@ -202,8 +92,8 @@ func makeCmdRepoAdd() *commander.Command {
|
||||
UsageLine: "add <name> <package file.deb>|<directory> ...",
|
||||
Short: "add packages to local repository",
|
||||
Long: `
|
||||
Command adds packages to local repository from .deb (binary packages) and .dsc (source packages) files.
|
||||
When importing from directory aptly would do recursive scan looking for all files matching *.deb or *.dsc
|
||||
Command adds packages to local repository from .deb, .udeb (binary packages) and .dsc (source packages) files.
|
||||
When importing from directory aptly would do recursive scan looking for all files matching *.[u]deb or *.dsc
|
||||
patterns. Every file discovered would be analyzed to extract metadata, package would then be created and added
|
||||
to the database. Files would be imported to internal package pool. For source packages, all required files are
|
||||
added automatically as well. Extra files for source package should be in the same directory as *.dsc file.
|
||||
@@ -216,6 +106,7 @@ Example:
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("remove-files", false, "remove files that have been imported successfully into repository")
|
||||
cmd.Flag.Bool("force-replace", false, "when adding package that conflicts with existing package, remove existing package")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+3
-3
@@ -14,9 +14,9 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error {
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
repo := deb.NewLocalRepo(args[0], context.flags.Lookup("comment").Value.String())
|
||||
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
|
||||
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
|
||||
repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String())
|
||||
repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
|
||||
repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Add(repo)
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to drop: local repo is published")
|
||||
}
|
||||
|
||||
force := context.flags.Lookup("force").Value.Get().(bool)
|
||||
force := context.Flags().Lookup("force").Value.Get().(bool)
|
||||
if !force {
|
||||
snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo)
|
||||
|
||||
|
||||
+7
-7
@@ -23,16 +23,16 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
if context.flags.Lookup("comment").Value.String() != "" {
|
||||
repo.Comment = context.flags.Lookup("comment").Value.String()
|
||||
if context.Flags().Lookup("comment").Value.String() != "" {
|
||||
repo.Comment = context.Flags().Lookup("comment").Value.String()
|
||||
}
|
||||
|
||||
if context.flags.Lookup("distribution").Value.String() != "" {
|
||||
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
|
||||
if context.Flags().Lookup("distribution").Value.String() != "" {
|
||||
repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
|
||||
}
|
||||
|
||||
if context.flags.Lookup("component").Value.String() != "" {
|
||||
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
|
||||
if context.Flags().Lookup("component").Value.String() != "" {
|
||||
repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
@@ -50,7 +50,7 @@ func makeCmdRepoEdit() *commander.Command {
|
||||
UsageLine: "edit <name>",
|
||||
Short: "edit properties of local repository",
|
||||
Long: `
|
||||
Command edit allows to change metadata of local repository:
|
||||
Command edit allows one to change metadata of local repository:
|
||||
comment, default distribution and component.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -33,6 +33,8 @@ func aptlyRepoList(cmd *commander.Command, args []string) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
context.CloseDatabase()
|
||||
|
||||
sort.Strings(repos)
|
||||
|
||||
if raw {
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
|
||||
|
||||
var architecturesList []string
|
||||
|
||||
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool)
|
||||
withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
|
||||
|
||||
if withDeps {
|
||||
dstList.PrepareIndex()
|
||||
@@ -145,7 +145,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
|
||||
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")
|
||||
} else {
|
||||
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
|
||||
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")
|
||||
} else {
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func makeCmdRepoSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMirrorRepoSearch,
|
||||
UsageLine: "search <name> <package-query>",
|
||||
Short: "search repo for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in local repository that match package query
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo search my-software '$Architecture (i386), Name (% *-dev)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-deps", false, "include dependencies into search results")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error {
|
||||
fmt.Printf("Default Component: %s\n", repo.DefaultComponent)
|
||||
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 {
|
||||
ListPackagesRefList(repo.RefList())
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ctx "github.com/smira/aptly/context"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
// Run runs single command starting from root cmd with args, optionally initializing context
|
||||
func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fatal, ok := r.(*ctx.FatalError)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
fmt.Println("ERROR:", fatal.Message)
|
||||
returnCode = fatal.ReturnCode
|
||||
}
|
||||
}()
|
||||
|
||||
returnCode = 0
|
||||
|
||||
flags, args, err := cmd.ParseFlags(cmdArgs)
|
||||
if err != nil {
|
||||
ctx.Fatal(err)
|
||||
}
|
||||
|
||||
if initContext {
|
||||
err = InitContext(flags)
|
||||
if err != nil {
|
||||
ctx.Fatal(err)
|
||||
}
|
||||
defer ShutdownContext()
|
||||
}
|
||||
|
||||
context.UpdateFlags(flags)
|
||||
|
||||
err = cmd.Dispatch(args)
|
||||
if err != nil {
|
||||
ctx.Fatal(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ func aptlyServe(cmd *commander.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
listen := context.flags.Lookup("listen").Value.String()
|
||||
listen := context.Flags().Lookup("listen").Value.String()
|
||||
|
||||
listenHost, listenPort, err := net.SplitHostPort(listen)
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ func makeCmdSnapshot() *commander.Command {
|
||||
makeCmdSnapshotMerge(),
|
||||
makeCmdSnapshotDrop(),
|
||||
makeCmdSnapshotRename(),
|
||||
makeCmdSnapshotSearch(),
|
||||
makeCmdSnapshotFilter(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ func aptlySnapshotCreate(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
|
||||
@@ -13,7 +13,7 @@ func aptlySnapshotDiff(cmd *commander.Command, args []string) error {
|
||||
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
|
||||
snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
|
||||
|
||||
@@ -35,7 +35,7 @@ func aptlySnapshotDrop(cmd *commander.Command, args []string) error {
|
||||
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 {
|
||||
snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot)
|
||||
if len(snapshots) > 0 {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlySnapshotFilter(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 3 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
|
||||
|
||||
// Load <source> snapshot
|
||||
source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to filter: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to filter: %s", err)
|
||||
}
|
||||
|
||||
// Convert snapshot to package list
|
||||
context.Progress().Printf("Loading packages (%d)...\n", source.RefList().Len())
|
||||
packageList, err := deb.NewPackageListFromRefList(source.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("Building indexes...\n")
|
||||
packageList.PrepareIndex()
|
||||
|
||||
// Calculate architectures
|
||||
var architecturesList []string
|
||||
|
||||
if len(context.ArchitecturesList()) > 0 {
|
||||
architecturesList = context.ArchitecturesList()
|
||||
} else {
|
||||
architecturesList = packageList.Architectures(false)
|
||||
}
|
||||
|
||||
sort.Strings(architecturesList)
|
||||
|
||||
if len(architecturesList) == 0 && withDeps {
|
||||
return fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||
}
|
||||
|
||||
// Initial queries out of arguments
|
||||
queries := make([]deb.PackageQuery, len(args)-2)
|
||||
for i, arg := range args[2:] {
|
||||
queries[i], err = query.Parse(arg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse query: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter with dependencies as requested
|
||||
result, err := packageList.Filter(queries, withDeps, nil, context.DependencyOptions(), architecturesList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to filter: %s", err)
|
||||
}
|
||||
|
||||
// Create <destination> snapshot
|
||||
destination := deb.NewSnapshotFromPackageList(args[1], []*deb.Snapshot{source}, result,
|
||||
fmt.Sprintf("Filtered '%s', query was: '%s'", source.Name, strings.Join(args[2:], " ")))
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().Add(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("\nSnapshot %s successfully filtered.\nYou can run 'aptly publish snapshot %s' to publish snapshot as Debian repository.\n", destination.Name, destination.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotFilter() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotFilter,
|
||||
UsageLine: "filter <source> <destination> <package-query> ...",
|
||||
Short: "filter packages in snapshot producing another snapshot",
|
||||
Long: `
|
||||
Command filter does filtering in snapshot <source>, producing another
|
||||
snapshot <destination>. Packages could be specified simply
|
||||
as 'package-name' or as package queries.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot filter wheezy-main wheezy-required 'Priorioty (required)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-filter", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-deps", false, "include dependent packages as well")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+12
-62
@@ -4,49 +4,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"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 {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
@@ -57,33 +16,24 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error {
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string)
|
||||
|
||||
snapshotsToSort := &snapshotListToSort{}
|
||||
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)
|
||||
collection := context.CollectionFactory().SnapshotCollection()
|
||||
|
||||
if raw {
|
||||
for _, snapshot := range snapshotsToSort.list {
|
||||
collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
|
||||
fmt.Printf("%s\n", snapshot.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
if len(snapshotsToSort.list) > 0 {
|
||||
if collection.Len() > 0 {
|
||||
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())
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotList() *commander.Command {
|
||||
|
||||
@@ -28,12 +28,12 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
latest := context.flags.Lookup("latest").Value.Get().(bool)
|
||||
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool)
|
||||
latest := context.Flags().Lookup("latest").Value.Get().(bool)
|
||||
noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool)
|
||||
|
||||
if noRemove && latest {
|
||||
return fmt.Errorf("-no-remove and -latest can't be specified together")
|
||||
}
|
||||
if noRemove && latest {
|
||||
return fmt.Errorf("-no-remove and -latest can't be specified together")
|
||||
}
|
||||
|
||||
overrideMatching := !latest && !noRemove
|
||||
|
||||
@@ -43,7 +43,7 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
if latest {
|
||||
deb.FilterLatestRefs(result)
|
||||
result.FilterLatestRefs()
|
||||
}
|
||||
|
||||
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("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
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
noDeps := context.flags.Lookup("no-deps").Value.Get().(bool)
|
||||
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool)
|
||||
allMatches := context.flags.Lookup("all-matches").Value.Get().(bool)
|
||||
noDeps := context.Flags().Lookup("no-deps").Value.Get().(bool)
|
||||
noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool)
|
||||
allMatches := context.Flags().Lookup("all-matches").Value.Get().(bool)
|
||||
|
||||
// Load <name> snapshot
|
||||
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)
|
||||
}
|
||||
// Add architecture filter
|
||||
queries[i] = &deb.AndQuery{queries[i], archQuery}
|
||||
queries[i] = &deb.AndQuery{L: queries[i], R: archQuery}
|
||||
}
|
||||
|
||||
// Filter with dependencies as requested
|
||||
@@ -129,7 +129,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
})
|
||||
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")
|
||||
} else {
|
||||
// Create <destination> snapshot
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
command := cmd.Parent.Name()
|
||||
|
||||
var reflist *deb.PackageRefList
|
||||
|
||||
if command == "snapshot" {
|
||||
snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
reflist = snapshot.RefList()
|
||||
} else if command == "mirror" {
|
||||
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
reflist = repo.RefList()
|
||||
} else if command == "repo" {
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
reflist = repo.RefList()
|
||||
} else {
|
||||
panic("unknown command")
|
||||
}
|
||||
|
||||
list, err := deb.NewPackageListFromRefList(reflist, context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
list.PrepareIndex()
|
||||
|
||||
q, err := query.Parse(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
|
||||
architecturesList := []string{}
|
||||
|
||||
if withDeps {
|
||||
if len(context.ArchitecturesList()) > 0 {
|
||||
architecturesList = context.ArchitecturesList()
|
||||
} else {
|
||||
architecturesList = list.Architectures(false)
|
||||
}
|
||||
|
||||
sort.Strings(architecturesList)
|
||||
|
||||
if len(architecturesList) == 0 {
|
||||
return fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||
}
|
||||
}
|
||||
|
||||
result, err := list.Filter([]deb.PackageQuery{q}, withDeps,
|
||||
nil, context.DependencyOptions(), architecturesList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to search: %s", err)
|
||||
}
|
||||
|
||||
result.ForEach(func(p *deb.Package) error {
|
||||
context.Progress().Printf("%s\n", p)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMirrorRepoSearch,
|
||||
UsageLine: "search <name> <package-query>",
|
||||
Short: "search snapshot for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in snapshot that match package query
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot search wheezy-main '$Architecture (i386), Name (% *-dev)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-search", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-deps", false, "include dependencies into search results")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error {
|
||||
fmt.Printf("Description: %s\n", snapshot.Description)
|
||||
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 {
|
||||
ListPackagesRefList(snapshot.RefList())
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func makeCmdTask() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "task",
|
||||
Short: "manage aptly tasks",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdTaskRun(),
|
||||
},
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlyTaskRun(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
var cmdList [][]string
|
||||
|
||||
if filename := cmd.Flag.Lookup("filename").Value.Get().(string); filename != "" {
|
||||
var text string
|
||||
cmdArgs := []string{}
|
||||
|
||||
var finfo os.FileInfo
|
||||
if finfo, err = os.Stat(filename); os.IsNotExist(err) || finfo.IsDir() {
|
||||
return fmt.Errorf("no such file, %s\n", filename)
|
||||
}
|
||||
|
||||
fmt.Print("Reading file...\n\n")
|
||||
|
||||
var file *os.File
|
||||
file, err = os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
text = strings.TrimSpace(scanner.Text()) + ","
|
||||
parsedArgs, _ := shellwords.Parse(text)
|
||||
cmdArgs = append(cmdArgs, parsedArgs...)
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cmdArgs) == 0 {
|
||||
return fmt.Errorf("the file is empty")
|
||||
}
|
||||
|
||||
cmdList = formatCommands(cmdArgs)
|
||||
} else if len(args) == 0 {
|
||||
var text string
|
||||
cmdArgs := []string{}
|
||||
|
||||
fmt.Println("Please enter one command per line and leave one blank when finished.")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Printf("> ")
|
||||
text, _ = reader.ReadString('\n')
|
||||
if text == "\n" {
|
||||
break
|
||||
} else {
|
||||
text = strings.TrimSpace(text) + ","
|
||||
parsedArgs, _ := shellwords.Parse(text)
|
||||
cmdArgs = append(cmdArgs, parsedArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmdArgs) == 0 {
|
||||
return fmt.Errorf("nothing entered")
|
||||
}
|
||||
|
||||
cmdList = formatCommands(cmdArgs)
|
||||
} else {
|
||||
cmdList = formatCommands(args)
|
||||
}
|
||||
|
||||
commandErrored := false
|
||||
|
||||
for i, command := range cmdList {
|
||||
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("\n@yBegin command output: ----------------------------@!")
|
||||
context.Progress().Flush()
|
||||
|
||||
returnCode := Run(RootCommand(), command, false)
|
||||
if returnCode != 0 {
|
||||
commandErrored = true
|
||||
}
|
||||
context.Progress().ColoredPrintf("\n@yEnd command output: ------------------------------@!")
|
||||
CleanupContext()
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@r%d) [Skipping]: %s@!", (i + 1), strings.Join(command, " "))
|
||||
}
|
||||
}
|
||||
|
||||
if commandErrored {
|
||||
err = fmt.Errorf("at least one command has reported an error")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func formatCommands(args []string) [][]string {
|
||||
var cmd []string
|
||||
var cmdArray [][]string
|
||||
|
||||
for _, s := range args {
|
||||
if sTrimmed := strings.TrimRight(s, ","); sTrimmed != s {
|
||||
cmd = append(cmd, sTrimmed)
|
||||
cmdArray = append(cmdArray, cmd)
|
||||
cmd = []string{}
|
||||
} else {
|
||||
cmd = append(cmd, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmd) > 0 {
|
||||
cmdArray = append(cmdArray, cmd)
|
||||
}
|
||||
|
||||
return cmdArray
|
||||
}
|
||||
|
||||
func makeCmdTaskRun() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyTaskRun,
|
||||
UsageLine: "run -filename=<filename> | <command1>, <command2>, ...",
|
||||
Short: "run aptly tasks",
|
||||
Long: `
|
||||
Command helps organise multiple aptly commands in one single aptly task, running as single thread.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly task run
|
||||
> repo create local
|
||||
> repo add local pkg1
|
||||
> publish repo local
|
||||
> serve
|
||||
>
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.String("filename", "", "specifies the filename that contains the commands to run")
|
||||
return cmd
|
||||
}
|
||||
+12
-1
@@ -14,6 +14,8 @@ const (
|
||||
codeHideProgress
|
||||
codeStop
|
||||
codeFlush
|
||||
codeBarEnabled
|
||||
codeBarDisabled
|
||||
)
|
||||
|
||||
type printTask struct {
|
||||
@@ -81,6 +83,8 @@ func (p *Progress) InitBar(count int64, isBytes bool) {
|
||||
p.bar.SetUnits(pb.U_BYTES)
|
||||
p.bar.ShowSpeed = true
|
||||
}
|
||||
|
||||
p.queue <- printTask{code: codeBarEnabled}
|
||||
p.bar.Start()
|
||||
}
|
||||
}
|
||||
@@ -91,6 +95,7 @@ func (p *Progress) ShutdownBar() {
|
||||
return
|
||||
}
|
||||
p.bar.Finish()
|
||||
p.queue <- printTask{code: codeBarDisabled}
|
||||
p.bar = nil
|
||||
p.queue <- printTask{code: codeHideProgress}
|
||||
}
|
||||
@@ -151,9 +156,15 @@ func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
|
||||
}
|
||||
|
||||
func (p *Progress) worker() {
|
||||
hasBar := false
|
||||
|
||||
for {
|
||||
task := <-p.queue
|
||||
switch task.code {
|
||||
case codeBarEnabled:
|
||||
hasBar = true
|
||||
case codeBarDisabled:
|
||||
hasBar = false
|
||||
case codePrint:
|
||||
if p.barShown {
|
||||
fmt.Print("\r\033[2K")
|
||||
@@ -161,7 +172,7 @@ func (p *Progress) worker() {
|
||||
}
|
||||
fmt.Print(task.message)
|
||||
case codeProgress:
|
||||
if p.bar != nil {
|
||||
if hasBar {
|
||||
fmt.Print("\r" + task.message)
|
||||
p.barShown = true
|
||||
}
|
||||
|
||||
+1
-3
@@ -1,9 +1,7 @@
|
||||
// +build !freebsd
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.crypto/ssh/terminal"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+28
-6
@@ -24,12 +24,14 @@ type Storage interface {
|
||||
KeysByPrefix(prefix []byte) [][]byte
|
||||
FetchByPrefix(prefix []byte) [][]byte
|
||||
Close() error
|
||||
ReOpen() error
|
||||
StartBatch()
|
||||
FinishBatch() error
|
||||
CompactDB() error
|
||||
}
|
||||
|
||||
type levelDB struct {
|
||||
path string
|
||||
db *leveldb.DB
|
||||
batch *leveldb.Batch
|
||||
}
|
||||
@@ -39,17 +41,21 @@ var (
|
||||
_ Storage = &levelDB{}
|
||||
)
|
||||
|
||||
// OpenDB opens (creates) LevelDB database
|
||||
func OpenDB(path string) (Storage, error) {
|
||||
func internalOpen(path string) (*leveldb.DB, error) {
|
||||
o := &opt.Options{
|
||||
Filter: filter.NewBloomFilter(10),
|
||||
}
|
||||
|
||||
db, err := leveldb.OpenFile(path, o)
|
||||
return leveldb.OpenFile(path, o)
|
||||
}
|
||||
|
||||
// OpenDB opens (creates) LevelDB database
|
||||
func OpenDB(path string) (Storage, error) {
|
||||
db, err := internalOpen(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &levelDB{db: db}, nil
|
||||
return &levelDB{db: db, path: path}, nil
|
||||
}
|
||||
|
||||
// RecoverDB recovers LevelDB database from corruption
|
||||
@@ -95,7 +101,7 @@ func (l *levelDB) Put(key []byte, value []byte) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if bytes.Compare(old, value) == 0 {
|
||||
if bytes.Equal(old, value) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -147,7 +153,23 @@ func (l *levelDB) FetchByPrefix(prefix []byte) [][]byte {
|
||||
|
||||
// Close finishes DB work
|
||||
func (l *levelDB) Close() error {
|
||||
return l.db.Close()
|
||||
if l.db == nil {
|
||||
return nil
|
||||
}
|
||||
err := l.db.Close()
|
||||
l.db = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Reopen tries to re-open the database
|
||||
func (l *levelDB) ReOpen() error {
|
||||
if l.db != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
l.db, err = internalOpen(l.path)
|
||||
return err
|
||||
}
|
||||
|
||||
// StartBatch starts batch processing of keys
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Launch gocheck tests
|
||||
@@ -155,3 +156,23 @@ func (s *LevelDBSuite) TestCompactDB(c *C) {
|
||||
|
||||
c.Check(s.db.CompactDB(), IsNil)
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestReOpen(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
value = []byte("value")
|
||||
)
|
||||
|
||||
err := s.db.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.db.Close()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.db.ReOpen()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(result, DeepEquals, value)
|
||||
}
|
||||
|
||||
+30
-1
@@ -2,10 +2,12 @@ package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CollectionFactory is a single place to generate all desired collections
|
||||
type CollectionFactory struct {
|
||||
*sync.Mutex
|
||||
db database.Storage
|
||||
packages *PackageCollection
|
||||
remoteRepos *RemoteRepoCollection
|
||||
@@ -16,11 +18,14 @@ type CollectionFactory struct {
|
||||
|
||||
// NewCollectionFactory creates new factory
|
||||
func NewCollectionFactory(db database.Storage) *CollectionFactory {
|
||||
return &CollectionFactory{db: db}
|
||||
return &CollectionFactory{Mutex: &sync.Mutex{}, db: db}
|
||||
}
|
||||
|
||||
// PackageCollection returns (or creates) new PackageCollection
|
||||
func (factory *CollectionFactory) PackageCollection() *PackageCollection {
|
||||
factory.Lock()
|
||||
defer factory.Unlock()
|
||||
|
||||
if factory.packages == nil {
|
||||
factory.packages = NewPackageCollection(factory.db)
|
||||
}
|
||||
@@ -30,6 +35,9 @@ func (factory *CollectionFactory) PackageCollection() *PackageCollection {
|
||||
|
||||
// RemoteRepoCollection returns (or creates) new RemoteRepoCollection
|
||||
func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection {
|
||||
factory.Lock()
|
||||
defer factory.Unlock()
|
||||
|
||||
if factory.remoteRepos == nil {
|
||||
factory.remoteRepos = NewRemoteRepoCollection(factory.db)
|
||||
}
|
||||
@@ -39,6 +47,9 @@ func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection {
|
||||
|
||||
// SnapshotCollection returns (or creates) new SnapshotCollection
|
||||
func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection {
|
||||
factory.Lock()
|
||||
defer factory.Unlock()
|
||||
|
||||
if factory.snapshots == nil {
|
||||
factory.snapshots = NewSnapshotCollection(factory.db)
|
||||
}
|
||||
@@ -48,6 +59,9 @@ func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection {
|
||||
|
||||
// LocalRepoCollection returns (or creates) new LocalRepoCollection
|
||||
func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection {
|
||||
factory.Lock()
|
||||
defer factory.Unlock()
|
||||
|
||||
if factory.localRepos == nil {
|
||||
factory.localRepos = NewLocalRepoCollection(factory.db)
|
||||
}
|
||||
@@ -57,9 +71,24 @@ func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection {
|
||||
|
||||
// PublishedRepoCollection returns (or creates) new PublishedRepoCollection
|
||||
func (factory *CollectionFactory) PublishedRepoCollection() *PublishedRepoCollection {
|
||||
factory.Lock()
|
||||
defer factory.Unlock()
|
||||
|
||||
if factory.publishedRepos == nil {
|
||||
factory.publishedRepos = NewPublishedRepoCollection(factory.db)
|
||||
}
|
||||
|
||||
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
@@ -47,7 +47,7 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) {
|
||||
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)
|
||||
stanza, err := reader.ReadStanza()
|
||||
if err != nil {
|
||||
|
||||
+2
-1
@@ -2,9 +2,10 @@ package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type DebSuite struct {
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Launch gocheck tests
|
||||
|
||||
+80
-5
@@ -11,9 +11,76 @@ import (
|
||||
type Stanza map[string]string
|
||||
|
||||
// Canonical order of fields in stanza
|
||||
var canocialOrder = []string{"Package", "Origin", "Label", "Suite", "Version", "Installed-Size", "Priority", "Section", "Maintainer",
|
||||
"Architecture", "Codename", "Date", "Architectures", "Components", "Description", "MD5sum", "MD5Sum", "SHA1", "SHA256",
|
||||
"Archive", "Component"}
|
||||
// Taken from: http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/vivid/apt/vivid/view/head:/apt-pkg/tagfile.cc#L504
|
||||
var (
|
||||
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
|
||||
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
|
||||
func (s Stanza) WriteTo(w *bufio.Writer) error {
|
||||
for _, field := range canocialOrder {
|
||||
func (s Stanza) WriteTo(w *bufio.Writer, isSource, isRelease bool) error {
|
||||
canonicalOrder := canonicalOrderBinary
|
||||
if isSource {
|
||||
canonicalOrder = canonicalOrderSource
|
||||
}
|
||||
if isRelease {
|
||||
canonicalOrder = canonicalOrderRelease
|
||||
}
|
||||
|
||||
for _, field := range canonicalOrder {
|
||||
value, ok := s[field]
|
||||
if ok {
|
||||
delete(s, field)
|
||||
|
||||
+3
-2
@@ -3,8 +3,9 @@ package deb
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
. "launchpad.net/gocheck"
|
||||
"strings"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ControlFileSuite struct {
|
||||
@@ -107,7 +108,7 @@ func (s *ControlFileSuite) TestReadWriteStanza(c *C) {
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w := bufio.NewWriter(buf)
|
||||
err = stanza.Copy().WriteTo(w)
|
||||
err = stanza.Copy().WriteTo(w, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
err = w.Flush()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
+120
@@ -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
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type indexFiles struct {
|
||||
publishedStorage aptly.PublishedStorage
|
||||
basePath string
|
||||
renameMap map[string]string
|
||||
generatedFiles map[string]utils.ChecksumInfo
|
||||
tempDir string
|
||||
suffix string
|
||||
indexes map[string]*indexFile
|
||||
}
|
||||
|
||||
type indexFile struct {
|
||||
parent *indexFiles
|
||||
discardable bool
|
||||
compressable bool
|
||||
signable bool
|
||||
relativePath string
|
||||
tempFilename string
|
||||
tempFile *os.File
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
func (file *indexFile) BufWriter() (*bufio.Writer, error) {
|
||||
if file.w == nil {
|
||||
var err error
|
||||
file.tempFilename = filepath.Join(file.parent.tempDir, strings.Replace(file.relativePath, "/", "_", -1))
|
||||
file.tempFile, err = os.Create(file.tempFilename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create temporary index file: %s", err)
|
||||
}
|
||||
|
||||
file.w = bufio.NewWriter(file.tempFile)
|
||||
}
|
||||
|
||||
return file.w, nil
|
||||
}
|
||||
|
||||
func (file *indexFile) Finalize(signer utils.Signer) error {
|
||||
if file.w == nil {
|
||||
if file.discardable {
|
||||
return nil
|
||||
}
|
||||
file.BufWriter()
|
||||
}
|
||||
|
||||
err := file.w.Flush()
|
||||
if err != nil {
|
||||
file.tempFile.Close()
|
||||
return fmt.Errorf("unable to write to index file: %s", err)
|
||||
}
|
||||
|
||||
if file.compressable {
|
||||
err = utils.CompressFile(file.tempFile)
|
||||
if err != nil {
|
||||
file.tempFile.Close()
|
||||
return fmt.Errorf("unable to compress index file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
file.tempFile.Close()
|
||||
|
||||
exts := []string{""}
|
||||
if file.compressable {
|
||||
exts = append(exts, ".gz", ".bz2")
|
||||
}
|
||||
|
||||
for _, ext := range exts {
|
||||
var checksumInfo utils.ChecksumInfo
|
||||
|
||||
checksumInfo, err = utils.ChecksumsForFile(file.tempFilename + ext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect checksums: %s", err)
|
||||
}
|
||||
file.parent.generatedFiles[file.relativePath+ext] = checksumInfo
|
||||
}
|
||||
|
||||
err = file.parent.publishedStorage.MkDir(filepath.Dir(filepath.Join(file.parent.basePath, file.relativePath)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create dir: %s", err)
|
||||
}
|
||||
|
||||
for _, ext := range exts {
|
||||
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext),
|
||||
file.tempFilename+ext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
|
||||
if file.parent.suffix != "" {
|
||||
file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext)] =
|
||||
filepath.Join(file.parent.basePath, file.relativePath+ext)
|
||||
}
|
||||
}
|
||||
|
||||
if file.signable && signer != nil {
|
||||
err = signer.DetachedSign(file.tempFilename, file.tempFilename+".gpg")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to detached sign file: %s", err)
|
||||
}
|
||||
|
||||
err = signer.ClearSign(file.tempFilename, filepath.Join(filepath.Dir(file.tempFilename), "In"+filepath.Base(file.tempFilename)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to clearsign file: %s", err)
|
||||
}
|
||||
|
||||
if file.parent.suffix != "" {
|
||||
file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg")] =
|
||||
filepath.Join(file.parent.basePath, file.relativePath+".gpg")
|
||||
file.parent.renameMap[filepath.Join(file.parent.basePath, "In"+file.relativePath+file.parent.suffix)] =
|
||||
filepath.Join(file.parent.basePath, "In"+file.relativePath)
|
||||
}
|
||||
|
||||
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg"),
|
||||
file.tempFilename+".gpg")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
|
||||
err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, "In"+file.relativePath+file.parent.suffix),
|
||||
filepath.Join(filepath.Dir(file.tempFilename), "In"+filepath.Base(file.tempFilename)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, suffix string) *indexFiles {
|
||||
return &indexFiles{
|
||||
publishedStorage: publishedStorage,
|
||||
basePath: basePath,
|
||||
renameMap: make(map[string]string),
|
||||
generatedFiles: make(map[string]utils.ChecksumInfo),
|
||||
tempDir: tempDir,
|
||||
suffix: suffix,
|
||||
indexes: make(map[string]*indexFile),
|
||||
}
|
||||
}
|
||||
|
||||
func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexFile {
|
||||
if arch == "source" {
|
||||
udeb = false
|
||||
}
|
||||
key := fmt.Sprintf("pi-%s-%s-%v", component, arch, udeb)
|
||||
file, ok := files.indexes[key]
|
||||
if !ok {
|
||||
var relativePath string
|
||||
|
||||
if arch == "source" {
|
||||
relativePath = filepath.Join(component, "source", "Sources")
|
||||
} else {
|
||||
if udeb {
|
||||
relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Packages")
|
||||
} else {
|
||||
relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages")
|
||||
}
|
||||
}
|
||||
|
||||
file = &indexFile{
|
||||
parent: files,
|
||||
discardable: false,
|
||||
compressable: true,
|
||||
signable: false,
|
||||
relativePath: relativePath,
|
||||
}
|
||||
|
||||
files.indexes[key] = file
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func (files *indexFiles) ReleaseIndex(component, arch string, udeb bool) *indexFile {
|
||||
if arch == "source" {
|
||||
udeb = false
|
||||
}
|
||||
key := fmt.Sprintf("ri-%s-%s-%v", component, arch, udeb)
|
||||
file, ok := files.indexes[key]
|
||||
if !ok {
|
||||
var relativePath string
|
||||
|
||||
if arch == "source" {
|
||||
relativePath = filepath.Join(component, "source", "Release")
|
||||
} else {
|
||||
if udeb {
|
||||
relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Release")
|
||||
} else {
|
||||
relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Release")
|
||||
}
|
||||
}
|
||||
|
||||
file = &indexFile{
|
||||
parent: files,
|
||||
discardable: udeb,
|
||||
compressable: false,
|
||||
signable: false,
|
||||
relativePath: relativePath,
|
||||
}
|
||||
|
||||
files.indexes[key] = file
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func (files *indexFiles) ReleaseFile() *indexFile {
|
||||
return &indexFile{
|
||||
parent: files,
|
||||
discardable: false,
|
||||
compressable: false,
|
||||
signable: true,
|
||||
relativePath: "Release",
|
||||
}
|
||||
}
|
||||
|
||||
func (files *indexFiles) FinalizeAll(progress aptly.Progress) (err error) {
|
||||
if progress != nil {
|
||||
progress.InitBar(int64(len(files.indexes)), false)
|
||||
defer progress.ShutdownBar()
|
||||
}
|
||||
|
||||
for _, file := range files.indexes {
|
||||
err = file.Finalize(nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if progress != nil {
|
||||
progress.AddBar(1)
|
||||
}
|
||||
}
|
||||
|
||||
files.indexes = make(map[string]*indexFile)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (files *indexFiles) RenameFiles() error {
|
||||
var err error
|
||||
|
||||
for oldName, newName := range files.renameMap {
|
||||
err = files.publishedStorage.RenameFile(oldName, newName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+64
-26
@@ -38,9 +38,15 @@ type PackageList struct {
|
||||
providesIndex map[string][]*Package
|
||||
}
|
||||
|
||||
// PackageConflictError means that package can't be added to the list due to error
|
||||
type PackageConflictError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// Verify interface
|
||||
var (
|
||||
_ sort.Interface = &PackageList{}
|
||||
_ PackageCatalog = &PackageList{}
|
||||
)
|
||||
|
||||
// NewPackageList creates empty package list
|
||||
@@ -89,7 +95,7 @@ func (l *PackageList) Add(p *Package) error {
|
||||
existing, ok := l.packages[key]
|
||||
if ok {
|
||||
if !existing.Equals(p) {
|
||||
return fmt.Errorf("conflict in package %s", p)
|
||||
return &PackageConflictError{fmt.Errorf("conflict in package %s", p)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -204,6 +210,19 @@ func (l *PackageList) Architectures(includeSource bool) (result []string) {
|
||||
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
|
||||
func depSliceDeduplicate(s []Dependency) []Dependency {
|
||||
l := len(s)
|
||||
@@ -235,6 +254,7 @@ func depSliceDeduplicate(s []Dependency) []Dependency {
|
||||
//
|
||||
// Analysis would be peformed for each architecture, in specified sources
|
||||
func (l *PackageList) VerifyDependencies(options int, architectures []string, sources *PackageList, progress aptly.Progress) ([]Dependency, error) {
|
||||
l.PrepareIndex()
|
||||
missing := make([]Dependency, 0, 128)
|
||||
|
||||
if progress != nil {
|
||||
@@ -244,7 +264,7 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so
|
||||
for _, arch := range architectures {
|
||||
cache := make(map[string]bool, 2048)
|
||||
|
||||
for _, p := range l.packages {
|
||||
for _, p := range l.packagesIndex {
|
||||
if progress != nil {
|
||||
progress.AddBar(1)
|
||||
}
|
||||
@@ -262,7 +282,6 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so
|
||||
variants = depSliceDeduplicate(variants)
|
||||
|
||||
variantsMissing := make([]Dependency, 0, len(variants))
|
||||
missingCount := 0
|
||||
|
||||
for _, dep := range variants {
|
||||
if dep.Architecture == "" {
|
||||
@@ -270,35 +289,23 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so
|
||||
}
|
||||
|
||||
hash := dep.Hash()
|
||||
r, ok := cache[hash]
|
||||
if ok {
|
||||
if !r {
|
||||
missingCount++
|
||||
}
|
||||
continue
|
||||
satisfied, ok := cache[hash]
|
||||
if !ok {
|
||||
satisfied = sources.Search(dep, false) != nil
|
||||
cache[hash] = satisfied
|
||||
}
|
||||
|
||||
if sources.Search(dep, false) == nil {
|
||||
if !satisfied && !ok {
|
||||
variantsMissing = append(variantsMissing, dep)
|
||||
missingCount++
|
||||
} else {
|
||||
cache[hash] = true
|
||||
}
|
||||
|
||||
if satisfied && options&DepFollowAllVariants == 0 {
|
||||
variantsMissing = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if options&DepFollowAllVariants == DepFollowAllVariants {
|
||||
missing = append(missing, variantsMissing...)
|
||||
for _, dep := range variantsMissing {
|
||||
cache[dep.Hash()] = false
|
||||
}
|
||||
} else {
|
||||
if missingCount == len(variants) {
|
||||
missing = append(missing, variantsMissing...)
|
||||
for _, dep := range variantsMissing {
|
||||
cache[dep.Hash()] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
missing = append(missing, variantsMissing...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +341,10 @@ func (l *PackageList) Less(i, j int) bool {
|
||||
|
||||
// PrepareIndex prepares list for indexing
|
||||
func (l *PackageList) PrepareIndex() {
|
||||
if l.indexed {
|
||||
return
|
||||
}
|
||||
|
||||
l.packagesIndex = make([]*Package, l.Len())
|
||||
l.providesIndex = make(map[string][]*Package, 128)
|
||||
|
||||
@@ -364,6 +375,23 @@ func (l *PackageList) Scan(q PackageQuery) (result *PackageList) {
|
||||
return
|
||||
}
|
||||
|
||||
// SearchSupported returns true for PackageList
|
||||
func (l *PackageList) SearchSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SearchByKey looks up package by exact key reference
|
||||
func (l *PackageList) SearchByKey(arch, name, version string) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
|
||||
pkg := l.packages["P"+arch+" "+name+" "+version]
|
||||
if pkg != nil {
|
||||
result.Add(pkg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Search searches package index for specified package(s) using optimized queries
|
||||
func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*Package) {
|
||||
if !l.indexed {
|
||||
@@ -414,6 +442,7 @@ func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, sour
|
||||
|
||||
if withDependencies {
|
||||
added := result.Len()
|
||||
result.PrepareIndex()
|
||||
|
||||
dependencySource := NewPackageList()
|
||||
if source != nil {
|
||||
@@ -434,12 +463,21 @@ func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, sour
|
||||
|
||||
// try to satisfy dependencies
|
||||
for _, dep := range missing {
|
||||
// dependency might have already been satisfied
|
||||
// with packages already been added
|
||||
if result.Search(dep, false) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
searchResults := l.Search(dep, false)
|
||||
if searchResults != nil {
|
||||
for _, p := range searchResults {
|
||||
result.Add(p)
|
||||
dependencySource.Add(p)
|
||||
added++
|
||||
if dependencyOptions&DepFollowAllVariants == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-30
@@ -2,10 +2,11 @@ package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "launchpad.net/gocheck"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type containsChecker struct {
|
||||
@@ -79,20 +80,20 @@ func (s *PackageListSuite) SetUpTest(c *C) {
|
||||
|
||||
s.il = NewPackageList()
|
||||
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"}}},
|
||||
&Package{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)"}}},
|
||||
&Package{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{}},
|
||||
&Package{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"}}},
|
||||
&Package{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)"}}},
|
||||
&Package{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)"}}},
|
||||
&Package{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{}},
|
||||
&Package{Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
|
||||
{Name: "lib", Version: "1.0", Architecture: "i386", Source: "lib (0.9)", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"mail-agent"}}},
|
||||
{Name: "dpkg", Version: "1.7", Architecture: "i386", Provides: []string{"package-installer"}, deps: &PackageDependencies{}},
|
||||
{Name: "data", Version: "1.1~bp1", Architecture: "all", Source: "app", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}},
|
||||
{Name: "app", Version: "1.1~bp1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
{Name: "mailer", Version: "3.5.8", Architecture: "i386", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}},
|
||||
{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: "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.0", Architecture: "s390", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
{Name: "aa", Version: "2.0-1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}},
|
||||
{Name: "dpkg", Version: "1.6.1-3", Architecture: "amd64", Provides: []string{"package-installer"}, deps: &PackageDependencies{}},
|
||||
{Name: "libx", Version: "1.5", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}},
|
||||
{Name: "dpkg", Version: "1.6.1-3", Architecture: "arm", Provides: []string{"package-installer"}, deps: &PackageDependencies{}},
|
||||
{Name: "dpkg", Version: "1.6.1-3", 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 {
|
||||
s.il.Add(p)
|
||||
@@ -101,12 +102,12 @@ func (s *PackageListSuite) SetUpTest(c *C) {
|
||||
|
||||
s.il2 = NewPackageList()
|
||||
s.packages2 = []*Package{
|
||||
&Package{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{}},
|
||||
&Package{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)"}}},
|
||||
&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"}}},
|
||||
&Package{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
{Name: "mailer", Version: "3.5.8", 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{}},
|
||||
{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-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
{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: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
}
|
||||
for _, p := range s.packages2 {
|
||||
s.il2.Add(p)
|
||||
@@ -114,10 +115,10 @@ func (s *PackageListSuite) SetUpTest(c *C) {
|
||||
s.il2.PrepareIndex()
|
||||
|
||||
s.sourcePackages = []*Package{
|
||||
&Package{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{}},
|
||||
&Package{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: "postfix", Version: "1.3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
|
||||
{Name: "app", Version: "1.1~bp1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}},
|
||||
{Name: "aa", Version: "2.0-1", 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)
|
||||
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)
|
||||
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)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "arm"},
|
||||
Dependency{Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}})
|
||||
c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "arm"},
|
||||
{Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}})
|
||||
|
||||
for _, p := range s.sourcePackages {
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
c.Check(err, ErrorMatches, "unable to process package app_1.0_s390:.*")
|
||||
|
||||
+6
-3
@@ -7,12 +7,13 @@ import (
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/ugorji/go/codec"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// LocalRepo is a collection of packages created locally
|
||||
type LocalRepo struct {
|
||||
// Permanent internal ID
|
||||
UUID string
|
||||
UUID string `json:"-"`
|
||||
// User-assigned name
|
||||
Name string
|
||||
// Comment
|
||||
@@ -88,6 +89,7 @@ func (repo *LocalRepo) RefKey() []byte {
|
||||
|
||||
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
||||
type LocalRepoCollection struct {
|
||||
*sync.RWMutex
|
||||
db database.Storage
|
||||
list []*LocalRepo
|
||||
}
|
||||
@@ -95,7 +97,8 @@ type LocalRepoCollection struct {
|
||||
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
||||
func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
|
||||
result := &LocalRepoCollection{
|
||||
db: db,
|
||||
RWMutex: &sync.RWMutex{},
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("L"))
|
||||
@@ -104,7 +107,7 @@ func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
|
||||
for _, blob := range blobs {
|
||||
r := &LocalRepo{}
|
||||
if err := r.Decode(blob); err != nil {
|
||||
log.Printf("Error decoding mirror: %s\n", err)
|
||||
log.Printf("Error decoding repo: %s\n", err)
|
||||
} else {
|
||||
result.list = append(result.list, r)
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@ package deb
|
||||
import (
|
||||
"errors"
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type LocalRepoSuite struct {
|
||||
|
||||
+43
-5
@@ -1,6 +1,7 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
@@ -24,6 +25,8 @@ type Package struct {
|
||||
Provides []string
|
||||
// Is this source package
|
||||
IsSource bool
|
||||
// Is this udeb package
|
||||
IsUdeb bool
|
||||
// Hash of files section
|
||||
FilesHash uint64
|
||||
// Is this >= 0.6 package?
|
||||
@@ -36,6 +39,11 @@ type Package struct {
|
||||
collection *PackageCollection
|
||||
}
|
||||
|
||||
// Check interface
|
||||
var (
|
||||
_ json.Marshaler = &Package{}
|
||||
)
|
||||
|
||||
// NewPackageFromControlFile creates Package from parsed Debian control file
|
||||
func NewPackageFromControlFile(input Stanza) *Package {
|
||||
result := &Package{
|
||||
@@ -53,12 +61,18 @@ func NewPackageFromControlFile(input Stanza) *Package {
|
||||
|
||||
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{
|
||||
Filename: filepath.Base(input["Filename"]),
|
||||
downloadPath: filepath.Dir(input["Filename"]),
|
||||
Checksums: utils.ChecksumInfo{
|
||||
Size: filesize,
|
||||
MD5: strings.TrimSpace(input["MD5sum"]),
|
||||
MD5: strings.TrimSpace(md5),
|
||||
SHA1: strings.TrimSpace(input["SHA1"]),
|
||||
SHA256: strings.TrimSpace(input["SHA256"]),
|
||||
},
|
||||
@@ -66,6 +80,7 @@ func NewPackageFromControlFile(input Stanza) *Package {
|
||||
|
||||
delete(input, "Filename")
|
||||
delete(input, "MD5sum")
|
||||
delete(input, "MD5Sum")
|
||||
delete(input, "SHA1")
|
||||
delete(input, "SHA256")
|
||||
delete(input, "Size")
|
||||
@@ -169,6 +184,14 @@ func NewSourcePackageFromControlFile(input Stanza) (*Package, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NewUdebPackageFromControlFile creates .udeb Package from parsed Debian control file
|
||||
func NewUdebPackageFromControlFile(input Stanza) *Package {
|
||||
p := NewPackageFromControlFile(input)
|
||||
p.IsUdeb = true
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Key returns unique key identifying package
|
||||
func (p *Package) Key(prefix string) []byte {
|
||||
if p.V06Plus {
|
||||
@@ -188,6 +211,16 @@ func (p *Package) String() string {
|
||||
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
|
||||
func (p *Package) GetField(name string) string {
|
||||
switch name {
|
||||
@@ -220,6 +253,9 @@ func (p *Package) GetField(name string) string {
|
||||
if p.IsSource {
|
||||
return "source"
|
||||
}
|
||||
if p.IsUdeb {
|
||||
return "udeb"
|
||||
}
|
||||
return "deb"
|
||||
case "Name":
|
||||
return p.Name
|
||||
@@ -249,7 +285,6 @@ func (p *Package) GetField(name string) string {
|
||||
default:
|
||||
return p.Extra()[name]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MatchesArchitecture checks whether packages matches specified architecture
|
||||
@@ -391,7 +426,9 @@ func (p *Package) Stanza() (result Stanza) {
|
||||
result["Architecture"] = p.SourceArchitecture
|
||||
} else {
|
||||
result["Architecture"] = p.Architecture
|
||||
result["Source"] = p.Source
|
||||
if p.Source != "" {
|
||||
result["Source"] = p.Source
|
||||
}
|
||||
}
|
||||
|
||||
if p.IsSource {
|
||||
@@ -462,7 +499,8 @@ func (p *Package) Equals(p2 *Package) bool {
|
||||
}
|
||||
|
||||
// LinkFromPool links package file from pool to dist's pool location
|
||||
func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packagePool aptly.PackagePool, prefix string, component string) error {
|
||||
func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packagePool aptly.PackagePool,
|
||||
prefix, component string, force bool) error {
|
||||
poolDir, err := p.PoolDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -477,7 +515,7 @@ func (p *Package) LinkFromPool(publishedStorage aptly.PublishedStorage, packageP
|
||||
relPath := filepath.Join("pool", component, poolDir)
|
||||
publishedDirectory := filepath.Join(prefix, relPath)
|
||||
|
||||
err = publishedStorage.LinkFromPool(publishedDirectory, packagePool, sourcePath, f.Checksums.MD5)
|
||||
err = publishedStorage.LinkFromPool(publishedDirectory, packagePool, sourcePath, f.Checksums.MD5, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ type PackageCollection struct {
|
||||
codecHandle *codec.MsgpackHandle
|
||||
}
|
||||
|
||||
// Verify interface
|
||||
var (
|
||||
_ PackageCatalog = &PackageCollection{}
|
||||
)
|
||||
|
||||
// NewPackageCollection creates new PackageCollection and binds it to database
|
||||
func NewPackageCollection(db database.Storage) *PackageCollection {
|
||||
return &PackageCollection{
|
||||
@@ -237,3 +242,49 @@ func (collection *PackageCollection) DeleteByKey(key []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scan does full scan on all the packages
|
||||
func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
|
||||
for _, key := range collection.db.KeysByPrefix([]byte("P")) {
|
||||
pkg, err := collection.ByKey(key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to load package: %s", err))
|
||||
}
|
||||
|
||||
if q.Matches(pkg) {
|
||||
result.Add(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Search is not implemented
|
||||
func (collection *PackageCollection) Search(dep Dependency, allMatches bool) (searchResults []*Package) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// SearchSupported returns false
|
||||
func (collection *PackageCollection) SearchSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SearchByKey finds package by exact key
|
||||
func (collection *PackageCollection) SearchByKey(arch, name, version string) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
|
||||
for _, key := range collection.db.KeysByPrefix([]byte(fmt.Sprintf("P%s %s %s", arch, name, version))) {
|
||||
pkg, err := collection.ByKey(key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to load package: %s", err))
|
||||
}
|
||||
|
||||
if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version {
|
||||
result.Add(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ package deb
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type PackageCollectionSuite struct {
|
||||
|
||||
@@ -3,9 +3,10 @@ package deb
|
||||
import (
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type PackageFilesSuite struct {
|
||||
|
||||
+46
-4
@@ -4,10 +4,11 @@ import (
|
||||
"bytes"
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type PackageSuite struct {
|
||||
@@ -28,6 +29,7 @@ func (s *PackageSuite) TestNewFromPara(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.IsSource, Equals, false)
|
||||
c.Check(p.IsUdeb, Equals, false)
|
||||
c.Check(p.Name, Equals, "alien-arena-common")
|
||||
c.Check(p.Version, Equals, "7.40-2")
|
||||
c.Check(p.Architecture, Equals, "i386")
|
||||
@@ -40,11 +42,27 @@ func (s *PackageSuite) TestNewFromPara(c *C) {
|
||||
c.Check(p.deps.Depends, DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)"})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestNewUdebFromPara(c *C) {
|
||||
stanza, _ := NewControlFileReader(bytes.NewBufferString(udebPackageMeta)).ReadStanza()
|
||||
p := NewUdebPackageFromControlFile(stanza)
|
||||
|
||||
c.Check(p.IsSource, Equals, false)
|
||||
c.Check(p.IsUdeb, Equals, true)
|
||||
c.Check(p.Name, Equals, "dmidecode-udeb")
|
||||
c.Check(p.Version, Equals, "2.11-9")
|
||||
c.Check(p.Architecture, Equals, "amd64")
|
||||
c.Check(p.Provides, DeepEquals, []string(nil))
|
||||
c.Check(p.Files(), HasLen, 1)
|
||||
c.Check(p.Files()[0].Filename, Equals, "dmidecode-udeb_2.11-9_amd64.udeb")
|
||||
c.Check(p.deps.Depends, DeepEquals, []string{"libc6-udeb (>= 2.13)"})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestNewSourceFromPara(c *C) {
|
||||
p, err := NewSourcePackageFromControlFile(s.sourceStanza)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.IsSource, Equals, true)
|
||||
c.Check(p.IsUdeb, Equals, false)
|
||||
c.Check(p.Name, Equals, "access-modifier-checker")
|
||||
c.Check(p.Version, Equals, "1.0-4")
|
||||
c.Check(p.Architecture, Equals, "source")
|
||||
@@ -134,21 +152,28 @@ func (s *PackageSuite) TestGetField(c *C) {
|
||||
|
||||
p4, _ := NewSourcePackageFromControlFile(s.sourceStanza.Copy())
|
||||
|
||||
stanza5, _ := NewControlFileReader(bytes.NewBufferString(udebPackageMeta)).ReadStanza()
|
||||
p5 := NewUdebPackageFromControlFile(stanza5)
|
||||
|
||||
c.Check(p.GetField("$Source"), Equals, "alien-arena")
|
||||
c.Check(p2.GetField("$Source"), Equals, "alien-arena-common")
|
||||
c.Check(p3.GetField("$Source"), Equals, "alien-arena")
|
||||
c.Check(p4.GetField("$Source"), Equals, "")
|
||||
c.Check(p5.GetField("$Source"), Equals, "dmidecode")
|
||||
|
||||
c.Check(p.GetField("$SourceVersion"), Equals, "7.40-2")
|
||||
c.Check(p2.GetField("$SourceVersion"), Equals, "7.40-2")
|
||||
c.Check(p3.GetField("$SourceVersion"), Equals, "3.5")
|
||||
c.Check(p4.GetField("$SourceVersion"), Equals, "")
|
||||
c.Check(p5.GetField("$SourceVersion"), Equals, "2.11-9")
|
||||
|
||||
c.Check(p.GetField("$Architecture"), Equals, "i386")
|
||||
c.Check(p4.GetField("$Architecture"), Equals, "source")
|
||||
c.Check(p5.GetField("$Architecture"), Equals, "amd64")
|
||||
|
||||
c.Check(p.GetField("$PackageType"), Equals, "deb")
|
||||
c.Check(p4.GetField("$PackageType"), Equals, "source")
|
||||
c.Check(p5.GetField("$PackageType"), Equals, "udeb")
|
||||
|
||||
c.Check(p.GetField("Name"), Equals, "alien-arena-common")
|
||||
c.Check(p4.GetField("Name"), Equals, "access-modifier-checker")
|
||||
@@ -345,13 +370,13 @@ func (s *PackageSuite) TestLinkFromPool(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
file.Close()
|
||||
|
||||
err = p.LinkFromPool(publishedStorage, packagePool, "", "non-free")
|
||||
err = p.LinkFromPool(publishedStorage, packagePool, "", "non-free", false)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.Files()[0].Filename, Equals, "alien-arena-common_7.40-2_i386.deb")
|
||||
c.Check(p.Files()[0].downloadPath, Equals, "pool/non-free/a/alien-arena")
|
||||
|
||||
p.IsSource = true
|
||||
err = p.LinkFromPool(publishedStorage, packagePool, "", "non-free")
|
||||
err = p.LinkFromPool(publishedStorage, packagePool, "", "non-free", false)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.Extra()["Directory"], Equals, "pool/non-free/a/alien-arena")
|
||||
}
|
||||
@@ -374,7 +399,7 @@ func (s *PackageSuite) TestDownloadList(c *C) {
|
||||
list, err := p.DownloadList(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(list, DeepEquals, []PackageDownloadTask{
|
||||
PackageDownloadTask{
|
||||
{
|
||||
RepoURI: "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
||||
DestinationPath: poolPath,
|
||||
Checksums: utils.ChecksumInfo{Size: 5,
|
||||
@@ -455,3 +480,20 @@ Directory: pool/main/a/access-modifier-checker
|
||||
Priority: source
|
||||
Section: java
|
||||
`
|
||||
|
||||
const udebPackageMeta = `Package: dmidecode-udeb
|
||||
Source: dmidecode
|
||||
Version: 2.11-9
|
||||
Installed-Size: 115
|
||||
Maintainer: Daniel Baumann <daniel.baumann@progress-technologies.net>
|
||||
Architecture: amd64
|
||||
Depends: libc6-udeb (>= 2.13)
|
||||
Description: SMBIOS/DMI table decoder (udeb)
|
||||
Description-md5: bdfb786c6a57097be8c8600b800e749f
|
||||
Section: debian-installer
|
||||
Priority: optional
|
||||
Filename: pool/main/d/dmidecode/dmidecode-udeb_2.11-9_amd64.udeb
|
||||
Size: 29188
|
||||
MD5sum: ae70341c4d96dcded89fa670bcfea31e
|
||||
SHA1: 9532ae4226a85805189a671ee0283f719d48a5ba
|
||||
SHA256: bbb3a2cb07f741c3995b6d4bb08d772d83582b93a0236d4ea7736bc0370fc320`
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@ package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type PpaSuite struct {
|
||||
|
||||
+155
-183
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/database"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -57,6 +59,21 @@ type PublishedRepo struct {
|
||||
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
|
||||
// gathering information about declared components and distributions
|
||||
func walkUpTree(source interface{}, collectionFactory *CollectionFactory) (rootDistributions []string, rootComponents []string) {
|
||||
@@ -169,6 +186,9 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
|
||||
if distribution == "" || component == "" {
|
||||
rootDistributions, rootComponents := walkUpTree(source, collectionFactory)
|
||||
if distribution == "" {
|
||||
for i := range rootDistributions {
|
||||
rootDistributions[i] = strings.Replace(rootDistributions[i], "/", "-", -1)
|
||||
}
|
||||
discoveredDistributions = append(discoveredDistributions, rootDistributions...)
|
||||
}
|
||||
if component == "" {
|
||||
@@ -228,11 +248,49 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Index(distribution, "/") != -1 {
|
||||
return nil, fmt.Errorf("invalid distribution %s, '/' is not allowed", distribution)
|
||||
}
|
||||
|
||||
result.Distribution = distribution
|
||||
|
||||
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
|
||||
func (p *PublishedRepo) String() string {
|
||||
var sources = []string{}
|
||||
@@ -393,7 +451,7 @@ func (p *PublishedRepo) GetLabel() string {
|
||||
|
||||
// Publish publishes snapshot (repository) contents, links package files, generates Packages & Release files, signs them
|
||||
func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageProvider aptly.PublishedStorageProvider,
|
||||
collectionFactory *CollectionFactory, signer utils.Signer, progress aptly.Progress) error {
|
||||
collectionFactory *CollectionFactory, signer utils.Signer, progress aptly.Progress, forceOverwrite bool) error {
|
||||
publishedStorage := publishedStorageProvider.GetPublishedStorage(p.Storage)
|
||||
|
||||
err := publishedStorage.MkDir(filepath.Join(p.Prefix, "pool"))
|
||||
@@ -440,9 +498,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
suffix = ".tmp"
|
||||
}
|
||||
|
||||
generatedFiles := map[string]utils.ChecksumInfo{}
|
||||
renameMap := map[string]string{}
|
||||
|
||||
if progress != nil {
|
||||
progress.Printf("Generating metadata files and linking package files...\n")
|
||||
}
|
||||
@@ -454,46 +509,53 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix)
|
||||
|
||||
for component, list := range lists {
|
||||
var relativePath string
|
||||
hadUdebs := false
|
||||
|
||||
// For all architectures, generate packages/sources files
|
||||
// For all architectures, pregenerate packages/sources files
|
||||
for _, arch := range p.Architectures {
|
||||
indexes.PackageIndex(component, arch, false)
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
progress.InitBar(int64(list.Len()), false)
|
||||
}
|
||||
|
||||
list.PrepareIndex()
|
||||
|
||||
err = list.ForEachIndexed(func(pkg *Package) error {
|
||||
if progress != nil {
|
||||
progress.InitBar(int64(list.Len()), false)
|
||||
progress.AddBar(1)
|
||||
}
|
||||
|
||||
if arch == "source" {
|
||||
relativePath = filepath.Join(component, "source", "Sources")
|
||||
} else {
|
||||
relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages")
|
||||
}
|
||||
err = publishedStorage.MkDir(filepath.Dir(filepath.Join(basePath, relativePath)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var packagesFile *os.File
|
||||
|
||||
packagesFileName := filepath.Join(tempDir, fmt.Sprintf("pkgs_%s_%s", component, arch))
|
||||
packagesFile, err = os.Create(packagesFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temporary Packages file: %s", err)
|
||||
}
|
||||
|
||||
bufWriter := bufio.NewWriter(packagesFile)
|
||||
|
||||
err = list.ForEach(func(pkg *Package) error {
|
||||
if progress != nil {
|
||||
progress.AddBar(1)
|
||||
}
|
||||
matches := false
|
||||
for _, arch := range p.Architectures {
|
||||
if pkg.MatchesArchitecture(arch) {
|
||||
err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, component)
|
||||
matches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matches {
|
||||
hadUdebs = hadUdebs || pkg.IsUdeb
|
||||
err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, component, forceOverwrite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, arch := range p.Architectures {
|
||||
if pkg.MatchesArchitecture(arch) {
|
||||
var bufWriter *bufio.Writer
|
||||
|
||||
bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb).BufWriter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pkg.Stanza().WriteTo(bufWriter)
|
||||
err = pkg.Stanza().WriteTo(bufWriter, pkg.IsSource, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -501,116 +563,68 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg.files = nil
|
||||
pkg.deps = nil
|
||||
pkg.extra = nil
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to process packages: %s", err)
|
||||
}
|
||||
|
||||
err = bufWriter.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write Packages file: %s", err)
|
||||
}
|
||||
|
||||
err = utils.CompressFile(packagesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to compress Packages files: %s", err)
|
||||
}
|
||||
|
||||
packagesFile.Close()
|
||||
|
||||
for _, ext := range []string{"", ".gz", ".bz2"} {
|
||||
var checksumInfo utils.ChecksumInfo
|
||||
|
||||
checksumInfo, err = utils.ChecksumsForFile(packagesFileName + ext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect checksums: %s", err)
|
||||
}
|
||||
generatedFiles[relativePath+ext] = checksumInfo
|
||||
|
||||
err = publishedStorage.PutFile(filepath.Join(basePath, relativePath+suffix+ext), packagesFileName+ext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
|
||||
if suffix != "" {
|
||||
renameMap[filepath.Join(basePath, relativePath+suffix+ext)] = filepath.Join(basePath, relativePath+ext)
|
||||
}
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
progress.ShutdownBar()
|
||||
pkg.files = nil
|
||||
pkg.deps = nil
|
||||
pkg.extra = nil
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to process packages: %s", err)
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
progress.ShutdownBar()
|
||||
}
|
||||
|
||||
udebs := []bool{false}
|
||||
if hadUdebs {
|
||||
udebs = append(udebs, true)
|
||||
|
||||
// For all architectures, pregenerate .udeb indexes
|
||||
for _, arch := range p.Architectures {
|
||||
indexes.PackageIndex(component, arch, true)
|
||||
}
|
||||
}
|
||||
|
||||
// For all architectures, generate Release files
|
||||
for _, arch := range p.Architectures {
|
||||
release := make(Stanza)
|
||||
release["Archive"] = p.Distribution
|
||||
release["Architecture"] = arch
|
||||
release["Component"] = component
|
||||
release["Origin"] = p.GetOrigin()
|
||||
release["Label"] = p.GetLabel()
|
||||
for _, udeb := range udebs {
|
||||
release := make(Stanza)
|
||||
release["Archive"] = p.Distribution
|
||||
release["Architecture"] = arch
|
||||
release["Component"] = component
|
||||
release["Origin"] = p.GetOrigin()
|
||||
release["Label"] = p.GetLabel()
|
||||
|
||||
if arch == "source" {
|
||||
relativePath = filepath.Join(component, "source", "Release")
|
||||
} else {
|
||||
relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Release")
|
||||
var bufWriter *bufio.Writer
|
||||
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
||||
|
||||
err = release.WriteTo(bufWriter, false, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var file *os.File
|
||||
|
||||
fileName := filepath.Join(tempDir, fmt.Sprintf("release_%s_%s", component, arch))
|
||||
file, err = os.Create(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temporary Release file: %s", err)
|
||||
}
|
||||
|
||||
bufWriter := bufio.NewWriter(file)
|
||||
|
||||
err = release.WriteTo(bufWriter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
err = bufWriter.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
var checksumInfo utils.ChecksumInfo
|
||||
checksumInfo, err = utils.ChecksumsForFile(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect checksums: %s", err)
|
||||
}
|
||||
generatedFiles[relativePath] = checksumInfo
|
||||
|
||||
err = publishedStorage.PutFile(filepath.Join(basePath, relativePath+suffix), fileName)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
|
||||
if suffix != "" {
|
||||
renameMap[filepath.Join(basePath, relativePath+suffix)] = filepath.Join(basePath, relativePath)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
progress.Printf("Finalizing metadata files...\n")
|
||||
}
|
||||
|
||||
err = indexes.FinalizeAll(progress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
release := make(Stanza)
|
||||
release["Origin"] = p.GetOrigin()
|
||||
release["Label"] = p.GetLabel()
|
||||
release["Suite"] = p.Distribution
|
||||
release["Codename"] = p.Distribution
|
||||
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
||||
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{"source"}), " ")
|
||||
@@ -621,80 +635,36 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
|
||||
release["Components"] = strings.Join(p.Components(), " ")
|
||||
|
||||
for path, info := range generatedFiles {
|
||||
for path, info := range indexes.generatedFiles {
|
||||
release["MD5Sum"] += fmt.Sprintf(" %s %8d %s\n", info.MD5, info.Size, path)
|
||||
release["SHA1"] += fmt.Sprintf(" %s %8d %s\n", info.SHA1, info.Size, path)
|
||||
release["SHA256"] += fmt.Sprintf(" %s %8d %s\n", info.SHA256, info.Size, path)
|
||||
}
|
||||
|
||||
var releaseFile *os.File
|
||||
releaseFilename := filepath.Join(tempDir, "Release")
|
||||
releaseFile, err = os.Create(releaseFilename)
|
||||
releaseFile := indexes.ReleaseFile()
|
||||
bufWriter, err := releaseFile.BufWriter()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temporary Release file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
bufWriter := bufio.NewWriter(releaseFile)
|
||||
|
||||
err = release.WriteTo(bufWriter)
|
||||
err = release.WriteTo(bufWriter, false, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
err = bufWriter.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
releaseFile.Close()
|
||||
|
||||
if suffix != "" {
|
||||
renameMap[filepath.Join(basePath, "Release"+suffix)] = filepath.Join(basePath, "Release")
|
||||
}
|
||||
|
||||
err = publishedStorage.PutFile(filepath.Join(basePath, "Release"+suffix), releaseFilename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
|
||||
// Signing files might output to console, so flush progress writer first
|
||||
if progress != nil {
|
||||
progress.Flush()
|
||||
}
|
||||
|
||||
if signer != nil {
|
||||
err = signer.DetachedSign(releaseFilename, releaseFilename+".gpg")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to sign Release file: %s", err)
|
||||
}
|
||||
|
||||
err = signer.ClearSign(releaseFilename, filepath.Join(filepath.Dir(releaseFilename), "InRelease"+suffix))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to sign Release file: %s", err)
|
||||
}
|
||||
|
||||
if suffix != "" {
|
||||
renameMap[filepath.Join(basePath, "Release"+suffix+".gpg")] = filepath.Join(basePath, "Release.gpg")
|
||||
renameMap[filepath.Join(basePath, "InRelease"+suffix)] = filepath.Join(basePath, "InRelease")
|
||||
}
|
||||
|
||||
err = publishedStorage.PutFile(filepath.Join(basePath, "Release"+suffix+".gpg"), releaseFilename+".gpg")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
|
||||
err = publishedStorage.PutFile(filepath.Join(basePath, "InRelease"+suffix),
|
||||
filepath.Join(filepath.Dir(releaseFilename), "InRelease"+suffix))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish file: %s", err)
|
||||
}
|
||||
err = releaseFile.Finalize(signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for oldName, newName := range renameMap {
|
||||
err = publishedStorage.RenameFile(oldName, newName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
err = indexes.RenameFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -736,6 +706,7 @@ func (p *PublishedRepo) RemoveFiles(publishedStorageProvider aptly.PublishedStor
|
||||
|
||||
// PublishedRepoCollection does listing, updating/adding/deleting of PublishedRepos
|
||||
type PublishedRepoCollection struct {
|
||||
*sync.RWMutex
|
||||
db database.Storage
|
||||
list []*PublishedRepo
|
||||
}
|
||||
@@ -743,7 +714,8 @@ type PublishedRepoCollection struct {
|
||||
// NewPublishedRepoCollection loads PublishedRepos from DB and makes up collection
|
||||
func NewPublishedRepoCollection(db database.Storage) *PublishedRepoCollection {
|
||||
result := &PublishedRepoCollection{
|
||||
db: db,
|
||||
RWMutex: &sync.RWMutex{},
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("U"))
|
||||
|
||||
+24
-7
@@ -9,9 +9,10 @@ import (
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/ugorji/go/codec"
|
||||
"io/ioutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type pathExistsChecker struct {
|
||||
@@ -36,9 +37,15 @@ func (n *NullSigner) Init() error {
|
||||
func (n *NullSigner) SetKey(keyRef string) {
|
||||
}
|
||||
|
||||
func (n *NullSigner) SetBatch(batch bool) {
|
||||
}
|
||||
|
||||
func (n *NullSigner) SetKeyRing(keyring, secretKeyring string) {
|
||||
}
|
||||
|
||||
func (n *NullSigner) SetPassphrase(passphrase, passphraseFile string) {
|
||||
}
|
||||
|
||||
func (n *NullSigner) DetachedSign(source string, destination string) error {
|
||||
return ioutil.WriteFile(destination, []byte{}, 0644)
|
||||
}
|
||||
@@ -90,7 +97,7 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
||||
"files:other": s.publishedStorage2}}
|
||||
s.packagePool = files.NewPackagePool(s.root)
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
repo.packageRefs = s.reflist
|
||||
s.factory.RemoteRepoCollection().Add(repo)
|
||||
|
||||
@@ -164,6 +171,9 @@ func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) {
|
||||
|
||||
_, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
c.Check(err, ErrorMatches, "duplicate component name: main")
|
||||
|
||||
_, err = NewPublishedRepo("", ".", "wheezy/updates", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
||||
c.Check(err, ErrorMatches, "invalid distribution wheezy/updates, '/' is not allowed")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) {
|
||||
@@ -264,6 +274,13 @@ func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) {
|
||||
c.Check(repo.Distribution, Equals, "precise")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
||||
|
||||
s.localRepo.DefaultDistribution = "precise/updates"
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "precise-updates")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "squeeze")
|
||||
@@ -274,7 +291,7 @@ func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublish(c *C) {
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil)
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.repo.Architectures, DeepEquals, []string{"i386"})
|
||||
@@ -321,7 +338,7 @@ func (s *PublishedRepoSuite) TestPublish(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"), PathExists)
|
||||
@@ -329,7 +346,7 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) {
|
||||
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), PathExists)
|
||||
@@ -337,7 +354,7 @@ func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishLocalSourceRepo(c *C) {
|
||||
err := s.repo4.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
err := s.repo4.Publish(s.packagePool, s.provider, s.factory, nil, nil, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), PathExists)
|
||||
@@ -345,7 +362,7 @@ func (s *PublishedRepoSuite) TestPublishLocalSourceRepo(c *C) {
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishOtherStorage(c *C) {
|
||||
err := s.repo5.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
err := s.repo5.Publish(s.packagePool, s.provider, s.factory, nil, nil, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/maverick/Release"), PathExists)
|
||||
|
||||
+36
-31
@@ -7,14 +7,22 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PackageCatalog is abstraction on top of PackageCollection and PackageList
|
||||
type PackageCatalog interface {
|
||||
Scan(q PackageQuery) (result *PackageList)
|
||||
Search(dep Dependency, allMatches bool) (searchResults []*Package)
|
||||
SearchSupported() bool
|
||||
SearchByKey(arch, name, version string) (result *PackageList)
|
||||
}
|
||||
|
||||
// PackageQuery is interface of predicate on Package
|
||||
type PackageQuery interface {
|
||||
// Matches calculates match of condition against package
|
||||
Matches(pkg *Package) bool
|
||||
// Fast returns if search strategy is possible for this query
|
||||
Fast() bool
|
||||
Fast(list PackageCatalog) bool
|
||||
// Query performs search on package list
|
||||
Query(list *PackageList) *PackageList
|
||||
Query(list PackageCatalog) *PackageList
|
||||
// String interface
|
||||
String() string
|
||||
}
|
||||
@@ -60,13 +68,13 @@ func (q *OrQuery) Matches(pkg *Package) bool {
|
||||
}
|
||||
|
||||
// Fast is true only if both parts are fast
|
||||
func (q *OrQuery) Fast() bool {
|
||||
return q.L.Fast() && q.R.Fast()
|
||||
func (q *OrQuery) Fast(list PackageCatalog) bool {
|
||||
return q.L.Fast(list) && q.R.Fast(list)
|
||||
}
|
||||
|
||||
// Query strategy depends on nodes
|
||||
func (q *OrQuery) Query(list *PackageList) (result *PackageList) {
|
||||
if q.Fast() {
|
||||
func (q *OrQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
if q.Fast(list) {
|
||||
result = q.L.Query(list)
|
||||
result.Append(q.R.Query(list))
|
||||
} else {
|
||||
@@ -86,16 +94,16 @@ func (q *AndQuery) Matches(pkg *Package) bool {
|
||||
}
|
||||
|
||||
// Fast is true if any of the parts are fast
|
||||
func (q *AndQuery) Fast() bool {
|
||||
return q.L.Fast() || q.R.Fast()
|
||||
func (q *AndQuery) Fast(list PackageCatalog) bool {
|
||||
return q.L.Fast(list) || q.R.Fast(list)
|
||||
}
|
||||
|
||||
// Query strategy depends on nodes
|
||||
func (q *AndQuery) Query(list *PackageList) (result *PackageList) {
|
||||
if !q.Fast() {
|
||||
func (q *AndQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
if !q.Fast(list) {
|
||||
result = list.Scan(q)
|
||||
} else {
|
||||
if q.L.Fast() {
|
||||
if q.L.Fast(list) {
|
||||
result = q.L.Query(list)
|
||||
result = result.Scan(q.R)
|
||||
} else {
|
||||
@@ -117,12 +125,12 @@ func (q *NotQuery) Matches(pkg *Package) bool {
|
||||
}
|
||||
|
||||
// Fast is false
|
||||
func (q *NotQuery) Fast() bool {
|
||||
func (q *NotQuery) Fast(list PackageCatalog) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Query strategy is scan always
|
||||
func (q *NotQuery) Query(list *PackageList) (result *PackageList) {
|
||||
func (q *NotQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
result = list.Scan(q)
|
||||
return
|
||||
}
|
||||
@@ -170,13 +178,13 @@ func (q *FieldQuery) Matches(pkg *Package) bool {
|
||||
}
|
||||
|
||||
// Query runs iteration through list
|
||||
func (q *FieldQuery) Query(list *PackageList) (result *PackageList) {
|
||||
func (q *FieldQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
result = list.Scan(q)
|
||||
return
|
||||
}
|
||||
|
||||
// Fast depends on the query
|
||||
func (q *FieldQuery) Fast() bool {
|
||||
func (q *FieldQuery) Fast(list PackageCatalog) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -215,15 +223,19 @@ func (q *DependencyQuery) Matches(pkg *Package) bool {
|
||||
}
|
||||
|
||||
// Fast is always true for dependency query
|
||||
func (q *DependencyQuery) Fast() bool {
|
||||
return true
|
||||
func (q *DependencyQuery) Fast(list PackageCatalog) bool {
|
||||
return list.SearchSupported()
|
||||
}
|
||||
|
||||
// Query runs PackageList.Search
|
||||
func (q *DependencyQuery) Query(list *PackageList) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
for _, pkg := range list.Search(q.Dep, true) {
|
||||
result.Add(pkg)
|
||||
func (q *DependencyQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
if q.Fast(list) {
|
||||
result = NewPackageList()
|
||||
for _, pkg := range list.Search(q.Dep, true) {
|
||||
result.Add(pkg)
|
||||
}
|
||||
} else {
|
||||
result = list.Scan(q)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -240,20 +252,13 @@ func (q *PkgQuery) Matches(pkg *Package) bool {
|
||||
}
|
||||
|
||||
// Fast is always true for package query
|
||||
func (q *PkgQuery) Fast() bool {
|
||||
func (q *PkgQuery) Fast(list PackageCatalog) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Query looks up specific package
|
||||
func (q *PkgQuery) Query(list *PackageList) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
|
||||
pkg := list.packages["P"+q.Arch+" "+q.Pkg+" "+q.Version]
|
||||
if pkg != nil {
|
||||
result.Add(pkg)
|
||||
}
|
||||
|
||||
return
|
||||
func (q *PkgQuery) Query(list PackageCatalog) (result *PackageList) {
|
||||
return list.SearchByKey(q.Arch, q.Pkg, q.Version)
|
||||
}
|
||||
|
||||
// String interface
|
||||
|
||||
+67
-11
@@ -2,6 +2,8 @@ package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/ugorji/go/codec"
|
||||
"sort"
|
||||
)
|
||||
@@ -84,6 +86,29 @@ func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Has checks whether package is part of reflist
|
||||
func (l *PackageRefList) Has(p *Package) bool {
|
||||
key := p.Key("")
|
||||
|
||||
i := sort.Search(len(l.Refs), func(j int) bool { return bytes.Compare(l.Refs[j], 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
|
||||
func (l *PackageRefList) Substract(r *PackageRefList) *PackageRefList {
|
||||
result := &PackageRefList{Refs: make([][]byte, 0, 128)}
|
||||
@@ -131,6 +156,27 @@ type PackageDiff struct {
|
||||
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
|
||||
type PackageDiffs []PackageDiff
|
||||
|
||||
@@ -262,13 +308,23 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result
|
||||
overridenName = nil
|
||||
overriddenArch = nil
|
||||
} 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 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 {
|
||||
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) {
|
||||
// this package has already been overriden on the right
|
||||
il++
|
||||
@@ -306,15 +362,15 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result
|
||||
// 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
|
||||
// while merging two or more snapshots together.
|
||||
func FilterLatestRefs(r *PackageRefList) {
|
||||
func (l *PackageRefList) FilterLatestRefs() {
|
||||
var (
|
||||
lastArch, lastName, lastVer []byte
|
||||
arch, name, ver []byte
|
||||
parts [][]byte
|
||||
)
|
||||
|
||||
for i := 0; i < len(r.Refs); i++ {
|
||||
parts = bytes.Split(r.Refs[i][1:], []byte(" "))
|
||||
for i := 0; i < len(l.Refs); i++ {
|
||||
parts = bytes.Split(l.Refs[i][1:], []byte(" "))
|
||||
arch, name, ver = parts[0], parts[1], parts[2]
|
||||
|
||||
if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) {
|
||||
@@ -324,10 +380,10 @@ func FilterLatestRefs(r *PackageRefList) {
|
||||
// Remove the older refs from the result
|
||||
if vres > 0 {
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
+72
-28
@@ -3,7 +3,8 @@ package deb
|
||||
import (
|
||||
"errors"
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type PackageRefListSuite struct {
|
||||
@@ -128,6 +129,19 @@ func (s *PackageRefListSuite) TestPackageRefListForeach(c *C) {
|
||||
c.Check(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestHas(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
c.Check(reflist.Has(s.p1), Equals, true)
|
||||
c.Check(reflist.Has(s.p3), Equals, true)
|
||||
c.Check(reflist.Has(s.p5), Equals, true)
|
||||
c.Check(reflist.Has(s.p2), Equals, true)
|
||||
c.Check(reflist.Has(s.p6), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestSubstract(c *C) {
|
||||
r1 := []byte("r1")
|
||||
r2 := []byte("r2")
|
||||
@@ -155,13 +169,13 @@ func (s *PackageRefListSuite) TestDiff(c *C) {
|
||||
coll := NewPackageCollection(db)
|
||||
|
||||
packages := []*Package{
|
||||
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0
|
||||
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
|
||||
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
|
||||
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
|
||||
&Package{Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6
|
||||
{Name: "lib", Version: "1.0", Architecture: "i386"}, //0
|
||||
{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
|
||||
{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
|
||||
{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
|
||||
{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
|
||||
{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
|
||||
{Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6
|
||||
}
|
||||
|
||||
for _, p := range packages {
|
||||
@@ -227,17 +241,20 @@ func (s *PackageRefListSuite) TestMerge(c *C) {
|
||||
coll := NewPackageCollection(db)
|
||||
|
||||
packages := []*Package{
|
||||
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0
|
||||
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
|
||||
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
|
||||
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
|
||||
&Package{Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6
|
||||
&Package{Name: "xyz", Version: "1.0", Architecture: "sparc"}, //7
|
||||
{Name: "lib", Version: "1.0", Architecture: "i386"}, //0
|
||||
{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
|
||||
{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
|
||||
{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
|
||||
{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
|
||||
{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
|
||||
{Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6
|
||||
{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 {
|
||||
p.V06Plus = true
|
||||
coll.Update(p)
|
||||
}
|
||||
|
||||
@@ -255,35 +272,62 @@ func (s *PackageRefListSuite) TestMerge(c *C) {
|
||||
listB.Add(packages[5])
|
||||
listB.Add(packages[6])
|
||||
|
||||
listC := NewPackageList()
|
||||
listC.Add(packages[0])
|
||||
listC.Add(packages[8])
|
||||
listC.Add(packages[9])
|
||||
|
||||
reflistA := NewPackageRefListFromPackageList(listA)
|
||||
reflistB := NewPackageRefListFromPackageList(listB)
|
||||
reflistC := NewPackageRefListFromPackageList(listC)
|
||||
|
||||
mergeAB := reflistA.Merge(reflistB, true)
|
||||
mergeBA := reflistB.Merge(reflistA, true)
|
||||
mergeAC := reflistA.Merge(reflistC, true)
|
||||
mergeBC := reflistB.Merge(reflistC, true)
|
||||
mergeCB := reflistC.Merge(reflistB, true)
|
||||
|
||||
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,
|
||||
[]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)
|
||||
mergeBAall := reflistB.Merge(reflistA, false)
|
||||
mergeACall := reflistA.Merge(reflistC, false)
|
||||
mergeBCall := reflistB.Merge(reflistC, false)
|
||||
mergeCBall := reflistC.Merge(reflistB, false)
|
||||
|
||||
c.Check(mergeABall, DeepEquals, mergeBAall)
|
||||
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"})
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) {
|
||||
packages := []*Package{
|
||||
&Package{Name: "lib", Version: "1.0", Architecture: "i386"},
|
||||
&Package{Name: "lib", Version: "1.2~bp1", Architecture: "i386"},
|
||||
&Package{Name: "lib", Version: "1.2", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.2", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.3", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.5", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.6", Architecture: "i386"},
|
||||
{Name: "lib", Version: "1.0", Architecture: "i386"},
|
||||
{Name: "lib", Version: "1.2~bp1", Architecture: "i386"},
|
||||
{Name: "lib", Version: "1.2", Architecture: "i386"},
|
||||
{Name: "dpkg", Version: "1.2", Architecture: "i386"},
|
||||
{Name: "dpkg", Version: "1.3", Architecture: "i386"},
|
||||
{Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"},
|
||||
{Name: "dpkg", Version: "1.5", Architecture: "i386"},
|
||||
{Name: "dpkg", Version: "1.6", Architecture: "i386"},
|
||||
}
|
||||
|
||||
rl := NewPackageList()
|
||||
@@ -297,7 +341,7 @@ func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) {
|
||||
rl.Add(packages[7])
|
||||
|
||||
result := NewPackageRefListFromPackageList(rl)
|
||||
FilterLatestRefs(result)
|
||||
result.FilterLatestRefs()
|
||||
|
||||
c.Check(toStrSlice(result), DeepEquals,
|
||||
[]string{"Pi386 dpkg 1.6", "Pi386 lib 1.2"})
|
||||
|
||||
+169
-75
@@ -14,11 +14,20 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RemoteRepo statuses
|
||||
const (
|
||||
MirrorIdle = iota
|
||||
MirrorUpdating
|
||||
)
|
||||
|
||||
// RemoteRepo represents remote (fetchable) Debian repository.
|
||||
//
|
||||
// Repostitory could be filtered when fetching by components, architectures
|
||||
@@ -37,6 +46,8 @@ type RemoteRepo struct {
|
||||
Architectures []string
|
||||
// Should we download sources?
|
||||
DownloadSources bool
|
||||
// Should we download .udebs?
|
||||
DownloadUdebs bool
|
||||
// Meta-information about repository
|
||||
Meta Stanza
|
||||
// Last update date
|
||||
@@ -47,15 +58,25 @@ type RemoteRepo struct {
|
||||
Filter string
|
||||
// FilterWithDeps to include dependencies from filter query
|
||||
FilterWithDeps bool
|
||||
// SkipComponentCheck skips component list verification
|
||||
SkipComponentCheck bool
|
||||
// Status marks state of repository (being updated, no action)
|
||||
Status int
|
||||
// WorkerPID is PID of the process modifying the mirror (if any)
|
||||
WorkerPID int
|
||||
// "Snapshot" of current list of packages
|
||||
packageRefs *PackageRefList
|
||||
// Temporary list of package refs
|
||||
tempPackageRefs *PackageRefList
|
||||
// Parsed archived root
|
||||
archiveRootURL *url.URL
|
||||
// Current list of packages (filled while updating mirror)
|
||||
packageList *PackageList
|
||||
}
|
||||
|
||||
// NewRemoteRepo creates new instance of Debian remote repository with specified params
|
||||
func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string,
|
||||
architectures []string, downloadSources bool) (*RemoteRepo, error) {
|
||||
architectures []string, downloadSources bool, downloadUdebs bool) (*RemoteRepo, error) {
|
||||
result := &RemoteRepo{
|
||||
UUID: uuid.New(),
|
||||
Name: name,
|
||||
@@ -64,6 +85,7 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone
|
||||
Components: components,
|
||||
Architectures: architectures,
|
||||
DownloadSources: downloadSources,
|
||||
DownloadUdebs: downloadUdebs,
|
||||
}
|
||||
|
||||
err := result.prepare()
|
||||
@@ -80,6 +102,9 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone
|
||||
if len(result.Components) > 0 {
|
||||
return nil, fmt.Errorf("components aren't supported for flat repos")
|
||||
}
|
||||
if result.DownloadUdebs {
|
||||
return nil, fmt.Errorf("debian-installer udebs aren't supported for flat repos")
|
||||
}
|
||||
result.Components = nil
|
||||
}
|
||||
|
||||
@@ -102,7 +127,10 @@ func (repo *RemoteRepo) prepare() error {
|
||||
func (repo *RemoteRepo) String() string {
|
||||
srcFlag := ""
|
||||
if repo.DownloadSources {
|
||||
srcFlag = " [src]"
|
||||
srcFlag += " [src]"
|
||||
}
|
||||
if repo.DownloadUdebs {
|
||||
srcFlag += " [udeb]"
|
||||
}
|
||||
distribution := repo.Distribution
|
||||
if distribution == "" {
|
||||
@@ -131,6 +159,37 @@ func (repo *RemoteRepo) RefList() *PackageRefList {
|
||||
return repo.packageRefs
|
||||
}
|
||||
|
||||
// MarkAsUpdating puts current PID and sets status to updating
|
||||
func (repo *RemoteRepo) MarkAsUpdating() {
|
||||
repo.Status = MirrorUpdating
|
||||
repo.WorkerPID = os.Getpid()
|
||||
}
|
||||
|
||||
// MarkAsIdle clears updating flag
|
||||
func (repo *RemoteRepo) MarkAsIdle() {
|
||||
repo.Status = MirrorIdle
|
||||
repo.WorkerPID = 0
|
||||
}
|
||||
|
||||
// CheckLock returns error if mirror is being updated by another process
|
||||
func (repo *RemoteRepo) CheckLock() error {
|
||||
if repo.Status == MirrorIdle || repo.WorkerPID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := os.FindProcess(repo.WorkerPID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = p.Signal(syscall.Signal(0))
|
||||
if err == nil {
|
||||
return fmt.Errorf("mirror is locked by update operation, PID %d", repo.WorkerPID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseURL returns URL to Release* files in repo root
|
||||
func (repo *RemoteRepo) ReleaseURL(name string) *url.URL {
|
||||
var path *url.URL
|
||||
@@ -169,6 +228,13 @@ func (repo *RemoteRepo) SourcesURL(component string) *url.URL {
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// UdebURL returns URL of Packages files for given component and
|
||||
// architecture
|
||||
func (repo *RemoteRepo) UdebURL(component string, architecture string) *url.URL {
|
||||
path := &url.URL{Path: fmt.Sprintf("dists/%s/%s/debian-installer/binary-%s/Packages", repo.Distribution, component, architecture)}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// PackageURL returns URL of package file relative to repository root
|
||||
// architecture
|
||||
func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
|
||||
@@ -245,6 +311,9 @@ ok:
|
||||
|
||||
if !repo.IsFlat() {
|
||||
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 {
|
||||
repo.Architectures = architectures
|
||||
} else {
|
||||
@@ -256,14 +325,19 @@ ok:
|
||||
}
|
||||
|
||||
components := strings.Split(stanza["Components"], " ")
|
||||
for i := range components {
|
||||
components[i] = path.Base(components[i])
|
||||
if strings.Contains(repo.Distribution, "/") {
|
||||
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 {
|
||||
repo.Components = components
|
||||
} else {
|
||||
} else if !repo.SkipComponentCheck {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -318,17 +392,20 @@ ok:
|
||||
return err
|
||||
}
|
||||
|
||||
delete(stanza, "SHA512")
|
||||
|
||||
repo.Meta = stanza
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download downloads all repo files
|
||||
func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory,
|
||||
packagePool aptly.PackagePool, ignoreMismatch bool, dependencyOptions int, filterQuery PackageQuery) error {
|
||||
list := NewPackageList()
|
||||
|
||||
progress.Printf("Downloading & parsing package files...\n")
|
||||
// DownloadPackageIndexes downloads & parses package index files
|
||||
func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory,
|
||||
ignoreMismatch bool) error {
|
||||
if repo.packageList != nil {
|
||||
panic("packageList != nil")
|
||||
}
|
||||
repo.packageList = NewPackageList()
|
||||
|
||||
// Download and parse all Packages & Source files
|
||||
packagesURLs := [][]string{}
|
||||
@@ -342,6 +419,9 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co
|
||||
for _, component := range repo.Components {
|
||||
for _, architecture := range repo.Architectures {
|
||||
packagesURLs = append(packagesURLs, []string{repo.BinaryURL(component, architecture).String(), "binary"})
|
||||
if repo.DownloadUdebs {
|
||||
packagesURLs = append(packagesURLs, []string{repo.UdebURL(component, architecture).String(), "udeb"})
|
||||
}
|
||||
}
|
||||
if repo.DownloadSources {
|
||||
packagesURLs = append(packagesURLs, []string{repo.SourcesURL(component).String(), "source"})
|
||||
@@ -378,15 +458,21 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co
|
||||
|
||||
if kind == "binary" {
|
||||
p = NewPackageFromControlFile(stanza)
|
||||
} else if kind == "udeb" {
|
||||
p = NewUdebPackageFromControlFile(stanza)
|
||||
} else if kind == "source" {
|
||||
p, err = NewSourcePackageFromControlFile(stanza)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = list.Add(p)
|
||||
err = repo.packageList.Add(p)
|
||||
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)
|
||||
@@ -398,33 +484,30 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co
|
||||
progress.ShutdownBar()
|
||||
}
|
||||
|
||||
var err error
|
||||
return nil
|
||||
}
|
||||
|
||||
if repo.Filter != "" {
|
||||
progress.Printf("Applying filter...\n")
|
||||
// ApplyFilter applies filtering to already built PackageList
|
||||
func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQuery) (oldLen, newLen int, err error) {
|
||||
repo.packageList.PrepareIndex()
|
||||
|
||||
list.PrepareIndex()
|
||||
emptyList := NewPackageList()
|
||||
emptyList.PrepareIndex()
|
||||
|
||||
emptyList := NewPackageList()
|
||||
emptyList.PrepareIndex()
|
||||
|
||||
origPackages := list.Len()
|
||||
list, err = list.Filter([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
progress.Printf("Packages filtered: %d -> %d.\n", origPackages, list.Len())
|
||||
oldLen = repo.packageList.Len()
|
||||
repo.packageList, err = repo.packageList.Filter([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures)
|
||||
if repo.packageList != nil {
|
||||
newLen = repo.packageList.Len()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
progress.Printf("Building download queue...\n")
|
||||
// BuildDownloadQueue builds queue, discards current PackageList
|
||||
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool) (queue []PackageDownloadTask, downloadSize int64, err error) {
|
||||
queue = make([]PackageDownloadTask, 0, repo.packageList.Len())
|
||||
seen := make(map[string]struct{}, repo.packageList.Len())
|
||||
|
||||
// Build download queue
|
||||
queued := make(map[string]PackageDownloadTask, list.Len())
|
||||
count := 0
|
||||
downloadSize := int64(0)
|
||||
|
||||
err = list.ForEach(func(p *Package) error {
|
||||
err = repo.packageList.ForEach(func(p *Package) error {
|
||||
list, err2 := p.DownloadList(packagePool)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
@@ -433,58 +516,31 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co
|
||||
|
||||
for _, task := range list {
|
||||
key := task.RepoURI + "-" + task.DestinationPath
|
||||
_, found := queued[key]
|
||||
_, found := seen[key]
|
||||
if !found {
|
||||
count++
|
||||
queue = append(queue, task)
|
||||
downloadSize += task.Checksums.Size
|
||||
queued[key] = task
|
||||
seen[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build download queue: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
repo.packageRefs = NewPackageRefListFromPackageList(list)
|
||||
repo.tempPackageRefs = NewPackageRefListFromPackageList(repo.packageList)
|
||||
// free up package list, we don't need it after this point
|
||||
list = nil
|
||||
repo.packageList = nil
|
||||
|
||||
progress.Printf("Download queue: %d items (%s)\n", count, utils.HumanBytes(downloadSize))
|
||||
|
||||
progress.InitBar(downloadSize, true)
|
||||
|
||||
// Download all package files
|
||||
ch := make(chan error, len(queued))
|
||||
|
||||
for _, task := range queued {
|
||||
d.DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch)
|
||||
}
|
||||
|
||||
// We don't need queued after this point
|
||||
queued = nil
|
||||
|
||||
// Wait for all downloads to finish
|
||||
errors := make([]string, 0)
|
||||
|
||||
for count > 0 {
|
||||
err = <-ch
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
count--
|
||||
}
|
||||
|
||||
progress.ShutdownBar()
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("download errors:\n %s\n", strings.Join(errors, "\n "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FinalizeDownload swaps for final value of package refs
|
||||
func (repo *RemoteRepo) FinalizeDownload() {
|
||||
repo.LastDownloadDate = time.Now()
|
||||
|
||||
return nil
|
||||
repo.packageRefs = repo.tempPackageRefs
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of RemoteRepo
|
||||
@@ -502,7 +558,43 @@ func (repo *RemoteRepo) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
err := decoder.Decode(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
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
|
||||
var repo11 struct {
|
||||
UUID string
|
||||
Name string
|
||||
ArchiveRoot string
|
||||
Distribution string
|
||||
Components []string
|
||||
Architectures []string
|
||||
DownloadSources bool
|
||||
Meta Stanza
|
||||
LastDownloadDate []byte
|
||||
ReleaseFiles map[string]utils.ChecksumInfo
|
||||
Filter string
|
||||
FilterWithDeps bool
|
||||
}
|
||||
|
||||
decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
err2 := decoder.Decode(&repo11)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.UUID = repo11.UUID
|
||||
repo.Name = repo11.Name
|
||||
repo.ArchiveRoot = repo11.ArchiveRoot
|
||||
repo.Distribution = repo11.Distribution
|
||||
repo.Components = repo11.Components
|
||||
repo.Architectures = repo11.Architectures
|
||||
repo.DownloadSources = repo11.DownloadSources
|
||||
repo.Meta = repo11.Meta
|
||||
repo.ReleaseFiles = repo11.ReleaseFiles
|
||||
repo.Filter = repo11.Filter
|
||||
repo.FilterWithDeps = repo11.FilterWithDeps
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return repo.prepare()
|
||||
}
|
||||
@@ -519,6 +611,7 @@ func (repo *RemoteRepo) RefKey() []byte {
|
||||
|
||||
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
||||
type RemoteRepoCollection struct {
|
||||
*sync.RWMutex
|
||||
db database.Storage
|
||||
list []*RemoteRepo
|
||||
}
|
||||
@@ -526,7 +619,8 @@ type RemoteRepoCollection struct {
|
||||
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
||||
func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
|
||||
result := &RemoteRepoCollection{
|
||||
db: db,
|
||||
RWMutex: &sync.RWMutex{},
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("R"))
|
||||
|
||||
+85
-67
@@ -10,8 +10,10 @@ import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type NullVerifier struct {
|
||||
@@ -79,8 +81,8 @@ type RemoteRepoSuite struct {
|
||||
var _ = Suite(&RemoteRepoSuite{})
|
||||
|
||||
func (s *RemoteRepoSuite) SetUpTest(c *C) {
|
||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false)
|
||||
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false)
|
||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false)
|
||||
s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
|
||||
s.progress = console.NewProgress()
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
@@ -96,7 +98,7 @@ func (s *RemoteRepoSuite) TearDownTest(c *C) {
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestInvalidURL(c *C) {
|
||||
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false)
|
||||
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
c.Assert(err, ErrorMatches, ".*hexadecimal escape in host.*")
|
||||
}
|
||||
|
||||
@@ -106,11 +108,11 @@ func (s *RemoteRepoSuite) TestFlatCreation(c *C) {
|
||||
c.Check(s.flat.Architectures, IsNil)
|
||||
c.Check(s.flat.Components, IsNil)
|
||||
|
||||
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false)
|
||||
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false)
|
||||
c.Check(flat2.IsFlat(), Equals, true)
|
||||
c.Check(flat2.Distribution, Equals, "./binary/")
|
||||
|
||||
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false)
|
||||
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false, false)
|
||||
c.Check(err, ErrorMatches, "components aren't supported for flat repos")
|
||||
}
|
||||
|
||||
@@ -119,8 +121,9 @@ func (s *RemoteRepoSuite) TestString(c *C) {
|
||||
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./")
|
||||
|
||||
s.repo.DownloadSources = true
|
||||
s.repo.DownloadUdebs = true
|
||||
s.flat.DownloadSources = true
|
||||
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src]")
|
||||
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb]")
|
||||
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./ [src]")
|
||||
}
|
||||
|
||||
@@ -151,6 +154,10 @@ func (s *RemoteRepoSuite) TestBinaryURL(c *C) {
|
||||
c.Assert(s.repo.BinaryURL("main", "amd64").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/binary-amd64/Packages")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestUdebURL(c *C) {
|
||||
c.Assert(s.repo.UdebURL("main", "amd64").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/debian-installer/binary-amd64/Packages")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestSourcesURL(c *C) {
|
||||
c.Assert(s.repo.SourcesURL("main").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources")
|
||||
}
|
||||
@@ -186,7 +193,7 @@ func (s *RemoteRepoSuite) TestFetch(c *C) {
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchNullVerifier1(c *C) {
|
||||
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.gpg", "GPG")
|
||||
|
||||
@@ -209,13 +216,13 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) {
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) {
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false)
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false)
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) {
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false)
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false)
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, ErrorMatches, "component xyz not available in repo.*")
|
||||
}
|
||||
@@ -246,23 +253,25 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
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.gz", 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", &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/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
|
||||
err = s.repo.Download(s.progress, s.downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool)
|
||||
c.Check(size, Equals, int64(3))
|
||||
c.Check(queue, HasLen, 1)
|
||||
c.Check(queue[0].RepoURI, Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb")
|
||||
|
||||
s.repo.FinalizeDownload()
|
||||
c.Assert(s.repo.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
}
|
||||
|
||||
@@ -273,64 +282,69 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
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.gz", 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", &http.HTTPError{Code: 404})
|
||||
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.gz", 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", &http.HTTPError{Code: 404})
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile)
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc", "abc")
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz", "abcd")
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz", "abcde")
|
||||
|
||||
err = s.repo.Download(s.progress, s.downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool)
|
||||
c.Check(size, Equals, int64(15))
|
||||
c.Check(queue, HasLen, 4)
|
||||
|
||||
q := make([]string, 4)
|
||||
for i := range q {
|
||||
q[i] = queue[i].RepoURI
|
||||
}
|
||||
sort.Strings(q)
|
||||
c.Check(q[3], Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb")
|
||||
c.Check(q[1], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc")
|
||||
c.Check(q[2], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz")
|
||||
c.Check(q[0], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz")
|
||||
|
||||
s.repo.FinalizeDownload()
|
||||
c.Assert(s.repo.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
|
||||
pkg, err = s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[1])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err = pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "access-modifier-checker")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
||||
downloader := http.NewFakeDownloader()
|
||||
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.gz", 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", &http.HTTPError{Code: 404})
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile)
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
|
||||
err := s.flat.Fetch(downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.flat.Download(s.progress, downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool)
|
||||
c.Check(size, Equals, int64(3))
|
||||
c.Check(queue, HasLen, 1)
|
||||
c.Check(queue[0].RepoURI, Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb")
|
||||
|
||||
s.flat.FinalizeDownload()
|
||||
c.Assert(s.flat.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
}
|
||||
|
||||
@@ -339,41 +353,45 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
||||
|
||||
downloader := http.NewFakeDownloader()
|
||||
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.gz", 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", &http.HTTPError{Code: 404})
|
||||
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.gz", 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", &http.HTTPError{Code: 404})
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile)
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc", "abc")
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz", "abcd")
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz", "abcde")
|
||||
|
||||
err := s.flat.Fetch(downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.flat.Download(s.progress, downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool)
|
||||
c.Check(size, Equals, int64(15))
|
||||
c.Check(queue, HasLen, 4)
|
||||
|
||||
q := make([]string, 4)
|
||||
for i := range q {
|
||||
q[i] = queue[i].RepoURI
|
||||
}
|
||||
sort.Strings(q)
|
||||
c.Check(q[3], Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb")
|
||||
c.Check(q[1], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc")
|
||||
c.Check(q[2], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz")
|
||||
c.Check(q[0], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz")
|
||||
|
||||
s.flat.FinalizeDownload()
|
||||
c.Assert(s.flat.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
|
||||
pkg, err = s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[1])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err = pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "access-modifier-checker")
|
||||
}
|
||||
|
||||
@@ -399,7 +417,7 @@ func (s *RemoteRepoCollectionSuite) TestAddByName(c *C) {
|
||||
r, err := s.collection.ByName("yandex")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
c.Assert(s.collection.Add(repo), IsNil)
|
||||
c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists")
|
||||
|
||||
@@ -417,7 +435,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
||||
r, err := s.collection.ByUUID("some-uuid")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
c.Assert(s.collection.Add(repo), IsNil)
|
||||
|
||||
r, err = s.collection.ByUUID(repo.UUID)
|
||||
@@ -426,7 +444,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
||||
}
|
||||
|
||||
func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
c.Assert(s.collection.Update(repo), IsNil)
|
||||
|
||||
collection := NewRemoteRepoCollection(s.db)
|
||||
@@ -447,7 +465,7 @@ func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
}
|
||||
|
||||
func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
s.collection.Add(repo)
|
||||
|
||||
count := 0
|
||||
@@ -469,10 +487,10 @@ func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||
}
|
||||
|
||||
func (s *RemoteRepoCollectionSuite) TestDrop(c *C) {
|
||||
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false)
|
||||
s.collection.Add(repo1)
|
||||
|
||||
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false)
|
||||
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false, false)
|
||||
s.collection.Add(repo2)
|
||||
|
||||
r1, _ := s.collection.ByUUID(repo1.UUID)
|
||||
|
||||
+108
-5
@@ -9,21 +9,24 @@ import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/ugorji/go/codec"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Snapshot is immutable state of repository: list of packages
|
||||
type Snapshot struct {
|
||||
// Persisten internal ID
|
||||
UUID string
|
||||
UUID string `json:"-"`
|
||||
// Human-readable name
|
||||
Name string
|
||||
// Date of creation
|
||||
CreatedAt time.Time
|
||||
|
||||
// Source: kind + ID
|
||||
SourceKind string
|
||||
SourceIDs []string
|
||||
SourceKind string `json:"-"`
|
||||
SourceIDs []string `json:"-"`
|
||||
// Description of how snapshot was created
|
||||
Description string
|
||||
|
||||
@@ -125,11 +128,41 @@ func (s *Snapshot) Encode() []byte {
|
||||
// Decode decodes msgpack representation into Snapshot
|
||||
func (s *Snapshot) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
return decoder.Decode(s)
|
||||
err := decoder.Decode(s)
|
||||
if err != nil {
|
||||
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
|
||||
var snapshot11 struct {
|
||||
UUID string
|
||||
Name string
|
||||
CreatedAt []byte
|
||||
|
||||
SourceKind string
|
||||
SourceIDs []string
|
||||
Description string
|
||||
}
|
||||
|
||||
decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
err2 := decoder.Decode(&snapshot11)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.UUID = snapshot11.UUID
|
||||
s.Name = snapshot11.Name
|
||||
s.SourceKind = snapshot11.SourceKind
|
||||
s.SourceIDs = snapshot11.SourceIDs
|
||||
s.Description = snapshot11.Description
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SnapshotCollection does listing, updating/adding/deleting of Snapshots
|
||||
type SnapshotCollection struct {
|
||||
*sync.RWMutex
|
||||
db database.Storage
|
||||
list []*Snapshot
|
||||
}
|
||||
@@ -137,7 +170,8 @@ type SnapshotCollection struct {
|
||||
// NewSnapshotCollection loads Snapshots from DB and makes up collection
|
||||
func NewSnapshotCollection(db database.Storage) *SnapshotCollection {
|
||||
result := &SnapshotCollection{
|
||||
db: db,
|
||||
RWMutex: &sync.RWMutex{},
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("S"))
|
||||
@@ -263,6 +297,23 @@ func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) 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
|
||||
// ForEach runs method for each snapshot
|
||||
func (collection *SnapshotCollection) Len() int {
|
||||
@@ -294,3 +345,55 @@ func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user