mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-31 04:30:44 +00:00
Compare commits
747 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 219315c01d | |||
| 62f44e53fd | |||
| b25f8e438c | |||
| f14fce01e9 | |||
| a790770a19 | |||
| 7bb052ac37 | |||
| 631fe44c6b | |||
| ca319c804e | |||
| 3e368690fd | |||
| 339bf0a90b | |||
| c5b48f0362 | |||
| d5f50732c1 | |||
| 9d973aeceb | |||
| 7caeac7515 | |||
| 7f6a52019f | |||
| 16101b56fe | |||
| a294a91685 | |||
| cf644289a3 | |||
| 33c905ce02 | |||
| 8fdc222196 | |||
| 1e4d825d36 | |||
| 76bf7cba04 | |||
| 08bc5ac934 | |||
| c160cbccc7 | |||
| c473a5cba8 | |||
| 84801bce78 | |||
| b95b3473bf | |||
| f1d5caab8b | |||
| 6a973554ad | |||
| 698e239f45 | |||
| 205297d0b8 | |||
| ba4669a9c4 | |||
| 8bda799545 | |||
| 6c28e3aca8 | |||
| 901babe500 | |||
| 0c6f38ab08 | |||
| a131d6093c | |||
| 974cec3e73 | |||
| 442c5f090f | |||
| d04f08c1cf | |||
| 767c7ca0db | |||
| dd27aad751 | |||
| ddfdeaf2d0 | |||
| 4c51350517 | |||
| 40e48c963a | |||
| c44d347540 | |||
| 4a54bff225 | |||
| e39736153d | |||
| f032196d70 | |||
| a030e24b96 | |||
| c2993c6691 | |||
| 7d4a70ba25 | |||
| 38dfe3435a | |||
| 313c71dff6 | |||
| a88d92436f | |||
| 9d298dee51 | |||
| 9abc772b16 | |||
| 2f1df39204 | |||
| 0f328ec1fe | |||
| 78b6d6ca7b | |||
| 5cd3c33854 | |||
| 9af76843b5 | |||
| 53506124a4 | |||
| 9bbf9c7b13 | |||
| 82e6e8242e | |||
| 2bf11a556c | |||
| c62828bf14 | |||
| b53cf7e710 | |||
| 780277d0a6 | |||
| a6f5631542 | |||
| 52b1501ec0 | |||
| c9339f5cca | |||
| a9c23fb4aa | |||
| 72e3eaebfe | |||
| f3bcaa6cfb | |||
| 1c8f1517f8 | |||
| 50ae34cc19 | |||
| 8cc7d1345b | |||
| 0791c88a02 | |||
| ba08ffe38b | |||
| 1bec1e4dc4 | |||
| bcf8074f31 | |||
| 6a2d564eee | |||
| 709e14ecc1 | |||
| 5b1f446a6b | |||
| f41146c750 | |||
| d56ac81fd6 | |||
| fb213ef6eb | |||
| 933b019f71 | |||
| 6293ca3206 | |||
| d46d8de5f7 | |||
| 4e3284cd98 | |||
| 10876b99f5 | |||
| 61d31ce7c0 | |||
| e0f284d68f | |||
| df887d871b | |||
| 99f6ffe1ca | |||
| 138f9f7994 | |||
| 3886db9d4f | |||
| b877e06a02 | |||
| 38f4fc209b | |||
| b223acdecb | |||
| cc8a87b448 | |||
| ee3d414ed5 | |||
| d791aa0f15 | |||
| 393ae8adbd | |||
| 7037c6be7e | |||
| c10645f4f2 | |||
| 27da1015af | |||
| 78b0fe0e90 | |||
| 4651e41247 | |||
| a6c40f3193 | |||
| 3e138fd6db | |||
| 3c20b5472e | |||
| 8b782ce370 | |||
| a160a39d53 | |||
| 1c4b44e772 | |||
| b4b03f2752 | |||
| 1d21d3cfeb | |||
| d2ce33e66a | |||
| f0fbb8259b | |||
| 962c4a842d | |||
| 54e21afee7 | |||
| cc3f5149c6 | |||
| c8713aa412 | |||
| 02a82f3545 | |||
| c573746896 | |||
| 813b9593fa | |||
| bc68513708 | |||
| c4692bec3d | |||
| c53060d95a | |||
| 22c656d18e | |||
| 4d622e467c | |||
| 36326788b0 | |||
| 782ac1a36a | |||
| 8ca07d9acd | |||
| 4a57fe3c39 | |||
| 7579f1998c | |||
| 67a31d5eaa | |||
| 5b9d287b62 | |||
| 775670181c | |||
| 2a3bd5546a | |||
| 197e230ef1 | |||
| c6eeac11a4 | |||
| 90d3b623b4 | |||
| a59c2ac859 | |||
| 103fa5310f | |||
| 71b7de7a63 | |||
| a937ebc744 | |||
| 925882b253 | |||
| 615a5ee3f9 | |||
| 4a6d6a85f7 | |||
| 2937435960 | |||
| 2f3b5f5a51 | |||
| 5b4563f250 | |||
| 5da4bde428 | |||
| 42c4644be3 | |||
| 1845c493f4 | |||
| 8a0f754fe2 | |||
| 77bb4d423d | |||
| 1d483dc817 | |||
| a7103623af | |||
| 903e999cdc | |||
| 69eff97b34 | |||
| 8e20daa927 | |||
| 9e39dbf81e | |||
| 7a4feebe6f | |||
| 1d1561c6c3 | |||
| 9a5b3aeedc | |||
| ed931e7ed4 | |||
| 5ff9cecc5a | |||
| f8bca463bb | |||
| d5c6f0b623 | |||
| 7e57f443ed | |||
| b4cf2e7065 | |||
| 2ceabb69e6 | |||
| aa9d3360ba | |||
| 4580a64192 | |||
| 4cb0526980 | |||
| 03e2a8d558 | |||
| ab09cbfe3c | |||
| 0467e0c929 | |||
| 6e1c9afdd9 | |||
| 4b3b961b69 | |||
| e63adffdf5 | |||
| d00659b0cb | |||
| 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 | |||
| 625a38c578 | |||
| 03a79ebe4c | |||
| 60fa0aa68e | |||
| 04bd9929e1 | |||
| 8407e70347 | |||
| bf91744078 | |||
| 2c470c1535 | |||
| a18011bdc0 | |||
| af8af0f3d7 | |||
| 89d26b7dc6 | |||
| 8649ee3b37 | |||
| b9c8a8d9da | |||
| c5922737ed | |||
| 772111ad26 | |||
| d7ef1a0c4b | |||
| bd221bf869 | |||
| 0485a36de1 | |||
| 77d6a10984 | |||
| 8015966663 | |||
| 94114f2c3d | |||
| 2906369a3b | |||
| 521c52f600 | |||
| 52bb33dc69 | |||
| 71d90947c9 | |||
| b3a4936e06 | |||
| 237d25fe5b | |||
| de0954732a | |||
| 915b0d1697 | |||
| 6d026afc69 | |||
| 27a5578d30 | |||
| 96e878a2e0 | |||
| 7a7bb56557 | |||
| 076ecd586f | |||
| c54406e29f | |||
| b260b0010a | |||
| fbf1bc14b7 | |||
| f12cf935ba | |||
| 4e169c3d10 | |||
| ea2bfea2a3 | |||
| cf4619784e | |||
| 69ad2ccd84 | |||
| fe1046a7a3 | |||
| ce1df9447d | |||
| 2a7a2de84a | |||
| 238bdfad96 | |||
| 56d777af0a | |||
| a632469890 | |||
| 3601cc15ed | |||
| 61cd4c6af1 | |||
| 401bb768d7 | |||
| 5880d11899 | |||
| ed6e261bd0 | |||
| fb660efeb5 | |||
| 80de65f28d | |||
| 9893e4af3d | |||
| 7416cc403d | |||
| 83ceee1e3f | |||
| a54a366c95 | |||
| fb1e28b91b | |||
| 86206df58d | |||
| 1d49a717b9 | |||
| 3b0b0b76ec | |||
| 904b9e101b | |||
| 9fb8a0ea4b | |||
| bc27c6e14d | |||
| ae3c98c210 | |||
| 34f545b8cf | |||
| d523d2b415 | |||
| e320ac31d5 | |||
| e08d44ff0a | |||
| 898870038a | |||
| c485cf41f7 | |||
| d54ef1e921 | |||
| b42fd71acf | |||
| ede5449440 | |||
| eef49516ef | |||
| e745747370 | |||
| 7e5b2ae8f5 | |||
| ada3ae0094 | |||
| d262a131cc | |||
| f0e69144ed | |||
| a7cb40ee7a | |||
| 2a9b2f87f9 | |||
| 9af10bc422 | |||
| bdbb5acb11 | |||
| 81d506b226 | |||
| 1c30b2b9de | |||
| 566604d4ba | |||
| 58a57f2b2c | |||
| 20d744f398 | |||
| 360981de4a | |||
| 79016f7f98 | |||
| 1a92d8bfe9 | |||
| d3707b4cfe | |||
| de1fa85127 | |||
| d9b35cea01 | |||
| b75b4d1488 | |||
| da55f18b0e | |||
| 165dd0053e | |||
| 22a4e6b67b | |||
| 20513e1c16 | |||
| b4ea963744 | |||
| 429788db0f | |||
| 1e70e954da | |||
| 319f3e6bb2 | |||
| 56915c4357 | |||
| e1348ab88f | |||
| 026dc540d2 | |||
| 44ce4c8a77 | |||
| 980102462b | |||
| 86b0860463 | |||
| e311d41dd7 | |||
| c3ce886990 | |||
| 959ecf696c | |||
| 48d01f5700 | |||
| aeecc1ec91 | |||
| 685a4de4e7 | |||
| 667efc2b90 | |||
| 3cf281965b | |||
| e19a615641 | |||
| ff77fbf5d9 | |||
| 856dd7021c | |||
| ebc47f7d5d | |||
| 082fda62b5 | |||
| 3199fd85fb | |||
| 0c6951fcd2 | |||
| 35e57026ac | |||
| 28e050c14e | |||
| 8fb399026d | |||
| e15f23962a | |||
| 81af5882b9 | |||
| e554d8befa | |||
| 17c564358a | |||
| 2e4c1c491e | |||
| e7230d9ee6 | |||
| 0f1074a721 | |||
| 17ed34fdaa | |||
| 17b320eac4 | |||
| e3a71c81e1 | |||
| bf900deb4b | |||
| 142387311b | |||
| 68fbb0cbb9 | |||
| 835da9cb3c | |||
| 7cd0d394d4 | |||
| 2040be2f8a | |||
| 1957c811e8 | |||
| 2dae9b01a1 | |||
| 9a34b4ff1f | |||
| d218159455 | |||
| 8be6911238 | |||
| 20a7c5ae2d | |||
| e161313efa | |||
| 7192049c16 | |||
| ee71b93669 | |||
| 43ee735aa4 | |||
| da5b0c9a66 | |||
| e1dbab6988 | |||
| bcdfb7d99a | |||
| ac85a0897a | |||
| 9a4543500c | |||
| b0f9a4a419 | |||
| 71ea2be6c1 | |||
| b717caeda4 | |||
| fcc283bdb1 | |||
| d3d41dd1c9 | |||
| c72ef05a2a | |||
| a1e360b07b | |||
| 90bba977d7 | |||
| dc248c5603 | |||
| f007465d18 | |||
| 869e83713d | |||
| 7b9e3429fd | |||
| 5b75dbc481 | |||
| d96839f99d | |||
| 8b2920d5dd | |||
| 4240b134e6 | |||
| 1d31a5c25f | |||
| 05a42f4cba | |||
| 2cbb486f6b | |||
| d0ff11390b |
+24
-4
@@ -1,20 +1,40 @@
|
||||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2.1
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- python-virtualenv
|
||||
- graphviz
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc="
|
||||
|
||||
- secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E="
|
||||
- secure: "OxiVNmre2JzUszwPNNilKDgIqtfX2gnRSsVz6nuySB1uO2yQsOQmKWJ9cVYgH2IB5H8eWXKOhexcSE28kz6TPLRuEcU9fnqKY3uEkdwm7rJfz9lf+7C4bJEUdA1OIzJppjnWUiXxD7CEPL1DlnMZM24eDQYqa/4WKACAgkK53gE="
|
||||
before_install:
|
||||
- virtualenv env
|
||||
- . env/bin/activate
|
||||
- pip install boto requests python-swiftclient
|
||||
install:
|
||||
- make prepare
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
List of contributors, in chronological order:
|
||||
|
||||
* Andrey Smirnov (https://github.com/smira)
|
||||
* Sebastien Binet (https://github.com/sbinet)
|
||||
* Ryan Uber (https://github.com/ryanuber)
|
||||
* Simon Aquino (https://github.com/queeno)
|
||||
* Vincent Batoufflet (https://github.com/vbatoufflet)
|
||||
* Ivan Kurnosov (https://github.com/zerkms)
|
||||
* Dmitrii Kashin (https://github.com/freehck)
|
||||
* Chris Read (https://github.com/cread)
|
||||
* Rohan Garg (https://github.com/shadeslayer)
|
||||
* Russ Allbery (https://github.com/rra)
|
||||
* Sylvain Baubeau (https://github.com/lebauce)
|
||||
* Andrea Bernardo Ciddio (https://github.com/bcandrea)
|
||||
* Michael Koval (https://github.com/mkoval)
|
||||
* Alexander Guy (https://github.com/alexanderguy)
|
||||
* Sebastien Badia (https://github.com/sbadia)
|
||||
* Szymon Sobik (https://github.com/sobczyk)
|
||||
* Paul Krohn (https://github.com/paul-krohn)
|
||||
* Vincent Bernat (https://github.com/vincentbernat)
|
||||
* x539 (https://github.com/x539)
|
||||
@@ -1,22 +1,35 @@
|
||||
gom 'code.google.com/p/go-uuid/uuid', :commit => '5fac954758f5'
|
||||
gom 'code.google.com/p/go.crypto/ssh/terminal', :commit => '7aa593ce8cea'
|
||||
gom 'code.google.com/p/gographviz', :commit => '212766062629'
|
||||
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
|
||||
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827'
|
||||
gom 'github.com/smira/commander', :commit => '082a3ce267a8225a8ccf94deaf18901223d38fed'
|
||||
gom 'github.com/smira/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841'
|
||||
gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b'
|
||||
gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'ff3719c6816e2cd194f05058452d660608e178ac'
|
||||
gom 'github.com/AlekSi/pointer', :commit => '5f6d527dae3d678b46fbb20331ddf44e2b841943'
|
||||
gom 'github.com/awalterschulze/gographviz', :commit => '20d1f693416d9be045340150094aa42035a41c9e'
|
||||
gom 'github.com/cheggaaa/pb', :commit => '2c1b74620cc58a81ac152ee2d322e28c806d81ed'
|
||||
gom 'github.com/DisposaBoy/JsonConfigReader', :commit => '33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4'
|
||||
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 => 'caaaea8b30ee15616494ee68abd5d8ebbbef05cf'
|
||||
gom 'github.com/mkrautz/goar', :commit => '282caa8bd9daba480b51f1d5a988714913b97aad'
|
||||
gom 'github.com/mxk/go-flowrate/flowrate', :commit => 'cca7078d478f8520f85629ad7c68962d31ed7682'
|
||||
gom 'github.com/ncw/swift', :commit => '384ef27c70645e285f8bb9d02276bf654d06027e'
|
||||
gom 'github.com/smira/go-xz', :commit => '0c531f070014e218b21f3cfca801cc992d52726d'
|
||||
gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47'
|
||||
gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107'
|
||||
gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa'
|
||||
gom 'github.com/smira/go-uuid/uuid', :commit => 'ed3ca8a15a931b141440a7e98e4f716eec255f7d'
|
||||
gom 'github.com/smira/lzma', :commit => '2a7c55cad4a2d02ab972a03357db5760833a49bc'
|
||||
gom 'github.com/golang/snappy', :commit => '723cc1e459b8eea2dea4583200fd60757d40097a'
|
||||
gom 'github.com/syndtr/goleveldb/leveldb', :commit => '1a9d62f03ea92815b46fcaab357cfd4df264b1a0'
|
||||
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,4 +1,4 @@
|
||||
Copyright 2013-2014 Andrey Smirnov. All rights reserved.
|
||||
Copyright 2013-2015 aptly authors. All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
GOVERSION=$(shell go version | awk '{print $$3;}')
|
||||
PACKAGES=database deb files http utils
|
||||
ALL_PACKAGES=aptly cmd console database deb files http 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 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
|
||||
|
||||
@@ -69,7 +67,8 @@ package:
|
||||
(cd root/etc/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/master/aptly)
|
||||
gzip root/usr/share/man/man1/aptly.1
|
||||
fpm -s dir -t deb -n aptly -v $(VERSION) --url=http://www.aptly.info/ --license=MIT --vendor="Andrey Smirnov <me@smira.ru>" \
|
||||
-f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" -C root/ .
|
||||
-f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" --deb-recommends bzip2 \
|
||||
--deb-recommends graphviz --deb-recommends xz-utils -C root/ .
|
||||
mv aptly_$(VERSION)_*.deb ~
|
||||
|
||||
src-package:
|
||||
@@ -77,11 +76,12 @@ src-package:
|
||||
mkdir -p aptly-$(VERSION)/src/github.com/smira/aptly/
|
||||
cd aptly-$(VERSION)/src/github.com/smira/ && git clone https://github.com/smira/aptly && cd aptly && git checkout v$(VERSION)
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . -name .git -print | xargs rm -rf
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . -name .bzr -print | xargs rm -rf
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . -name .hg -print | xargs rm -rf
|
||||
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
|
||||
|
||||
+44
-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.4+ required)::
|
||||
|
||||
go get -u github.com/mattn/gom
|
||||
mkdir -p $GOPATH/src/github.com/smira/aptly
|
||||
@@ -65,4 +78,29 @@ should work as well, but might fail or produce different result (if external lib
|
||||
|
||||
If you don't have Go installed (or older version), you can easily install Go using `gvm <https://github.com/moovweb/gvm/>`_.
|
||||
|
||||
Integrations
|
||||
------------
|
||||
|
||||
Vagrant:
|
||||
|
||||
- `Vagrant configuration <https://github.com/sepulworld/aptly-vagrant>`_ by
|
||||
Zane Williamson, allowing to bring two virtual servers, one with aptly installed
|
||||
and another one set up to install packages from repository published by aptly
|
||||
|
||||
Docker:
|
||||
|
||||
- `Docker container <https://github.com/mikepurvis/aptly-docker>`_ with aptly inside by Mike Purvis
|
||||
|
||||
With configuration management systems:
|
||||
|
||||
- `Chef cookbook <https://github.com/hw-cookbooks/aptly>`_ by Aaron Baer
|
||||
(Heavy Water Operations, LLC)
|
||||
- `Puppet module <https://github.com/alphagov/puppet-aptly>`_ by
|
||||
Government Digital Services
|
||||
- `SaltStack Formula <https://github.com/saltstack-formulas/aptly-formula>`_ by
|
||||
Forrest Alvarez and Brian Jackson
|
||||
- `Ansible role <https://github.com/aioue/ansible-role-aptly>`_ by Tom Paine
|
||||
|
||||
CLI for aptly API:
|
||||
|
||||
- `Ruby aptly CLI/library <https://github.com/sepulworld/aptly_cli>`_ by Zane Williamson
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// 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})
|
||||
}
|
||||
|
||||
const (
|
||||
ACQUIREDB = iota
|
||||
RELEASEDB
|
||||
)
|
||||
|
||||
// Periodically flushes CollectionFactory to free up memory used by
|
||||
// collections, flushing caches. If the two channels are provided,
|
||||
// they are used to acquire and release the database.
|
||||
//
|
||||
// Should be run in goroutine!
|
||||
func cacheFlusher(requests chan int, acks chan error) {
|
||||
ticker := time.Tick(15 * time.Minute)
|
||||
|
||||
for {
|
||||
<-ticker
|
||||
|
||||
func() {
|
||||
// lock database if needed
|
||||
if requests != nil {
|
||||
requests <- ACQUIREDB
|
||||
err := <-acks
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
requests <- RELEASEDB
|
||||
<-acks
|
||||
}()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire database lock and release it when not needed anymore. Two
|
||||
// channels must be provided. The first one is to receive requests to
|
||||
// acquire/release the database and the second one is to send acks.
|
||||
//
|
||||
// Should be run in a goroutine!
|
||||
func acquireDatabase(requests chan int, acks chan error) {
|
||||
clients := 0
|
||||
for {
|
||||
request := <-requests
|
||||
switch request {
|
||||
case ACQUIREDB:
|
||||
if clients == 0 {
|
||||
acks <- context.ReOpenDatabase()
|
||||
} else {
|
||||
acks <- nil
|
||||
}
|
||||
clients++
|
||||
case RELEASEDB:
|
||||
clients--
|
||||
if clients == 0 {
|
||||
acks <- context.CloseDatabase()
|
||||
} else {
|
||||
acks <- nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
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) {
|
||||
force := c.Request.URL.Query().Get("force") == "1"
|
||||
|
||||
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(), force)
|
||||
if err != nil {
|
||||
c.Fail(500, fmt.Errorf("unable to drop: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{})
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
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 = deb.CollectPackageFiles(sources, reporter)
|
||||
|
||||
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, nil)
|
||||
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,
|
||||
})
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
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
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(gin.ErrorLogger())
|
||||
|
||||
if context.Flags().Lookup("no-lock").Value.Get().(bool) {
|
||||
// We use a goroutine to count the number of
|
||||
// concurrent requests. When no more requests are
|
||||
// running, we close the database to free the lock.
|
||||
requests := make(chan int)
|
||||
acks := make(chan error)
|
||||
|
||||
go acquireDatabase(requests, acks)
|
||||
go cacheFlusher(requests, acks)
|
||||
|
||||
router.Use(func(c *gin.Context) {
|
||||
requests <- ACQUIREDB
|
||||
err := <-acks
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
requests <- RELEASEDB
|
||||
err = <-acks
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
})
|
||||
|
||||
} else {
|
||||
go cacheFlusher(nil, nil)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
+17
-8
@@ -5,7 +5,6 @@ package aptly
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// PackagePool is asbtraction of package pool storage.
|
||||
@@ -26,26 +25,34 @@ type PackagePool interface {
|
||||
|
||||
// PublishedStorage is abstraction of filesystem storing all published repositories
|
||||
type PublishedStorage interface {
|
||||
// PublicPath returns root of public part
|
||||
PublicPath() string
|
||||
// MkDir creates directory recursively under public path
|
||||
MkDir(path string) error
|
||||
// CreateFile creates file for writing under public path
|
||||
CreateFile(path string) (*os.File, error)
|
||||
// PutFile puts file into published storage at specified path
|
||||
PutFile(path string, sourceFilename string) error
|
||||
// RemoveDirs removes directory structure under public path
|
||||
RemoveDirs(path string, progress Progress) error
|
||||
// Remove removes single file under public path
|
||||
Remove(path string) error
|
||||
// LinkFromPool links package file from pool to dist's pool location
|
||||
LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath string) error
|
||||
LinkFromPool(publishedDirectory string, sourcePool PackagePool, sourcePath, sourceMD5 string, force bool) error
|
||||
// Filelist returns list of files under prefix
|
||||
Filelist(prefix string) ([]string, error)
|
||||
// ChecksumsForFile proxies requests to utils.ChecksumsForFile, joining public path
|
||||
ChecksumsForFile(path string) (utils.ChecksumInfo, error)
|
||||
// RenameFile renames (moves) file
|
||||
RenameFile(oldName, newName string) error
|
||||
}
|
||||
|
||||
// LocalPublishedStorage is published storage on local filesystem
|
||||
type LocalPublishedStorage interface {
|
||||
// PublicPath returns root of public part
|
||||
PublicPath() string
|
||||
}
|
||||
|
||||
// PublishedStorageProvider is a thing that returns PublishedStorage by name
|
||||
type PublishedStorageProvider interface {
|
||||
// GetPublishedStorage returns PublishedStorage by name
|
||||
GetPublishedStorage(name string) PublishedStorage
|
||||
}
|
||||
|
||||
// Progress is a progress displaying entity, it allows progress bars & simple prints
|
||||
type Progress interface {
|
||||
// Writer interface to support progress bar ticking
|
||||
@@ -83,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.5.1"
|
||||
const Version = "0.9.6"
|
||||
|
||||
// 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,53 @@
|
||||
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")
|
||||
cmd.Flag.Bool("no-lock", false, "don't lock the database")
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
+47
-3
@@ -2,12 +2,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -34,6 +36,44 @@ func ListPackagesRefList(reflist *deb.PackageRefList) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// PrintPackageList shows package list with specified format or default representation
|
||||
func PrintPackageList(result *deb.PackageList, format string) error {
|
||||
if format == "" {
|
||||
return result.ForEach(func(p *deb.Package) error {
|
||||
context.Progress().Printf("%s\n", p)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
formatTemplate, err := template.New("format").Parse(format)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing -format template: %s", err)
|
||||
}
|
||||
|
||||
return result.ForEach(func(p *deb.Package) error {
|
||||
b := &bytes.Buffer{}
|
||||
err = formatTemplate.Execute(b, p.ExtendedStanza())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying template: %s", err)
|
||||
}
|
||||
context.Progress().Printf("%s\n", b.String())
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// LookupOption checks boolean flag with default (usually config) and command-line
|
||||
// setting
|
||||
func LookupOption(defaultValue bool, flags *flag.FlagSet, name string) (result bool) {
|
||||
result = defaultValue
|
||||
|
||||
if flags.IsSet(name) {
|
||||
result = flags.Lookup(name).Value.Get().(bool)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RootCommand creates root command in command tree
|
||||
func RootCommand() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
@@ -46,28 +86,32 @@ 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(),
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("dep-follow-suggests", false, "when processing dependencies, follow Suggests")
|
||||
cmd.Flag.Bool("dep-follow-source", false, "when processing dependencies, follow from binary to Source packages")
|
||||
cmd.Flag.Bool("dep-follow-recommends", false, "when processing dependencies, follow Recommends")
|
||||
cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if depdency is 'a|b'")
|
||||
cmd.Flag.Bool("dep-follow-all-variants", false, "when processing dependencies, follow a & b if dependency is 'a|b'")
|
||||
cmd.Flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available")
|
||||
cmd.Flag.String("config", "", "location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf)")
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+13
-263
@@ -1,281 +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/utils"
|
||||
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
|
||||
publishedStorage aptly.PublishedStorage
|
||||
collectionFactory *deb.CollectionFactory
|
||||
dependencyOptions int
|
||||
architecturesList []string
|
||||
// Debug features
|
||||
fileCPUProfile *os.File
|
||||
fileMemProfile *os.File
|
||||
fileMemStats *os.File
|
||||
}
|
||||
|
||||
var context *AptlyContext
|
||||
|
||||
// FatalError is type for panicking to abort execution with non-zero
|
||||
// exit code and print meaningful explanation
|
||||
type FatalError struct {
|
||||
ReturnCode int
|
||||
Message string
|
||||
}
|
||||
|
||||
// Fatal panics and aborts execution with exit code 1
|
||||
func Fatal(err error) {
|
||||
panic(&FatalError{ReturnCode: 1, Message: err.Error()})
|
||||
}
|
||||
|
||||
// Config loads and returns current configuration
|
||||
func (context *AptlyContext) Config() *utils.ConfigStructure {
|
||||
if !context.configLoaded {
|
||||
var err error
|
||||
|
||||
configLocation := context.flags.Lookup("config").Value.String()
|
||||
if configLocation != "" {
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
} else {
|
||||
configLocations := []string{
|
||||
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
|
||||
"/etc/aptly.conf",
|
||||
}
|
||||
|
||||
for _, configLocation := range configLocations {
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0])
|
||||
utils.SaveConfig(configLocations[0], &utils.Config)
|
||||
}
|
||||
}
|
||||
|
||||
context.configLoaded = true
|
||||
|
||||
}
|
||||
return &utils.Config
|
||||
}
|
||||
|
||||
// DependencyOptions calculates options related to dependecy handling
|
||||
func (context *AptlyContext) DependencyOptions() int {
|
||||
if context.dependencyOptions == -1 {
|
||||
context.dependencyOptions = 0
|
||||
if context.Config().DepFollowSuggests || context.flags.Lookup("dep-follow-suggests").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowSuggests
|
||||
}
|
||||
if context.Config().DepFollowRecommends || context.flags.Lookup("dep-follow-recommends").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowRecommends
|
||||
}
|
||||
if context.Config().DepFollowAllVariants || context.flags.Lookup("dep-follow-all-variants").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowAllVariants
|
||||
}
|
||||
if context.Config().DepFollowSource || context.flags.Lookup("dep-follow-source").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowSource
|
||||
}
|
||||
}
|
||||
|
||||
return context.dependencyOptions
|
||||
}
|
||||
|
||||
// ArchitecturesList returns list of architectures fixed via command line or config
|
||||
func (context *AptlyContext) ArchitecturesList() []string {
|
||||
if context.architecturesList == nil {
|
||||
context.architecturesList = context.Config().Architectures
|
||||
optionArchitectures := context.flags.Lookup("architectures").Value.String()
|
||||
if optionArchitectures != "" {
|
||||
context.architecturesList = strings.Split(optionArchitectures, ",")
|
||||
}
|
||||
}
|
||||
|
||||
return context.architecturesList
|
||||
}
|
||||
|
||||
// Progress creates or returns Progress object
|
||||
func (context *AptlyContext) Progress() aptly.Progress {
|
||||
if context.progress == nil {
|
||||
context.progress = console.NewProgress()
|
||||
context.progress.Start()
|
||||
}
|
||||
|
||||
return context.progress
|
||||
}
|
||||
|
||||
// Downloader returns instance of current downloader
|
||||
func (context *AptlyContext) Downloader() aptly.Downloader {
|
||||
if context.downloader == nil {
|
||||
context.downloader = http.NewDownloader(context.Config().DownloadConcurrency, context.Progress())
|
||||
}
|
||||
|
||||
return context.downloader
|
||||
}
|
||||
|
||||
// DBPath builds path to database
|
||||
func (context *AptlyContext) DBPath() string {
|
||||
return filepath.Join(context.Config().RootDir, "db")
|
||||
}
|
||||
|
||||
// Database opens and returns current instance of database
|
||||
func (context *AptlyContext) Database() (database.Storage, error) {
|
||||
if context.database == nil {
|
||||
var err error
|
||||
|
||||
context.database, err = database.OpenDB(context.DBPath())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open database: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return context.database, nil
|
||||
}
|
||||
|
||||
// CollectionFactory builds factory producing all kinds of collections
|
||||
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
|
||||
if context.collectionFactory == nil {
|
||||
db, err := context.Database()
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
context.collectionFactory = deb.NewCollectionFactory(db)
|
||||
}
|
||||
|
||||
return context.collectionFactory
|
||||
}
|
||||
|
||||
// PackagePool returns instance of PackagePool
|
||||
func (context *AptlyContext) PackagePool() aptly.PackagePool {
|
||||
if context.packagePool == nil {
|
||||
context.packagePool = files.NewPackagePool(context.Config().RootDir)
|
||||
}
|
||||
|
||||
return context.packagePool
|
||||
}
|
||||
|
||||
// PublishedStorage returns instance of PublishedStorage
|
||||
func (context *AptlyContext) PublishedStorage() aptly.PublishedStorage {
|
||||
if context.publishedStorage == nil {
|
||||
context.publishedStorage = files.NewPublishedStorage(context.Config().RootDir)
|
||||
}
|
||||
|
||||
return context.publishedStorage
|
||||
}
|
||||
var context *ctx.AptlyContext
|
||||
|
||||
// ShutdownContext shuts context down
|
||||
func ShutdownContext() {
|
||||
if aptly.EnableDebug {
|
||||
if context.fileMemProfile != nil {
|
||||
pprof.WriteHeapProfile(context.fileMemProfile)
|
||||
context.fileMemProfile.Close()
|
||||
context.fileMemProfile = nil
|
||||
}
|
||||
if context.fileCPUProfile != nil {
|
||||
pprof.StopCPUProfile()
|
||||
context.fileCPUProfile.Close()
|
||||
context.fileCPUProfile = nil
|
||||
}
|
||||
if context.fileMemProfile != nil {
|
||||
context.fileMemProfile.Close()
|
||||
context.fileMemProfile = nil
|
||||
}
|
||||
}
|
||||
if context.database != nil {
|
||||
context.database.Close()
|
||||
}
|
||||
if context.downloader != nil {
|
||||
context.downloader.Shutdown()
|
||||
}
|
||||
if context.progress != nil {
|
||||
context.progress.Shutdown()
|
||||
}
|
||||
context.Shutdown()
|
||||
}
|
||||
|
||||
// CleanupContext does partial shutdown of context
|
||||
func CleanupContext() {
|
||||
context.Cleanup()
|
||||
}
|
||||
|
||||
// InitContext initializes context with default settings
|
||||
func InitContext(flags *flag.FlagSet) error {
|
||||
var err error
|
||||
|
||||
context = &AptlyContext{flags: flags, dependencyOptions: -1}
|
||||
|
||||
if aptly.EnableDebug {
|
||||
cpuprofile := flags.Lookup("cpuprofile").Value.String()
|
||||
if cpuprofile != "" {
|
||||
context.fileCPUProfile, err = os.Create(cpuprofile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pprof.StartCPUProfile(context.fileCPUProfile)
|
||||
}
|
||||
|
||||
memprofile := flags.Lookup("memprofile").Value.String()
|
||||
if memprofile != "" {
|
||||
context.fileMemProfile, err = os.Create(memprofile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
memstats := flags.Lookup("memstats").Value.String()
|
||||
if memstats != "" {
|
||||
interval := flags.Lookup("meminterval").Value.Get().(time.Duration)
|
||||
|
||||
context.fileMemStats, err = os.Create(memstats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
|
||||
|
||||
go func() {
|
||||
var stats runtime.MemStats
|
||||
|
||||
start := time.Now().UnixNano()
|
||||
|
||||
for {
|
||||
runtime.ReadMemStats(&stats)
|
||||
if context.fileMemStats != nil {
|
||||
context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
|
||||
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
|
||||
time.Sleep(interval)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if context != nil {
|
||||
panic("context already initialized")
|
||||
}
|
||||
|
||||
return nil
|
||||
context, err = ctx.NewContext(flags)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
+170
-37
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// aptly db cleanup
|
||||
@@ -14,34 +15,101 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
verbose := context.Flags().Lookup("verbose").Value.Get().(bool)
|
||||
dryRun := context.Flags().Lookup("dry-run").Value.Get().(bool)
|
||||
|
||||
// collect information about references packages...
|
||||
existingPackageRefs := deb.NewPackageRefList()
|
||||
|
||||
context.Progress().Printf("Loading mirrors, local repos and snapshots...\n")
|
||||
// used only in verbose mode to report package use source
|
||||
packageRefSources := map[string][]string{}
|
||||
|
||||
context.Progress().ColoredPrintf("@{w!}Loading mirrors, local repos, snapshots and published repos...@|")
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("@{y}Loading mirrors:@|")
|
||||
}
|
||||
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("- @{g}%s@|", repo.Name)
|
||||
}
|
||||
|
||||
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.RefList() != nil {
|
||||
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false)
|
||||
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
|
||||
|
||||
if verbose {
|
||||
description := fmt.Sprintf("mirror %s", repo.Name)
|
||||
repo.RefList().ForEach(func(key []byte) error {
|
||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("@{y}Loading local repos:@|")
|
||||
}
|
||||
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("- @{g}%s@|", repo.Name)
|
||||
}
|
||||
|
||||
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if repo.RefList() != nil {
|
||||
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false)
|
||||
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true)
|
||||
|
||||
if verbose {
|
||||
description := fmt.Sprintf("local repo %s", repo.Name)
|
||||
repo.RefList().ForEach(func(key []byte) error {
|
||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("@{y}Loading snapshots:@|")
|
||||
}
|
||||
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("- @{g}%s@|", snapshot.Name)
|
||||
}
|
||||
|
||||
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false, true)
|
||||
|
||||
if verbose {
|
||||
description := fmt.Sprintf("snapshot %s", snapshot.Name)
|
||||
snapshot.RefList().ForEach(func(key []byte) error {
|
||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -49,12 +117,32 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("@{y}Loading published repositories:@|")
|
||||
}
|
||||
err = context.CollectionFactory().PublishedRepoCollection().ForEach(func(published *deb.PublishedRepo) error {
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("- @{g}%s:%s/%s{|}", published.Storage, published.Prefix, published.Distribution)
|
||||
}
|
||||
if published.SourceKind != "local" {
|
||||
return nil
|
||||
}
|
||||
err := context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false)
|
||||
|
||||
for _, component := range published.Components() {
|
||||
existingPackageRefs = existingPackageRefs.Merge(published.RefList(component), false, true)
|
||||
if verbose {
|
||||
description := fmt.Sprintf("published repository %s:%s/%s component %s",
|
||||
published.Storage, published.Prefix, published.Distribution, component)
|
||||
published.RefList(component).ForEach(func(key []byte) error {
|
||||
packageRefSources[string(key)] = append(packageRefSources[string(key)], description)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -62,38 +150,65 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
// ... and compare it to the list of all packages
|
||||
context.Progress().Printf("Loading list of all packages...\n")
|
||||
context.Progress().ColoredPrintf("@{w!}Loading list of all packages...@|")
|
||||
allPackageRefs := context.CollectionFactory().PackageCollection().AllPackageRefs()
|
||||
|
||||
toDelete := allPackageRefs.Substract(existingPackageRefs)
|
||||
|
||||
// delete packages that are no longer referenced
|
||||
context.Progress().Printf("Deleting unreferenced packages (%d)...\n", toDelete.Len())
|
||||
context.Progress().ColoredPrintf("@{r!}Deleting unreferenced packages (%d)...@|", toDelete.Len())
|
||||
|
||||
// database can't err as collection factory already constructed
|
||||
db, _ := context.Database()
|
||||
db.StartBatch()
|
||||
err = toDelete.ForEach(func(ref []byte) error {
|
||||
return context.CollectionFactory().PackageCollection().DeleteByKey(ref)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.FinishBatch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write to DB: %s", err)
|
||||
if toDelete.Len() > 0 {
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("@{r}List of package keys to delete:@|")
|
||||
err = toDelete.ForEach(func(ref []byte) error {
|
||||
context.Progress().ColoredPrintf(" - @{r}%s@|", string(ref))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !dryRun {
|
||||
db.StartBatch()
|
||||
err = toDelete.ForEach(func(ref []byte) error {
|
||||
return context.CollectionFactory().PackageCollection().DeleteByKey(ref)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.FinishBatch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write to DB: %s", err)
|
||||
}
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@{y!}Skipped deletion, as -dry-run has been requested.@|")
|
||||
}
|
||||
}
|
||||
|
||||
// now, build a list of files that should be present in Repository (package pool)
|
||||
context.Progress().Printf("Building list of files referenced by packages...\n")
|
||||
context.Progress().ColoredPrintf("@{w!}Building list of files referenced by packages...@|")
|
||||
referencedFiles := make([]string, 0, existingPackageRefs.Len())
|
||||
context.Progress().InitBar(int64(existingPackageRefs.Len()), false)
|
||||
|
||||
err = existingPackageRefs.ForEach(func(key []byte) error {
|
||||
pkg, err2 := context.CollectionFactory().PackageCollection().ByKey(key)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
tail := ""
|
||||
if verbose {
|
||||
tail = fmt.Sprintf(" (sources: %s)", strings.Join(packageRefSources[string(key)], ", "))
|
||||
}
|
||||
if dryRun {
|
||||
context.Progress().ColoredPrintf("@{r!}Unresolvable package reference, skipping (-dry-run): %s: %s%s",
|
||||
string(key), err2, tail)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to load package %s: %s%s", string(key), err2, tail)
|
||||
}
|
||||
paths, err2 := pkg.FilepathList(context.PackagePool())
|
||||
if err2 != nil {
|
||||
@@ -112,7 +227,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
context.Progress().ShutdownBar()
|
||||
|
||||
// build a list of files in the package pool
|
||||
context.Progress().Printf("Building list of files in package pool...\n")
|
||||
context.Progress().ColoredPrintf("@{w!}Building list of files in package pool...@|")
|
||||
existingFiles, err := context.PackagePool().FilepathList(context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect file paths: %s", err)
|
||||
@@ -122,28 +237,43 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
filesToDelete := utils.StrSlicesSubstract(existingFiles, referencedFiles)
|
||||
|
||||
// delete files that are no longer referenced
|
||||
context.Progress().Printf("Deleting unreferenced files (%d)...\n", len(filesToDelete))
|
||||
context.Progress().ColoredPrintf("@{r!}Deleting unreferenced files (%d)...@|", len(filesToDelete))
|
||||
|
||||
if len(filesToDelete) > 0 {
|
||||
context.Progress().InitBar(int64(len(filesToDelete)), false)
|
||||
|
||||
var size, totalSize int64
|
||||
for _, file := range filesToDelete {
|
||||
size, err = context.PackagePool().Remove(file)
|
||||
if err != nil {
|
||||
return err
|
||||
if verbose {
|
||||
context.Progress().ColoredPrintf("@{r}List of files to be deleted:@|")
|
||||
for _, file := range filesToDelete {
|
||||
context.Progress().ColoredPrintf(" - @{r}%s@|", file)
|
||||
}
|
||||
|
||||
context.Progress().AddBar(1)
|
||||
totalSize += size
|
||||
}
|
||||
context.Progress().ShutdownBar()
|
||||
|
||||
context.Progress().Printf("Disk space freed: %s...\n", utils.HumanBytes(totalSize))
|
||||
if !dryRun {
|
||||
context.Progress().InitBar(int64(len(filesToDelete)), false)
|
||||
|
||||
var size, totalSize int64
|
||||
for _, file := range filesToDelete {
|
||||
size, err = context.PackagePool().Remove(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context.Progress().AddBar(1)
|
||||
totalSize += size
|
||||
}
|
||||
context.Progress().ShutdownBar()
|
||||
|
||||
context.Progress().ColoredPrintf("@{w!}Disk space freed: %s...@|", utils.HumanBytes(totalSize))
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@{y!}Skipped file deletion, as -dry-run has been requested.@|")
|
||||
}
|
||||
}
|
||||
|
||||
context.Progress().Printf("Compacting database...\n")
|
||||
err = db.CompactDB()
|
||||
if !dryRun {
|
||||
context.Progress().ColoredPrintf("@{w!}Compacting database...@|")
|
||||
err = db.CompactDB()
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@{y!}Skipped DB compaction, as -dry-run has been requested.@|")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -163,5 +293,8 @@ Example:
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("verbose", false, "be verbose when loading objects/removing them")
|
||||
cmd.Flag.Bool("dry-run", false, "don't delete anything")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ func aptlyDbRecover(cmd *commander.Command, args []string) error {
|
||||
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
context.Progress().Printf("Recovering database...\n")
|
||||
|
||||
+33
-121
@@ -2,136 +2,30 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/gographviz"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func graphvizEscape(s string) string {
|
||||
return fmt.Sprintf("\"%s\"", strings.Replace(s, "\"", "\\\"", 0))
|
||||
}
|
||||
|
||||
func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
graph := gographviz.NewGraph()
|
||||
graph.SetDir(true)
|
||||
graph.SetName("aptly")
|
||||
|
||||
existingNodes := map[string]bool{}
|
||||
|
||||
fmt.Printf("Loading mirrors...\n")
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph.AddNode("aptly", graphvizEscape(repo.UUID), map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "darkgoldenrod1",
|
||||
"label": graphvizEscape(fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}",
|
||||
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "),
|
||||
strings.Join(repo.Architectures, ", "), repo.NumPackages())),
|
||||
})
|
||||
existingNodes[repo.UUID] = true
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
fmt.Printf("Loading local repos...\n")
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph.AddNode("aptly", graphvizEscape(repo.UUID), map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "mediumseagreen",
|
||||
"label": graphvizEscape(fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
|
||||
repo.Name, repo.Comment, repo.NumPackages())),
|
||||
})
|
||||
existingNodes[repo.UUID] = true
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Loading snapshots...\n")
|
||||
|
||||
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
existingNodes[snapshot.UUID] = true
|
||||
return nil
|
||||
})
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
description := snapshot.Description
|
||||
if snapshot.SourceKind == "repo" {
|
||||
description = "Snapshot from repo"
|
||||
}
|
||||
|
||||
graph.AddNode("aptly", graphvizEscape(snapshot.UUID), map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "cadetblue1",
|
||||
"label": graphvizEscape(fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages())),
|
||||
})
|
||||
|
||||
if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
|
||||
for _, uuid := range snapshot.SourceIDs {
|
||||
_, exists := existingNodes[uuid]
|
||||
if exists {
|
||||
graph.AddEdge(graphvizEscape(uuid), "", graphvizEscape(snapshot.UUID), "", true, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Loading published repos...\n")
|
||||
|
||||
context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
||||
graph.AddNode("aptly", graphvizEscape(repo.UUID), map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "darkolivegreen1",
|
||||
"label": graphvizEscape(fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, repo.Component, strings.Join(repo.Architectures, ", "))),
|
||||
})
|
||||
|
||||
_, exists := existingNodes[repo.SourceUUID]
|
||||
if exists {
|
||||
graph.AddEdge(graphvizEscape(repo.SourceUUID), "", graphvizEscape(repo.UUID), "", true, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
fmt.Printf("Generating graph...\n")
|
||||
graph, err := deb.BuildGraph(context.CollectionFactory())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(graph.String())
|
||||
|
||||
@@ -142,9 +36,16 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
tempfile.Close()
|
||||
os.Remove(tempfile.Name())
|
||||
|
||||
tempfilename := tempfile.Name() + ".png"
|
||||
format := context.Flags().Lookup("format").Value.String()
|
||||
output := context.Flags().Lookup("output").Value.String()
|
||||
|
||||
command := exec.Command("dot", "-Tpng", "-o"+tempfilename)
|
||||
if filepath.Ext(output) != "" {
|
||||
format = filepath.Ext(output)[1:]
|
||||
}
|
||||
|
||||
tempfilename := tempfile.Name() + "." + format
|
||||
|
||||
command := exec.Command("dot", "-T"+format, "-o"+tempfilename)
|
||||
command.Stderr = os.Stderr
|
||||
|
||||
stdin, err := command.StdinPipe()
|
||||
@@ -172,10 +73,18 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = exec.Command("open", tempfilename).Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Rendered to PNG file: %s\n", tempfilename)
|
||||
err = nil
|
||||
if output != "" {
|
||||
err = utils.CopyFile(tempfilename, output)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to copy %s -> %s: %s", tempfilename, output, err)
|
||||
}
|
||||
_ = os.Remove(tempfilename)
|
||||
|
||||
fmt.Printf("Output saved to %s\n", output)
|
||||
} else {
|
||||
fmt.Printf("Rendered to %s file: %s, trying to open it...\n", format, tempfilename)
|
||||
|
||||
_ = exec.Command("open", tempfilename).Run()
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -197,5 +106,8 @@ Example:
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.String("format", "png", "render graph to specified format (png, svg, pdf, etc.)")
|
||||
cmd.Flag.String("output", "", "specify output filename, default is to open result in viewer")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+4
-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
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ func makeCmdMirror() *commander.Command {
|
||||
makeCmdMirrorShow(),
|
||||
makeCmdMirrorDrop(),
|
||||
makeCmdMirrorUpdate(),
|
||||
makeCmdMirrorRename(),
|
||||
makeCmdMirrorEdit(),
|
||||
makeCmdMirrorSearch(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+23
-5
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
@@ -12,10 +13,11 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if !(len(args) == 2 && strings.HasPrefix(args[1], "ppa:") || len(args) >= 3) {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
downloadSources := context.Config().DownloadSourcePackages || context.flags.Lookup("with-sources").Value.Get().(bool)
|
||||
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
||||
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
||||
|
||||
var (
|
||||
mirrorName, archiveURL, distribution string
|
||||
@@ -32,12 +34,24 @@ 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)
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(context.flags)
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create mirror: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||
}
|
||||
@@ -63,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:
|
||||
@@ -79,6 +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
|
||||
|
||||
+7
-2
@@ -10,7 +10,7 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fmt.Printf("Mirror %s successfully updated.\n", repo)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorEdit() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorEdit,
|
||||
UsageLine: "edit <name>",
|
||||
Short: "edit mirror settings",
|
||||
Long: `
|
||||
Command edit allows one to change settings of mirror:
|
||||
filters, list of architectures.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror edit -filter=nginx -filter-with-deps some-mirror
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-edit", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.String("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
|
||||
}
|
||||
+3
-1
@@ -11,7 +11,7 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
@@ -28,6 +28,8 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
context.CloseDatabase()
|
||||
|
||||
sort.Strings(repos)
|
||||
|
||||
if raw {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlyMirrorRename(cmd *commander.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
repo *deb.RemoteRepo
|
||||
)
|
||||
|
||||
if len(args) != 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
oldName, newName := args[0], args[1]
|
||||
|
||||
repo, err = context.CollectionFactory().RemoteRepoCollection().ByName(oldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
err = repo.CheckLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
_, err = context.CollectionFactory().RemoteRepoCollection().ByName(newName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("unable to rename: mirror %s already exists", newName)
|
||||
}
|
||||
|
||||
repo.Name = newName
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nMirror %s -> %s has been successfully renamed.\n", oldName, newName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorRename() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorRename,
|
||||
UsageLine: "rename <old-name> <new-name>",
|
||||
Short: "renames mirror",
|
||||
Long: `
|
||||
Command changes name of the mirror.Mirror name should be unique.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror rename wheezy-min wheezy-main
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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")
|
||||
cmd.Flag.String("format", "", "custom format for result printing")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+19
-2
@@ -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"
|
||||
@@ -12,7 +13,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -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,19 @@ 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"
|
||||
if repo.FilterWithDeps {
|
||||
filterWithDeps = "yes"
|
||||
}
|
||||
fmt.Printf("Filter With Deps: %s\n", filterWithDeps)
|
||||
}
|
||||
if repo.LastDownloadDate.IsZero() {
|
||||
fmt.Printf("Last update: never\n")
|
||||
} else {
|
||||
@@ -49,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")
|
||||
|
||||
+121
-4
@@ -2,15 +2,21 @@ package cmd
|
||||
|
||||
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 {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -25,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)
|
||||
}
|
||||
@@ -37,11 +51,112 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = repo.Download(context.Progress(), context.Downloader(), context.CollectionFactory(), context.PackagePool(), ignoreMismatch)
|
||||
context.Progress().Printf("Downloading & parsing package files...\n")
|
||||
err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), 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)
|
||||
@@ -68,8 +183,10 @@ 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)")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -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,51 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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")
|
||||
}
|
||||
|
||||
format := context.Flags().Lookup("format").Value.String()
|
||||
PrintPackageList(result, format)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPackageSearch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPackageSearch,
|
||||
UsageLine: "search <package-query>",
|
||||
Short: "search for packages matching query",
|
||||
Long: `
|
||||
Command search displays list of packages in whole DB that match package query
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly package search '$Architecture (i386), Name (% *-dev)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-package-search", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.String("format", "", "custom format for result printing")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -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
-1
@@ -7,13 +7,15 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
|
||||
+13
-8
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
@@ -9,18 +10,20 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
prefix := "."
|
||||
param := "."
|
||||
|
||||
if len(args) == 2 {
|
||||
prefix = args[1]
|
||||
param = args[1]
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Remove(context.PublishedStorage(), prefix, distribution,
|
||||
context.CollectionFactory(), context.Progress())
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Remove(context, storage, prefix, distribution,
|
||||
context.CollectionFactory(), context.Progress(), context.Flags().Lookup("force-drop").Value.Get().(bool))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
@@ -33,11 +36,11 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error {
|
||||
func makeCmdPublishDrop() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishDrop,
|
||||
UsageLine: "drop <distribution> [<prefix>]",
|
||||
UsageLine: "drop <distribution> [[<endpoint>:]<prefix>]",
|
||||
Short: "remove published repository",
|
||||
Long: `
|
||||
Command removes whatever has been published under specified <prefix> and
|
||||
<distribution> name.
|
||||
Command removes whatever has been published under specified <prefix>,
|
||||
publishing <endpoint> and <distribution> name.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -45,5 +48,7 @@ Example:
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("force-drop", false, "remove published repository even if some files could not be cleaned up")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+4
-2
@@ -11,7 +11,7 @@ func aptlyPublishList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
@@ -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 {
|
||||
|
||||
+13
-2
@@ -8,13 +8,19 @@ import (
|
||||
func makeCmdPublishRepo() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSnapshotOrRepo,
|
||||
UsageLine: "repo <name> [<prefix>]",
|
||||
UsageLine: "repo <name> [[<endpoint>:]<prefix>]",
|
||||
Short: "publish local repository",
|
||||
Long: `
|
||||
Command publishes current state of local repository ready to be consumed
|
||||
by apt tools. Published repostiories appear under rootDir/public directory.
|
||||
Valid GPG key is required for publishing.
|
||||
|
||||
Multiple component repository could be published by specifying several
|
||||
components split by commas via -component flag and multiple local
|
||||
repositories as the arguments:
|
||||
|
||||
aptly publish repo -component=main,contrib repo-main repo-contrib
|
||||
|
||||
It is not recommended to publish local repositories directly unless the
|
||||
repository is for testing purposes and changes happen frequently. For
|
||||
production usage please take snapshot of repository and publish it
|
||||
@@ -27,13 +33,18 @@ Example:
|
||||
Flag: *flag.NewFlagSet("aptly-publish-repo", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("distribution", "", "distribution name to publish")
|
||||
cmd.Flag.String("component", "", "component name to publish")
|
||||
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&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("skip-contents", false, "don't generate Contents indexes")
|
||||
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
|
||||
}
|
||||
|
||||
+115
-41
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
@@ -11,66 +12,116 @@ import (
|
||||
|
||||
func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
|
||||
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
|
||||
|
||||
if len(args) < len(components) || len(args) > len(components)+1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
var prefix string
|
||||
if len(args) == 2 {
|
||||
prefix = args[1]
|
||||
var param string
|
||||
if len(args) == len(components)+1 {
|
||||
param = args[len(components)]
|
||||
args = args[0 : len(args)-1]
|
||||
} else {
|
||||
prefix = ""
|
||||
param = ""
|
||||
}
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
var (
|
||||
source interface{}
|
||||
sources = []interface{}{}
|
||||
message string
|
||||
)
|
||||
|
||||
if cmd.Name() == "snapshot" {
|
||||
var snapshot *deb.Snapshot
|
||||
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
var (
|
||||
snapshot *deb.Snapshot
|
||||
emptyWarning = false
|
||||
parts = []string{}
|
||||
)
|
||||
|
||||
for _, name := range args {
|
||||
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
sources = append(sources, snapshot)
|
||||
parts = append(parts, snapshot.Name)
|
||||
|
||||
if snapshot.NumPackages() == 0 {
|
||||
emptyWarning = true
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
if len(parts) == 1 {
|
||||
message = fmt.Sprintf("Snapshot %s has", parts[0])
|
||||
} else {
|
||||
message = fmt.Sprintf("Snapshots %s have", strings.Join(parts, ", "))
|
||||
|
||||
}
|
||||
|
||||
source = snapshot
|
||||
message = fmt.Sprintf("Snapshot %s", snapshot.Name)
|
||||
if emptyWarning {
|
||||
context.Progress().Printf("Warning: publishing from empty source, architectures list should be complete, it can't be changed after publishing (use -architectures flag)\n")
|
||||
}
|
||||
} else if cmd.Name() == "repo" {
|
||||
var localRepo *deb.LocalRepo
|
||||
localRepo, err = context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
var (
|
||||
localRepo *deb.LocalRepo
|
||||
emptyWarning = false
|
||||
parts = []string{}
|
||||
)
|
||||
|
||||
for _, name := range args {
|
||||
localRepo, err = context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(localRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
sources = append(sources, localRepo)
|
||||
parts = append(parts, localRepo.Name)
|
||||
|
||||
if localRepo.NumPackages() == 0 {
|
||||
emptyWarning = true
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(localRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
if len(parts) == 1 {
|
||||
message = fmt.Sprintf("Local repo %s has", parts[0])
|
||||
} else {
|
||||
message = fmt.Sprintf("Local repos %s have", strings.Join(parts, ", "))
|
||||
|
||||
}
|
||||
|
||||
source = localRepo
|
||||
message = fmt.Sprintf("Local repo %s", localRepo.Name)
|
||||
if emptyWarning {
|
||||
context.Progress().Printf("Warning: publishing from empty source, architectures list should be complete, it can't be changed after publishing (use -architectures flag)\n")
|
||||
}
|
||||
} else {
|
||||
panic("unknown command")
|
||||
}
|
||||
|
||||
component := context.flags.Lookup("component").Value.String()
|
||||
distribution := context.flags.Lookup("distribution").Value.String()
|
||||
distribution := context.Flags().Lookup("distribution").Value.String()
|
||||
|
||||
published, err := deb.NewPublishedRepo(prefix, distribution, component, context.ArchitecturesList(), source, context.CollectionFactory())
|
||||
published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
published.Origin = cmd.Flag.Lookup("origin").Value.String()
|
||||
published.Label = cmd.Flag.Lookup("label").Value.String()
|
||||
published.Origin = context.Flags().Lookup("origin").Value.String()
|
||||
published.Label = context.Flags().Lookup("label").Value.String()
|
||||
|
||||
if context.Flags().IsSet("skip-contents") {
|
||||
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
|
||||
}
|
||||
|
||||
duplicate := context.CollectionFactory().PublishedRepoCollection().CheckDuplicate(published)
|
||||
if duplicate != nil {
|
||||
@@ -78,12 +129,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.PublishedStorage(), context.CollectionFactory(), signer, context.Progress())
|
||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||
if forceOverwrite {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
||||
"the same package pool.\n")
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress(), forceOverwrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
@@ -93,19 +150,25 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
|
||||
prefix, component, distribution = published.Prefix, published.Component, published.Distribution
|
||||
var repoComponents string
|
||||
prefix, repoComponents, distribution = published.Prefix, strings.Join(published.Components(), " "), published.Distribution
|
||||
if prefix == "." {
|
||||
prefix = ""
|
||||
} else if !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
|
||||
context.Progress().Printf("\n%s has been successfully published.\nPlease setup your webserver to serve directory '%s' with autoindexing.\n",
|
||||
message, context.PublishedStorage().PublicPath())
|
||||
context.Progress().Printf("\n%s been successfully published.\n", message)
|
||||
|
||||
if localStorage, ok := context.GetPublishedStorage(storage).(aptly.LocalPublishedStorage); ok {
|
||||
context.Progress().Printf("Please setup your webserver to serve directory '%s' with autoindexing.\n",
|
||||
localStorage.PublicPath())
|
||||
}
|
||||
|
||||
context.Progress().Printf("Now you can add following line to apt sources:\n")
|
||||
context.Progress().Printf(" deb http://your-server/%s %s %s\n", prefix, distribution, component)
|
||||
context.Progress().Printf(" deb http://your-server/%s %s %s\n", prefix, distribution, repoComponents)
|
||||
if utils.StrSliceHasItem(published.Architectures, "source") {
|
||||
context.Progress().Printf(" deb-src http://your-server/%s %s %s\n", prefix, distribution, component)
|
||||
context.Progress().Printf(" deb-src http://your-server/%s %s %s\n", prefix, distribution, repoComponents)
|
||||
}
|
||||
context.Progress().Printf("Don't forget to add your GPG key to apt with apt-key.\n")
|
||||
context.Progress().Printf("\nYou can also use `aptly serve` to publish your repositories over HTTP quickly.\n")
|
||||
@@ -116,13 +179,19 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
func makeCmdPublishSnapshot() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSnapshotOrRepo,
|
||||
UsageLine: "snapshot <name> [<prefix>]",
|
||||
UsageLine: "snapshot <name> [[<endpoint>:]<prefix>]",
|
||||
Short: "publish snapshot",
|
||||
Long: `
|
||||
Command publishes snapshot as Debian repository ready to be consumed
|
||||
by apt tools. Published repostiories appear under rootDir/public directory.
|
||||
Valid GPG key is required for publishing.
|
||||
|
||||
Multiple component repository could be published by specifying several
|
||||
components split by commas via -component flag and multiple snapshots
|
||||
as the arguments:
|
||||
|
||||
aptly publish snapshot -component=main,contrib snap-main snap-contrib
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish snapshot wheezy-main
|
||||
@@ -130,13 +199,18 @@ Example:
|
||||
Flag: *flag.NewFlagSet("aptly-publish-snapshot", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("distribution", "", "distribution name to publish")
|
||||
cmd.Flag.String("component", "", "component name to publish")
|
||||
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&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("skip-contents", false, "don't generate Contents indexes")
|
||||
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
|
||||
}
|
||||
|
||||
+75
-27
@@ -3,45 +3,42 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 2 || len(args) > 3 {
|
||||
|
||||
components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
|
||||
|
||||
if len(args) < len(components)+1 || len(args) > len(components)+2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
prefix := "."
|
||||
param := "."
|
||||
|
||||
var (
|
||||
name string
|
||||
names []string
|
||||
snapshot *deb.Snapshot
|
||||
)
|
||||
|
||||
if len(args) == 3 {
|
||||
prefix = args[1]
|
||||
name = args[2]
|
||||
if len(args) == len(components)+2 {
|
||||
param = args[1]
|
||||
names = args[2:]
|
||||
} else {
|
||||
name = args[1]
|
||||
names = args[1:]
|
||||
}
|
||||
|
||||
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
var published *deb.PublishedRepo
|
||||
|
||||
published, err = context.CollectionFactory().PublishedRepoCollection().ByPrefixDistribution(prefix, distribution)
|
||||
published, err = context.CollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -55,14 +52,49 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
published.UpdateSnapshot(snapshot)
|
||||
publishedComponents := published.Components()
|
||||
if len(components) == 1 && len(publishedComponents) == 1 && components[0] == "" {
|
||||
components = publishedComponents
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.flags)
|
||||
if len(names) != len(components) {
|
||||
return fmt.Errorf("mismatch in number of components (%d) and snapshots (%d)", len(components), len(names))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to switch: %s", err)
|
||||
}
|
||||
|
||||
published.UpdateSnapshot(component, snapshot)
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG signer: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context.PublishedStorage(), context.CollectionFactory(), signer, context.Progress())
|
||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||
if forceOverwrite {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
||||
"the same package pool.\n")
|
||||
}
|
||||
|
||||
if context.Flags().IsSet("skip-contents") {
|
||||
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress(), forceOverwrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
@@ -72,8 +104,8 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, published.Component,
|
||||
context.PublishedStorage(), context.CollectionFactory(), context.Progress())
|
||||
err = context.CollectionFactory().PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components,
|
||||
context.GetPublishedStorage(storage), context.CollectionFactory(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -86,22 +118,38 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
func makeCmdPublishSwitch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSwitch,
|
||||
UsageLine: "switch <distribution> [<prefix>] <new-snapshot>",
|
||||
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
|
||||
publishing parameters are preserved (architecture list, distribution, component).
|
||||
Command switches in-place published snapshots with new snapshot contents. All
|
||||
publishing parameters are preserved (architecture list, distribution,
|
||||
component).
|
||||
|
||||
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 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.Bool("skip-contents", false, "don't generate Contents indexes")
|
||||
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
|
||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+32
-10
@@ -11,19 +11,20 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
prefix := "."
|
||||
param := "."
|
||||
|
||||
if len(args) == 2 {
|
||||
prefix = args[1]
|
||||
param = args[1]
|
||||
}
|
||||
storage, prefix := deb.ParsePrefix(param)
|
||||
|
||||
var published *deb.PublishedRepo
|
||||
|
||||
published, err = context.CollectionFactory().PublishedRepoCollection().ByPrefixDistribution(prefix, distribution)
|
||||
published, err = context.CollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -37,14 +38,27 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
published.UpdateLocalRepo()
|
||||
components := published.Components()
|
||||
for _, component := range components {
|
||||
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.PublishedStorage(), context.CollectionFactory(), signer, context.Progress())
|
||||
forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
|
||||
if forceOverwrite {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
|
||||
"the same package pool.\n")
|
||||
}
|
||||
|
||||
if context.Flags().IsSet("skip-contents") {
|
||||
published.SkipContents = context.Flags().Lookup("skip-contents").Value.Get().(bool)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress(), forceOverwrite)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
@@ -54,8 +68,8 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, published.Component,
|
||||
context.PublishedStorage(), context.CollectionFactory(), context.Progress())
|
||||
err = context.CollectionFactory().PublishedRepoCollection().CleanupPrefixComponentFiles(published.Prefix, components,
|
||||
context.GetPublishedStorage(storage), context.CollectionFactory(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
@@ -68,7 +82,7 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
func makeCmdPublishUpdate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishUpdate,
|
||||
UsageLine: "update <distribution> [<prefix>]",
|
||||
UsageLine: "update <distribution> [[<endpoint>:]<prefix>]",
|
||||
Short: "update published local repository",
|
||||
Long: `
|
||||
Command re-publishes (updates) published local repository. <distribution>
|
||||
@@ -76,6 +90,9 @@ and <prefix> should be occupied with local repository published
|
||||
using command aptly publish repo. Update happens in-place with
|
||||
minimum possible downtime for published repository.
|
||||
|
||||
For multiple component published repositories, all local repositories
|
||||
are updated.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish update wheezy ppa
|
||||
@@ -85,7 +102,12 @@ 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("skip-contents", false, "don't generate Contents indexes")
|
||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ func makeCmdRepo() *commander.Command {
|
||||
makeCmdRepoMove(),
|
||||
makeCmdRepoRemove(),
|
||||
makeCmdRepoShow(),
|
||||
makeCmdRepoRename(),
|
||||
makeCmdRepoSearch(),
|
||||
makeCmdRepoInclude(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+24
-119
@@ -2,21 +2,19 @@ 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 {
|
||||
var err error
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -40,122 +38,19 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
packageFiles := []string{}
|
||||
forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
|
||||
|
||||
for _, location := range args[1:] {
|
||||
info, err2 := os.Stat(location)
|
||||
if err2 != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to process %s: %s@|", location, err2)
|
||||
continue
|
||||
}
|
||||
if info.IsDir() {
|
||||
err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error {
|
||||
if err3 != nil {
|
||||
return err3
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
var packageFiles, failedFiles []string
|
||||
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
|
||||
packageFiles = append(packageFiles, path)
|
||||
}
|
||||
packageFiles, failedFiles = deb.CollectPackageFiles(args[1:], &aptly.ConsoleResultReporter{Progress: context.Progress()})
|
||||
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
|
||||
packageFiles = append(packageFiles, location)
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
var processedFiles, failedFiles2 []string
|
||||
|
||||
processedFiles := []string{}
|
||||
sort.Strings(packageFiles)
|
||||
|
||||
for _, file := range packageFiles {
|
||||
var (
|
||||
stanza deb.Stanza
|
||||
p *deb.Package
|
||||
)
|
||||
|
||||
candidateProcessedFiles := []string{}
|
||||
isSourcePackage := strings.HasSuffix(file, ".dsc")
|
||||
|
||||
if isSourcePackage {
|
||||
stanza, err = deb.GetControlFileFromDsc(file, verifier)
|
||||
|
||||
if err == nil {
|
||||
stanza["Package"] = stanza["Source"]
|
||||
delete(stanza, "Source")
|
||||
|
||||
p, err = deb.NewSourcePackageFromControlFile(stanza)
|
||||
}
|
||||
} else {
|
||||
stanza, err = deb.GetControlFileFromDeb(file)
|
||||
p = deb.NewPackageFromControlFile(stanza)
|
||||
}
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to read file %s: %s@|", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var checksums utils.ChecksumInfo
|
||||
checksums, err = utils.ChecksumsForFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isSourcePackage {
|
||||
p.UpdateFiles(append(p.Files(), deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}))
|
||||
} else {
|
||||
p.UpdateFiles([]deb.PackageFile{deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}})
|
||||
}
|
||||
|
||||
err = context.PackagePool().Import(file, checksums.MD5)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
candidateProcessedFiles = append(candidateProcessedFiles, file)
|
||||
|
||||
// go over all files, except for the last one (.dsc/.deb itself)
|
||||
for _, f := range p.Files() {
|
||||
if filepath.Base(f.Filename) == filepath.Base(file) {
|
||||
continue
|
||||
}
|
||||
sourceFile := filepath.Join(filepath.Dir(file), filepath.Base(f.Filename))
|
||||
err = context.PackagePool().Import(sourceFile, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", sourceFile, err)
|
||||
break
|
||||
}
|
||||
|
||||
candidateProcessedFiles = append(candidateProcessedFiles, sourceFile)
|
||||
}
|
||||
if err != nil {
|
||||
// some files haven't been imported
|
||||
continue
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PackageCollection().Update(p)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to save package %s: %s@|", p, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = list.Add(p)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to add package to repo %s: %s@|", p, err)
|
||||
continue
|
||||
}
|
||||
|
||||
context.Progress().ColoredPrintf("@g[+]@| %s added@|", p)
|
||||
processedFiles = append(processedFiles, candidateProcessedFiles...)
|
||||
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
|
||||
context.CollectionFactory().PackageCollection(), &aptly.ConsoleResultReporter{Progress: context.Progress()}, nil)
|
||||
failedFiles = append(failedFiles, failedFiles2...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to import package files: %s", err)
|
||||
}
|
||||
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
@@ -165,7 +60,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 {
|
||||
@@ -176,6 +71,15 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(failedFiles) > 0 {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Some files were skipped due to errors:@|")
|
||||
for _, file := range failedFiles {
|
||||
context.Progress().ColoredPrintf(" %s", file)
|
||||
}
|
||||
|
||||
return fmt.Errorf("some files failed to be added")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -185,8 +89,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.
|
||||
@@ -199,6 +103,7 @@ Example:
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("remove-files", false, "remove files that have been imported successfully into repository")
|
||||
cmd.Flag.Bool("force-replace", false, "when adding package that conflicts with existing package, remove existing package")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,10 +8,10 @@ import (
|
||||
func makeCmdRepoCopy() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoMoveCopyImport,
|
||||
UsageLine: "copy <src-name> <dst-name> <package-spec> ...",
|
||||
UsageLine: "copy <src-name> <dst-name> <package-query> ...",
|
||||
Short: "copy packages between local repositories",
|
||||
Long: `
|
||||
Command copy copies packages matching <package-spec> from local repo
|
||||
Command copy copies packages matching <package-query> from local repo
|
||||
<src-name> to local repo <dst-name>.
|
||||
|
||||
Example:
|
||||
|
||||
+13
-4
@@ -11,12 +11,20 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
repo := deb.NewLocalRepo(args[0], context.flags.Lookup("comment").Value.String())
|
||||
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
|
||||
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
|
||||
repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String())
|
||||
repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
|
||||
repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
|
||||
|
||||
uploadersFile := context.Flags().Lookup("uploaders-file").Value.Get().(string)
|
||||
if uploadersFile != "" {
|
||||
repo.Uploaders, err = deb.NewUploadersFromFile(uploadersFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Add(repo)
|
||||
if err != nil {
|
||||
@@ -47,6 +55,7 @@ Example:
|
||||
cmd.Flag.String("comment", "", "any text that would be used to described local repository")
|
||||
cmd.Flag.String("distribution", "", "default distribution when publishing")
|
||||
cmd.Flag.String("component", "main", "default component when publishing")
|
||||
cmd.Flag.String("uploaders-file", "", "uploaders.json to be used when including .changes into this repository")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -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)
|
||||
|
||||
|
||||
+27
-10
@@ -2,6 +2,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
@@ -10,7 +12,7 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(args[0])
|
||||
@@ -23,16 +25,30 @@ 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()
|
||||
}
|
||||
var uploadersFile *string
|
||||
|
||||
if context.flags.Lookup("distribution").Value.String() != "" {
|
||||
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
|
||||
}
|
||||
context.Flags().Visit(func(flag *flag.Flag) {
|
||||
switch flag.Name {
|
||||
case "comment":
|
||||
repo.Comment = flag.Value.String()
|
||||
case "distribution":
|
||||
repo.DefaultDistribution = flag.Value.String()
|
||||
case "component":
|
||||
repo.DefaultComponent = flag.Value.String()
|
||||
case "uploaders-file":
|
||||
uploadersFile = pointer.ToString(flag.Value.String())
|
||||
}
|
||||
})
|
||||
|
||||
if context.flags.Lookup("component").Value.String() != "" {
|
||||
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
|
||||
if uploadersFile != nil {
|
||||
if *uploadersFile != "" {
|
||||
repo.Uploaders, err = deb.NewUploadersFromFile(*uploadersFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
repo.Uploaders = nil
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
@@ -50,7 +66,7 @@ func makeCmdRepoEdit() *commander.Command {
|
||||
UsageLine: "edit <name>",
|
||||
Short: "edit properties of local repository",
|
||||
Long: `
|
||||
Command edit allows to change metadata of local repository:
|
||||
Command edit allows one to change metadata of local repository:
|
||||
comment, default distribution and component.
|
||||
|
||||
Example:
|
||||
@@ -63,6 +79,7 @@ Example:
|
||||
cmd.Flag.String("comment", "", "any text that would be used to described local repository")
|
||||
cmd.Flag.String("distribution", "", "default distribution when publishing")
|
||||
cmd.Flag.String("component", "", "default component when publishing")
|
||||
cmd.Flag.String("uploaders-file", "", "uploaders.json to be used when including .changes into this repository")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,10 +8,10 @@ import (
|
||||
func makeCmdRepoImport() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoMoveCopyImport,
|
||||
UsageLine: "import <src-mirror> <dst-repo> <package-spec> ...",
|
||||
UsageLine: "import <src-mirror> <dst-repo> <package-query> ...",
|
||||
Short: "import packages from mirror to local repository",
|
||||
Long: `
|
||||
Command import looks up packages matching <package-spec> in mirror <src-mirror>
|
||||
Command import looks up packages matching <package-query> in mirror <src-mirror>
|
||||
and copies them to local repo <dst-repo>.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func aptlyRepoInclude(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(context.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||
}
|
||||
|
||||
if verifier == nil {
|
||||
verifier = &utils.GpgVerifier{}
|
||||
}
|
||||
|
||||
forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
|
||||
acceptUnsigned := context.Flags().Lookup("accept-unsigned").Value.Get().(bool)
|
||||
ignoreSignatures := context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||
noRemoveFiles := context.Flags().Lookup("no-remove-files").Value.Get().(bool)
|
||||
|
||||
repoTemplate, err := template.New("repo").Parse(context.Flags().Lookup("repo").Value.Get().(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing -repo template: %s", err)
|
||||
}
|
||||
|
||||
uploaders := (*deb.Uploaders)(nil)
|
||||
uploadersFile := context.Flags().Lookup("uploaders-file").Value.Get().(string)
|
||||
if uploadersFile != "" {
|
||||
uploaders, err = deb.NewUploadersFromFile(uploadersFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range uploaders.Rules {
|
||||
uploaders.Rules[i].CompiledCondition, err = query.Parse(uploaders.Rules[i].Condition)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing query %s: %s", uploaders.Rules[i].Condition, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reporter := &aptly.ConsoleResultReporter{Progress: context.Progress()}
|
||||
|
||||
var changesFiles, failedFiles, processedFiles []string
|
||||
|
||||
changesFiles, failedFiles = deb.CollectChangesFiles(args, reporter)
|
||||
|
||||
for _, path := range changesFiles {
|
||||
var changes *deb.Changes
|
||||
|
||||
changes, err = deb.NewChanges(path)
|
||||
if err != nil {
|
||||
failedFiles = append(failedFiles, path)
|
||||
reporter.Warning("unable to process file %s: %s", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = changes.VerifyAndParse(acceptUnsigned, ignoreSignatures, verifier)
|
||||
if err != nil {
|
||||
failedFiles = append(failedFiles, path)
|
||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||
changes.Cleanup()
|
||||
continue
|
||||
}
|
||||
|
||||
err = changes.Prepare()
|
||||
if err != nil {
|
||||
failedFiles = append(failedFiles, path)
|
||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||
changes.Cleanup()
|
||||
continue
|
||||
}
|
||||
|
||||
repoName := &bytes.Buffer{}
|
||||
err = repoTemplate.Execute(repoName, changes.Stanza)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying template to repo: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("Loading repository %s for changes file %s...\n", repoName.String(), changes.ChangesName)
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(repoName.String())
|
||||
if err != nil {
|
||||
failedFiles = append(failedFiles, path)
|
||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||
changes.Cleanup()
|
||||
continue
|
||||
}
|
||||
|
||||
currentUploaders := uploaders
|
||||
if repo.Uploaders != nil {
|
||||
currentUploaders = repo.Uploaders
|
||||
for i := range currentUploaders.Rules {
|
||||
currentUploaders.Rules[i].CompiledCondition, err = query.Parse(currentUploaders.Rules[i].Condition)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing query %s: %s", currentUploaders.Rules[i].Condition, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if currentUploaders != nil {
|
||||
if err = currentUploaders.IsAllowed(changes); err != nil {
|
||||
failedFiles = append(failedFiles, path)
|
||||
reporter.Warning("changes file skipped due to uploaders config: %s, keys %#v: %s",
|
||||
changes.ChangesName, changes.SignatureKeys, err)
|
||||
changes.Cleanup()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load repo: %s", err)
|
||||
}
|
||||
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
packageFiles, _ := deb.CollectPackageFiles([]string{changes.TempDir}, reporter)
|
||||
|
||||
var restriction deb.PackageQuery
|
||||
|
||||
restriction, err = changes.PackageQuery()
|
||||
if err != nil {
|
||||
failedFiles = append(failedFiles, path)
|
||||
reporter.Warning("unable to process file %s: %s", changes.ChangesName, err)
|
||||
changes.Cleanup()
|
||||
continue
|
||||
}
|
||||
|
||||
var processedFiles2, failedFiles2 []string
|
||||
|
||||
processedFiles2, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
|
||||
context.CollectionFactory().PackageCollection(), reporter, restriction)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to import package files: %s", err)
|
||||
}
|
||||
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
|
||||
err = changes.Cleanup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range failedFiles2 {
|
||||
failedFiles = append(failedFiles, filepath.Join(changes.BasePath, filepath.Base(file)))
|
||||
}
|
||||
|
||||
for _, file := range processedFiles2 {
|
||||
processedFiles = append(processedFiles, filepath.Join(changes.BasePath, filepath.Base(file)))
|
||||
}
|
||||
|
||||
processedFiles = append(processedFiles, path)
|
||||
}
|
||||
|
||||
if !noRemoveFiles {
|
||||
processedFiles = utils.StrSliceDeduplicate(processedFiles)
|
||||
|
||||
for _, file := range processedFiles {
|
||||
err := os.Remove(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove file: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(failedFiles) > 0 {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Some files were skipped due to errors:@|")
|
||||
for _, file := range failedFiles {
|
||||
context.Progress().ColoredPrintf(" %s", file)
|
||||
}
|
||||
|
||||
return fmt.Errorf("some files failed to be added")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoInclude() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoInclude,
|
||||
UsageLine: "include <file.changes>|<directory> ...",
|
||||
Short: "add packages to local repositories based on .changes files",
|
||||
Long: `
|
||||
Command include looks for .changes files in list of arguments or specified directories. Each
|
||||
.changes file is verified, parsed, referenced files are put into separate temporary directory
|
||||
and added into local repository. Successfully imported files are removed by default.
|
||||
|
||||
Additionally uploads could be restricted with <uploaders.json> file. Rules in this file control
|
||||
uploads based on GPG key ID of .changes file signature and queries on .changes file fields.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo include -repo=foo-release incoming/
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-include", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("no-remove-files", false, "don't 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")
|
||||
cmd.Flag.String("repo", "{{.Distribution}}", "which repo should files go to, defaults to Distribution field of .changes file")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
|
||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of .changes file signature")
|
||||
cmd.Flag.Bool("accept-unsigned", false, "accept unsigned .changes files")
|
||||
cmd.Flag.String("uploaders-file", "", "path to uploaders.json file")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+3
-1
@@ -11,7 +11,7 @@ func aptlyRepoList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
@@ -33,6 +33,8 @@ func aptlyRepoList(cmd *commander.Command, args []string) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
context.CloseDatabase()
|
||||
|
||||
sort.Strings(repos)
|
||||
|
||||
if raw {
|
||||
|
||||
+15
-6
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"sort"
|
||||
@@ -12,7 +13,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 3 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
command := cmd.Name()
|
||||
@@ -86,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()
|
||||
@@ -105,7 +106,15 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
toProcess, err := srcList.Filter(args[2:], withDeps, dstList, context.DependencyOptions(), architecturesList)
|
||||
queries := make([]deb.PackageQuery, len(args)-2)
|
||||
for i := 0; i < len(args)-2; i++ {
|
||||
queries[i], err = query.Parse(args[i+2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
}
|
||||
|
||||
toProcess, err := srcList.Filter(queries, withDeps, dstList, context.DependencyOptions(), architecturesList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
@@ -136,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))
|
||||
@@ -162,10 +171,10 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
|
||||
func makeCmdRepoMove() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoMoveCopyImport,
|
||||
UsageLine: "move <src-name> <dst-name> <package-spec> ...",
|
||||
UsageLine: "move <src-name> <dst-name> <package-query> ...",
|
||||
Short: "move packages between local repositories",
|
||||
Long: `
|
||||
Command move moves packages matching <package-spec> from local repo
|
||||
Command move moves packages matching <package-query> from local repo
|
||||
<src-name> to local repo <dst-name>.
|
||||
|
||||
Example:
|
||||
|
||||
+14
-5
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -33,8 +34,16 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
queries := make([]deb.PackageQuery, len(args)-1)
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
queries[i], err = query.Parse(args[i+1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
list.PrepareIndex()
|
||||
toRemove, err := list.Filter(args[1:], false, nil, 0, nil)
|
||||
toRemove, err := list.Filter(queries, false, nil, 0, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
@@ -45,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))
|
||||
@@ -62,10 +71,10 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
|
||||
func makeCmdRepoRemove() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoRemove,
|
||||
UsageLine: "remove <name> <package-spec> ...",
|
||||
UsageLine: "remove <name> <package-query> ...",
|
||||
Short: "remove packages from local repository",
|
||||
Long: `
|
||||
Commands removes packages matching <package-spec> from local repository
|
||||
Commands removes packages matching <package-query> from local repository
|
||||
<name>. If removed packages are not referenced by other repos or
|
||||
snapshots, they can be removed completely (including files) by running
|
||||
'aptly db cleanup'.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlyRepoRename(cmd *commander.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
repo *deb.LocalRepo
|
||||
)
|
||||
|
||||
if len(args) != 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
oldName, newName := args[0], args[1]
|
||||
|
||||
repo, err = context.CollectionFactory().LocalRepoCollection().ByName(oldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
_, err = context.CollectionFactory().LocalRepoCollection().ByName(newName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("unable to rename: local repo %s already exists", newName)
|
||||
}
|
||||
|
||||
repo.Name = newName
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nLocal repo %s -> %s has been successfully renamed.\n", oldName, newName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoRename() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoRename,
|
||||
UsageLine: "rename <old-name> <new-name>",
|
||||
Short: "renames local repository",
|
||||
Long: `
|
||||
Command changes name of the local repo. Local repo name should be unique.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo rename wheezy-min wheezy-main
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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")
|
||||
cmd.Flag.String("format", "", "custom format for result printing")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+5
-2
@@ -10,7 +10,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -29,9 +29,12 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error {
|
||||
fmt.Printf("Comment: %s\n", repo.Comment)
|
||||
fmt.Printf("Default Distribution: %s\n", repo.DefaultDistribution)
|
||||
fmt.Printf("Default Component: %s\n", repo.DefaultComponent)
|
||||
if repo.Uploaders != nil {
|
||||
fmt.Printf("Uploaders: %s\n", repo.Uploaders)
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ctx "github.com/smira/aptly/context"
|
||||
"github.com/smira/commander"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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.Fprintln(os.Stderr, "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
|
||||
}
|
||||
+11
-4
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
@@ -10,17 +11,23 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyServe(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
if context.CollectionFactory().PublishedRepoCollection().Len() == 0 {
|
||||
fmt.Printf("No published repositories, unable to serve.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
listen := context.flags.Lookup("listen").Value.String()
|
||||
listen := context.Flags().Lookup("listen").Value.String()
|
||||
|
||||
listenHost, listenPort, err := net.SplitHostPort(listen)
|
||||
|
||||
@@ -69,15 +76,15 @@ func aptlyServe(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
|
||||
fmt.Printf("# %s\ndeb http://%s:%s/%s %s %s\n",
|
||||
repo, listenHost, listenPort, prefix, repo.Distribution, repo.Component)
|
||||
repo, listenHost, listenPort, prefix, repo.Distribution, strings.Join(repo.Components(), " "))
|
||||
|
||||
if utils.StrSliceHasItem(repo.Architectures, "source") {
|
||||
fmt.Printf("deb-src http://%s:%s/%s %s %s\n",
|
||||
listenHost, listenPort, prefix, repo.Distribution, repo.Component)
|
||||
listenHost, listenPort, prefix, repo.Distribution, strings.Join(repo.Components(), " "))
|
||||
}
|
||||
}
|
||||
|
||||
publicPath := context.PublishedStorage().PublicPath()
|
||||
publicPath := context.GetPublishedStorage("").(aptly.LocalPublishedStorage).PublicPath()
|
||||
ShutdownContext()
|
||||
|
||||
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
|
||||
|
||||
@@ -17,6 +17,9 @@ func makeCmdSnapshot() *commander.Command {
|
||||
makeCmdSnapshotDiff(),
|
||||
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)
|
||||
@@ -61,7 +66,7 @@ func aptlySnapshotCreate(cmd *commander.Command, args []string) error {
|
||||
snapshot = deb.NewSnapshotFromPackageList(snapshotName, nil, packageList, "Created as empty")
|
||||
} else {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().Add(snapshot)
|
||||
|
||||
@@ -10,10 +10,10 @@ func aptlySnapshotDiff(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
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])
|
||||
|
||||
@@ -10,7 +10,7 @@ func aptlySnapshotDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := 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
|
||||
}
|
||||
+17
-23
@@ -4,43 +4,36 @@ import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlySnapshotList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string)
|
||||
|
||||
snapshots := make([]string, context.CollectionFactory().SnapshotCollection().Len())
|
||||
|
||||
i := 0
|
||||
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
if raw {
|
||||
snapshots[i] = snapshot.Name
|
||||
} else {
|
||||
snapshots[i] = snapshot.String()
|
||||
}
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.Strings(snapshots)
|
||||
collection := context.CollectionFactory().SnapshotCollection()
|
||||
|
||||
if raw {
|
||||
for _, snapshot := range snapshots {
|
||||
fmt.Printf("%s\n", snapshot)
|
||||
}
|
||||
collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error {
|
||||
fmt.Printf("%s\n", snapshot.Name)
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
if len(snapshots) > 0 {
|
||||
if collection.Len() > 0 {
|
||||
fmt.Printf("List of snapshots:\n")
|
||||
|
||||
for _, snapshot := range snapshots {
|
||||
fmt.Printf(" * %s\n", snapshot)
|
||||
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")
|
||||
@@ -48,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 {
|
||||
@@ -67,6 +60,7 @@ Example:
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("raw", false, "display list in machine-readable format")
|
||||
cmd.Flag.String("sort", "name", "display list in 'name' or creation 'time' order")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+12
-6
@@ -11,7 +11,7 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
sources := make([]*deb.Snapshot, len(args)-1)
|
||||
@@ -28,17 +28,22 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
latest := context.flags.Lookup("latest").Value.Get().(bool)
|
||||
overrideMatching := !latest
|
||||
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")
|
||||
}
|
||||
|
||||
overrideMatching := !latest && !noRemove
|
||||
|
||||
result := sources[0].RefList()
|
||||
|
||||
for i := 1; i < len(sources); i++ {
|
||||
result = result.Merge(sources[i].RefList(), overrideMatching)
|
||||
result = result.Merge(sources[i].RefList(), overrideMatching, false)
|
||||
}
|
||||
|
||||
if latest {
|
||||
deb.FilterLatestRefs(result)
|
||||
result.FilterLatestRefs()
|
||||
}
|
||||
|
||||
sourceDescription := make([]string, len(sources))
|
||||
@@ -79,6 +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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+53
-70
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"sort"
|
||||
@@ -13,11 +14,12 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 4 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
noDeps := context.flags.Lookup("no-deps").Value.Get().(bool)
|
||||
noRemove := context.flags.Lookup("no-remove").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])
|
||||
@@ -75,79 +77,59 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
return fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||
}
|
||||
|
||||
// Initial dependencies out of arguments
|
||||
initialDependencies := make([]deb.Dependency, len(args)-3)
|
||||
for i, arg := range args[3:] {
|
||||
initialDependencies[i], err = deb.ParseDependency(arg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse argument: %s", err)
|
||||
}
|
||||
// Build architecture query: (arch == "i386" | arch == "amd64" | ...)
|
||||
var archQuery deb.PackageQuery = &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: ""}
|
||||
for _, arch := range architecturesList {
|
||||
archQuery = &deb.OrQuery{L: &deb.FieldQuery{Field: "$Architecture", Relation: deb.VersionEqual, Value: arch}, R: archQuery}
|
||||
}
|
||||
|
||||
// Perform pull
|
||||
for _, arch := range architecturesList {
|
||||
dependencies := make([]deb.Dependency, len(initialDependencies), 128)
|
||||
for i := range dependencies {
|
||||
dependencies[i] = initialDependencies[i]
|
||||
dependencies[i].Architecture = arch
|
||||
// Initial queries out of arguments
|
||||
queries := make([]deb.PackageQuery, len(args)-3)
|
||||
for i, arg := range args[3:] {
|
||||
queries[i], err = query.Parse(arg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse query: %s", err)
|
||||
}
|
||||
// Add architecture filter
|
||||
queries[i] = &deb.AndQuery{L: queries[i], R: archQuery}
|
||||
}
|
||||
|
||||
// Filter with dependencies as requested
|
||||
result, err := sourcePackageList.Filter(queries, !noDeps, packageList, context.DependencyOptions(), architecturesList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to pull: %s", err)
|
||||
}
|
||||
result.PrepareIndex()
|
||||
|
||||
alreadySeen := map[string]bool{}
|
||||
|
||||
result.ForEachIndexed(func(pkg *deb.Package) error {
|
||||
key := pkg.Architecture + "_" + pkg.Name
|
||||
_, seen := alreadySeen[key]
|
||||
|
||||
// If we haven't seen such name-architecture pair and were instructed to remove, remove it
|
||||
if !noRemove && !seen {
|
||||
// Remove all packages with the same name and architecture
|
||||
pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true)
|
||||
for _, p := range pS {
|
||||
packageList.Remove(p)
|
||||
context.Progress().ColoredPrintf("@r[-]@| %s removed", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Go over list of initial dependencies + list of dependencies found
|
||||
for i := 0; i < len(dependencies); i++ {
|
||||
dep := dependencies[i]
|
||||
|
||||
// Search for package that can satisfy dependencies
|
||||
pkg := sourcePackageList.Search(dep)
|
||||
if pkg == nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Dependency %s can't be satisfied with source %s@|", &dep, source)
|
||||
continue
|
||||
}
|
||||
|
||||
if !noRemove {
|
||||
// Remove all packages with the same name and architecture
|
||||
for p := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}); p != nil; {
|
||||
packageList.Remove(p)
|
||||
context.Progress().ColoredPrintf("@r[-]@| %s removed", p)
|
||||
p = packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name})
|
||||
}
|
||||
}
|
||||
|
||||
// Add new discovered package
|
||||
// If !allMatches, add only first matching name-arch package
|
||||
if !seen || allMatches {
|
||||
packageList.Add(pkg)
|
||||
context.Progress().ColoredPrintf("@g[+]@| %s added", pkg)
|
||||
|
||||
if noDeps {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find missing dependencies for single added package
|
||||
pL := deb.NewPackageList()
|
||||
pL.Add(pkg)
|
||||
|
||||
var missing []deb.Dependency
|
||||
missing, err = pL.VerifyDependencies(context.DependencyOptions(), []string{arch}, packageList, nil)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Error while verifying dependencies for pkg %s: %s@|", pkg, err)
|
||||
}
|
||||
|
||||
// Append missing dependencies to the list of dependencies to satisfy
|
||||
for _, misDep := range missing {
|
||||
found := false
|
||||
for _, d := range dependencies {
|
||||
if d == misDep {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
dependencies = append(dependencies, misDep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if context.flags.Lookup("dry-run").Value.Get().(bool) {
|
||||
alreadySeen[key] = true
|
||||
|
||||
return nil
|
||||
})
|
||||
alreadySeen = nil
|
||||
|
||||
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
|
||||
@@ -167,14 +149,14 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
func makeCmdSnapshotPull() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotPull,
|
||||
UsageLine: "pull <name> <source> <destination> <package-name> ...",
|
||||
UsageLine: "pull <name> <source> <destination> <package-query> ...",
|
||||
Short: "pull packages from another snapshot",
|
||||
Long: `
|
||||
Command pull pulls new packages along with its' dependencies to snapshot <name>
|
||||
from snapshot <source>. Pull can upgrade package version in <name> with
|
||||
versions from <source> following dependencies. New snapshot <destination>
|
||||
is created as a result of this process. Packages could be specified simply
|
||||
as 'package-name' or as dependency 'package-name (>= version)'.
|
||||
as 'package-name' or as package queries.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -186,6 +168,7 @@ Example:
|
||||
cmd.Flag.Bool("dry-run", false, "don't create destination snapshot, just show what would be pulled")
|
||||
cmd.Flag.Bool("no-deps", false, "don't process dependencies, just pull listed packages")
|
||||
cmd.Flag.Bool("no-remove", false, "don't remove other package versions when pulling package")
|
||||
cmd.Flag.Bool("all-matches", false, "pull all the packages that satisfy the dependency version requirements")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlySnapshotRename(cmd *commander.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
snapshot *deb.Snapshot
|
||||
)
|
||||
|
||||
if len(args) != 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
oldName, newName := args[0], args[1]
|
||||
|
||||
snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(oldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
_, err = context.CollectionFactory().SnapshotCollection().ByName(newName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("unable to rename: snapshot %s already exists", newName)
|
||||
}
|
||||
|
||||
snapshot.Name = newName
|
||||
err = context.CollectionFactory().SnapshotCollection().Update(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nSnapshot %s -> %s has been successfully renamed.\n", oldName, newName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotRename() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotRename,
|
||||
UsageLine: "rename <old-name> <new-name>",
|
||||
Short: "renames snapshot",
|
||||
Long: `
|
||||
Command changes name of the snapshot. Snapshot name should be unique.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot rename wheezy-min wheezy-main
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
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)
|
||||
}
|
||||
|
||||
if result.Len() == 0 {
|
||||
return fmt.Errorf("no results")
|
||||
}
|
||||
|
||||
format := context.Flags().Lookup("format").Value.String()
|
||||
PrintPackageList(result, format)
|
||||
|
||||
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")
|
||||
cmd.Flag.String("format", "", "custom format for result printing")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -10,7 +10,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ func aptlySnapshotVerify(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
snapshots := make([]*deb.Snapshot, len(args))
|
||||
@@ -31,25 +31,25 @@ func aptlySnapshotVerify(cmd *commander.Command, args []string) error {
|
||||
|
||||
packageList, err := deb.NewPackageListFromRefList(snapshots[0].RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
fmt.Errorf("unable to load packages: %s", err)
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
sourcePackageList := deb.NewPackageList()
|
||||
err = sourcePackageList.Append(packageList)
|
||||
if err != nil {
|
||||
fmt.Errorf("unable to merge sources: %s", err)
|
||||
return fmt.Errorf("unable to merge sources: %s", err)
|
||||
}
|
||||
|
||||
var pL *deb.PackageList
|
||||
for i := 1; i < len(snapshots); i++ {
|
||||
pL, err = deb.NewPackageListFromRefList(snapshots[i].RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
fmt.Errorf("unable to load packages: %s", err)
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
err = sourcePackageList.Append(pL)
|
||||
if err != nil {
|
||||
fmt.Errorf("unable to merge sources: %s", err)
|
||||
return fmt.Errorf("unable to merge sources: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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
|
||||
}
|
||||
@@ -7,6 +7,11 @@ import (
|
||||
)
|
||||
|
||||
func aptlyVersion(cmd *commander.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
fmt.Printf("aptly version: %s\n", aptly.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
+31
-9
@@ -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}
|
||||
}
|
||||
@@ -128,19 +133,30 @@ func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
|
||||
p.queue <- printTask{code: codePrint, message: color.Sprintf(msg, a...) + "\n"}
|
||||
} else {
|
||||
// stip color marks
|
||||
var prev rune
|
||||
var inColorMark, inCurly bool
|
||||
msg = strings.Map(func(r rune) rune {
|
||||
if prev == '@' {
|
||||
prev = 0
|
||||
if r == '@' {
|
||||
return r
|
||||
if inColorMark {
|
||||
if inCurly {
|
||||
if r == '}' {
|
||||
inCurly = false
|
||||
inColorMark = false
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
if r == '{' {
|
||||
inCurly = true
|
||||
} else if r == '@' {
|
||||
return '@'
|
||||
} else {
|
||||
inColorMark = false
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
prev = r
|
||||
if r == '@' {
|
||||
return -1
|
||||
|
||||
if r == '@' {
|
||||
inColorMark = true
|
||||
return -1
|
||||
}
|
||||
|
||||
return r
|
||||
@@ -151,9 +167,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 +183,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.Fprintf(os.Stderr, "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.Endpoint, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
|
||||
params.EncryptionMethod, params.PlusWorkaround, params.DisableMultiDel)
|
||||
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
|
||||
}
|
||||
+30
-7
@@ -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,22 @@ 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),
|
||||
Filter: filter.NewBloomFilter(10),
|
||||
OpenFilesCacheCapacity: 256,
|
||||
}
|
||||
|
||||
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 +102,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 +154,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)
|
||||
}
|
||||
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Changes is a result of .changes file parsing
|
||||
type Changes struct {
|
||||
Changes string
|
||||
Distribution string
|
||||
Files PackageFiles
|
||||
BasePath, ChangesName string
|
||||
TempDir string
|
||||
Source string
|
||||
Binary []string
|
||||
Architectures []string
|
||||
Stanza Stanza
|
||||
SignatureKeys []utils.GpgKey
|
||||
}
|
||||
|
||||
// NewChanges moves .changes file into temporary directory and creates Changes structure
|
||||
func NewChanges(path string) (*Changes, error) {
|
||||
var err error
|
||||
|
||||
c := &Changes{
|
||||
BasePath: filepath.Dir(path),
|
||||
ChangesName: filepath.Base(path),
|
||||
}
|
||||
|
||||
c.TempDir, err = ioutil.TempDir(os.TempDir(), "aptly")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// copy .changes file into temporary directory
|
||||
err = utils.CopyFile(filepath.Join(c.BasePath, c.ChangesName), filepath.Join(c.TempDir, c.ChangesName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// VerifyAndParse does optional signature verification and parses changes files
|
||||
func (c *Changes) VerifyAndParse(acceptUnsigned, ignoreSignature bool, verifier utils.Verifier) error {
|
||||
input, err := os.Open(filepath.Join(c.TempDir, c.ChangesName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
isClearSigned, err := verifier.IsClearSigned(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
input.Seek(0, 0)
|
||||
|
||||
if !isClearSigned && !acceptUnsigned {
|
||||
return fmt.Errorf(".changes file is not signed and unsigned processing hasn't been enabled")
|
||||
}
|
||||
|
||||
if isClearSigned && !ignoreSignature {
|
||||
keyInfo, err := verifier.VerifyClearsigned(input, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
input.Seek(0, 0)
|
||||
|
||||
c.SignatureKeys = keyInfo.GoodKeys
|
||||
}
|
||||
|
||||
var text *os.File
|
||||
|
||||
if isClearSigned {
|
||||
text, err = verifier.ExtractClearsigned(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer text.Close()
|
||||
} else {
|
||||
text = input
|
||||
}
|
||||
|
||||
reader := NewControlFileReader(text)
|
||||
c.Stanza, err = reader.ReadStanza(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Distribution = c.Stanza["Distribution"]
|
||||
c.Changes = c.Stanza["Changes"]
|
||||
c.Source = c.Stanza["Source"]
|
||||
c.Binary = strings.Fields(c.Stanza["Binary"])
|
||||
c.Architectures = strings.Fields(c.Stanza["Architecture"])
|
||||
|
||||
c.Files, err = c.Files.ParseSumFields(c.Stanza)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepare creates temporary directory, copies file there and verifies checksums
|
||||
func (c *Changes) Prepare() error {
|
||||
var err error
|
||||
|
||||
for _, file := range c.Files {
|
||||
if filepath.Dir(file.Filename) != "." {
|
||||
return fmt.Errorf("file is not in the same folder as .changes file: %s", file.Filename)
|
||||
}
|
||||
|
||||
file.Filename = filepath.Base(file.Filename)
|
||||
|
||||
err = utils.CopyFile(filepath.Join(c.BasePath, file.Filename), filepath.Join(c.TempDir, file.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range c.Files {
|
||||
var info utils.ChecksumInfo
|
||||
|
||||
info, err = utils.ChecksumsForFile(filepath.Join(c.TempDir, file.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.Size != file.Checksums.Size {
|
||||
return fmt.Errorf("size mismatch: expected %v != obtained %v", file.Checksums.Size, info.Size)
|
||||
}
|
||||
|
||||
if info.MD5 != file.Checksums.MD5 {
|
||||
return fmt.Errorf("checksum mismatch MD5: expected %v != obtained %v", file.Checksums.MD5, info.MD5)
|
||||
}
|
||||
|
||||
if info.SHA1 != file.Checksums.SHA1 {
|
||||
return fmt.Errorf("checksum mismatch SHA1: expected %v != obtained %v", file.Checksums.SHA1, info.SHA1)
|
||||
}
|
||||
|
||||
if info.SHA256 != file.Checksums.SHA256 {
|
||||
return fmt.Errorf("checksum mismatch SHA256 expected %v != obtained %v", file.Checksums.SHA256, info.SHA256)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup removes all temporary files
|
||||
func (c *Changes) Cleanup() error {
|
||||
if c.TempDir == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.RemoveAll(c.TempDir)
|
||||
}
|
||||
|
||||
// PackageQuery returns query that every package should match to be included
|
||||
func (c *Changes) PackageQuery() (PackageQuery, error) {
|
||||
var archQuery PackageQuery = &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: ""}
|
||||
for _, arch := range c.Architectures {
|
||||
archQuery = &OrQuery{L: &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: arch}, R: archQuery}
|
||||
}
|
||||
|
||||
// if c.Source is empty, this would never match
|
||||
sourceQuery := &AndQuery{
|
||||
L: &FieldQuery{Field: "$PackageType", Relation: VersionEqual, Value: "source"},
|
||||
R: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: c.Source},
|
||||
}
|
||||
|
||||
var binaryQuery PackageQuery
|
||||
if len(c.Binary) > 0 {
|
||||
binaryQuery = &FieldQuery{Field: "Name", Relation: VersionEqual, Value: c.Binary[0]}
|
||||
for _, binary := range c.Binary[1:] {
|
||||
binaryQuery = &OrQuery{
|
||||
L: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: binary},
|
||||
R: binaryQuery,
|
||||
}
|
||||
}
|
||||
|
||||
binaryQuery = &AndQuery{
|
||||
L: &NotQuery{Q: &FieldQuery{Field: "$PackageType", Relation: VersionEqual, Value: "source"}},
|
||||
R: binaryQuery}
|
||||
}
|
||||
|
||||
var nameQuery PackageQuery
|
||||
if binaryQuery == nil {
|
||||
nameQuery = sourceQuery
|
||||
} else {
|
||||
nameQuery = &OrQuery{L: sourceQuery, R: binaryQuery}
|
||||
}
|
||||
|
||||
return &AndQuery{L: archQuery, R: nameQuery}, nil
|
||||
}
|
||||
|
||||
// GetField implements PackageLike interface
|
||||
func (c *Changes) GetField(field string) string {
|
||||
return c.Stanza[field]
|
||||
}
|
||||
|
||||
// MatchesDependency implements PackageLike interface
|
||||
func (c *Changes) MatchesDependency(d Dependency) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchesArchitecture implements PackageLike interface
|
||||
func (c *Changes) MatchesArchitecture(arch string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetName implements PackageLike interface
|
||||
func (c *Changes) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetVersion implements PackageLike interface
|
||||
func (c *Changes) GetVersion() string {
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// GetArchitecture implements PackageLike interface
|
||||
func (c *Changes) GetArchitecture() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// CollectChangesFiles walks filesystem collecting all .changes files
|
||||
func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (changesFiles, failedFiles []string) {
|
||||
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(), ".changes") {
|
||||
changesFiles = append(changesFiles, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
} else if strings.HasSuffix(info.Name(), ".changes") {
|
||||
changesFiles = append(changesFiles, location)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(changesFiles)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "gopkg.in/check.v1"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ChangesSuite struct {
|
||||
Dir, Path string
|
||||
}
|
||||
|
||||
var _ = Suite(&ChangesSuite{})
|
||||
|
||||
func (s *ChangesSuite) SetUpTest(c *C) {
|
||||
s.Dir = c.MkDir()
|
||||
s.Path = filepath.Join(s.Dir, "calamares.changes")
|
||||
|
||||
f, err := os.Create(s.Path)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
f.WriteString(changesFile)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func (s *ChangesSuite) TestParseAndVerify(c *C) {
|
||||
changes, err := NewChanges(s.Path)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = changes.VerifyAndParse(true, true, &NullVerifier{})
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(changes.Distribution, Equals, "sid")
|
||||
c.Check(changes.Files, HasLen, 4)
|
||||
c.Check(changes.Files[0].Filename, Equals, "calamares_0+git20141127.99.dsc")
|
||||
c.Check(changes.Files[0].Checksums.Size, Equals, int64(1106))
|
||||
c.Check(changes.Files[0].Checksums.MD5, Equals, "05fd8f3ffe8f362c5ef9bad2f936a56e")
|
||||
c.Check(changes.Files[0].Checksums.SHA1, Equals, "79f10e955dab6eb25b7f7bae18213f367a3a0396")
|
||||
c.Check(changes.Files[0].Checksums.SHA256, Equals, "35b3280a7b1ffe159a276128cb5c408d687318f60ecbb8ab6dedb2e49c4e82dc")
|
||||
c.Check(changes.BasePath, Equals, s.Dir)
|
||||
c.Check(changes.Architectures, DeepEquals, []string{"source", "amd64"})
|
||||
c.Check(changes.Source, Equals, "calamares")
|
||||
c.Check(changes.Binary, DeepEquals, []string{"calamares", "calamares-dbg"})
|
||||
}
|
||||
|
||||
func (s *ChangesSuite) TestPackageQuery(c *C) {
|
||||
changes, err := NewChanges(s.Path)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = changes.VerifyAndParse(true, true, &NullVerifier{})
|
||||
c.Check(err, IsNil)
|
||||
|
||||
q, err := changes.PackageQuery()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(q.String(), Equals,
|
||||
"(($Architecture (= amd64)) | (($Architecture (= source)) | ($Architecture (= )))), ((($PackageType (= source)), (Name (= calamares))) | ((!($PackageType (= source))), ((Name (= calamares-dbg)) | (Name (= calamares)))))")
|
||||
}
|
||||
|
||||
var changesFile = `Format: 1.8
|
||||
Date: Thu, 27 Nov 2014 13:24:53 +0000
|
||||
Source: calamares
|
||||
Binary: calamares calamares-dbg
|
||||
Architecture: source amd64
|
||||
Version: 0+git20141127.99
|
||||
Distribution: sid
|
||||
Urgency: medium
|
||||
Maintainer: Rohan Garg <rohan@kde.org>
|
||||
Changed-By: Rohan <rohan@kde.org>
|
||||
Description:
|
||||
calamares - distribution-independent installer framework
|
||||
calamares-dbg - distribution-independent installer framework -- debug symbols
|
||||
Changes:
|
||||
calamares (0+git20141127.99) sid; urgency=medium
|
||||
.
|
||||
* Update from git
|
||||
Checksums-Sha1:
|
||||
79f10e955dab6eb25b7f7bae18213f367a3a0396 1106 calamares_0+git20141127.99.dsc
|
||||
294c28e2c8e34e72ca9ee0d9da5c14f3bf4188db 2694800 calamares_0+git20141127.99.tar.xz
|
||||
d6c26c04b5407c7511f61cb3e3de60c4a1d6c4ff 1698924 calamares_0+git20141127.99_amd64.deb
|
||||
a3da632d193007b0d4a1aff73159fde1b532d7a8 12835902 calamares-dbg_0+git20141127.99_amd64.deb
|
||||
Checksums-Sha256:
|
||||
35b3280a7b1ffe159a276128cb5c408d687318f60ecbb8ab6dedb2e49c4e82dc 1106 calamares_0+git20141127.99.dsc
|
||||
5576b9caaf814564830f95561227e4f04ee87b31da22c1371aab155cbf7ce395 2694800 calamares_0+git20141127.99.tar.xz
|
||||
2e6e2f232ed7ffe52369928ebdf5436d90feb37840286ffba79e87d57a43a2e9 1698924 calamares_0+git20141127.99_amd64.deb
|
||||
8dd926080ed7bad2e2439e37e49ce12d5f1357c5041b7da4d860a1041f878a8a 12835902 calamares-dbg_0+git20141127.99_amd64.deb
|
||||
Files:
|
||||
05fd8f3ffe8f362c5ef9bad2f936a56e 1106 devel optional calamares_0+git20141127.99.dsc
|
||||
097e55c81abd8e5f30bb2eed90c2c1e9 2694800 devel optional calamares_0+git20141127.99.tar.xz
|
||||
827fb3b12534241e119815d331e8197b 1698924 devel optional calamares_0+git20141127.99_amd64.deb
|
||||
e6f8ce70f564d1f68cb57758b15b13e3 12835902 debug optional calamares-dbg_0+git20141127.99_amd64.deb`
|
||||
+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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContentsIndex calculates mapping from files to packages, with sorting and aggregation
|
||||
type ContentsIndex struct {
|
||||
index map[string][]*Package
|
||||
}
|
||||
|
||||
// NewContentsIndex creates empty ContentsIndex
|
||||
func NewContentsIndex() *ContentsIndex {
|
||||
return &ContentsIndex{
|
||||
index: make(map[string][]*Package),
|
||||
}
|
||||
}
|
||||
|
||||
// Push adds package to contents index, calculating package contents as required
|
||||
func (index *ContentsIndex) Push(p *Package, packagePool aptly.PackagePool) {
|
||||
contents := p.Contents(packagePool)
|
||||
|
||||
for _, path := range contents {
|
||||
index.index[path] = append(index.index[path], p)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty checks whether index contains no packages
|
||||
func (index *ContentsIndex) Empty() bool {
|
||||
return len(index.index) == 0
|
||||
}
|
||||
|
||||
// WriteTo dumps sorted mapping of files to qualified package names
|
||||
func (index *ContentsIndex) WriteTo(w io.Writer) (int64, error) {
|
||||
var n int64
|
||||
|
||||
paths := make([]string, len(index.index))
|
||||
|
||||
i := 0
|
||||
for path := range index.index {
|
||||
paths[i] = path
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(paths)
|
||||
|
||||
nn, err := fmt.Fprintf(w, "%s %s\n", "FILE", "LOCATION")
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
packages := index.index[path]
|
||||
parts := make([]string, 0, len(packages))
|
||||
for i := range packages {
|
||||
name := packages[i].QualifiedName()
|
||||
if !utils.StrSliceHasItem(parts, name) {
|
||||
parts = append(parts, name)
|
||||
}
|
||||
}
|
||||
nn, err = fmt.Fprintf(w, "%s %s\n", path, strings.Join(parts, ","))
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
+87
-13
@@ -2,11 +2,13 @@ package deb
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"github.com/mkrautz/goar"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/go-xz"
|
||||
"github.com/smira/lzma"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -24,16 +26,16 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) {
|
||||
for {
|
||||
header, err := library.Next()
|
||||
if err == io.EOF {
|
||||
return nil, fmt.Errorf("unable to find control.tar.gz part")
|
||||
return nil, fmt.Errorf("unable to find control.tar.gz part in package %s", packageFile)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read .deb archive: %s", err)
|
||||
return nil, fmt.Errorf("unable to read .deb archive %s: %s", packageFile, err)
|
||||
}
|
||||
|
||||
if header.Name == "control.tar.gz" {
|
||||
ungzip, err := gzip.NewReader(library)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ungzip: %s", err)
|
||||
return nil, fmt.Errorf("unable to ungzip control file from %s. Error: %s", packageFile, err)
|
||||
}
|
||||
defer ungzip.Close()
|
||||
|
||||
@@ -41,15 +43,15 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) {
|
||||
for {
|
||||
tarHeader, err := untar.Next()
|
||||
if err == io.EOF {
|
||||
return nil, fmt.Errorf("unable to find control file")
|
||||
return nil, fmt.Errorf("unable to find control file in %s", packageFile)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read .tar archive: %s", err)
|
||||
return nil, fmt.Errorf("unable to read .tar archive from %s. Error: %s", packageFile, err)
|
||||
}
|
||||
|
||||
if tarHeader.Name == "./control" {
|
||||
if tarHeader.Name == "./control" || tarHeader.Name == "control" {
|
||||
reader := NewControlFileReader(untar)
|
||||
stanza, err := reader.ReadStanza()
|
||||
stanza, err := reader.ReadStanza(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -69,16 +71,16 @@ func GetControlFileFromDsc(dscFile string, verifier utils.Verifier) (Stanza, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
line, err := bufio.NewReader(file).ReadString('\n')
|
||||
isClearSigned, err := verifier.IsClearSigned(file)
|
||||
file.Seek(0, 0)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file.Seek(0, 0)
|
||||
|
||||
var text *os.File
|
||||
|
||||
if strings.Index(line, "BEGIN PGP SIGN") != -1 {
|
||||
if isClearSigned {
|
||||
text, err = verifier.ExtractClearsigned(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -89,7 +91,7 @@ func GetControlFileFromDsc(dscFile string, verifier utils.Verifier) (Stanza, err
|
||||
}
|
||||
|
||||
reader := NewControlFileReader(text)
|
||||
stanza, err := reader.ReadStanza()
|
||||
stanza, err := reader.ReadStanza(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -97,3 +99,75 @@ func GetControlFileFromDsc(dscFile string, verifier utils.Verifier) (Stanza, err
|
||||
return stanza, nil
|
||||
|
||||
}
|
||||
|
||||
// GetContentsFromDeb returns list of files installed by .deb package
|
||||
func GetContentsFromDeb(packageFile string) ([]string, error) {
|
||||
file, err := os.Open(packageFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
library := ar.NewReader(file)
|
||||
for {
|
||||
header, err := library.Next()
|
||||
if err == io.EOF {
|
||||
return nil, fmt.Errorf("unable to find data.tar.* part in %s", packageFile)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read .deb archive from %s: %s", packageFile, err)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(header.Name, "data.tar") {
|
||||
var tarInput io.Reader
|
||||
|
||||
switch header.Name {
|
||||
case "data.tar":
|
||||
tarInput = library
|
||||
case "data.tar.gz":
|
||||
ungzip, err := gzip.NewReader(library)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ungzip data.tar.gz from %s: %s", packageFile,err)
|
||||
}
|
||||
defer ungzip.Close()
|
||||
tarInput = ungzip
|
||||
case "data.tar.bz2":
|
||||
tarInput = bzip2.NewReader(library)
|
||||
case "data.tar.xz":
|
||||
unxz, err := xz.NewReader(library)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unxz data.tar.xz from %s: %s", packageFile, err)
|
||||
}
|
||||
defer unxz.Close()
|
||||
tarInput = unxz
|
||||
case "data.tar.lzma":
|
||||
unlzma := lzma.NewReader(library)
|
||||
defer unlzma.Close()
|
||||
tarInput = unlzma
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported tar compression in %s: %s", packageFile, header.Name)
|
||||
}
|
||||
|
||||
untar := tar.NewReader(tarInput)
|
||||
var results []string
|
||||
for {
|
||||
tarHeader, err := untar.Next()
|
||||
if err == io.EOF {
|
||||
return results, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read .tar archive from %s: %s", packageFile, err)
|
||||
}
|
||||
|
||||
if tarHeader.Typeflag == tar.TypeDir {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(tarHeader.Name, "./") {
|
||||
tarHeader.Name = tarHeader.Name[2:]
|
||||
}
|
||||
results = append(results, tarHeader.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-3
@@ -2,13 +2,14 @@ package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type DebSuite struct {
|
||||
debFile, dscFile, dscFileNoSign string
|
||||
debFile, debFile2, dscFile, dscFileNoSign string
|
||||
}
|
||||
|
||||
var _ = Suite(&DebSuite{})
|
||||
@@ -16,6 +17,7 @@ var _ = Suite(&DebSuite{})
|
||||
func (s *DebSuite) SetUpSuite(c *C) {
|
||||
_, _File, _, _ := runtime.Caller(0)
|
||||
s.debFile = filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb")
|
||||
s.debFile2 = filepath.Join(filepath.Dir(_File), "../system/changes/hardlink_0.2.1_amd64.deb")
|
||||
s.dscFile = filepath.Join(filepath.Dir(_File), "../system/files/pyspi_0.6.1-1.3.dsc")
|
||||
s.dscFileNoSign = filepath.Join(filepath.Dir(_File), "../system/files/pyspi-0.6.1-1.3.stripped.dsc")
|
||||
}
|
||||
@@ -26,7 +28,7 @@ func (s *DebSuite) TestGetControlFileFromDeb(c *C) {
|
||||
|
||||
_, _File, _, _ := runtime.Caller(0)
|
||||
_, err = GetControlFileFromDeb(_File)
|
||||
c.Check(err, ErrorMatches, "unable to read .deb archive: ar: missing global header")
|
||||
c.Check(err, ErrorMatches, "^.+ar: missing global header")
|
||||
|
||||
st, err := GetControlFileFromDeb(s.debFile)
|
||||
c.Check(err, IsNil)
|
||||
@@ -54,3 +56,15 @@ func (s *DebSuite) TestGetControlFileFromDsc(c *C) {
|
||||
c.Check(st["Version"], Equals, "0.6.1-1.4")
|
||||
c.Check(st["Source"], Equals, "pyspi")
|
||||
}
|
||||
|
||||
func (s *DebSuite) TestGetContentsFromDeb(c *C) {
|
||||
contents, err := GetContentsFromDeb(s.debFile)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(contents, DeepEquals, []string{"usr/share/doc/libboost-program-options-dev/changelog.gz",
|
||||
"usr/share/doc/libboost-program-options-dev/copyright"})
|
||||
|
||||
contents, err = GetContentsFromDeb(s.debFile2)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(contents, DeepEquals, []string{"usr/bin/hardlink", "usr/share/man/man1/hardlink.1.gz",
|
||||
"usr/share/doc/hardlink/changelog.gz", "usr/share/doc/hardlink/copyright", "usr/share/doc/hardlink/NEWS.Debian.gz"})
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Launch gocheck tests
|
||||
|
||||
+143
-25
@@ -5,14 +5,83 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Stanza or paragraph of Debian control file
|
||||
type Stanza map[string]string
|
||||
|
||||
// Canonical order of fields in stanza
|
||||
var canocialOrder = []string{"Origin", "Label", "Suite", "Package", "Version", "Installed-Size", "Priority", "Section", "Maintainer",
|
||||
"Architecture", "Codename", "Date", "Architectures", "Components", "Description", "MD5sum", "MD5Sum", "SHA1", "SHA256"}
|
||||
// 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) {
|
||||
@@ -23,16 +92,42 @@ func (s Stanza) Copy() (result Stanza) {
|
||||
return
|
||||
}
|
||||
|
||||
// Write single field from Stanza to writer
|
||||
func writeField(w *bufio.Writer, field, value string) (err error) {
|
||||
_, multiline := multilineFields[field]
|
||||
func isMultilineField(field string, isRelease bool) bool {
|
||||
switch field {
|
||||
case "Description":
|
||||
return true
|
||||
case "Files":
|
||||
return true
|
||||
case "Changes":
|
||||
return true
|
||||
case "Checksums-Sha1":
|
||||
return true
|
||||
case "Checksums-Sha256":
|
||||
return true
|
||||
case "Package-List":
|
||||
return true
|
||||
case "MD5Sum":
|
||||
return isRelease
|
||||
case "SHA1":
|
||||
return isRelease
|
||||
case "SHA256":
|
||||
return isRelease
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if !multiline {
|
||||
// Write single field from Stanza to writer
|
||||
func writeField(w *bufio.Writer, field, value string, isRelease bool) (err error) {
|
||||
if !isMultilineField(field, isRelease) {
|
||||
_, err = w.WriteString(field + ": " + value + "\n")
|
||||
} else {
|
||||
if !strings.HasSuffix(value, "\n") {
|
||||
value = value + "\n"
|
||||
}
|
||||
|
||||
if field != "Description" {
|
||||
value = "\n" + value
|
||||
}
|
||||
_, err = w.WriteString(field + ":" + value)
|
||||
}
|
||||
|
||||
@@ -40,12 +135,20 @@ 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)
|
||||
err := writeField(w, field, value)
|
||||
err := writeField(w, field, value, isRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -53,7 +156,7 @@ func (s Stanza) WriteTo(w *bufio.Writer) error {
|
||||
}
|
||||
|
||||
for field, value := range s {
|
||||
err := writeField(w, field, value)
|
||||
err := writeField(w, field, value, isRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -67,18 +170,33 @@ var (
|
||||
ErrMalformedStanza = errors.New("malformed stanza syntax")
|
||||
)
|
||||
|
||||
var multilineFields = make(map[string]bool)
|
||||
func canonicalCase(field string) string {
|
||||
upper := strings.ToUpper(field)
|
||||
switch upper {
|
||||
case "SHA1", "SHA256", "SHA512":
|
||||
return upper
|
||||
case "MD5SUM":
|
||||
return "MD5Sum"
|
||||
case "NOTAUTOMATIC":
|
||||
return "NotAutomatic"
|
||||
case "BUTAUTOMATICUPGRADES":
|
||||
return "ButAutomaticUpgrades"
|
||||
}
|
||||
|
||||
func init() {
|
||||
multilineFields["Description"] = true
|
||||
multilineFields["Files"] = true
|
||||
multilineFields["Changes"] = true
|
||||
multilineFields["Checksums-Sha1"] = true
|
||||
multilineFields["Checksums-Sha256"] = true
|
||||
multilineFields["Package-List"] = true
|
||||
multilineFields["SHA256"] = true
|
||||
multilineFields["SHA1"] = true
|
||||
multilineFields["MD5Sum"] = true
|
||||
startOfWord := true
|
||||
|
||||
return strings.Map(func(r rune) rune {
|
||||
if startOfWord {
|
||||
startOfWord = false
|
||||
return unicode.ToUpper(r)
|
||||
}
|
||||
|
||||
if r == '-' {
|
||||
startOfWord = true
|
||||
}
|
||||
|
||||
return unicode.ToLower(r)
|
||||
}, field)
|
||||
}
|
||||
|
||||
// ControlFileReader implements reading of control files stanza by stanza
|
||||
@@ -92,7 +210,7 @@ func NewControlFileReader(r io.Reader) *ControlFileReader {
|
||||
}
|
||||
|
||||
// ReadStanza reeads one stanza from control file
|
||||
func (c *ControlFileReader) ReadStanza() (Stanza, error) {
|
||||
func (c *ControlFileReader) ReadStanza(isRelease bool) (Stanza, error) {
|
||||
stanza := make(Stanza, 32)
|
||||
lastField := ""
|
||||
lastFieldMultiline := false
|
||||
@@ -112,15 +230,15 @@ func (c *ControlFileReader) ReadStanza() (Stanza, error) {
|
||||
if lastFieldMultiline {
|
||||
stanza[lastField] += line + "\n"
|
||||
} else {
|
||||
stanza[lastField] += strings.TrimSpace(line)
|
||||
stanza[lastField] += " " + strings.TrimSpace(line)
|
||||
}
|
||||
} else {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, ErrMalformedStanza
|
||||
}
|
||||
lastField = parts[0]
|
||||
_, lastFieldMultiline = multilineFields[lastField]
|
||||
lastField = canonicalCase(parts[0])
|
||||
lastFieldMultiline = isMultilineField(lastField, isRelease)
|
||||
if lastFieldMultiline {
|
||||
stanza[lastField] = parts[1]
|
||||
if parts[1] != "" {
|
||||
|
||||
+22
-9
@@ -3,8 +3,9 @@ package deb
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
. "launchpad.net/gocheck"
|
||||
"strings"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ControlFileSuite struct {
|
||||
@@ -83,18 +84,18 @@ func (s *ControlFileSuite) SetUpTest(c *C) {
|
||||
func (s *ControlFileSuite) TestReadStanza(c *C) {
|
||||
r := NewControlFileReader(s.reader)
|
||||
|
||||
stanza1, err := r.ReadStanza()
|
||||
stanza1, err := r.ReadStanza(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
stanza2, err := r.ReadStanza()
|
||||
stanza2, err := r.ReadStanza(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
stanza3, err := r.ReadStanza()
|
||||
stanza3, err := r.ReadStanza(false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(stanza3, IsNil)
|
||||
|
||||
c.Check(stanza1["Format"], Equals, "3.0 (quilt)")
|
||||
c.Check(stanza1["Build-Depends"], Equals, "debhelper (>= 8),bash-completion (>= 1:1.1-3),libcurl4-nss-dev, libreadline-dev, libxml2-dev, libpcre3-dev, liboauth-dev, xsltproc, docbook-xsl, docbook-xml, dh-autoreconf")
|
||||
c.Check(stanza1["Build-Depends"], Equals, "debhelper (>= 8), bash-completion (>= 1:1.1-3), libcurl4-nss-dev, libreadline-dev, libxml2-dev, libpcre3-dev, liboauth-dev, xsltproc, docbook-xsl, docbook-xml, dh-autoreconf")
|
||||
c.Check(stanza1["Files"], Equals, " 3d5f65778bf3f89be03c313b0024b62c 1980 bti_032-1.dsc\n"+
|
||||
" 1e0d0b693fdeebec268004ba41701baf 59773 bti_032.orig.tar.gz\n"+" ac1229a6d685023aeb8fcb0806324aa8 5065 bti_032-1.debian.tar.gz\n")
|
||||
c.Check(len(stanza2), Equals, 20)
|
||||
@@ -102,12 +103,12 @@ func (s *ControlFileSuite) TestReadStanza(c *C) {
|
||||
|
||||
func (s *ControlFileSuite) TestReadWriteStanza(c *C) {
|
||||
r := NewControlFileReader(s.reader)
|
||||
stanza, err := r.ReadStanza()
|
||||
stanza, err := r.ReadStanza(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w := bufio.NewWriter(buf)
|
||||
err = stanza.Copy().WriteTo(w)
|
||||
err = stanza.Copy().WriteTo(w, true, false)
|
||||
c.Assert(err, IsNil)
|
||||
err = w.Flush()
|
||||
c.Assert(err, IsNil)
|
||||
@@ -115,19 +116,31 @@ func (s *ControlFileSuite) TestReadWriteStanza(c *C) {
|
||||
str := buf.String()
|
||||
|
||||
r = NewControlFileReader(buf)
|
||||
stanza2, err := r.ReadStanza()
|
||||
stanza2, err := r.ReadStanza(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(stanza2, DeepEquals, stanza)
|
||||
c.Assert(strings.HasPrefix(str, "Package: "), Equals, true)
|
||||
}
|
||||
|
||||
func (s *ControlFileSuite) TestCanonicalCase(c *C) {
|
||||
c.Check(canonicalCase("Package"), Equals, "Package")
|
||||
c.Check(canonicalCase("package"), Equals, "Package")
|
||||
c.Check(canonicalCase("pAckaGe"), Equals, "Package")
|
||||
c.Check(canonicalCase("MD5Sum"), Equals, "MD5Sum")
|
||||
c.Check(canonicalCase("SHA1"), Equals, "SHA1")
|
||||
c.Check(canonicalCase("SHA256"), Equals, "SHA256")
|
||||
c.Check(canonicalCase("Package-List"), Equals, "Package-List")
|
||||
c.Check(canonicalCase("package-list"), Equals, "Package-List")
|
||||
c.Check(canonicalCase("packaGe-lIst"), Equals, "Package-List")
|
||||
}
|
||||
|
||||
func (s *ControlFileSuite) BenchmarkReadStanza(c *C) {
|
||||
for i := 0; i < c.N; i++ {
|
||||
reader := bytes.NewBufferString(controlFile)
|
||||
r := NewControlFileReader(reader)
|
||||
for {
|
||||
s, e := r.ReadStanza()
|
||||
s, e := r.ReadStanza(false)
|
||||
if s == nil && e == nil {
|
||||
break
|
||||
}
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/awalterschulze/gographviz"
|
||||
"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
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
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) {
|
||||
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") || strings.HasSuffix(info.Name(), ".ddeb") {
|
||||
packageFiles = append(packageFiles, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") ||
|
||||
strings.HasSuffix(info.Name(), ".dsc") || strings.HasSuffix(info.Name(), ".ddeb") {
|
||||
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, restriction PackageQuery) (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
|
||||
}
|
||||
|
||||
if p.Name == "" {
|
||||
reporter.Warning("Empty package name on %s", file)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
if p.Version == "" {
|
||||
reporter.Warning("Empty version on %s", file)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
if p.Architecture == "" {
|
||||
reporter.Warning("Empty architecture on %s", file)
|
||||
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
|
||||
}
|
||||
|
||||
if restriction != nil && !restriction.Matches(p) {
|
||||
reporter.Warning("%s has been ignored as it doesn't match restriction", p)
|
||||
failedFiles = append(failedFiles, file)
|
||||
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,294 @@
|
||||
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
|
||||
onlyGzip 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")
|
||||
if file.onlyGzip {
|
||||
exts = []string{".gz"}
|
||||
}
|
||||
}
|
||||
|
||||
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) ContentsIndex(component, arch string, udeb bool) *indexFile {
|
||||
if arch == "source" {
|
||||
udeb = false
|
||||
}
|
||||
key := fmt.Sprintf("ci-%s-%s-%v", component, arch, udeb)
|
||||
file, ok := files.indexes[key]
|
||||
if !ok {
|
||||
var relativePath string
|
||||
|
||||
if udeb {
|
||||
relativePath = filepath.Join(component, fmt.Sprintf("Contents-udeb-%s", arch))
|
||||
} else {
|
||||
relativePath = filepath.Join(component, fmt.Sprintf("Contents-%s", arch))
|
||||
}
|
||||
|
||||
file = &indexFile{
|
||||
parent: files,
|
||||
discardable: true,
|
||||
compressable: true,
|
||||
onlyGzip: true,
|
||||
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
|
||||
}
|
||||
+137
-86
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dependency options
|
||||
@@ -39,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
|
||||
@@ -86,11 +91,11 @@ func NewPackageListFromRefList(reflist *PackageRefList, collection *PackageColle
|
||||
|
||||
// Add appends package to package list, additionally checking for uniqueness
|
||||
func (l *PackageList) Add(p *Package) error {
|
||||
key := string(p.Key(""))
|
||||
key := string(p.ShortKey(""))
|
||||
existing, ok := l.packages[key]
|
||||
if ok {
|
||||
if !existing.Equals(p) {
|
||||
return fmt.Errorf("conflict in package %s: %#v != %#v", p, existing, p)
|
||||
return &PackageConflictError{fmt.Errorf("conflict in package %s", p)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -101,7 +106,7 @@ func (l *PackageList) Add(p *Package) error {
|
||||
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
||||
}
|
||||
|
||||
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= p.Name })
|
||||
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.lessPackages(p, l.packagesIndex[j]) })
|
||||
|
||||
// insert p into l.packagesIndex in position i
|
||||
l.packagesIndex = append(l.packagesIndex, nil)
|
||||
@@ -123,6 +128,22 @@ func (l *PackageList) ForEach(handler func(*Package) error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ForEachIndexed calls handler for each package in list in indexed order
|
||||
func (l *PackageList) ForEachIndexed(handler func(*Package) error) error {
|
||||
if !l.indexed {
|
||||
panic("list not indexed, can't iterate")
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, p := range l.packagesIndex {
|
||||
err = handler(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Len returns number of packages in the list
|
||||
func (l *PackageList) Len() int {
|
||||
return len(l.packages)
|
||||
@@ -137,7 +158,7 @@ func (l *PackageList) Append(pl *PackageList) error {
|
||||
existing, ok := l.packages[k]
|
||||
if ok {
|
||||
if !existing.Equals(p) {
|
||||
return fmt.Errorf("conflict in package %s: %#v != %#v", p, existing, p)
|
||||
return fmt.Errorf("conflict in package %s", p)
|
||||
}
|
||||
} else {
|
||||
l.packages[k] = p
|
||||
@@ -149,7 +170,7 @@ func (l *PackageList) Append(pl *PackageList) error {
|
||||
|
||||
// Remove removes package from the list, and updates index when required
|
||||
func (l *PackageList) Remove(p *Package) {
|
||||
delete(l.packages, string(p.Key("")))
|
||||
delete(l.packages, string(p.ShortKey("")))
|
||||
if l.indexed {
|
||||
for _, provides := range p.Provides {
|
||||
for i, pkg := range l.providesIndex[provides] {
|
||||
@@ -189,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)
|
||||
@@ -220,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 {
|
||||
@@ -229,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)
|
||||
}
|
||||
@@ -247,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 == "" {
|
||||
@@ -255,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) == 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...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,13 +322,29 @@ func (l *PackageList) Swap(i, j int) {
|
||||
l.packagesIndex[i], l.packagesIndex[j] = l.packagesIndex[j], l.packagesIndex[i]
|
||||
}
|
||||
|
||||
// Compare compares two names in lexographical order
|
||||
func (l *PackageList) lessPackages(iPkg, jPkg *Package) bool {
|
||||
if iPkg.Name == jPkg.Name {
|
||||
cmp := CompareVersions(iPkg.Version, jPkg.Version)
|
||||
if cmp == 0 {
|
||||
return iPkg.Architecture < jPkg.Architecture
|
||||
}
|
||||
return cmp == 1
|
||||
}
|
||||
|
||||
return iPkg.Name < jPkg.Name
|
||||
}
|
||||
|
||||
// Less compares two packages by name (lexographical) and version (latest to oldest)
|
||||
func (l *PackageList) Less(i, j int) bool {
|
||||
return l.packagesIndex[i].Name < l.packagesIndex[j].Name
|
||||
return l.lessPackages(l.packagesIndex[i], l.packagesIndex[j])
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -325,16 +363,49 @@ func (l *PackageList) PrepareIndex() {
|
||||
l.indexed = true
|
||||
}
|
||||
|
||||
// Search searches package index for specified package
|
||||
func (l *PackageList) Search(dep Dependency) *Package {
|
||||
// Scan searches package index using full scan
|
||||
func (l *PackageList) Scan(q PackageQuery) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
for _, pkg := range l.packages {
|
||||
if q.Matches(pkg) {
|
||||
result.Add(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
panic("list not indexed, can't search")
|
||||
}
|
||||
|
||||
if dep.Relation == VersionDontCare {
|
||||
for _, p := range l.providesIndex[dep.Pkg] {
|
||||
if p.MatchesArchitecture(dep.Architecture) {
|
||||
return p
|
||||
if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) {
|
||||
searchResults = append(searchResults, p)
|
||||
|
||||
if !allMatches {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,16 +415,21 @@ func (l *PackageList) Search(dep Dependency) *Package {
|
||||
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg {
|
||||
p := l.packagesIndex[i]
|
||||
if p.MatchesDependency(dep) {
|
||||
return p
|
||||
searchResults = append(searchResults, p)
|
||||
|
||||
if !allMatches {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filter filters package index by specified queries (ORed together), possibly pulling dependencies
|
||||
func (l *PackageList) Filter(queries []string, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) {
|
||||
func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) {
|
||||
if !l.indexed {
|
||||
panic("list not indexed, can't filter")
|
||||
}
|
||||
@@ -361,53 +437,17 @@ func (l *PackageList) Filter(queries []string, withDependencies bool, source *Pa
|
||||
result := NewPackageList()
|
||||
|
||||
for _, query := range queries {
|
||||
isDepQuery := strings.IndexAny(query, " (){}=<>") != -1
|
||||
|
||||
if !isDepQuery {
|
||||
// try to interpret query as package string representation
|
||||
|
||||
// convert Package.String() to Package.Key()
|
||||
i := strings.Index(query, "_")
|
||||
if i != -1 {
|
||||
pkg, query := query[:i], query[i+1:]
|
||||
j := strings.LastIndex(query, "_")
|
||||
if j != -1 {
|
||||
version, arch := query[:j], query[j+1:]
|
||||
p := l.packages["P"+arch+" "+pkg+" "+version]
|
||||
if p != nil {
|
||||
result.Add(p)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try as dependency
|
||||
dep, err := ParseDependency(query)
|
||||
if err != nil {
|
||||
if isDepQuery {
|
||||
return nil, err
|
||||
}
|
||||
// parsing failed, but probably that wasn't a dep query
|
||||
continue
|
||||
}
|
||||
|
||||
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= dep.Pkg })
|
||||
|
||||
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg {
|
||||
p := l.packagesIndex[i]
|
||||
if p.MatchesDependency(dep) {
|
||||
result.Add(p)
|
||||
}
|
||||
i++
|
||||
}
|
||||
result.Append(query.Query(l))
|
||||
}
|
||||
|
||||
if withDependencies {
|
||||
added := result.Len()
|
||||
result.PrepareIndex()
|
||||
|
||||
dependencySource := NewPackageList()
|
||||
dependencySource.Append(source)
|
||||
if source != nil {
|
||||
dependencySource.Append(source)
|
||||
}
|
||||
dependencySource.Append(result)
|
||||
dependencySource.PrepareIndex()
|
||||
|
||||
@@ -423,11 +463,22 @@ func (l *PackageList) Filter(queries []string, withDependencies bool, source *Pa
|
||||
|
||||
// try to satisfy dependencies
|
||||
for _, dep := range missing {
|
||||
p := l.Search(dep)
|
||||
if p != nil {
|
||||
result.Add(p)
|
||||
dependencySource.Add(p)
|
||||
added++
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+187
-50
@@ -2,11 +2,49 @@ package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "launchpad.net/gocheck"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type containsChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
func (c *containsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
var (
|
||||
pkgSlice1 []*Package
|
||||
pkgSlice2 []*Package
|
||||
ok bool
|
||||
)
|
||||
|
||||
pkgMap := make(map[*Package]bool)
|
||||
|
||||
pkgSlice1, ok = params[0].([]*Package)
|
||||
if !ok {
|
||||
return false, "The first parameter is not a Package slice"
|
||||
}
|
||||
pkgSlice2, ok = params[1].([]*Package)
|
||||
if !ok {
|
||||
return false, "The second parameter is not a Package slice"
|
||||
}
|
||||
|
||||
for _, pkg := range pkgSlice2 {
|
||||
pkgMap[pkg] = true
|
||||
}
|
||||
|
||||
for _, pkg := range pkgSlice1 {
|
||||
if _, ok := pkgMap[pkg]; !ok {
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
var Contains = &containsChecker{&CheckerInfo{Name: "Contains", Params: []string{"Container", "Expected to contain"}}}
|
||||
|
||||
type PackageListSuite struct {
|
||||
// Simple list with "real" packages from stanzas
|
||||
list *PackageList
|
||||
@@ -14,8 +52,10 @@ type PackageListSuite struct {
|
||||
|
||||
// Mocked packages in list
|
||||
packages []*Package
|
||||
packages2 []*Package
|
||||
sourcePackages []*Package
|
||||
il *PackageList
|
||||
il2 *PackageList
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageListSuite{})
|
||||
@@ -29,7 +69,7 @@ func (s *PackageListSuite) SetUpTest(c *C) {
|
||||
stanza["Package"] = "mars-invaders"
|
||||
s.p3 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Size"] = "42"
|
||||
stanza["Source"] = "unknown-planet"
|
||||
s.p4 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Package"] = "lonely-strangers"
|
||||
@@ -40,31 +80,45 @@ 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)
|
||||
}
|
||||
s.il.PrepareIndex()
|
||||
|
||||
s.il2 = NewPackageList()
|
||||
s.packages2 = []*Package{
|
||||
{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)
|
||||
}
|
||||
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{}},
|
||||
}
|
||||
|
||||
}
|
||||
@@ -196,35 +250,60 @@ func (s *PackageListSuite) TestAppend(c *C) {
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestSearch(c *C) {
|
||||
c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}) }, Panics, "list not indexed, can't search")
|
||||
//allMatches = False
|
||||
c.Check(func() { s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}, false) }, Panics, "list not indexed, can't search")
|
||||
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}), Equals, s.packages[4])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}), IsNil)
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}, false), DeepEquals, []*Package{s.packages[4]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "puppy"}, false), IsNil)
|
||||
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}), IsNil)
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}, false), IsNil)
|
||||
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}), IsNil)
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}, false), IsNil)
|
||||
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}), IsNil)
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}, false), IsNil)
|
||||
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}), IsNil)
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}, false), IsNil)
|
||||
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}), Equals, s.packages[3])
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}), IsNil)
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, false), DeepEquals, []*Package{s.packages[3]})
|
||||
c.Check(s.il.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, false), IsNil)
|
||||
|
||||
// search w/o version should return package with latest version
|
||||
c.Check(s.il.Search(Dependency{Architecture: "source", Pkg: "dpkg"}, false), DeepEquals, []*Package{s.packages[13]})
|
||||
|
||||
// allMatches = True
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "mail-agent"}, true), Contains, []*Package{s.packages2[0], s.packages2[1]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "puppy"}, true), IsNil)
|
||||
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]})
|
||||
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionEqual, Version: "3"}, true), Contains, []*Package{s.packages2[5]})
|
||||
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLess, Version: "1.1~"}, true), IsNil)
|
||||
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1-bp1"}, true), Contains, []*Package{s.packages2[2]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.0"}, true), IsNil)
|
||||
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "1.1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreater, Version: "5.0"}, true), IsNil)
|
||||
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}, true), Contains, []*Package{s.packages2[4], s.packages2[5]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}, true), Contains, []*Package{s.packages2[2], s.packages2[3], s.packages2[4], s.packages2[5]})
|
||||
c.Check(s.il2.Search(Dependency{Architecture: "amd64", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "5.0"}, true), IsNil)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestFilter(c *C) {
|
||||
c.Check(func() { s.list.Filter([]string{"abcd_0.3_i386"}, false, nil, 0, nil) }, Panics, "list not indexed, can't filter")
|
||||
|
||||
_, err := s.il.Filter([]string{"app >3)"}, false, nil, 0, nil)
|
||||
c.Check(err, ErrorMatches, "unable to parse dependency.*")
|
||||
c.Check(func() { s.list.Filter([]PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}}, false, nil, 0, nil) }, Panics, "list not indexed, can't filter")
|
||||
|
||||
plString := func(l *PackageList) string {
|
||||
list := make([]string, 0, l.Len())
|
||||
@@ -237,25 +316,83 @@ func (s *PackageListSuite) TestFilter(c *C) {
|
||||
return strings.Join(list, " ")
|
||||
}
|
||||
|
||||
result, err := s.il.Filter([]string{"app_1.1~bp1_i386"}, false, nil, 0, nil)
|
||||
result, err := s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
|
||||
|
||||
result, err = s.il.Filter([]string{"app_1.1~bp1_i386", "dpkg_1.7_source", "dpkg_1.8_amd64"}, false, nil, 0, nil)
|
||||
result, err = s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, &PkgQuery{"dpkg", "1.7", "source"},
|
||||
&PkgQuery{"dpkg", "1.8", "amd64"}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_source")
|
||||
|
||||
result, err = s.il.Filter([]string{"app", "dpkg (>>1.6.1-3)", "app (>=1.0)", "xyz", "aa (>>3.0)"}, false, nil, 0, nil)
|
||||
result, err = s.il.Filter([]PackageQuery{
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "app"}},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "xyz"}},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "aa", Relation: VersionGreater, Version: "3.0"}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source")
|
||||
|
||||
result, err = s.il.Filter([]string{"app {i386}"}, true, NewPackageList(), 0, []string{"i386"})
|
||||
result, err = s.il.Filter([]PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "app", Architecture: "i386"}}}, true, NewPackageList(), 0, []string{"i386"})
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386")
|
||||
|
||||
result, err = s.il.Filter([]string{"app (>=0.9)", "lib", "data"}, true, NewPackageList(), 0, []string{"i386", "amd64"})
|
||||
result, err = s.il.Filter([]PackageQuery{
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "0.9"}},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "lib"}},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "data"}}}, true, NewPackageList(), 0, []string{"i386", "amd64"})
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&AndQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
|
||||
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
|
||||
&FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_i386 data_1.1~bp1_all")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&AndQuery{&FieldQuery{Field: "Version", Relation: VersionGreaterOrEqual, Value: "1.0"},
|
||||
&FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: "s390"}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.0_s390 data_1.1~bp1_all")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&AndQuery{
|
||||
&FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&NotQuery{
|
||||
&FieldQuery{Field: "$Architecture", Relation: VersionPatternMatch, Value: "i*6"}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.6.1-3_arm dpkg_1.6.1-3_source dpkg_1.7_source libx_1.5_arm")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&AndQuery{
|
||||
&FieldQuery{Field: "$Architecture", Relation: VersionRegexp, Value: "i.*6", Regexp: regexp.MustCompile("i.*6")}, &PkgQuery{"app", "1.1~bp1", "i386"}}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&AndQuery{
|
||||
&FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")},
|
||||
&NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}},
|
||||
}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "aa_2.0-1_i386 app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 mailer_3.5.8_i386")
|
||||
|
||||
result, err = s.il.Filter([]PackageQuery{&AndQuery{
|
||||
&NotQuery{Q: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: "data"}},
|
||||
&FieldQuery{Field: "Name", Relation: VersionRegexp, Value: "a", Regexp: regexp.MustCompile("a")},
|
||||
}}, false, nil, 0, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(plString(result), Equals, "aa_2.0-1_i386 app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 mailer_3.5.8_i386")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestVerifyDependencies(c *C) {
|
||||
@@ -265,7 +402,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)
|
||||
@@ -273,8 +410,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)
|
||||
@@ -282,11 +419,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:.*")
|
||||
|
||||
+9
-4
@@ -2,17 +2,18 @@ package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/go-uuid/uuid"
|
||||
"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
|
||||
@@ -21,6 +22,8 @@ type LocalRepo struct {
|
||||
DefaultDistribution string `codec:",omitempty"`
|
||||
// DefaultComponent
|
||||
DefaultComponent string `codec:",omitempty"`
|
||||
// Uploaders configuration
|
||||
Uploaders *Uploaders `code:",omitempty" json:"-"`
|
||||
// "Snapshot" of current list of packages
|
||||
packageRefs *PackageRefList
|
||||
}
|
||||
@@ -88,6 +91,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 +99,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 +109,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 {
|
||||
|
||||
+217
-67
@@ -1,6 +1,7 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
@@ -24,16 +25,26 @@ 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?
|
||||
V06Plus bool
|
||||
// Offload fields
|
||||
deps *PackageDependencies
|
||||
extra *Stanza
|
||||
files *PackageFiles
|
||||
deps *PackageDependencies
|
||||
extra *Stanza
|
||||
files *PackageFiles
|
||||
contents []string
|
||||
// Mother collection
|
||||
collection *PackageCollection
|
||||
}
|
||||
|
||||
// Check interface
|
||||
var (
|
||||
_ json.Marshaler = &Package{}
|
||||
)
|
||||
|
||||
// NewPackageFromControlFile creates Package from parsed Debian control file
|
||||
func NewPackageFromControlFile(input Stanza) *Package {
|
||||
result := &Package{
|
||||
@@ -41,6 +52,7 @@ func NewPackageFromControlFile(input Stanza) *Package {
|
||||
Version: input["Version"],
|
||||
Architecture: input["Architecture"],
|
||||
Source: input["Source"],
|
||||
V06Plus: true,
|
||||
}
|
||||
|
||||
delete(input, "Package")
|
||||
@@ -50,12 +62,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"]),
|
||||
},
|
||||
@@ -63,6 +81,7 @@ func NewPackageFromControlFile(input Stanza) *Package {
|
||||
|
||||
delete(input, "Filename")
|
||||
delete(input, "MD5sum")
|
||||
delete(input, "MD5Sum")
|
||||
delete(input, "SHA1")
|
||||
delete(input, "SHA256")
|
||||
delete(input, "Size")
|
||||
@@ -89,68 +108,27 @@ func NewSourcePackageFromControlFile(input Stanza) (*Package, error) {
|
||||
Version: input["Version"],
|
||||
Architecture: "source",
|
||||
SourceArchitecture: input["Architecture"],
|
||||
V06Plus: true,
|
||||
}
|
||||
|
||||
delete(input, "Package")
|
||||
delete(input, "Version")
|
||||
delete(input, "Architecture")
|
||||
|
||||
var err error
|
||||
|
||||
files := make(PackageFiles, 0, 3)
|
||||
|
||||
parseSums := func(field string, setter func(sum *utils.ChecksumInfo, data string)) error {
|
||||
for _, line := range strings.Split(input[field], "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
|
||||
if len(parts) != 3 {
|
||||
return fmt.Errorf("unparseable hash sum line: %#v", line)
|
||||
}
|
||||
|
||||
size, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse size: %s", err)
|
||||
}
|
||||
|
||||
filename := filepath.Base(parts[2])
|
||||
|
||||
found := false
|
||||
pos := 0
|
||||
for i, file := range files {
|
||||
if file.Filename == filename {
|
||||
found = true
|
||||
pos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
files = append(files, PackageFile{Filename: filename, downloadPath: input["Directory"]})
|
||||
pos = len(files) - 1
|
||||
}
|
||||
|
||||
files[pos].Checksums.Size = size
|
||||
setter(&files[pos].Checksums, parts[0])
|
||||
}
|
||||
|
||||
delete(input, field)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := parseSums("Files", func(sum *utils.ChecksumInfo, data string) { sum.MD5 = data })
|
||||
files, err = files.ParseSumFields(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = parseSums("Checksums-Sha1", func(sum *utils.ChecksumInfo, data string) { sum.SHA1 = data })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = parseSums("Checksums-Sha256", func(sum *utils.ChecksumInfo, data string) { sum.SHA256 = data })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
delete(input, "Files")
|
||||
delete(input, "Checksums-Sha1")
|
||||
delete(input, "Checksums-Sha256")
|
||||
|
||||
for i := range files {
|
||||
files[i].downloadPath = input["Directory"]
|
||||
}
|
||||
|
||||
result.UpdateFiles(files)
|
||||
@@ -165,9 +143,26 @@ 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 {
|
||||
return []byte(prefix + "P" + p.Architecture + " " + p.Name + " " + p.Version)
|
||||
if p.V06Plus {
|
||||
return []byte(fmt.Sprintf("%sP%s %s %s %08x", prefix, p.Architecture, p.Name, p.Version, p.FilesHash))
|
||||
}
|
||||
|
||||
return p.ShortKey(prefix)
|
||||
}
|
||||
|
||||
// ShortKey returns key for the package that should be unique in one list
|
||||
func (p *Package) ShortKey(prefix string) []byte {
|
||||
return []byte(fmt.Sprintf("%sP%s %s %s", prefix, p.Architecture, p.Name, p.Version))
|
||||
}
|
||||
|
||||
// String creates readable representation
|
||||
@@ -175,6 +170,87 @@ func (p *Package) String() string {
|
||||
return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture)
|
||||
}
|
||||
|
||||
// ExtendedStanza returns package stanza enhanced with aptly-specific fields
|
||||
func (p *Package) ExtendedStanza() Stanza {
|
||||
stanza := p.Stanza()
|
||||
stanza["FilesHash"] = fmt.Sprintf("%08x", p.FilesHash)
|
||||
stanza["Key"] = string(p.Key(""))
|
||||
stanza["ShortKey"] = string(p.ShortKey(""))
|
||||
|
||||
return stanza
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaller interface
|
||||
func (p *Package) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.ExtendedStanza())
|
||||
}
|
||||
|
||||
// GetField returns fields from package
|
||||
func (p *Package) GetField(name string) string {
|
||||
switch name {
|
||||
// $Version is handled in FieldQuery
|
||||
case "$Source":
|
||||
if p.IsSource {
|
||||
return ""
|
||||
}
|
||||
source := p.Source
|
||||
if source == "" {
|
||||
return p.Name
|
||||
} else if pos := strings.Index(source, "("); pos != -1 {
|
||||
return strings.TrimSpace(source[:pos])
|
||||
}
|
||||
return source
|
||||
case "$SourceVersion":
|
||||
if p.IsSource {
|
||||
return ""
|
||||
}
|
||||
source := p.Source
|
||||
if pos := strings.Index(source, "("); pos != -1 {
|
||||
if pos2 := strings.LastIndex(source, ")"); pos2 != -1 && pos2 > pos {
|
||||
return strings.TrimSpace(source[pos+1 : pos2])
|
||||
}
|
||||
}
|
||||
return p.Version
|
||||
case "$Architecture":
|
||||
return p.Architecture
|
||||
case "$PackageType":
|
||||
if p.IsSource {
|
||||
return "source"
|
||||
}
|
||||
if p.IsUdeb {
|
||||
return "udeb"
|
||||
}
|
||||
return "deb"
|
||||
case "Name":
|
||||
return p.Name
|
||||
case "Version":
|
||||
return p.Version
|
||||
case "Architecture":
|
||||
if p.IsSource {
|
||||
return p.SourceArchitecture
|
||||
}
|
||||
return p.Architecture
|
||||
case "Source":
|
||||
return p.Source
|
||||
case "Depends":
|
||||
return strings.Join(p.Deps().Depends, ", ")
|
||||
case "Pre-Depends":
|
||||
return strings.Join(p.Deps().PreDepends, ", ")
|
||||
case "Suggests":
|
||||
return strings.Join(p.Deps().Suggests, ", ")
|
||||
case "Recommends":
|
||||
return strings.Join(p.Deps().Recommends, ", ")
|
||||
case "Provides":
|
||||
return strings.Join(p.Provides, ", ")
|
||||
case "Build-Depends":
|
||||
return strings.Join(p.Deps().BuildDepends, ", ")
|
||||
case "Build-Depends-Indep":
|
||||
return strings.Join(p.Deps().BuildDependsInDep, ", ")
|
||||
default:
|
||||
return p.Extra()[name]
|
||||
}
|
||||
}
|
||||
|
||||
// MatchesArchitecture checks whether packages matches specified architecture
|
||||
func (p *Package) MatchesArchitecture(arch string) bool {
|
||||
if p.Architecture == "all" && arch != "source" {
|
||||
@@ -186,19 +262,23 @@ func (p *Package) MatchesArchitecture(arch string) bool {
|
||||
|
||||
// MatchesDependency checks whether package matches specified dependency
|
||||
func (p *Package) MatchesDependency(dep Dependency) bool {
|
||||
if dep.Pkg != p.Name {
|
||||
return false
|
||||
}
|
||||
|
||||
if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) {
|
||||
return false
|
||||
}
|
||||
|
||||
if dep.Relation == VersionDontCare {
|
||||
return true
|
||||
if utils.StrSliceHasItem(p.Provides, dep.Pkg) {
|
||||
return true
|
||||
}
|
||||
return dep.Pkg == p.Name
|
||||
}
|
||||
|
||||
if dep.Pkg != p.Name {
|
||||
return false
|
||||
}
|
||||
|
||||
r := CompareVersions(p.Version, dep.Version)
|
||||
|
||||
switch dep.Relation {
|
||||
case VersionEqual:
|
||||
return r == 0
|
||||
@@ -210,11 +290,31 @@ func (p *Package) MatchesDependency(dep Dependency) bool {
|
||||
return r <= 0
|
||||
case VersionGreaterOrEqual:
|
||||
return r >= 0
|
||||
case VersionPatternMatch:
|
||||
matched, err := filepath.Match(dep.Version, p.Version)
|
||||
return err == nil && matched
|
||||
case VersionRegexp:
|
||||
return dep.Regexp.FindStringIndex(p.Version) != nil
|
||||
}
|
||||
|
||||
panic("unknown relation")
|
||||
}
|
||||
|
||||
// GetName returns package name
|
||||
func (p *Package) GetName() string {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
// GetVersion returns package version
|
||||
func (p *Package) GetVersion() string {
|
||||
return p.Version
|
||||
}
|
||||
|
||||
// GetArchitecture returns package arch
|
||||
func (p *Package) GetArchitecture() string {
|
||||
return p.Architecture
|
||||
}
|
||||
|
||||
// GetDependencies compiles list of dependenices by flags from options
|
||||
func (p *Package) GetDependencies(options int) (dependencies []string) {
|
||||
deps := p.Deps()
|
||||
@@ -251,6 +351,16 @@ func (p *Package) GetDependencies(options int) (dependencies []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// QualifiedName returns [$SECTION/]$NAME
|
||||
func (p *Package) QualifiedName() string {
|
||||
section := p.Extra()["Section"]
|
||||
if section != "" {
|
||||
return section + "/" + p.Name
|
||||
}
|
||||
|
||||
return p.Name
|
||||
}
|
||||
|
||||
// Extra returns Stanza of extra fields (it may load it from collection)
|
||||
func (p *Package) Extra() Stanza {
|
||||
if p.extra == nil {
|
||||
@@ -289,6 +399,43 @@ func (p *Package) Files() PackageFiles {
|
||||
return *p.files
|
||||
}
|
||||
|
||||
// Contents returns cached package contents
|
||||
func (p *Package) Contents(packagePool aptly.PackagePool) []string {
|
||||
if p.IsSource {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.contents == nil {
|
||||
if p.collection == nil {
|
||||
panic("contents == nil && collection == nil")
|
||||
}
|
||||
|
||||
p.contents = p.collection.loadContents(p, packagePool)
|
||||
}
|
||||
|
||||
return p.contents
|
||||
}
|
||||
|
||||
// CalculateContents looks up contents in package file
|
||||
func (p *Package) CalculateContents(packagePool aptly.PackagePool) []string {
|
||||
if p.IsSource {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := p.Files()[0]
|
||||
path, err := packagePool.Path(file.Filename, file.Checksums.MD5)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
contents, err := GetContentsFromDeb(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
// UpdateFiles saves new state of files
|
||||
func (p *Package) UpdateFiles(files PackageFiles) {
|
||||
p.files = &files
|
||||
@@ -305,7 +452,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 {
|
||||
@@ -333,10 +482,10 @@ func (p *Package) Stanza() (result Stanza) {
|
||||
result["MD5sum"] = f.Checksums.MD5
|
||||
}
|
||||
if f.Checksums.SHA1 != "" {
|
||||
result["SHA1"] = " " + f.Checksums.SHA1
|
||||
result["SHA1"] = f.Checksums.SHA1
|
||||
}
|
||||
if f.Checksums.SHA256 != "" {
|
||||
result["SHA256"] = " " + f.Checksums.SHA256
|
||||
result["SHA256"] = f.Checksums.SHA256
|
||||
}
|
||||
result["Size"] = fmt.Sprintf("%d", f.Checksums.Size)
|
||||
}
|
||||
@@ -376,7 +525,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
|
||||
@@ -391,7 +541,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)
|
||||
err = publishedStorage.LinkFromPool(publishedDirectory, packagePool, sourcePath, f.Checksums.MD5, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+106
-35
@@ -3,6 +3,7 @@ package deb
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/ugorji/go/codec"
|
||||
"path/filepath"
|
||||
@@ -10,14 +11,20 @@ import (
|
||||
|
||||
// PackageCollection does management of packages in DB
|
||||
type PackageCollection struct {
|
||||
db database.Storage
|
||||
encodeBuffer bytes.Buffer
|
||||
db database.Storage
|
||||
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{
|
||||
db: db,
|
||||
db: db,
|
||||
codecHandle: &codec.MsgpackHandle{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +60,7 @@ func (collection *PackageCollection) ByKey(key []byte) (*Package, error) {
|
||||
if len(encoded) > 2 && (encoded[0] != 0xc1 || encoded[1] != 0x1) {
|
||||
oldp := &oldPackage{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, &codec.MsgpackHandle{})
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(oldp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -83,12 +90,12 @@ func (collection *PackageCollection) ByKey(key []byte) (*Package, error) {
|
||||
p.UpdateFiles(PackageFiles(oldp.Files))
|
||||
|
||||
// Save in new format
|
||||
err = collection.internalUpdate(p)
|
||||
err = collection.Update(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
decoder := codec.NewDecoderBytes(encoded[2:], &codec.MsgpackHandle{})
|
||||
decoder := codec.NewDecoderBytes(encoded[2:], collection.codecHandle)
|
||||
err = decoder.Decode(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -109,7 +116,7 @@ func (collection *PackageCollection) loadExtra(p *Package) *Stanza {
|
||||
|
||||
stanza := &Stanza{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, &codec.MsgpackHandle{})
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(stanza)
|
||||
if err != nil {
|
||||
panic("unable to decode extra")
|
||||
@@ -127,7 +134,7 @@ func (collection *PackageCollection) loadDependencies(p *Package) *PackageDepend
|
||||
|
||||
deps := &PackageDependencies{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, &codec.MsgpackHandle{})
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(deps)
|
||||
if err != nil {
|
||||
panic("unable to decode deps")
|
||||
@@ -145,7 +152,7 @@ func (collection *PackageCollection) loadFiles(p *Package) *PackageFiles {
|
||||
|
||||
files := &PackageFiles{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, &codec.MsgpackHandle{})
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(files)
|
||||
if err != nil {
|
||||
panic("unable to decode files")
|
||||
@@ -154,64 +161,82 @@ func (collection *PackageCollection) loadFiles(p *Package) *PackageFiles {
|
||||
return files
|
||||
}
|
||||
|
||||
// Update adds or updates information about package in DB checking for conficts first
|
||||
func (collection *PackageCollection) Update(p *Package) error {
|
||||
existing, err := collection.ByKey(p.Key(""))
|
||||
// loadContents loads or calculates and saves package contents
|
||||
func (collection *PackageCollection) loadContents(p *Package, packagePool aptly.PackagePool) []string {
|
||||
encoded, err := collection.db.Get(p.Key("xC"))
|
||||
if err == nil {
|
||||
// if .Files is different, consider to be conflict
|
||||
if p.FilesHash != existing.FilesHash {
|
||||
return fmt.Errorf("unable to save: %s, conflict with existing packge", p)
|
||||
contents := []string{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(&contents)
|
||||
if err != nil {
|
||||
panic("unable to decode contents")
|
||||
}
|
||||
// ok, .Files are the same, but maybe some meta-data is different, proceed to saving
|
||||
} else {
|
||||
if err != database.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
// ok, package doesn't exist yet
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
return collection.internalUpdate(p)
|
||||
if err != database.ErrNotFound {
|
||||
panic("unable to load contents")
|
||||
}
|
||||
|
||||
contents := p.CalculateContents(packagePool)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = codec.NewEncoder(&buf, collection.codecHandle).Encode(contents)
|
||||
if err != nil {
|
||||
panic("unable to encode contents")
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xC"), buf.Bytes())
|
||||
if err != nil {
|
||||
panic("unable to save contents")
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
// internalUpdate updates information in DB about package and offloaded fields
|
||||
func (collection *PackageCollection) internalUpdate(p *Package) error {
|
||||
encoder := codec.NewEncoder(&collection.encodeBuffer, &codec.MsgpackHandle{})
|
||||
// Update adds or updates information about package in DB checking for conficts first
|
||||
func (collection *PackageCollection) Update(p *Package) error {
|
||||
var encodeBuffer bytes.Buffer
|
||||
|
||||
collection.encodeBuffer.Reset()
|
||||
collection.encodeBuffer.WriteByte(0xc1)
|
||||
collection.encodeBuffer.WriteByte(0x1)
|
||||
encoder := codec.NewEncoder(&encodeBuffer, collection.codecHandle)
|
||||
|
||||
encodeBuffer.Reset()
|
||||
encodeBuffer.WriteByte(0xc1)
|
||||
encodeBuffer.WriteByte(0x1)
|
||||
err := encoder.Encode(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key(""), collection.encodeBuffer.Bytes())
|
||||
err = collection.db.Put(p.Key(""), encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Encode offloaded fields one by one
|
||||
if p.files != nil {
|
||||
collection.encodeBuffer.Reset()
|
||||
encodeBuffer.Reset()
|
||||
err = encoder.Encode(*p.files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xF"), collection.encodeBuffer.Bytes())
|
||||
err = collection.db.Put(p.Key("xF"), encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if p.deps != nil {
|
||||
collection.encodeBuffer.Reset()
|
||||
encodeBuffer.Reset()
|
||||
err = encoder.Encode(*p.deps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xD"), collection.encodeBuffer.Bytes())
|
||||
err = collection.db.Put(p.Key("xD"), encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -220,13 +245,13 @@ func (collection *PackageCollection) internalUpdate(p *Package) error {
|
||||
}
|
||||
|
||||
if p.extra != nil {
|
||||
collection.encodeBuffer.Reset()
|
||||
encodeBuffer.Reset()
|
||||
err = encoder.Encode(*p.extra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xE"), collection.encodeBuffer.Bytes())
|
||||
err = collection.db.Put(p.Key("xE"), encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -254,3 +279,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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user