mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-01 04:40:38 +00:00
Compare commits
670 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| b5d025f141 | |||
| 3c7a2281b2 | |||
| be3ad21fbe | |||
| 5301e8a341 | |||
| 49eed59238 | |||
| 3e78240b39 | |||
| 10bbefeb25 | |||
| 35eac72226 | |||
| 5371f94b7a | |||
| 53adf39d89 | |||
| bc7972ff68 | |||
| 21e8aa5519 | |||
| f0825d93be | |||
| 9e538d9475 | |||
| 042602f991 | |||
| e8a894bc88 | |||
| 59647fe6d0 | |||
| 37a6fb336a | |||
| 7c2faafa91 | |||
| 87295c6580 | |||
| 4ce4923f58 | |||
| 2a83596307 | |||
| 5f29cb202a | |||
| 3800f2c957 | |||
| 6c3b2f686e | |||
| 708fd800df | |||
| 385ac1afd0 | |||
| d9f8673286 | |||
| d1cc562f3c | |||
| e6992d822d | |||
| 0d8debe7b6 | |||
| 89eafd1b21 | |||
| 1a735e849b | |||
| a93052aa8a | |||
| 133d67bffa | |||
| 60d48e890c | |||
| dbcfd6f58b | |||
| cd369f5fa0 | |||
| 992a5cee37 | |||
| fb8686a634 | |||
| cc8baec317 | |||
| 1200e9cc95 | |||
| 3342ce490a | |||
| 4541e0bdae | |||
| 522684aabb | |||
| 8963cd8027 | |||
| 9445f3a0fa | |||
| d69eaeff4e | |||
| 1bac201687 | |||
| 45335da0ed | |||
| b10aeacfc0 | |||
| d9f4686e2c | |||
| 7eb2fdf425 | |||
| c70c196420 | |||
| e81f86f942 | |||
| 6352ce30ed | |||
| cefc3cc2dc | |||
| 376bb69803 | |||
| e33f5792e1 | |||
| 8d214e6d12 | |||
| 73761c311e | |||
| b85f46547b | |||
| 108dc235a7 | |||
| 90dd21b270 | |||
| ce615facf9 | |||
| a8cf83774a | |||
| 470571c7db | |||
| 55807412a1 | |||
| c106e66cff | |||
| d6fd4e46a0 | |||
| b34707faed | |||
| e4de1738ce | |||
| ff045f9a48 | |||
| fd662c9275 | |||
| 83b2e0250d | |||
| 3db7125932 | |||
| efcce4ef3c | |||
| d90f8dba7f | |||
| 173dd775bc | |||
| 4afa3126e4 | |||
| 5a6ccb7259 | |||
| 2c3553ef0b | |||
| 400d0da7d4 | |||
| 4caeea49b1 | |||
| f648c9547c | |||
| d84226a054 | |||
| 006d173d4f | |||
| 6ca62a9d50 | |||
| c7dcc8ff59 | |||
| 4c237ed1b1 | |||
| ec866eb403 | |||
| 53e73c52a4 | |||
| 0f8f43b9f0 | |||
| 7f2f435e2d | |||
| c325119081 | |||
| 3b16ca156a | |||
| 22014206d7 | |||
| 9ff49ff24a | |||
| 37ea845fd9 | |||
| 1a88876e63 | |||
| 1a60ac6aa0 | |||
| a0497058ee | |||
| 140c925079 | |||
| 5bd5e0a827 | |||
| 3c32cd3884 | |||
| 32717e92ba | |||
| c2fc2f9988 | |||
| 1189bca5a4 | |||
| 2315c00ae1 | |||
| e5de8b9353 | |||
| 099806aa82 | |||
| 6a42aad322 | |||
| b13e50a570 | |||
| dff0ab2fa3 | |||
| a7f135a441 | |||
| c71a57169c | |||
| 04588d7566 | |||
| 8153c7e2e9 | |||
| b3e92717bd | |||
| d61a77d6e3 | |||
| 91da3b2046 | |||
| aade09e74e | |||
| 4dcfd74323 | |||
| f2a432b96d | |||
| 431a1da0b7 | |||
| 16d5da8889 | |||
| 0f8d4df344 | |||
| e08e8716ae | |||
| d1d05aaefb | |||
| c6bf47d3ea | |||
| 4c81f0f52a | |||
| c28a641293 | |||
| a96ab00afc | |||
| c55733fc05 | |||
| 1571a3331d | |||
| 57722597ee | |||
| 9496c7e9ac | |||
| 74c88f3ef6 | |||
| 04d6603f38 | |||
| 6f86bfec72 | |||
| 81dd5a398b | |||
| f143277a8e | |||
| d5f0c576d1 | |||
| b5f35cd540 | |||
| d9bd016d1f | |||
| bed0ac475e | |||
| 92c3bf0220 | |||
| 9f1f5aa92f | |||
| 410caa6141 | |||
| 1902f38e6b | |||
| e59c327a7c | |||
| ef9267c722 | |||
| f9cb66a955 | |||
| 9c6253d86d | |||
| 3fe8a09928 | |||
| 32e517b4f2 | |||
| 0894c41636 | |||
| 073374eb78 | |||
| d0c3659679 | |||
| e055581e34 | |||
| 735593fc84 | |||
| 7d6387e78e | |||
| c554a5c7df | |||
| c6e4239a22 | |||
| e881a6df00 | |||
| d251a519b6 | |||
| 268128482c | |||
| 91a45a2fde | |||
| 9e26207659 | |||
| f1f008f2f5 | |||
| abe9a37408 | |||
| a45d2f3ce9 | |||
| a08cbc2edc | |||
| 0549db6833 | |||
| 07cf61a641 | |||
| d6dec91c93 | |||
| f430e78a2b | |||
| 774d1d9aab | |||
| 03100c28ce | |||
| 1ae8063873 | |||
| 85f3d15e8c | |||
| 565e82bd1a | |||
| 4df80d38cf | |||
| eff530a284 | |||
| df811ff36a | |||
| f19a334b2b | |||
| 544724e59e | |||
| 059abc465b | |||
| 2f30cf0846 | |||
| f643d3f04d | |||
| c7d8772b9b | |||
| 5e97e18d9d | |||
| d65ed73a8a | |||
| 10e1a85fb8 | |||
| 7c26c3ac14 | |||
| 8ebc3e65a5 | |||
| 653a7d8d5a | |||
| 3ddf39ee58 | |||
| e0cb43fbd3 | |||
| 70df28bdf4 | |||
| ee62dd34f7 | |||
| e94bca2733 | |||
| 943d089e7e | |||
| da550188de | |||
| 901ce3ed00 | |||
| 00a9e25706 | |||
| 98bd76f350 | |||
| 750a947479 | |||
| 1400b45d9d | |||
| e36971fdc3 | |||
| 8328c44c39 | |||
| c06e69a485 | |||
| 1b10c87bad | |||
| 4277f09e2a | |||
| 65c790b6cf | |||
| 1b64612aef | |||
| 97becf199f | |||
| 5f40e02a84 | |||
| e2067eab23 | |||
| 33c9c08632 | |||
| 1fe8a8b703 | |||
| a44742f6b8 | |||
| 8951b4f42a | |||
| f63b0dd315 | |||
| 196dc56dd9 | |||
| 54421a9377 | |||
| 63cd4a80bb | |||
| 8df4378f4c | |||
| c3819d6724 | |||
| 7da203e8d2 | |||
| 5617385c44 | |||
| d43a15e658 | |||
| e72f178ca9 | |||
| 66cf2fe53e | |||
| ccff7935bd | |||
| eb18b04c40 | |||
| 35c2178074 | |||
| 2d589bd23d | |||
| bd119dbfed | |||
| d1e16a0ef0 | |||
| 7864ce241b | |||
| 190a81e141 | |||
| 19af0547e8 | |||
| e51f226cdc | |||
| dccb6d10a9 | |||
| 532c85eaa6 | |||
| 3942734eca | |||
| 3b77d7f3c7 | |||
| e9449a9b15 | |||
| 841771c18e | |||
| d8fe97e0cb | |||
| 2d1c6e5cf3 | |||
| 506987d31f | |||
| b8fd33a92c | |||
| 812bc6e1e1 | |||
| 9b0bb17908 | |||
| e3ef4038b4 | |||
| f32c19047f | |||
| a0b1ff8abe | |||
| c2f01c8aa1 | |||
| b19e6cfadd | |||
| af2266d572 | |||
| 21123ac6a4 | |||
| c96491e873 | |||
| 7ab456f6ff | |||
| 5af0c45e10 | |||
| 09a1b60946 | |||
| 54ac38c56b | |||
| ba178b9863 | |||
| d999258744 | |||
| e8de4db522 | |||
| 47b3f3ed6a | |||
| 73e0d8c213 | |||
| a0757aadd6 | |||
| ce793c6dae | |||
| 5e8b6da1db | |||
| 7b41df7049 | |||
| e4d8ef4744 | |||
| fb9b90e715 | |||
| 5fb512f86e | |||
| b2523b4215 | |||
| 519082a61e | |||
| 36446e46a1 | |||
| e6a2f27d47 | |||
| 2b4dfe257e | |||
| 22c427bb96 | |||
| 213c1e0b4f | |||
| ad2680aeba | |||
| fb6df84ec7 | |||
| 796489e88d | |||
| fb2e1adb5c | |||
| 5bf370e18a | |||
| ced832b1c0 | |||
| d4fb5853cc | |||
| 2557c41d49 | |||
| ce3ae80feb | |||
| 7ec8d80053 | |||
| bd89d7c62e | |||
| 62ea87dc6c | |||
| 130efaa350 | |||
| b5da3e9680 | |||
| 974d30b837 | |||
| 8ae1f7aab0 | |||
| 3ba7bc7943 | |||
| c30862dff9 | |||
| bff299d268 | |||
| 6b02b18c8e | |||
| 7be4404a87 | |||
| 94616e1b06 | |||
| 8d72f1a959 | |||
| aea8ae9a96 | |||
| 3008147b3a | |||
| 421283d161 | |||
| 4020e30f07 | |||
| 766d634fbb | |||
| 1bba0319cb | |||
| 651f6e934c | |||
| f2a7018335 | |||
| 7a3063963c | |||
| 0c85e5252f | |||
| d0e73a3e00 | |||
| 955b09a41c | |||
| 3075addd46 | |||
| ecbd146cd3 | |||
| 80100a2eda | |||
| fc51eb8414 | |||
| 8c83706684 | |||
| 3e5d54f3ef | |||
| 9b4e858734 | |||
| 14e66c03a6 | |||
| 803570bf26 | |||
| 66d1f40a49 | |||
| 610940ae5d | |||
| cddce9a5f7 | |||
| 36892a2797 | |||
| f3bad4ee2c | |||
| bb13462f7b | |||
| a3dbc8444b | |||
| 9dccf2a4ee | |||
| 919dc53814 | |||
| 87c0430628 | |||
| ca4736674e | |||
| cf3dc6be27 | |||
| d4307ad03c | |||
| 47225a0e2d | |||
| f4bf144145 | |||
| ad623f7d74 | |||
| 3a51116881 | |||
| 39d2dd2483 | |||
| b532cb19ee | |||
| f5ee710098 | |||
| 7cbba33fe7 | |||
| a9c812a1b3 | |||
| 09648044db | |||
| a97365377f | |||
| 0e9ccb4481 | |||
| c4a30ce0ce | |||
| d350e9edba | |||
| ab468bcac4 | |||
| 679fc3e5fd | |||
| e99fee3908 | |||
| c3bcc9f7cb | |||
| 62d3c625ed | |||
| 3a9ce36662 | |||
| b4d7f8ec55 | |||
| 79eaf5d460 | |||
| 502ed4366a | |||
| d65de9bd30 | |||
| c1feb4210c | |||
| 360e09cc7d | |||
| 182a4bd39c | |||
| 35976f5ff1 | |||
| 982a25992e | |||
| 08123ef5e3 | |||
| 0dd44f98b8 | |||
| 996fc445be | |||
| df0a678863 | |||
| 89ac907ca3 | |||
| 7ce8b0ff7b | |||
| a1258f2125 | |||
| 59d72c8112 | |||
| 5bd0546fb7 | |||
| 67422642ef | |||
| de5e39818e | |||
| cfdcf84fa5 | |||
| 043ed13c18 | |||
| 0271456ca4 | |||
| 08bf46e486 | |||
| 93eef2cc80 | |||
| c4f1179b65 | |||
| 2487d5da37 | |||
| 772035ad44 | |||
| e49afbcd2d | |||
| 1803252f33 | |||
| 555256c1fe | |||
| 26267802e9 | |||
| 69eb6a85b0 | |||
| 78c7bf6af1 | |||
| d5bc13751b | |||
| b04027edad | |||
| 5c6e4fffc4 | |||
| 0da53a2d87 | |||
| db4ccce9e4 | |||
| c538ca8cc6 | |||
| 00bb27fcea | |||
| b571e4d79b | |||
| 29cb5d9c9e | |||
| 899a28cc27 | |||
| 898f726659 | |||
| 5a9e13265f | |||
| 17f5afb494 | |||
| d87fc1be21 | |||
| c3b3e580bd | |||
| abc117531d | |||
| 03b800882c | |||
| 6ffc6056c4 | |||
| 2fe8f5cc1a | |||
| a738d4843d | |||
| 86f3a0b463 | |||
| 121f93957d | |||
| be3cd88a31 | |||
| 81d75ccba4 | |||
| a3df28ec4b | |||
| 06ed37225a | |||
| a9cb70dc08 | |||
| 845ef28a79 | |||
| c9e8d98e37 | |||
| 1728691462 | |||
| 4197af902e | |||
| 656dddda53 | |||
| e1ca459329 | |||
| 7ec27ad88c | |||
| e21506d373 | |||
| e183ddb981 | |||
| c61a6b6dd8 | |||
| 211cce1a8e | |||
| 362cdbcd57 | |||
| 0f902ee74b | |||
| f1d892c759 | |||
| ce5f277f36 | |||
| 9b4ea531d2 | |||
| 69b48de0af | |||
| 99621119e8 | |||
| aa803efd28 | |||
| b48ebbae81 | |||
| 4fea570f5e | |||
| cf12c0b751 | |||
| bbec7ef948 | |||
| 311c8ca6ad | |||
| 33a8dcdacd | |||
| a8e6251a80 | |||
| 326d589f56 | |||
| 7adc065bb8 | |||
| 640c9fe8bd | |||
| 81d6325730 | |||
| 8ece5368b1 | |||
| 7afcc93507 | |||
| e63bbe839b | |||
| 49c8c8bdc0 | |||
| e70517b9ca | |||
| d66fe47def | |||
| 667703a9ac | |||
| 1268f744ab | |||
| d684c87fd1 | |||
| f9853de144 | |||
| b144227fdf | |||
| 7e11e5c652 | |||
| 9ca005147c | |||
| c0b41a7e96 | |||
| 335298d074 | |||
| c903633363 | |||
| 5bf015b2b7 | |||
| 01338d6037 | |||
| 356187f3ae | |||
| 645bba1924 | |||
| 10da8330b0 | |||
| ba30a42d6d | |||
| 6412bee952 | |||
| f168feb9b8 | |||
| 4e2fce7251 | |||
| e37fe33203 | |||
| 5dbb771ba8 | |||
| 4969c31e38 | |||
| b1e9b82ce3 | |||
| 275db14b9f | |||
| 5ccbd232f4 | |||
| 82c06f78f7 | |||
| 4aa24048d5 | |||
| 06f1c62ef0 | |||
| 2c5dcde29d |
+9
-1
@@ -23,4 +23,12 @@ _testmain.go
|
||||
*.test
|
||||
|
||||
coverage.html
|
||||
coverage*.out
|
||||
coverage*.out
|
||||
|
||||
*.pyc
|
||||
|
||||
_vendor/
|
||||
|
||||
gen
|
||||
man/aptly.1.html
|
||||
man/aptly.1.ronn
|
||||
+8
-2
@@ -2,7 +2,9 @@ language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.2.1
|
||||
- 1.3
|
||||
- tip
|
||||
|
||||
env:
|
||||
global:
|
||||
@@ -12,4 +14,8 @@ install:
|
||||
- make prepare
|
||||
|
||||
|
||||
script: make travis
|
||||
script: make travis
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
List of contributors, in chronological order:
|
||||
|
||||
* Andrey Smirnov (https://github.com/smira)
|
||||
* Sebastien Binet (https://github.com/sbinet)
|
||||
* Ryan Uber (https://github.com/ryanuber)
|
||||
* Simon Aquino (https://github.com/simonaquino)
|
||||
@@ -0,0 +1,24 @@
|
||||
gom 'code.google.com/p/go-uuid/uuid', :commit => '5fac954758f5'
|
||||
gom 'code.google.com/p/go.crypto/ssh/terminal', :commit => '7aa593ce8cea'
|
||||
gom 'code.google.com/p/gographviz', :commit => '454bc64fdfa2'
|
||||
gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556'
|
||||
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
|
||||
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827'
|
||||
gom 'github.com/mitchellh/goamz/s3', :commit => '55f224c07975fddef9d2116600c664e30df3d594'
|
||||
gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b'
|
||||
gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47'
|
||||
gom 'github.com/smira/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841'
|
||||
gom 'github.com/syndtr/goleveldb/leveldb', :commit => '9888007'
|
||||
gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b'
|
||||
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
|
||||
|
||||
group :test do
|
||||
gom 'launchpad.net/gocheck'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gom 'github.com/golang/lint/golint'
|
||||
gom 'github.com/mattn/goveralls'
|
||||
gom 'github.com/axw/gocov/gocov'
|
||||
gom 'code.google.com/p/go.tools/cmd/cover'
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2013 Andrey Smirnov. All rights reserved.
|
||||
Copyright 2013-2014 Andrey Smirnov. All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -14,8 +14,8 @@ all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
@@ -1,56 +1,85 @@
|
||||
GOVERSION=$(shell go version | awk '{print $$3;}')
|
||||
PACKAGES=database deb files http query s3 utils
|
||||
ALL_PACKAGES=aptly cmd console database deb files http query s3 utils
|
||||
BINPATH=$(abspath ./_vendor/bin)
|
||||
GOM_ENVIRONMENT=-test
|
||||
PYTHON?=python
|
||||
|
||||
ifeq ($(TRAVIS), true)
|
||||
GOVERALLS?=$(HOME)/gopath/bin/goveralls
|
||||
SRCPATH?=$(HOME)/gopath/src
|
||||
else
|
||||
GOVERALLS?=goveralls
|
||||
SRCPATH?=$(GOPATH)/src
|
||||
endif
|
||||
|
||||
ifeq ($(GOVERSION), go1.2)
|
||||
ifeq ($(GOVERSION), devel)
|
||||
TRAVIS_TARGET=coveralls
|
||||
PREPARE_LIST=cover-prepare
|
||||
GOM_ENVIRONMENT+=-development
|
||||
else
|
||||
TRAVIS_TARGET=test
|
||||
PREPARE_LIST=
|
||||
endif
|
||||
|
||||
all: test check
|
||||
ifeq ($(TRAVIS), true)
|
||||
GOM=$(HOME)/gopath/bin/gom
|
||||
else
|
||||
GOM=gom
|
||||
endif
|
||||
|
||||
prepare: $(PREPARE_LIST)
|
||||
go get -d -v ./...
|
||||
go get launchpad.net/gocheck
|
||||
# temporary fix: use commander develop version for now (https://github.com/smira/aptly/pull/1)
|
||||
cd $(SRCPATH)/github.com/gonuts/commander && git fetch && git checkout develop
|
||||
all: test check system-test
|
||||
|
||||
cover-prepare:
|
||||
go get github.com/golang/lint/golint
|
||||
go get github.com/mattn/goveralls
|
||||
go get github.com/axw/gocov/gocov
|
||||
go get code.google.com/p/go.tools/cmd/cover
|
||||
prepare:
|
||||
go get -u github.com/mattn/gom
|
||||
$(GOM) $(GOM_ENVIRONMENT) install
|
||||
|
||||
coverage.out:
|
||||
go test -coverprofile=coverage.debian.out -covermode=count ./debian
|
||||
go test -coverprofile=coverage.utils.out -covermode=count ./utils
|
||||
go test -coverprofile=coverage.database.out -covermode=count ./database
|
||||
rm -f coverage.*.out
|
||||
for i in $(PACKAGES); do $(GOM) test -coverprofile=coverage.$$i.out -covermode=count ./$$i; done
|
||||
echo "mode: count" > coverage.out
|
||||
grep -v -h "mode: count" coverage.*.out >> coverage.out
|
||||
rm -f coverage.*.out
|
||||
|
||||
coverage: coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
$(GOM) exec go tool cover -html=coverage.out
|
||||
rm -f coverage.out
|
||||
|
||||
check:
|
||||
go tool vet -all=true .
|
||||
golint .
|
||||
$(GOM) exec go tool vet -all=true -shadow=true $(ALL_PACKAGES:%=./%)
|
||||
$(GOM) exec golint $(ALL_PACKAGES:%=./%)
|
||||
|
||||
travis: $(TRAVIS_TARGET)
|
||||
install:
|
||||
$(GOM) build -o $(BINPATH)/aptly
|
||||
|
||||
system-test: install
|
||||
ifeq ($(GOVERSION),$(filter $(GOVERSION),go1.2 go1.2.1 go1.3 devel))
|
||||
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
|
||||
endif
|
||||
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
|
||||
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long
|
||||
|
||||
travis: $(TRAVIS_TARGET) system-test
|
||||
|
||||
test:
|
||||
go test -v ./... -gocheck.v=true
|
||||
$(GOM) test -v ./... -gocheck.v=true
|
||||
|
||||
coveralls: coverage.out
|
||||
@$(GOVERALLS) -service travis-ci.org -coverprofile=coverage.out $(COVERALLS_TOKEN)
|
||||
$(GOM) exec $(BINPATH)/goveralls -service travis-ci.org -coverprofile=coverage.out -repotoken=$(COVERALLS_TOKEN)
|
||||
|
||||
.PHONY: coverage.out
|
||||
mem.png: mem.dat mem.gp
|
||||
gnuplot mem.gp
|
||||
open mem.png
|
||||
|
||||
package:
|
||||
rm -rf root/
|
||||
mkdir -p root/usr/bin/ root/usr/share/man/man1/ root/etc/bash_completion.d
|
||||
cp $(BINPATH)/aptly root/usr/bin
|
||||
cp man/aptly.1 root/usr/share/man/man1
|
||||
(cd root/etc/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/master/aptly)
|
||||
gzip root/usr/share/man/man1/aptly.1
|
||||
fpm -s dir -t deb -n aptly -v $(VERSION) --url=http://www.aptly.info/ --license=MIT --vendor="Andrey Smirnov <me@smira.ru>" \
|
||||
-f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" --deb-recommends bzip2 -C root/ .
|
||||
mv aptly_$(VERSION)_*.deb ~
|
||||
|
||||
src-package:
|
||||
rm -rf aptly-$(VERSION)
|
||||
mkdir -p aptly-$(VERSION)/src/github.com/smira/aptly/
|
||||
cd aptly-$(VERSION)/src/github.com/smira/ && git clone https://github.com/smira/aptly && cd aptly && git checkout v$(VERSION)
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install
|
||||
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf
|
||||
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin}
|
||||
tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
|
||||
rm -rf aptly-$(VERSION)
|
||||
|
||||
.PHONY: coverage.out
|
||||
|
||||
+30
-337
@@ -10,366 +10,59 @@ aptly
|
||||
|
||||
Aptly is a swiss army knife for Debian repository management.
|
||||
|
||||
It allows to: ("+" means planned features)
|
||||
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>`_.
|
||||
|
||||
Aptly features: ("+" means planned features)
|
||||
|
||||
* make mirrors of remote Debian/Ubuntu repositories, limiting by components/architectures
|
||||
* take snapshots of mirrors at any point in time, fixing state of repository at some moment of time
|
||||
* publish snapshot as Debian repository, ready to be consumed by apt
|
||||
* merge two or more snapshots into one (+)
|
||||
* 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 (+)
|
||||
* controlled update of one or more packages in snapshot from upstream mirror, tracking dependencies (+)
|
||||
* publish self-made packages as Debian repositories (+)
|
||||
* mirror repositories "as-is" (without resigning with user's key) (+)
|
||||
* support for yum repositories (+)
|
||||
|
||||
Current limitations:
|
||||
|
||||
* source packages, debian-installer and translations not supported yet
|
||||
* checksums and signature are not verified while downloading
|
||||
* deleting created items is not implemented
|
||||
* cleaning up stale files is not implemented
|
||||
|
||||
Currently aptly is under heavy development, so please use it with care.
|
||||
|
||||
.. contents::
|
||||
* debian-installer and translations not supported yet
|
||||
|
||||
Download
|
||||
--------
|
||||
|
||||
TBD
|
||||
To install aptly on Debian/Ubuntu, add new repository to /etc/apt/sources.list::
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
deb http://repo.aptly.info/ squeeze main
|
||||
|
||||
aptly looks for configuration file in ``/etc/aptly.conf`` and ``~/.aptly.conf``, if no config file found,
|
||||
new one is created. Also aptly needs root directory for database, package and published repository storage.
|
||||
If not specified, directory defaults to ``~/.aptly``, it will be created if missing.
|
||||
And import key that is used to sign the release::
|
||||
|
||||
Configuration file is stored in JSON format::
|
||||
$ gpg --keyserver keys.gnupg.net --recv-keys 2A194991
|
||||
$ gpg -a --export 2A194991 | sudo apt-key add -
|
||||
|
||||
{
|
||||
"rootDir": "/var/aptly",
|
||||
"downloadConcurrency": 4
|
||||
}
|
||||
After that you can install aptly as any other software package::
|
||||
|
||||
Options:
|
||||
$ apt-get update
|
||||
$ apt-get install aptly
|
||||
|
||||
* ``rootDir`` is root of directory storage to store datbase (``rootDir/db``), downloaded packages (``rootDir/pool``) and
|
||||
published repositories (``rootDir/public``)
|
||||
* ``downloadConcurrency`` is a number of parallel download threads to use when downloading packages
|
||||
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.
|
||||
|
||||
Example
|
||||
-------
|
||||
Binary executables (depends almost only on libc) are available for download from `Bintray <http://dl.bintray.com/smira/aptly/>`_.
|
||||
|
||||
Create mirror::
|
||||
If you have Go environment set up, you can build aptly from source by running (go 1.1+ required)::
|
||||
|
||||
$ aptly mirror create --architecture="amd64" debian-main http://ftp.ru.debian.org/debian/ squeeze main
|
||||
2013/12/28 19:44:45 Downloading http://ftp.ru.debian.org/debian/dists/squeeze/Release...
|
||||
...
|
||||
go get -u github.com/mattn/gom
|
||||
mkdir -p $GOPATH/src/github.com/smira/aptly
|
||||
git clone https://github.com/smira/aptly $GOPATH/src/github.com/smira/aptly
|
||||
cd $GOPATH/src/github.com/smira/aptly
|
||||
gom -production install
|
||||
gom build -o $GOPATH/bin/aptly
|
||||
|
||||
Mirror [debian-main]: http://ftp.ru.debian.org/debian/ squeeze successfully added.
|
||||
You can run 'aptly mirror update debian-main' to download repository contents.
|
||||
Aptly is using `gom <https://github.com/mattn/gom>`_ to fix external dependencies, so regular ``go get github.com/smira/aptly``
|
||||
should work as well, but might fail or produce different result (if external libraries got updated).
|
||||
|
||||
Take snapshot::
|
||||
If you don't have Go installed (or older version), you can easily install Go using `gvm <https://github.com/moovweb/gvm/>`_.
|
||||
|
||||
$ aptly snapshot create debian-3112 from mirror debian-main
|
||||
|
||||
Snapshot debian-3112 successfully created.
|
||||
You can run 'aptly publish snapshot debian-3112' to publish snapshot as Debian repository.
|
||||
|
||||
Publish snapshot (requires generated GPG key)::
|
||||
|
||||
$ aptly publish snapshot debian-3112
|
||||
|
||||
...
|
||||
|
||||
Snapshot back has been successfully published.
|
||||
Please setup your webserver to serve directory '/var/aptly/public' with autoindexing.
|
||||
Now you can add following line to apt sources:
|
||||
deb http://your-server/ squeeze main
|
||||
Don't forget to add your GPG key to apt with apt-key.
|
||||
|
||||
Set up webserver (e.g. nginx)::
|
||||
|
||||
server {
|
||||
root /home/example/.aptly/public;
|
||||
server_name mirror.local;
|
||||
|
||||
location / {
|
||||
autoindex on;
|
||||
}
|
||||
|
||||
Add new repository to apt's sources::
|
||||
|
||||
deb http://mirror.local/ squeeze main
|
||||
|
||||
Run apt-get to fetch repository metadata::
|
||||
|
||||
apt-get update
|
||||
|
||||
Enjoy!
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Aptly supports commands in three basic categories:
|
||||
|
||||
* ``mirror``
|
||||
* ``snapshot``
|
||||
* ``publish``
|
||||
|
||||
Command ``mirror``
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Mirror subcommands manage mirrors of remote Debian repositories.
|
||||
|
||||
``aptly mirror create``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Creates mirror of remote repository. It supports only HTTP repositories.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly mirror create <name> <archive url> <distribution> [<component1> ...]
|
||||
|
||||
Params are:
|
||||
|
||||
* ``name`` is a name that would be used in aptly to reference this mirror
|
||||
* ``archive url`` is a root of archive, e.g. http://ftp.ru.debian.org/debian/
|
||||
* ``distribution`` is a distribution name, e.g. ``squeeze``
|
||||
* ``component1`` is an optional list of components to download, if not
|
||||
specified aptly would fetch all components, e.g. ``main``
|
||||
|
||||
Options:
|
||||
|
||||
* ``--architecture="i386,amd64"`` list of architectures to fetch, if not specified,
|
||||
aptly would fetch packages for all architectures
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly mirror create --architecture="amd64" debian-main http://ftp.ru.debian.org/debian/ squeeze main
|
||||
2013/12/28 19:44:45 Downloading http://ftp.ru.debian.org/debian/dists/squeeze/Release...
|
||||
...
|
||||
|
||||
Mirror [debian-main]: http://ftp.ru.debian.org/debian/ squeeze successfully added.
|
||||
You can run 'aptly mirror update debian-main' to download repository contents.
|
||||
|
||||
``aptly mirror update``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Updates (fetches packages and meta) remote mirror. When mirror is created, it should be run for the
|
||||
first time to fetch mirror contents. This command could be run many times. If interrupted, it could
|
||||
be restarted in a safe way.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly mirror update <name>
|
||||
|
||||
Params are:
|
||||
|
||||
* ``name`` is a mirror name (given when mirror was created)
|
||||
|
||||
All packages would be stored under aptly's root dir (see section on Configuration).
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly mirror update debian-main
|
||||
|
||||
2013/12/29 18:32:34 Downloading http://ftp.ru.debian.org/debian/dists/squeeze/Release...
|
||||
2013/12/29 18:32:37 Downloading http://ftp.ru.debian.org/debian/dists/squeeze/main/binary-amd64/Packages.bz2...
|
||||
2013/12/29 18:37:19 Downloading http://ftp.ru.debian.org/debian/pool/main/libg/libgwenhywfar/libgwenhywfar47-dev_3.11.3-1_amd64.deb...
|
||||
....
|
||||
|
||||
``aptly mirror list``
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Shows list of registered mirrors of repositories.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly mirror list
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly mirror list
|
||||
List of mirrors:
|
||||
* [backports]: http://mirror.yandex.ru/backports.org/ squeeze-backports
|
||||
* [debian-main]: http://ftp.ru.debian.org/debian/ squeeze
|
||||
|
||||
To get more information about repository, run `aptly mirror show <name>`.
|
||||
|
||||
``aptly mirror show``
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Shows detailed information about mirror.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly mirror show <name>
|
||||
|
||||
Params are:
|
||||
|
||||
* ``name`` is a mirror name (given when mirror was created)
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly mirror show backports2
|
||||
Name: backports2
|
||||
Archive Root URL: http://mirror.yandex.ru/backports.org/
|
||||
Distribution: squeeze-backports
|
||||
Components: main, contrib, non-free
|
||||
Architectures: i386, amd64
|
||||
Last update: 2013-12-27 19:30:19 MSK
|
||||
Number of packages: 3898
|
||||
|
||||
Information from release file:
|
||||
...
|
||||
|
||||
In detailed information, one can see basiс parameters of the mirror, filters by component & architecture, timestamp
|
||||
of last successful repository fetch and number of packages.
|
||||
|
||||
Command ``snapshot``
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Snapshot is a fixed state of remote repository. Internally snapshot is list of packages with explicit version.
|
||||
Snapshot is immutable, i.e. it can't change since it has been created.
|
||||
|
||||
``aptly snapshot create .. from mirror``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Creates snapshot from current state of remote mirror. Mirros should be updated at least once before using this command.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly snapshot create <name> from mirror <mirror-name>
|
||||
|
||||
Params are:
|
||||
|
||||
* ``name`` is a name for the snapshot to be created
|
||||
* ``mirror-name`` is a mirror name (given when mirror was created)
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly snapshot create monday-updates from mirror backports2
|
||||
|
||||
Snapshot monday-updates successfully created.
|
||||
You can run 'aptly publish snapshot monday-updates' to publish snapshot as Debian repository.
|
||||
|
||||
``aptly snapshot list``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Displays list of all created snapshots.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly snapshot list
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly snapshot list
|
||||
List of snapshots:
|
||||
* [monday-updates]: Snapshot from mirror [backports2]: http://mirror.yandex.ru/backports.org/ squeeze-backports
|
||||
* [back]: Snapshot from mirror [backports2]: http://mirror.yandex.ru/backports.org/ squeeze-backports
|
||||
|
||||
To get more information about snapshot, run `aptly snapshot show <name>`.
|
||||
|
||||
With snapshot information, basic information about snapshot origin is displayed: which mirror it has been created from.
|
||||
|
||||
``aptly snapshot show``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Shows detailed information about snapshot. Full list of packages in the snapshot is displayed as well.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly snapshot show <name>
|
||||
|
||||
Params:
|
||||
|
||||
* ``name`` is snapshot name which has been given during snapshot creation
|
||||
|
||||
Example::
|
||||
|
||||
Name: back
|
||||
Created At: 2013-12-24 15:39:29 MSK
|
||||
Description: Snapshot from mirror [backports2]: http://mirror.yandex.ru/backports.org/ squeeze-backports
|
||||
Number of packages: 3898
|
||||
Packages:
|
||||
altos-1.0.3~bpo60+1_i386
|
||||
amanda-client-1:3.3.1-3~bpo60+1_amd64
|
||||
...
|
||||
|
||||
Command ``publish``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Publishing snapshot as Debian repository which could be served by HTTP/FTP/rsync server. Repository is signed by
|
||||
user's key with GnuPG. Key should be created beforehand (see section GPG Keys). Published repository could
|
||||
be consumed directly by apt.
|
||||
|
||||
``aptly publish snapshot``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Published repositories appear under ``rootDir/public`` directory.
|
||||
|
||||
Usage::
|
||||
|
||||
$ aptly publish snapshot <name> [<prefix>]
|
||||
|
||||
Params:
|
||||
|
||||
* ``name`` is a snapshot name that snould be published
|
||||
* ``prefix`` is an optional prefix for publishing, if not specified, repository would be published to the root of
|
||||
publiс directory
|
||||
|
||||
Options:
|
||||
|
||||
* ``-architectures=""``: list of architectures to publish (comma-separated); derived automatically from
|
||||
snapshot contents
|
||||
* ``-component=""``: component name to publish; guessed from original repository (if any), or defaults to
|
||||
main
|
||||
* ``-distribution=""``: distribution name to publish; guessed from original repository distribution
|
||||
* ``-gpg-key=""``: GPG key ID to use when signing the release, if not specified default key is used
|
||||
|
||||
Example::
|
||||
|
||||
$ aptly publish snapshot back
|
||||
Signing file '/var/aptly/public/dists/squeeze-backports/Release' with gpg, please enter your passphrase when prompted:
|
||||
|
||||
<<gpg asks for passphrase>>
|
||||
|
||||
Clearsigning file '/var/aptly/public/dists/squeeze-backports/Release' with gpg, please enter your passphrase when prompted:
|
||||
|
||||
<<gpg asks for passphrase>>
|
||||
|
||||
Snapshot back has been successfully published.
|
||||
Please setup your webserver to serve directory '/var/aptly/public' with autoindexing.
|
||||
Now you can add following line to apt sources:
|
||||
deb http://your-server/ squeeze-backports main
|
||||
Don't forget to add your GPG key to apt with apt-key.
|
||||
|
||||
Directory structure for published repositories::
|
||||
|
||||
public/ - root of published tree (root for webserver)
|
||||
dists/
|
||||
squeeze/ - distribution name
|
||||
Release - raw file
|
||||
InRelease - clearsigned file
|
||||
Release.gpg - signature for Release file
|
||||
binary-i386/
|
||||
Packages - list of metadata for packages
|
||||
Packages.gz
|
||||
Packages.bz2
|
||||
pool/
|
||||
main/ - component name
|
||||
m/
|
||||
mars-invaders/
|
||||
mars-invaders_1.0.3_i386.deb - package (hard link to package from main pool)
|
||||
|
||||
GPG Keys
|
||||
--------
|
||||
|
||||
GPG key is required to sign any published repository. Key should be generated before publishing first repository.
|
||||
|
||||
Key generation, storage, backup and revocation is out of scope of this document, there are many tutorials available,
|
||||
e.g. `this one <http://fedoraproject.org/wiki/Creating_GPG_Keys>`_.
|
||||
|
||||
Publiс part of the key should be exported (``gpg --export --armor``) and imported into apt keyring on all machines that would be using
|
||||
published repositories using ``apt-key``.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Package aptly provides common infrastructure that doesn't depend directly on
|
||||
// Debian or CentOS
|
||||
package aptly
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
// PackagePool is asbtraction of package pool storage.
|
||||
//
|
||||
// PackagePool stores all the package files, deduplicating them.
|
||||
type PackagePool interface {
|
||||
// Path returns full path to package file in pool given any name and hash of file contents
|
||||
Path(filename string, hashMD5 string) (string, error)
|
||||
// RelativePath returns path relative to pool's root for package files given MD5 and original filename
|
||||
RelativePath(filename string, hashMD5 string) (string, error)
|
||||
// FilepathList returns file paths of all the files in the pool
|
||||
FilepathList(progress Progress) ([]string, error)
|
||||
// Remove deletes file in package pool returns its size
|
||||
Remove(path string) (size int64, err error)
|
||||
// Import copies file into package pool
|
||||
Import(path string, hashMD5 string) error
|
||||
}
|
||||
|
||||
// PublishedStorage is abstraction of filesystem storing all published repositories
|
||||
type PublishedStorage interface {
|
||||
// MkDir creates directory recursively under public path
|
||||
MkDir(path string) 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, sourceMD5 string) error
|
||||
// Filelist returns list of files under prefix
|
||||
Filelist(prefix string) ([]string, 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
|
||||
io.Writer
|
||||
// Start makes progress start its work
|
||||
Start()
|
||||
// Shutdown shuts down progress display
|
||||
Shutdown()
|
||||
// Flush returns when all queued messages are sent
|
||||
Flush()
|
||||
// InitBar starts progressbar for count bytes or count items
|
||||
InitBar(count int64, isBytes bool)
|
||||
// ShutdownBar stops progress bar and hides it
|
||||
ShutdownBar()
|
||||
// AddBar increments progress for progress bar
|
||||
AddBar(count int)
|
||||
// SetBar sets current position for progress bar
|
||||
SetBar(count int)
|
||||
// Printf does printf but in safe manner: not overwriting progress bar
|
||||
Printf(msg string, a ...interface{})
|
||||
// ColoredPrintf does printf in colored way + newline
|
||||
ColoredPrintf(msg string, a ...interface{})
|
||||
}
|
||||
|
||||
// Downloader is parallel HTTP fetcher
|
||||
type Downloader interface {
|
||||
// Download starts new download task
|
||||
Download(url string, destination string, result chan<- error)
|
||||
// DownloadWithChecksum starts new download task with checksum verification
|
||||
DownloadWithChecksum(url string, destination string, result chan<- error, expected utils.ChecksumInfo, ignoreMismatch bool)
|
||||
// Pause pauses task processing
|
||||
Pause()
|
||||
// Resume resumes task processing
|
||||
Resume()
|
||||
// Shutdown stops downloader after current tasks are finished,
|
||||
// but doesn't process rest of queue
|
||||
Shutdown()
|
||||
// GetProgress returns Progress object
|
||||
GetProgress() Progress
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package aptly
|
||||
|
||||
// Version of aptly
|
||||
const Version = "0.7"
|
||||
|
||||
// Enable debugging features?
|
||||
const EnableDebug = false
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Package cmd implements console commands
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ListPackagesRefList shows list of packages in PackageRefList
|
||||
func ListPackagesRefList(reflist *deb.PackageRefList) (err error) {
|
||||
fmt.Printf("Packages:\n")
|
||||
|
||||
if reflist == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = reflist.ForEach(func(key []byte) error {
|
||||
p, err2 := context.CollectionFactory().PackageCollection().ByKey(key)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
fmt.Printf(" %s\n", p)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RootCommand creates root command in command tree
|
||||
func RootCommand() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
UsageLine: os.Args[0],
|
||||
Short: "Debian repository management tool",
|
||||
Long: `
|
||||
aptly is a tool to create partial and full mirrors of remote
|
||||
repositories, manage local repositories, filter them, merge,
|
||||
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 repository, so that package installation and upgrade becomes
|
||||
deterministic. At the same time aptly allows to perform controlled,
|
||||
fine-grained changes in repository contents to transition your
|
||||
package environment to new version.`,
|
||||
Flag: *flag.NewFlagSet("aptly", flag.ExitOnError),
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdDb(),
|
||||
makeCmdGraph(),
|
||||
makeCmdMirror(),
|
||||
makeCmdRepo(),
|
||||
makeCmdServe(),
|
||||
makeCmdSnapshot(),
|
||||
makeCmdPublish(),
|
||||
makeCmdVersion(),
|
||||
},
|
||||
}
|
||||
|
||||
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.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)")
|
||||
|
||||
if aptly.EnableDebug {
|
||||
cmd.Flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
cmd.Flag.String("memprofile", "", "write memory profile to this file")
|
||||
cmd.Flag.String("memstats", "", "write memory stats periodically to this file")
|
||||
cmd.Flag.Duration("meminterval", 100*time.Millisecond, "memory stats dump interval")
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/console"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/http"
|
||||
"github.com/smira/aptly/s3"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AptlyContext is a common context shared by all commands
|
||||
type AptlyContext struct {
|
||||
flags *flag.FlagSet
|
||||
configLoaded bool
|
||||
|
||||
progress aptly.Progress
|
||||
downloader aptly.Downloader
|
||||
database database.Storage
|
||||
packagePool aptly.PackagePool
|
||||
publishedStorages map[string]aptly.PublishedStorage
|
||||
collectionFactory *deb.CollectionFactory
|
||||
dependencyOptions int
|
||||
architecturesList []string
|
||||
// Debug features
|
||||
fileCPUProfile *os.File
|
||||
fileMemProfile *os.File
|
||||
fileMemStats *os.File
|
||||
}
|
||||
|
||||
var context *AptlyContext
|
||||
|
||||
// Check interface
|
||||
var _ aptly.PublishedStorageProvider = &AptlyContext{}
|
||||
|
||||
// FatalError is type for panicking to abort execution with non-zero
|
||||
// exit code and print meaningful explanation
|
||||
type FatalError struct {
|
||||
ReturnCode int
|
||||
Message string
|
||||
}
|
||||
|
||||
// Fatal panics and aborts execution with exit code 1
|
||||
func Fatal(err error) {
|
||||
returnCode := 1
|
||||
if err == commander.ErrFlagError || err == commander.ErrCommandError {
|
||||
returnCode = 2
|
||||
}
|
||||
panic(&FatalError{ReturnCode: returnCode, Message: err.Error()})
|
||||
}
|
||||
|
||||
// Config loads and returns current configuration
|
||||
func (context *AptlyContext) Config() *utils.ConfigStructure {
|
||||
if !context.configLoaded {
|
||||
var err error
|
||||
|
||||
configLocation := context.flags.Lookup("config").Value.String()
|
||||
if configLocation != "" {
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
} else {
|
||||
configLocations := []string{
|
||||
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
|
||||
"/etc/aptly.conf",
|
||||
}
|
||||
|
||||
for _, configLocation := range configLocations {
|
||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0])
|
||||
utils.SaveConfig(configLocations[0], &utils.Config)
|
||||
}
|
||||
}
|
||||
|
||||
context.configLoaded = true
|
||||
|
||||
}
|
||||
return &utils.Config
|
||||
}
|
||||
|
||||
// DependencyOptions calculates options related to dependecy handling
|
||||
func (context *AptlyContext) DependencyOptions() int {
|
||||
if context.dependencyOptions == -1 {
|
||||
context.dependencyOptions = 0
|
||||
if context.Config().DepFollowSuggests || context.flags.Lookup("dep-follow-suggests").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowSuggests
|
||||
}
|
||||
if context.Config().DepFollowRecommends || context.flags.Lookup("dep-follow-recommends").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowRecommends
|
||||
}
|
||||
if context.Config().DepFollowAllVariants || context.flags.Lookup("dep-follow-all-variants").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowAllVariants
|
||||
}
|
||||
if context.Config().DepFollowSource || context.flags.Lookup("dep-follow-source").Value.Get().(bool) {
|
||||
context.dependencyOptions |= deb.DepFollowSource
|
||||
}
|
||||
}
|
||||
|
||||
return context.dependencyOptions
|
||||
}
|
||||
|
||||
// ArchitecturesList returns list of architectures fixed via command line or config
|
||||
func (context *AptlyContext) ArchitecturesList() []string {
|
||||
if context.architecturesList == nil {
|
||||
context.architecturesList = context.Config().Architectures
|
||||
optionArchitectures := context.flags.Lookup("architectures").Value.String()
|
||||
if optionArchitectures != "" {
|
||||
context.architecturesList = strings.Split(optionArchitectures, ",")
|
||||
}
|
||||
}
|
||||
|
||||
return context.architecturesList
|
||||
}
|
||||
|
||||
// Progress creates or returns Progress object
|
||||
func (context *AptlyContext) Progress() aptly.Progress {
|
||||
if context.progress == nil {
|
||||
context.progress = console.NewProgress()
|
||||
context.progress.Start()
|
||||
}
|
||||
|
||||
return context.progress
|
||||
}
|
||||
|
||||
// Downloader returns instance of current downloader
|
||||
func (context *AptlyContext) Downloader() aptly.Downloader {
|
||||
if context.downloader == nil {
|
||||
var downloadLimit int64
|
||||
limitFlag := context.flags.Lookup("download-limit")
|
||||
if limitFlag != nil {
|
||||
downloadLimit = limitFlag.Value.Get().(int64)
|
||||
}
|
||||
if downloadLimit == 0 {
|
||||
downloadLimit = context.Config().DownloadLimit
|
||||
}
|
||||
context.downloader = http.NewDownloader(context.Config().DownloadConcurrency,
|
||||
downloadLimit*1024, context.Progress())
|
||||
}
|
||||
|
||||
return context.downloader
|
||||
}
|
||||
|
||||
// DBPath builds path to database
|
||||
func (context *AptlyContext) DBPath() string {
|
||||
return filepath.Join(context.Config().RootDir, "db")
|
||||
}
|
||||
|
||||
// Database opens and returns current instance of database
|
||||
func (context *AptlyContext) Database() (database.Storage, error) {
|
||||
if context.database == nil {
|
||||
var err error
|
||||
|
||||
context.database, err = database.OpenDB(context.DBPath())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open database: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return context.database, nil
|
||||
}
|
||||
|
||||
// CollectionFactory builds factory producing all kinds of collections
|
||||
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
|
||||
if context.collectionFactory == nil {
|
||||
db, err := context.Database()
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
context.collectionFactory = deb.NewCollectionFactory(db)
|
||||
}
|
||||
|
||||
return context.collectionFactory
|
||||
}
|
||||
|
||||
// PackagePool returns instance of PackagePool
|
||||
func (context *AptlyContext) PackagePool() aptly.PackagePool {
|
||||
if context.packagePool == nil {
|
||||
context.packagePool = files.NewPackagePool(context.Config().RootDir)
|
||||
}
|
||||
|
||||
return context.packagePool
|
||||
}
|
||||
|
||||
// PublishedStorage returns instance of PublishedStorage
|
||||
func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage {
|
||||
publishedStorage, ok := context.publishedStorages[name]
|
||||
if !ok {
|
||||
if name == "" {
|
||||
publishedStorage = files.NewPublishedStorage(context.Config().RootDir)
|
||||
} else if strings.HasPrefix(name, "s3:") {
|
||||
params, ok := context.Config().S3PublishRoots[name[3:]]
|
||||
if !ok {
|
||||
Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:]))
|
||||
}
|
||||
|
||||
var err error
|
||||
publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey,
|
||||
params.Region, params.Bucket, params.ACL, params.Prefix)
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
} else {
|
||||
Fatal(fmt.Errorf("unknown published storage format: %v", name))
|
||||
}
|
||||
context.publishedStorages[name] = publishedStorage
|
||||
}
|
||||
|
||||
return publishedStorage
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
// InitContext initializes context with default settings
|
||||
func InitContext(flags *flag.FlagSet) error {
|
||||
var err error
|
||||
|
||||
context = &AptlyContext{
|
||||
flags: flags,
|
||||
dependencyOptions: -1,
|
||||
publishedStorages: map[string]aptly.PublishedStorage{},
|
||||
}
|
||||
|
||||
if 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func makeCmdDb() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "db",
|
||||
Short: "manage aptly's internal database and package pool",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdDbCleanup(),
|
||||
makeCmdDbRecover(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// aptly db cleanup
|
||||
func aptlyDbCleanup(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
// collect information about references packages...
|
||||
existingPackageRefs := deb.NewPackageRefList()
|
||||
|
||||
context.Progress().Printf("Loading mirrors, local repos and snapshots...\n")
|
||||
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.RefList() != nil {
|
||||
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false)
|
||||
}
|
||||
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 {
|
||||
existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false)
|
||||
}
|
||||
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
|
||||
}
|
||||
existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ... and compare it to the list of all packages
|
||||
context.Progress().Printf("Loading list of all packages...\n")
|
||||
allPackageRefs := context.CollectionFactory().PackageCollection().AllPackageRefs()
|
||||
|
||||
toDelete := allPackageRefs.Substract(existingPackageRefs)
|
||||
|
||||
// delete packages that are no longer referenced
|
||||
context.Progress().Printf("Deleting unreferenced packages (%d)...\n", 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)
|
||||
}
|
||||
|
||||
// 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")
|
||||
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
|
||||
}
|
||||
paths, err2 := pkg.FilepathList(context.PackagePool())
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
referencedFiles = append(referencedFiles, paths...)
|
||||
context.Progress().AddBar(1)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Strings(referencedFiles)
|
||||
context.Progress().ShutdownBar()
|
||||
|
||||
// build a list of files in the package pool
|
||||
context.Progress().Printf("Building list of files in package pool...\n")
|
||||
existingFiles, err := context.PackagePool().FilepathList(context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect file paths: %s", err)
|
||||
}
|
||||
|
||||
// find files which are in the pool but not referenced by packages
|
||||
filesToDelete := utils.StrSlicesSubstract(existingFiles, referencedFiles)
|
||||
|
||||
// delete files that are no longer referenced
|
||||
context.Progress().Printf("Deleting unreferenced files (%d)...\n", 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
|
||||
}
|
||||
|
||||
context.Progress().AddBar(1)
|
||||
totalSize += size
|
||||
}
|
||||
context.Progress().ShutdownBar()
|
||||
|
||||
context.Progress().Printf("Disk space freed: %s...\n", utils.HumanBytes(totalSize))
|
||||
}
|
||||
|
||||
context.Progress().Printf("Compacting database...\n")
|
||||
err = db.CompactDB()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdDbCleanup() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyDbCleanup,
|
||||
UsageLine: "cleanup",
|
||||
Short: "cleanup DB and package pool",
|
||||
Long: `
|
||||
Database cleanup removes information about unreferenced packages and removes
|
||||
files in the package pool that aren't used by packages anymore
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly db cleanup
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
// aptly db recover
|
||||
func aptlyDbRecover(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
context.Progress().Printf("Recovering database...\n")
|
||||
err = database.RecoverDB(context.DBPath())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdDbRecover() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyDbRecover,
|
||||
UsageLine: "recover",
|
||||
Short: "recover DB after crash",
|
||||
Long: `
|
||||
Database recover does its' best to recover the database after a crash.
|
||||
It is recommended to backup the DB before running recover.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly db recover
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/gographviz"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
graph := gographviz.NewEscape()
|
||||
graph.SetDir(true)
|
||||
graph.SetName("aptly")
|
||||
|
||||
existingNodes := map[string]bool{}
|
||||
|
||||
fmt.Printf("Loading mirrors...\n")
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "darkgoldenrod1",
|
||||
"label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}",
|
||||
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "),
|
||||
strings.Join(repo.Architectures, ", "), repo.NumPackages()),
|
||||
})
|
||||
existingNodes[repo.UUID] = true
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Loading local repos...\n")
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "mediumseagreen",
|
||||
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
|
||||
repo.Name, repo.Comment, repo.NumPackages()),
|
||||
})
|
||||
existingNodes[repo.UUID] = true
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Loading snapshots...\n")
|
||||
|
||||
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
existingNodes[snapshot.UUID] = true
|
||||
return nil
|
||||
})
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
description := snapshot.Description
|
||||
if snapshot.SourceKind == "repo" {
|
||||
description = "Snapshot from repo"
|
||||
}
|
||||
|
||||
graph.AddNode("aptly", snapshot.UUID, map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "cadetblue1",
|
||||
"label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()),
|
||||
})
|
||||
|
||||
if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
|
||||
for _, uuid := range snapshot.SourceIDs {
|
||||
_, exists := existingNodes[uuid]
|
||||
if exists {
|
||||
graph.AddEdge(uuid, snapshot.UUID, true, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Loading published repos...\n")
|
||||
|
||||
context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||
"shape": "Mrecord",
|
||||
"style": "filled",
|
||||
"fillcolor": "darkolivegreen1",
|
||||
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution,
|
||||
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")),
|
||||
})
|
||||
|
||||
for _, uuid := range repo.Sources {
|
||||
_, exists := existingNodes[uuid]
|
||||
if exists {
|
||||
graph.AddEdge(uuid, repo.UUID, true, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
fmt.Printf("Generating graph...\n")
|
||||
|
||||
buf := bytes.NewBufferString(graph.String())
|
||||
|
||||
tempfile, err := ioutil.TempFile("", "aptly-graph")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempfile.Close()
|
||||
os.Remove(tempfile.Name())
|
||||
|
||||
tempfilename := tempfile.Name() + ".png"
|
||||
|
||||
command := exec.Command("dot", "-Tpng", "-o"+tempfilename)
|
||||
command.Stderr = os.Stderr
|
||||
|
||||
stdin, err := command.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = command.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(stdin, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = stdin.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = command.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = exec.Command("open", tempfilename).Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Rendered to PNG file: %s\n", tempfilename)
|
||||
err = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdGraph() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyGraph,
|
||||
UsageLine: "graph",
|
||||
Short: "render graph of relationships",
|
||||
Long: `
|
||||
Command graph displays relationship between mirrors, local repositories,
|
||||
snapshots and published repositories using graphviz package to render
|
||||
graph as an image.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly graph
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getVerifier(flags *flag.FlagSet) (utils.Verifier, error) {
|
||||
if context.Config().GpgDisableVerify || flags.Lookup("ignore-signatures").Value.Get().(bool) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
keyRings := flags.Lookup("keyring").Value.Get().([]string)
|
||||
|
||||
verifier := &utils.GpgVerifier{}
|
||||
for _, keyRing := range keyRings {
|
||||
verifier.AddKeyring(keyRing)
|
||||
}
|
||||
|
||||
err := verifier.InitKeyring()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return verifier, nil
|
||||
}
|
||||
|
||||
type keyRingsFlag struct {
|
||||
keyRings []string
|
||||
}
|
||||
|
||||
func (k *keyRingsFlag) Set(value string) error {
|
||||
k.keyRings = append(k.keyRings, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *keyRingsFlag) Get() interface{} {
|
||||
return k.keyRings
|
||||
}
|
||||
|
||||
func (k *keyRingsFlag) String() string {
|
||||
return strings.Join(k.keyRings, ",")
|
||||
}
|
||||
|
||||
func makeCmdMirror() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "mirror",
|
||||
Short: "manage mirrors of remote repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdMirrorCreate(),
|
||||
makeCmdMirrorList(),
|
||||
makeCmdMirrorShow(),
|
||||
makeCmdMirrorDrop(),
|
||||
makeCmdMirrorUpdate(),
|
||||
makeCmdMirrorRename(),
|
||||
makeCmdMirrorEdit(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if !(len(args) == 2 && strings.HasPrefix(args[1], "ppa:") || len(args) >= 3) {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
downloadSources := context.Config().DownloadSourcePackages || context.flags.Lookup("with-sources").Value.Get().(bool)
|
||||
|
||||
var (
|
||||
mirrorName, archiveURL, distribution string
|
||||
components []string
|
||||
)
|
||||
|
||||
mirrorName = args[0]
|
||||
if len(args) == 2 {
|
||||
archiveURL, distribution, components, err = deb.ParsePPA(args[1], context.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
archiveURL, distribution, components = args[1], args[2], args[3:]
|
||||
}
|
||||
|
||||
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(), downloadSources)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create mirror: %s", err)
|
||||
}
|
||||
|
||||
repo.Filter = context.flags.Lookup("filter").Value.String()
|
||||
repo.FilterWithDeps = context.flags.Lookup("filter-with-deps").Value.Get().(bool)
|
||||
|
||||
if repo.Filter != "" {
|
||||
_, err = query.Parse(repo.Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create mirror: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(context.flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG verifier: %s", err)
|
||||
}
|
||||
|
||||
err = repo.Fetch(context.Downloader(), verifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch mirror: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Add(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add mirror: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nMirror %s successfully added.\nYou can run 'aptly mirror update %s' to download repository contents.\n", repo, repo.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorCreate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorCreate,
|
||||
UsageLine: "create <name> <archive url> <distribution> [<component1> ...]",
|
||||
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
|
||||
line format resembles apt utlitily sources.list(5).
|
||||
|
||||
PPA urls could specified in short format:
|
||||
|
||||
$ aptly mirror create <name> ppa:<user>/<project>
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror create wheezy-main http://mirror.yandex.ru/debian/ wheezy main
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-create", flag.ExitOnError),
|
||||
}
|
||||
|
||||
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.String("filter", "", "filter packages in mirror")
|
||||
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
|
||||
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)
|
||||
|
||||
if len(snapshots) > 0 {
|
||||
fmt.Printf("Mirror `%s` was used to create following snapshots:\n", repo.Name)
|
||||
for _, snapshot := range snapshots {
|
||||
fmt.Printf(" * %s\n", snapshot)
|
||||
}
|
||||
|
||||
return fmt.Errorf("won't delete mirror with snapshots, use -force to override")
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Drop(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Mirror `%s` has been removed.\n", repo.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorDrop() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorDrop,
|
||||
UsageLine: "drop <name>",
|
||||
Short: "delete mirror",
|
||||
Long: `
|
||||
Drop deletes information about remote repository mirror <name>. Package data is not deleted
|
||||
(since it could still be used by other mirrors or snapshots). If mirror is used as source
|
||||
to create a snapshot, aptly would refuse to delete such mirror, use flag -force to override.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror drop wheezy-main
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-drop", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("force", false, "force mirror deletion even if used by snapshots")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
if repo.Filter != "" {
|
||||
_, err = query.Parse(repo.Filter)
|
||||
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 properties of mirorr",
|
||||
Long: `
|
||||
Command edit allows to change settings of mirror:
|
||||
filters.
|
||||
|
||||
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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlyMirrorList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
|
||||
repos := make([]string, context.CollectionFactory().RemoteRepoCollection().Len())
|
||||
i := 0
|
||||
context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
||||
if raw {
|
||||
repos[i] = repo.Name
|
||||
} else {
|
||||
repos[i] = repo.String()
|
||||
}
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.Strings(repos)
|
||||
|
||||
if raw {
|
||||
for _, repo := range repos {
|
||||
fmt.Printf("%s\n", repo)
|
||||
}
|
||||
} else {
|
||||
if len(repos) > 0 {
|
||||
fmt.Printf("List of mirrors:\n")
|
||||
for _, repo := range repos {
|
||||
fmt.Printf(" * %s\n", repo)
|
||||
}
|
||||
|
||||
fmt.Printf("\nTo get more information about mirror, run `aptly mirror show <name>`.\n")
|
||||
} else {
|
||||
fmt.Printf("No mirrors found, create one with `aptly mirror create ...`.\n")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorList() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorList,
|
||||
UsageLine: "list",
|
||||
Short: "list mirrors",
|
||||
Long: `
|
||||
List shows full list of remote repository mirrors.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror list
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("raw", false, "display list in machine-readable format")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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 = 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,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s\n", repo.Name)
|
||||
fmt.Printf("Archive Root URL: %s\n", repo.ArchiveRoot)
|
||||
fmt.Printf("Distribution: %s\n", repo.Distribution)
|
||||
fmt.Printf("Components: %s\n", strings.Join(repo.Components, ", "))
|
||||
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, ", "))
|
||||
downloadSources := "no"
|
||||
if repo.DownloadSources {
|
||||
downloadSources = "yes"
|
||||
}
|
||||
fmt.Printf("Download Sources: %s\n", downloadSources)
|
||||
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 {
|
||||
fmt.Printf("Last update: %s\n", repo.LastDownloadDate.Format("2006-01-02 15:04:05 MST"))
|
||||
fmt.Printf("Number of packages: %d\n", repo.NumPackages())
|
||||
}
|
||||
|
||||
fmt.Printf("\nInformation from release file:\n")
|
||||
for _, k := range utils.StrMapSortedKeys(repo.Meta) {
|
||||
fmt.Printf("%s: %s\n", k, repo.Meta[k])
|
||||
}
|
||||
|
||||
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool)
|
||||
if withPackages {
|
||||
if repo.LastDownloadDate.IsZero() {
|
||||
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
|
||||
} else {
|
||||
ListPackagesRefList(repo.RefList())
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorShow,
|
||||
UsageLine: "show <name>",
|
||||
Short: "show details about mirror",
|
||||
Long: `
|
||||
Shows detailed information about the mirror.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror show wheezy-main
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-packages", false, "show detailed list of packages and versions stored in the mirror")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
err = repo.Fetch(context.Downloader(), verifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
var filterQuery deb.PackageQuery
|
||||
|
||||
if repo.Filter != "" {
|
||||
filterQuery, err = query.Parse(repo.Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = repo.Download(context.Progress(), context.Downloader(), context.CollectionFactory(), context.PackagePool(), ignoreMismatch,
|
||||
context.DependencyOptions(), filterQuery)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("\nMirror `%s` has been successfully updated.\n", repo.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorUpdate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorUpdate,
|
||||
UsageLine: "update <name>",
|
||||
Short: "update mirror",
|
||||
Long: `
|
||||
Updates remote mirror (downloads package files and meta information). When mirror is created,
|
||||
this command should be run for the first time to fetch mirror contents. This command can be
|
||||
run multiple times to get updated repository contents. If interrupted, command can be safely restarted.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly mirror update wheezy-main
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-update", flag.ExitOnError),
|
||||
}
|
||||
|
||||
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,55 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getSigner(flags *flag.FlagSet) (utils.Signer, error) {
|
||||
if flags.Lookup("skip-signing").Value.Get().(bool) || context.Config().GpgDisableSign {
|
||||
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())
|
||||
|
||||
err := signer.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
|
||||
}
|
||||
|
||||
func parsePrefix(param string) (storage, prefix string) {
|
||||
i := strings.LastIndex(param, ":")
|
||||
if i != -1 {
|
||||
storage = param[:i]
|
||||
prefix = param[i+1:]
|
||||
if prefix == "" {
|
||||
prefix = "."
|
||||
}
|
||||
} else {
|
||||
prefix = param
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeCmdPublish() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "publish",
|
||||
Short: "manage published repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdPublishDrop(),
|
||||
makeCmdPublishList(),
|
||||
makeCmdPublishRepo(),
|
||||
makeCmdPublishSnapshot(),
|
||||
makeCmdPublishSwitch(),
|
||||
makeCmdPublishUpdate(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlyPublishDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
param := "."
|
||||
|
||||
if len(args) == 2 {
|
||||
param = args[1]
|
||||
}
|
||||
|
||||
storage, prefix := parsePrefix(param)
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Remove(context, storage, prefix, distribution,
|
||||
context.CollectionFactory(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("\nPublished repository has been removed successfully.\n")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishDrop() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishDrop,
|
||||
UsageLine: "drop <distribution> [[<endpoint>:]<prefix>]",
|
||||
Short: "remove published repository",
|
||||
Long: `
|
||||
Command removes whatever has been published under specified <prefix>,
|
||||
publishing <endpoint> and <distribution> name.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish drop wheezy
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlyPublishList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
|
||||
published := make([]string, 0, context.CollectionFactory().PublishedRepoCollection().Len())
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
||||
err := context.CollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if raw {
|
||||
published = append(published, fmt.Sprintf("%s %s", repo.Prefix, repo.Distribution))
|
||||
} else {
|
||||
published = append(published, repo.String())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load list of repos: %s", err)
|
||||
}
|
||||
|
||||
sort.Strings(published)
|
||||
|
||||
if raw {
|
||||
for _, info := range published {
|
||||
fmt.Printf("%s\n", info)
|
||||
}
|
||||
} else {
|
||||
if len(published) == 0 {
|
||||
fmt.Printf("No snapshots/local repos have been published. Publish a snapshot by running `aptly publish snapshot ...`.\n")
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Published repositories:\n")
|
||||
|
||||
for _, description := range published {
|
||||
fmt.Printf(" * %s\n", description)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishList() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishList,
|
||||
UsageLine: "list",
|
||||
Short: "list of published repositories",
|
||||
Long: `
|
||||
Display list of currently published snapshots.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish list
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("raw", false, "display list in machine-readable format")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func makeCmdPublishRepo() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSnapshotOrRepo,
|
||||
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
|
||||
using publish snapshot command.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish repo testing
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-publish-repo", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("distribution", "", "distribution name to publish")
|
||||
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
|
||||
cmd.Flag.String("origin", "", "origin name to publish")
|
||||
cmd.Flag.String("label", "", "label to publish")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
components := strings.Split(context.flags.Lookup("component").Value.String(), ",")
|
||||
|
||||
if len(args) < len(components) || len(args) > len(components)+1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
var param string
|
||||
if len(args) == len(components)+1 {
|
||||
param = args[len(components)]
|
||||
args = args[0 : len(args)-1]
|
||||
} else {
|
||||
param = ""
|
||||
}
|
||||
storage, prefix := parsePrefix(param)
|
||||
|
||||
var (
|
||||
sources = []interface{}{}
|
||||
message string
|
||||
)
|
||||
|
||||
if cmd.Name() == "snapshot" {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
message = fmt.Sprintf("Snapshot %s has", parts[0])
|
||||
} else {
|
||||
message = fmt.Sprintf("Snapshots %s have", strings.Join(parts, ", "))
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
message = fmt.Sprintf("Local repo %s has", parts[0])
|
||||
} else {
|
||||
message = fmt.Sprintf("Local repos %s have", strings.Join(parts, ", "))
|
||||
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
distribution := context.flags.Lookup("distribution").Value.String()
|
||||
|
||||
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()
|
||||
|
||||
duplicate := context.CollectionFactory().PublishedRepoCollection().CheckDuplicate(published)
|
||||
if duplicate != nil {
|
||||
context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory())
|
||||
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG signer: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Add(published)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
|
||||
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 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, repoComponents)
|
||||
if utils.StrSliceHasItem(published.Architectures, "source") {
|
||||
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")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishSnapshot() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSnapshotOrRepo,
|
||||
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
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-publish-snapshot", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("distribution", "", "distribution name to publish")
|
||||
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
|
||||
cmd.Flag.String("origin", "", "origin name to publish")
|
||||
cmd.Flag.String("label", "", "label to publish")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
components := strings.Split(context.flags.Lookup("component").Value.String(), ",")
|
||||
|
||||
if len(args) < len(components)+1 || len(args) > len(components)+2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
param := "."
|
||||
|
||||
var (
|
||||
names []string
|
||||
snapshot *deb.Snapshot
|
||||
)
|
||||
|
||||
if len(args) == len(components)+2 {
|
||||
param = args[1]
|
||||
names = args[2:]
|
||||
} else {
|
||||
names = args[1:]
|
||||
}
|
||||
|
||||
storage, prefix := parsePrefix(param)
|
||||
|
||||
var published *deb.PublishedRepo
|
||||
|
||||
published, err = context.CollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
if published.SourceKind != "snapshot" {
|
||||
return fmt.Errorf("unable to update: not a snapshot publish")
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
publishedComponents := published.Components()
|
||||
if len(components) == 1 && len(publishedComponents) == 1 && components[0] == "" {
|
||||
components = publishedComponents
|
||||
}
|
||||
|
||||
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 {
|
||||
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, context.CollectionFactory(), signer, context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Update(published)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
context.Progress().Printf("\nPublish for snapshot %s has been successfully switched to new snapshot.\n", published.String())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishSwitch() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSwitch,
|
||||
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).
|
||||
|
||||
For multiple component repositories, flag -component should be given with
|
||||
list of components to update. Corresponding snapshots should be given in the
|
||||
same order, e.g.:
|
||||
|
||||
aptly publish update -component=main,contrib wheezy wh-main wh-contrib
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish update wheezy ppa 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.Bool("skip-signing", false, "don't sign Release files with GPG")
|
||||
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
distribution := args[0]
|
||||
param := "."
|
||||
|
||||
if len(args) == 2 {
|
||||
param = args[1]
|
||||
}
|
||||
storage, prefix := parsePrefix(param)
|
||||
|
||||
var published *deb.PublishedRepo
|
||||
|
||||
published, err = context.CollectionFactory().PublishedRepoCollection().ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
if published.SourceKind != "local" {
|
||||
return fmt.Errorf("unable to update: not a local repository publish")
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
components := published.Components()
|
||||
for _, component := range components {
|
||||
published.UpdateLocalRepo(component)
|
||||
}
|
||||
|
||||
signer, err := getSigner(context.flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize GPG signer: %s", err)
|
||||
}
|
||||
|
||||
err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().Update(published)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save to DB: %s", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
context.Progress().Printf("\nPublish for local repo %s has been successfully updated.\n", published.String())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishUpdate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishUpdate,
|
||||
UsageLine: "update <distribution> [[<endpoint>:]<prefix>]",
|
||||
Short: "update published local repository",
|
||||
Long: `
|
||||
Command re-publishes (updates) published local repository. <distribution>
|
||||
and <prefix> should be occupied with local repository published
|
||||
using command aptly publish repo. Update happens in-place with
|
||||
minimum possible downtime for published repository.
|
||||
|
||||
For multiple component published repositories, all local repositories
|
||||
are updated.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly publish update wheezy ppa
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-publish-update", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
|
||||
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
|
||||
cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func makeCmdRepo() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "repo",
|
||||
Short: "manage local package repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdRepoAdd(),
|
||||
makeCmdRepoCopy(),
|
||||
makeCmdRepoCreate(),
|
||||
makeCmdRepoDrop(),
|
||||
makeCmdRepoEdit(),
|
||||
makeCmdRepoImport(),
|
||||
makeCmdRepoList(),
|
||||
makeCmdRepoMove(),
|
||||
makeCmdRepoRemove(),
|
||||
makeCmdRepoShow(),
|
||||
makeCmdRepoRename(),
|
||||
},
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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 commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
verifier := &utils.GpgVerifier{}
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("Loading packages...\n")
|
||||
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
packageFiles := []string{}
|
||||
failedFiles := []string{}
|
||||
|
||||
for _, location := range args[1:] {
|
||||
info, err2 := os.Stat(location)
|
||||
if err2 != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to process %s: %s@|", location, err2)
|
||||
failedFiles = append(failedFiles, location)
|
||||
continue
|
||||
}
|
||||
if info.IsDir() {
|
||||
err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error {
|
||||
if err3 != nil {
|
||||
return err3
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
|
||||
packageFiles = append(packageFiles, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") {
|
||||
packageFiles = append(packageFiles, location)
|
||||
} else {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location)
|
||||
failedFiles = append(failedFiles, location)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
var checksums utils.ChecksumInfo
|
||||
checksums, err = utils.ChecksumsForFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isSourcePackage {
|
||||
p.UpdateFiles(append(p.Files(), deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}))
|
||||
} else {
|
||||
p.UpdateFiles([]deb.PackageFile{deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}})
|
||||
}
|
||||
|
||||
err = context.PackagePool().Import(file, checksums.MD5)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", file, err)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
candidateProcessedFiles = append(candidateProcessedFiles, file)
|
||||
|
||||
// go over all files, except for the last one (.dsc/.deb itself)
|
||||
for _, f := range p.Files() {
|
||||
if filepath.Base(f.Filename) == filepath.Base(file) {
|
||||
continue
|
||||
}
|
||||
sourceFile := filepath.Join(filepath.Dir(file), filepath.Base(f.Filename))
|
||||
err = context.PackagePool().Import(sourceFile, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", sourceFile, err)
|
||||
failedFiles = append(failedFiles, file)
|
||||
break
|
||||
}
|
||||
|
||||
candidateProcessedFiles = append(candidateProcessedFiles, sourceFile)
|
||||
}
|
||||
if err != nil {
|
||||
// some files haven't been imported
|
||||
continue
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().PackageCollection().Update(p)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to save package %s: %s@|", p, err)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
err = list.Add(p)
|
||||
if err != nil {
|
||||
context.Progress().ColoredPrintf("@y[!]@| @!Unable to add package to repo %s: %s@|", p, err)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
context.Progress().ColoredPrintf("@g[+]@| %s added@|", p)
|
||||
processedFiles = append(processedFiles, candidateProcessedFiles...)
|
||||
}
|
||||
|
||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
|
||||
if context.flags.Lookup("remove-files").Value.Get().(bool) {
|
||||
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 makeCmdRepoAdd() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoAdd,
|
||||
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
|
||||
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.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo add testing myapp-0.1.2.deb incoming/
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-add", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("remove-files", false, "remove files that have been imported successfully into repository")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func makeCmdRepoCopy() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoMoveCopyImport,
|
||||
UsageLine: "copy <src-name> <dst-name> <package-query> ...",
|
||||
Short: "copy packages between local repositories",
|
||||
Long: `
|
||||
Command copy copies packages matching <package-query> from local repo
|
||||
<src-name> to local repo <dst-name>.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo copy testing stable 'myapp (=0.1.12)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-copy", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("dry-run", false, "don't copy, just show what would be copied")
|
||||
cmd.Flag.Bool("with-deps", false, "follow dependencies when processing package-spec")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyRepoCreate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
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()
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Add(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add local repo: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nLocal repo %s successfully added.\nYou can run 'aptly repo add %s ...' to add packages to repository.\n", repo, repo.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoCreate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoCreate,
|
||||
UsageLine: "create <name>",
|
||||
Short: "create local repository",
|
||||
Long: `
|
||||
Create local package repository. Repository would be empty when
|
||||
created, packages could be added from files, copied or moved from
|
||||
another local repository or imported from the mirror.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo create testing
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-create", flag.ExitOnError),
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyRepoDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
published := context.CollectionFactory().PublishedRepoCollection().ByLocalRepo(repo)
|
||||
if len(published) > 0 {
|
||||
fmt.Printf("Local repo `%s` is published currently:\n", repo.Name)
|
||||
for _, repo := range published {
|
||||
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load published: %s", err)
|
||||
}
|
||||
fmt.Printf(" * %s\n", repo)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to drop: local repo is published")
|
||||
}
|
||||
|
||||
force := context.flags.Lookup("force").Value.Get().(bool)
|
||||
if !force {
|
||||
snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo)
|
||||
|
||||
if len(snapshots) > 0 {
|
||||
fmt.Printf("Local repo `%s` was used to create following snapshots:\n", repo.Name)
|
||||
for _, snapshot := range snapshots {
|
||||
fmt.Printf(" * %s\n", snapshot)
|
||||
}
|
||||
|
||||
return fmt.Errorf("won't delete local repo with snapshots, use -force to override")
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Drop(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Local repo `%s` has been removed.\n", repo.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoDrop() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoDrop,
|
||||
UsageLine: "drop <name>",
|
||||
Short: "delete local repository",
|
||||
Long: `
|
||||
Drop information about deletions from local repo. Package data is not deleted
|
||||
(since it could be still used by other mirrors or snapshots).
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo drop local-repo
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-drop", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("force", false, "force local repo deletion even if used by snapshots")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyRepoEdit(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
if context.flags.Lookup("comment").Value.String() != "" {
|
||||
repo.Comment = context.flags.Lookup("comment").Value.String()
|
||||
}
|
||||
|
||||
if context.flags.Lookup("distribution").Value.String() != "" {
|
||||
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String()
|
||||
}
|
||||
|
||||
if context.flags.Lookup("component").Value.String() != "" {
|
||||
repo.DefaultComponent = context.flags.Lookup("component").Value.String()
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to edit: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Local repo %s successfully updated.\n", repo)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoEdit() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoEdit,
|
||||
UsageLine: "edit <name>",
|
||||
Short: "edit properties of local repository",
|
||||
Long: `
|
||||
Command edit allows to change metadata of local repository:
|
||||
comment, default distribution and component.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo edit -distribution=wheezy testing
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-edit", flag.ExitOnError),
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func makeCmdRepoImport() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoMoveCopyImport,
|
||||
UsageLine: "import <src-mirror> <dst-repo> <package-query> ...",
|
||||
Short: "import packages from mirror to local repository",
|
||||
Long: `
|
||||
Command import looks up packages matching <package-query> in mirror <src-mirror>
|
||||
and copies them to local repo <dst-repo>.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo import wheezy-main testing nginx
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-import", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("dry-run", false, "don't import, just show what would be imported")
|
||||
cmd.Flag.Bool("with-deps", false, "follow dependencies when processing package-spec")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlyRepoList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
|
||||
repos := make([]string, context.CollectionFactory().LocalRepoCollection().Len())
|
||||
i := 0
|
||||
context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
||||
if raw {
|
||||
repos[i] = repo.Name
|
||||
} else {
|
||||
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repos[i] = fmt.Sprintf(" * %s (packages: %d)", repo.String(), repo.NumPackages())
|
||||
}
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.Strings(repos)
|
||||
|
||||
if raw {
|
||||
for _, repo := range repos {
|
||||
fmt.Printf("%s\n", repo)
|
||||
}
|
||||
} else {
|
||||
if len(repos) > 0 {
|
||||
fmt.Printf("List of local repos:\n")
|
||||
for _, repo := range repos {
|
||||
fmt.Println(repo)
|
||||
}
|
||||
|
||||
fmt.Printf("\nTo get more information about local repository, run `aptly repo show <name>`.\n")
|
||||
} else {
|
||||
fmt.Printf("No local repositories found, create one with `aptly repo create ...`.\n")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoList() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoList,
|
||||
UsageLine: "list",
|
||||
Short: "list local repositories",
|
||||
Long: `
|
||||
List command shows full list of local package repositories.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo list
|
||||
`,
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("raw", false, "display list in machine-readable format")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 3 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
command := cmd.Name()
|
||||
|
||||
dstRepo, err := context.CollectionFactory().LocalRepoCollection().ByName(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(dstRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
var (
|
||||
srcRefList *deb.PackageRefList
|
||||
srcRepo *deb.LocalRepo
|
||||
)
|
||||
|
||||
if command == "copy" || command == "move" {
|
||||
srcRepo, err = context.CollectionFactory().LocalRepoCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
if srcRepo.UUID == dstRepo.UUID {
|
||||
return fmt.Errorf("unable to %s: source and destination are the same", command)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(srcRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
srcRefList = srcRepo.RefList()
|
||||
} else if command == "import" {
|
||||
var srcRemoteRepo *deb.RemoteRepo
|
||||
|
||||
srcRemoteRepo, err = context.CollectionFactory().RemoteRepoCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(srcRemoteRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
if srcRemoteRepo.RefList() == nil {
|
||||
return fmt.Errorf("unable to %s: mirror not updated", command)
|
||||
}
|
||||
|
||||
srcRefList = srcRemoteRepo.RefList()
|
||||
} else {
|
||||
panic("unexpected command")
|
||||
}
|
||||
|
||||
context.Progress().Printf("Loading packages...\n")
|
||||
|
||||
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
srcList, err := deb.NewPackageListFromRefList(srcRefList, context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
srcList.PrepareIndex()
|
||||
|
||||
var architecturesList []string
|
||||
|
||||
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool)
|
||||
|
||||
if withDeps {
|
||||
dstList.PrepareIndex()
|
||||
|
||||
// Calculate architectures
|
||||
if len(context.ArchitecturesList()) > 0 {
|
||||
architecturesList = context.ArchitecturesList()
|
||||
} else {
|
||||
architecturesList = dstList.Architectures(false)
|
||||
}
|
||||
|
||||
sort.Strings(architecturesList)
|
||||
|
||||
if len(architecturesList) == 0 {
|
||||
return fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var verb string
|
||||
|
||||
if command == "move" {
|
||||
verb = "moved"
|
||||
} else if command == "copy" {
|
||||
verb = "copied"
|
||||
} else if command == "import" {
|
||||
verb = "imported"
|
||||
}
|
||||
|
||||
err = toProcess.ForEach(func(p *deb.Package) error {
|
||||
err = dstList.Add(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if command == "move" {
|
||||
srcList.Remove(p)
|
||||
}
|
||||
context.Progress().ColoredPrintf("@g[o]@| %s %s", p, verb)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to %s: %s", command, err)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(dstRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
|
||||
if command == "move" {
|
||||
srcRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(srcList))
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(srcRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoMove() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoMoveCopyImport,
|
||||
UsageLine: "move <src-name> <dst-name> <package-query> ...",
|
||||
Short: "move packages between local repositories",
|
||||
Long: `
|
||||
Command move moves packages matching <package-query> from local repo
|
||||
<src-name> to local repo <dst-name>.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo move testing stable 'myapp (=0.1.12)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-move", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("dry-run", false, "don't move, just show what would be moved")
|
||||
cmd.Flag.Bool("with-deps", false, "follow dependencies when processing package-spec")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/aptly/query"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyRepoRemove(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("Loading packages...\n")
|
||||
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
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(queries, false, nil, 0, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove: %s", err)
|
||||
}
|
||||
|
||||
toRemove.ForEach(func(p *deb.Package) error {
|
||||
list.Remove(p)
|
||||
context.Progress().ColoredPrintf("@r[-]@| %s removed", p)
|
||||
return nil
|
||||
})
|
||||
|
||||
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))
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoRemove() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoRemove,
|
||||
UsageLine: "remove <name> <package-query> ...",
|
||||
Short: "remove packages from local repository",
|
||||
Long: `
|
||||
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'.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly repo remove testing 'myapp (=0.1.12)'
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-add", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("dry-run", false, "don't remove, just show what would be removed")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -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,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlyRepoShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s\n", repo.Name)
|
||||
fmt.Printf("Comment: %s\n", repo.Comment)
|
||||
fmt.Printf("Default Distribution: %s\n", repo.DefaultDistribution)
|
||||
fmt.Printf("Default Component: %s\n", repo.DefaultComponent)
|
||||
fmt.Printf("Number of packages: %d\n", repo.NumPackages())
|
||||
|
||||
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool)
|
||||
if withPackages {
|
||||
ListPackagesRefList(repo.RefList())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdRepoShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyRepoShow,
|
||||
UsageLine: "show <name>",
|
||||
Short: "show details about local repository",
|
||||
Long: `
|
||||
Show command shows full information about local package repository.
|
||||
|
||||
ex:
|
||||
$ aptly repo show testing
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-repo-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-packages", false, "show list of packages")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
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"
|
||||
"net"
|
||||
"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()
|
||||
|
||||
listenHost, listenPort, err := net.SplitHostPort(listen)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong -listen specification: %s", err)
|
||||
}
|
||||
|
||||
if listenHost == "" {
|
||||
listenHost, err = os.Hostname()
|
||||
if err != nil {
|
||||
listenHost = "localhost"
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Serving published repositories, recommended apt sources list:\n\n")
|
||||
|
||||
sources := make(sort.StringSlice, 0, context.CollectionFactory().PublishedRepoCollection().Len())
|
||||
published := make(map[string]*deb.PublishedRepo, context.CollectionFactory().PublishedRepoCollection().Len())
|
||||
|
||||
err = context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
||||
err := context.CollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sources = append(sources, repo.String())
|
||||
published[repo.String()] = repo
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to serve: %s", err)
|
||||
}
|
||||
|
||||
sort.Strings(sources)
|
||||
|
||||
for _, source := range sources {
|
||||
repo := published[source]
|
||||
|
||||
prefix := repo.Prefix
|
||||
if prefix == "." {
|
||||
prefix = ""
|
||||
} else {
|
||||
prefix += "/"
|
||||
}
|
||||
|
||||
fmt.Printf("# %s\ndeb http://%s:%s/%s %s %s\n",
|
||||
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, strings.Join(repo.Components(), " "))
|
||||
}
|
||||
}
|
||||
|
||||
publicPath := context.GetPublishedStorage("").(aptly.LocalPublishedStorage).PublicPath()
|
||||
ShutdownContext()
|
||||
|
||||
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
|
||||
|
||||
err = http.ListenAndServe(listen, http.FileServer(http.Dir(publicPath)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to serve: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeCmdServe() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyServe,
|
||||
UsageLine: "serve",
|
||||
Short: "HTTP serve published repositories",
|
||||
Long: `
|
||||
Command serve starts embedded HTTP server (not suitable for real production usage) to serve
|
||||
contents of public/ subdirectory of aptly's root that contains published repositories.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly serve -listen=:8080
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.String("listen", ":8080", "host:port for HTTP listening")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func makeCmdSnapshot() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "snapshot",
|
||||
Short: "manage snapshots of repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdSnapshotCreate(),
|
||||
makeCmdSnapshotList(),
|
||||
makeCmdSnapshotShow(),
|
||||
makeCmdSnapshotVerify(),
|
||||
makeCmdSnapshotPull(),
|
||||
makeCmdSnapshotDiff(),
|
||||
makeCmdSnapshotMerge(),
|
||||
makeCmdSnapshotDrop(),
|
||||
makeCmdSnapshotRename(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlySnapshotCreate(cmd *commander.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
snapshot *deb.Snapshot
|
||||
)
|
||||
|
||||
if len(args) == 4 && args[1] == "from" && args[2] == "mirror" {
|
||||
// aptly snapshot create snap from mirror mirror
|
||||
var repo *deb.RemoteRepo
|
||||
|
||||
repoName, snapshotName := args[3], args[0]
|
||||
|
||||
repo, err = context.CollectionFactory().RemoteRepoCollection().ByName(repoName)
|
||||
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)
|
||||
}
|
||||
|
||||
snapshot, err = deb.NewSnapshotFromRepository(snapshotName, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
} else if len(args) == 4 && args[1] == "from" && args[2] == "repo" {
|
||||
// aptly snapshot create snap from repo repo
|
||||
var repo *deb.LocalRepo
|
||||
|
||||
localRepoName, snapshotName := args[3], args[0]
|
||||
|
||||
repo, err = context.CollectionFactory().LocalRepoCollection().ByName(localRepoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
snapshot, err = deb.NewSnapshotFromLocalRepo(snapshotName, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
} else if len(args) == 2 && args[1] == "empty" {
|
||||
// aptly snapshot create snap empty
|
||||
snapshotName := args[0]
|
||||
|
||||
packageList := deb.NewPackageList()
|
||||
|
||||
snapshot = deb.NewSnapshotFromPackageList(snapshotName, nil, packageList, "Created as empty")
|
||||
} else {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().Add(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add snapshot: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nSnapshot %s successfully created.\nYou can run 'aptly publish snapshot %s' to publish snapshot as Debian repository.\n", snapshot.Name, snapshot.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotCreate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotCreate,
|
||||
UsageLine: "create <name> from mirror <mirror-name> | from repo <repo-name> | empty",
|
||||
Short: "creates snapshot of mirror (local repository) contents",
|
||||
Long: `
|
||||
Command create <name> from mirror makes persistent immutable snapshot of remote
|
||||
repository mirror. Snapshot could be published or further modified using
|
||||
merge, pull and other aptly features.
|
||||
|
||||
Command create <name> from repo makes persistent immutable snapshot of local
|
||||
repository. Snapshot could be processed as mirror snapshots, and mixed with
|
||||
snapshots of remote mirrors.
|
||||
|
||||
Command create <name> empty creates empty snapshot that could be used as a
|
||||
basis for snapshot pull operations, for example. As snapshots are immutable,
|
||||
creating one empty snapshot should be enough.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot create wheezy-main-today from mirror wheezy-main
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlySnapshotDiff(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
onlyMatching := context.flags.Lookup("only-matching").Value.Get().(bool)
|
||||
|
||||
// Load <name-a> snapshot
|
||||
snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load snapshot A: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshotA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load snapshot A: %s", err)
|
||||
}
|
||||
|
||||
// Load <name-b> snapshot
|
||||
snapshotB, err := context.CollectionFactory().SnapshotCollection().ByName(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load snapshot B: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshotB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load snapshot B: %s", err)
|
||||
}
|
||||
|
||||
// Calculate diff
|
||||
diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), context.CollectionFactory().PackageCollection())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to calculate diff: %s", err)
|
||||
}
|
||||
|
||||
if len(diff) == 0 {
|
||||
context.Progress().Printf("Snapshots are identical.\n")
|
||||
} else {
|
||||
context.Progress().Printf(" Arch | Package | Version in A | Version in B\n")
|
||||
for _, pdiff := range diff {
|
||||
if onlyMatching && (pdiff.Left == nil || pdiff.Right == nil) {
|
||||
continue
|
||||
}
|
||||
|
||||
var verA, verB, pkg, arch, code string
|
||||
|
||||
if pdiff.Left == nil {
|
||||
verA = "-"
|
||||
verB = pdiff.Right.Version
|
||||
pkg = pdiff.Right.Name
|
||||
arch = pdiff.Right.Architecture
|
||||
} else {
|
||||
pkg = pdiff.Left.Name
|
||||
arch = pdiff.Left.Architecture
|
||||
verA = pdiff.Left.Version
|
||||
if pdiff.Right == nil {
|
||||
verB = "-"
|
||||
} else {
|
||||
verB = pdiff.Right.Version
|
||||
}
|
||||
}
|
||||
|
||||
if pdiff.Left == nil {
|
||||
code = "@g+@|"
|
||||
} else {
|
||||
if pdiff.Right == nil {
|
||||
code = "@r-@|"
|
||||
} else {
|
||||
code = "@y!@|"
|
||||
}
|
||||
}
|
||||
|
||||
context.Progress().ColoredPrintf(code+" %-6s | %-40s | %-40s | %-40s", arch, pkg, verA, verB)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotDiff() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotDiff,
|
||||
UsageLine: "diff <name-a> <name-b>",
|
||||
Short: "difference between two snapshots",
|
||||
Long: `
|
||||
Displays difference in packages between two snapshots. Snapshot is a list
|
||||
of packages, so difference between snapshots is a difference between package
|
||||
lists. Package could be either completely missing in one snapshot, or package
|
||||
is present in both snapshots with different versions.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot diff -only-matching wheezy-main wheezy-backports
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-diff", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("only-matching", false, "display diff only for matching packages (don't display missing packages)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlySnapshotDrop(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
published := context.CollectionFactory().PublishedRepoCollection().BySnapshot(snapshot)
|
||||
|
||||
if len(published) > 0 {
|
||||
fmt.Printf("Snapshot `%s` is published currently:\n", snapshot.Name)
|
||||
for _, repo := range published {
|
||||
err = context.CollectionFactory().PublishedRepoCollection().LoadComplete(repo, context.CollectionFactory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load published: %s", err)
|
||||
}
|
||||
fmt.Printf(" * %s\n", repo)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to drop: snapshot is published")
|
||||
}
|
||||
|
||||
force := context.flags.Lookup("force").Value.Get().(bool)
|
||||
if !force {
|
||||
snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot)
|
||||
if len(snapshots) > 0 {
|
||||
fmt.Printf("Snapshot `%s` was used as a source in following snapshots:\n", snapshot.Name)
|
||||
for _, snap := range snapshots {
|
||||
fmt.Printf(" * %s\n", snap)
|
||||
}
|
||||
|
||||
return fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use -force to override")
|
||||
}
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().Drop(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to drop: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Snapshot `%s` has been dropped.\n", snapshot.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotDrop() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotDrop,
|
||||
UsageLine: "drop <name>",
|
||||
Short: "delete snapshot",
|
||||
Long: `
|
||||
Drop removes information about a snapshot. If snapshot is published,
|
||||
it can't be dropped.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot drop wheezy-main
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-drop", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("force", false, "remove snapshot even if it was used as source for other snapshots")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Snapshot sorting methods
|
||||
const (
|
||||
SortName = iota
|
||||
SortTime
|
||||
)
|
||||
|
||||
type snapshotListToSort struct {
|
||||
list []*deb.Snapshot
|
||||
sortMethod int
|
||||
}
|
||||
|
||||
func parseSortMethod(sortMethod string) (int, error) {
|
||||
switch sortMethod {
|
||||
case "time", "Time":
|
||||
return SortTime, nil
|
||||
case "name", "Name":
|
||||
return SortName, nil
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
|
||||
}
|
||||
|
||||
func (s snapshotListToSort) Swap(i, j int) {
|
||||
s.list[i], s.list[j] = s.list[j], s.list[i]
|
||||
}
|
||||
|
||||
func (s snapshotListToSort) Less(i, j int) bool {
|
||||
switch s.sortMethod {
|
||||
case SortName:
|
||||
return s.list[i].Name < s.list[j].Name
|
||||
case SortTime:
|
||||
return s.list[i].CreatedAt.Before(s.list[j].CreatedAt)
|
||||
}
|
||||
panic("unknown sort method")
|
||||
}
|
||||
|
||||
func (s snapshotListToSort) Len() int {
|
||||
return len(s.list)
|
||||
}
|
||||
|
||||
func aptlySnapshotList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
raw := cmd.Flag.Lookup("raw").Value.Get().(bool)
|
||||
sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string)
|
||||
|
||||
snapshotsToSort := &snapshotListToSort{}
|
||||
snapshotsToSort.list = make([]*deb.Snapshot, context.CollectionFactory().SnapshotCollection().Len())
|
||||
snapshotsToSort.sortMethod, err = parseSortMethod(sortMethodString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i := 0
|
||||
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
||||
snapshotsToSort.list[i] = snapshot
|
||||
i++
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.Sort(snapshotsToSort)
|
||||
|
||||
if raw {
|
||||
for _, snapshot := range snapshotsToSort.list {
|
||||
fmt.Printf("%s\n", snapshot.Name)
|
||||
}
|
||||
} else {
|
||||
if len(snapshotsToSort.list) > 0 {
|
||||
fmt.Printf("List of snapshots:\n")
|
||||
|
||||
for _, snapshot := range snapshotsToSort.list {
|
||||
fmt.Printf(" * %s\n", snapshot.String())
|
||||
}
|
||||
|
||||
fmt.Printf("\nTo get more information about snapshot, run `aptly snapshot show <name>`.\n")
|
||||
} else {
|
||||
fmt.Printf("\nNo snapshots found, create one with `aptly snapshot create...`.\n")
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func makeCmdSnapshotList() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotList,
|
||||
UsageLine: "list",
|
||||
Short: "list snapshots",
|
||||
Long: `
|
||||
Command list shows full list of snapshots created.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot list
|
||||
`,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
sources := make([]*deb.Snapshot, len(args)-1)
|
||||
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
sources[i], err = context.CollectionFactory().SnapshotCollection().ByName(args[i+1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load snapshot: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(sources[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load snapshot: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if latest {
|
||||
deb.FilterLatestRefs(result)
|
||||
}
|
||||
|
||||
sourceDescription := make([]string, len(sources))
|
||||
for i, s := range sources {
|
||||
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
|
||||
}
|
||||
|
||||
// Create <destination> snapshot
|
||||
destination := deb.NewSnapshotFromRefList(args[0], sources, result,
|
||||
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().Add(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nSnapshot %s successfully created.\nYou can run 'aptly publish snapshot %s' to publish snapshot as Debian repository.\n", destination.Name, destination.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotMerge() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotMerge,
|
||||
UsageLine: "merge <destination> <source> [<source>...]",
|
||||
Short: "merges snapshots",
|
||||
Long: `
|
||||
Merge command merges several <source> snapshots into one <destination> snapshot.
|
||||
Merge happens from left to right. By default, packages with the same
|
||||
name-architecture pair are replaced during merge (package from latest snapshot
|
||||
on the list wins). If run with only one source snapshot, merge copies <source> into
|
||||
<destination>.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot merge wheezy-w-backports wheezy-main wheezy-backports
|
||||
`,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
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 aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 4 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
noDeps := context.flags.Lookup("no-deps").Value.Get().(bool)
|
||||
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool)
|
||||
allMatches := context.flags.Lookup("all-matches").Value.Get().(bool)
|
||||
|
||||
// Load <name> snapshot
|
||||
snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to pull: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to pull: %s", err)
|
||||
}
|
||||
|
||||
// Load <source> snapshot
|
||||
source, err := context.CollectionFactory().SnapshotCollection().ByName(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to pull: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to pull: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("Dependencies would be pulled into snapshot:\n %s\nfrom snapshot:\n %s\nand result would be saved as new snapshot %s.\n",
|
||||
snapshot, source, args[2])
|
||||
|
||||
// Convert snapshot to package list
|
||||
context.Progress().Printf("Loading packages (%d)...\n", snapshot.RefList().Len()+source.RefList().Len())
|
||||
packageList, err := deb.NewPackageListFromRefList(snapshot.RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
sourcePackageList, 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()
|
||||
sourcePackageList.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 {
|
||||
return fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
// 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{queries[i], 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)
|
||||
}
|
||||
}
|
||||
|
||||
// If !allMatches, add only first matching name-arch package
|
||||
if !seen || allMatches {
|
||||
packageList.Add(pkg)
|
||||
context.Progress().ColoredPrintf("@g[+]@| %s added", pkg)
|
||||
}
|
||||
|
||||
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
|
||||
destination := deb.NewSnapshotFromPackageList(args[2], []*deb.Snapshot{snapshot, source}, packageList,
|
||||
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", snapshot.Name, source.Name, strings.Join(args[3:], " ")))
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().Add(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
context.Progress().Printf("\nSnapshot %s successfully created.\nYou can run 'aptly publish snapshot %s' to publish snapshot as Debian repository.\n", destination.Name, destination.Name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotPull() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotPull,
|
||||
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 package queries.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot pull wheezy-main wheezy-backports wheezy-new-xorg xorg-server-server
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-pull", flag.ExitOnError),
|
||||
}
|
||||
|
||||
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,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
func aptlySnapshotShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s\n", snapshot.Name)
|
||||
fmt.Printf("Created At: %s\n", snapshot.CreatedAt.Format("2006-01-02 15:04:05 MST"))
|
||||
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)
|
||||
if withPackages {
|
||||
ListPackagesRefList(snapshot.RefList())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotShow,
|
||||
UsageLine: "show <name>",
|
||||
Short: "shows details about snapshot",
|
||||
Long: `
|
||||
Command show displays full information about a snapshot.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot show wheezy-main
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.Bool("with-packages", false, "show list of packages")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/deb"
|
||||
"github.com/smira/commander"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func aptlySnapshotVerify(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
snapshots := make([]*deb.Snapshot, len(args))
|
||||
for i := range snapshots {
|
||||
snapshots[i], err = context.CollectionFactory().SnapshotCollection().ByName(args[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to verify: %s", err)
|
||||
}
|
||||
|
||||
err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshots[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to verify: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
context.Progress().Printf("Loading packages...\n")
|
||||
|
||||
packageList, err := deb.NewPackageListFromRefList(snapshots[0].RefList(), context.CollectionFactory().PackageCollection(), context.Progress())
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
err = sourcePackageList.Append(pL)
|
||||
if err != nil {
|
||||
fmt.Errorf("unable to merge sources: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
sourcePackageList.PrepareIndex()
|
||||
|
||||
var architecturesList []string
|
||||
|
||||
if len(context.ArchitecturesList()) > 0 {
|
||||
architecturesList = context.ArchitecturesList()
|
||||
} else {
|
||||
architecturesList = packageList.Architectures(true)
|
||||
}
|
||||
|
||||
if len(architecturesList) == 0 {
|
||||
return fmt.Errorf("unable to determine list of architectures, please specify explicitly")
|
||||
}
|
||||
|
||||
context.Progress().Printf("Verifying...\n")
|
||||
|
||||
missing, err := packageList.VerifyDependencies(context.DependencyOptions(), architecturesList, sourcePackageList, context.Progress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to verify dependencies: %s", err)
|
||||
}
|
||||
|
||||
if len(missing) == 0 {
|
||||
context.Progress().Printf("All dependencies are satisfied.\n")
|
||||
} else {
|
||||
context.Progress().Printf("Missing dependencies (%d):\n", len(missing))
|
||||
deps := make([]string, len(missing))
|
||||
i := 0
|
||||
for _, dep := range missing {
|
||||
deps[i] = dep.String()
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(deps)
|
||||
|
||||
for _, dep := range deps {
|
||||
context.Progress().Printf(" %s\n", dep)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotVerify() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotVerify,
|
||||
UsageLine: "verify <name> [<source> ...]",
|
||||
Short: "verify dependencies in snapshot",
|
||||
Long: `
|
||||
Verify does dependency resolution in snapshot <name>, possibly using additional
|
||||
snapshots <source> as dependency sources. All unsatisfied dependencies are
|
||||
printed.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly snapshot verify wheezy-main wheezy-contrib wheezy-non-free
|
||||
`,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/commander"
|
||||
)
|
||||
|
||||
func aptlyVersion(cmd *commander.Command, args []string) error {
|
||||
fmt.Printf("aptly version: %s\n", Version)
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return commander.ErrCommandError
|
||||
}
|
||||
|
||||
fmt.Printf("aptly version: %s\n", aptly.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +27,5 @@ Shows aptly version.
|
||||
ex:
|
||||
$ aptly version
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-version", flag.ExitOnError),
|
||||
}
|
||||
}
|
||||
-228
@@ -1,228 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
"github.com/smira/aptly/debian"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyMirrorList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("List of mirrors:\n")
|
||||
|
||||
repoCollection := debian.NewRemoteRepoCollection(context.database)
|
||||
repoCollection.ForEach(func(repo *debian.RemoteRepo) error {
|
||||
fmt.Printf(" * %s\n", repo)
|
||||
return nil
|
||||
})
|
||||
|
||||
fmt.Printf("\nTo get more information about repository, run `aptly mirror show <name>`.\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 3 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
var architectures []string
|
||||
archs := cmd.Flag.Lookup("architecture").Value.String()
|
||||
if len(archs) > 0 {
|
||||
architectures = strings.Split(archs, ",")
|
||||
}
|
||||
|
||||
repo, err := debian.NewRemoteRepo(args[0], args[1], args[2], args[3:], architectures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create mirror: %s", err)
|
||||
}
|
||||
|
||||
err = repo.Fetch(context.downloader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch mirror: %s", err)
|
||||
}
|
||||
|
||||
repoCollection := debian.NewRemoteRepoCollection(context.database)
|
||||
|
||||
err = repoCollection.Add(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add mirror: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nMirror %s successfully added.\nYou can run 'aptly mirror update %s' to download repository contents.\n", repo, repo.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func aptlyMirrorShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repoCollection := debian.NewRemoteRepoCollection(context.database)
|
||||
repo, err := repoCollection.ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
err = repoCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s\n", repo.Name)
|
||||
fmt.Printf("Archive Root URL: %s\n", repo.ArchiveRoot)
|
||||
fmt.Printf("Distribution: %s\n", repo.Distribution)
|
||||
fmt.Printf("Components: %s\n", strings.Join(repo.Components, ", "))
|
||||
fmt.Printf("Architectures: %s\n", strings.Join(repo.Architectures, ", "))
|
||||
if repo.LastDownloadDate.IsZero() {
|
||||
fmt.Printf("Last update: never\n")
|
||||
} else {
|
||||
fmt.Printf("Last update: %s\n", repo.LastDownloadDate.Format("2006-01-02 15:04:05 MST"))
|
||||
fmt.Printf("Number of packages: %d\n", repo.NumPackages())
|
||||
}
|
||||
|
||||
fmt.Printf("\nInformation from release file:\n")
|
||||
for name, value := range repo.Meta {
|
||||
fmt.Printf("%s: %s\n", name, value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
repoCollection := debian.NewRemoteRepoCollection(context.database)
|
||||
repo, err := repoCollection.ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = repoCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = repo.Fetch(context.downloader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
packageCollection := debian.NewPackageCollection(context.database)
|
||||
|
||||
err = repo.Download(context.downloader, packageCollection, context.packageRepository)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
err = repoCollection.Update(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nMirror `%s` has been successfully updated.\n", repo.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdMirrorCreate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorCreate,
|
||||
UsageLine: "create",
|
||||
Short: "create new mirror of Debian repository",
|
||||
Long: `
|
||||
create only stores metadata about new mirror, and fetches Release files (it doesn't download packages)
|
||||
|
||||
ex:
|
||||
$ aptly mirror create <name> <archive url> <distribution> [<component1> ...]
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-create", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.String("architecture", "", "limit architectures to download, comma-delimited list")
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
|
||||
func makeCmdMirrorList() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorList,
|
||||
UsageLine: "list",
|
||||
Short: "list mirrors of remote repositories",
|
||||
Long: `
|
||||
list shows full list of remote repositories.
|
||||
|
||||
ex:
|
||||
$ aptly mirror list
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-list", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.Bool("v", false, "enable verbose output")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeCmdMirrorShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorShow,
|
||||
UsageLine: "show",
|
||||
Short: "show details about remote repository mirror",
|
||||
Long: `
|
||||
show shows full information about mirror.
|
||||
|
||||
ex:
|
||||
$ aptly mirror show <name>
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeCmdMirrorUpdate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyMirrorUpdate,
|
||||
UsageLine: "update",
|
||||
Short: "update packages from remote mirror",
|
||||
Long: `
|
||||
Update downloads list of packages and packages themselves.
|
||||
|
||||
ex:
|
||||
$ aptly mirror update <name>
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-mirror-update", flag.ExitOnError),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeCmdMirror() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "mirror",
|
||||
Short: "manage mirrors of remote repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdMirrorCreate(),
|
||||
makeCmdMirrorList(),
|
||||
makeCmdMirrorShow(),
|
||||
//makeCmdMirrorDestroy(),
|
||||
makeCmdMirrorUpdate(),
|
||||
},
|
||||
Flag: *flag.NewFlagSet("aptly-mirror", flag.ExitOnError),
|
||||
}
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
"github.com/smira/aptly/debian"
|
||||
"github.com/smira/aptly/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func aptlyPublishSnapshot(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
var prefix string
|
||||
if len(args) == 2 {
|
||||
prefix = args[1]
|
||||
} else {
|
||||
prefix = ""
|
||||
}
|
||||
|
||||
snapshotCollection := debian.NewSnapshotCollection(context.database)
|
||||
snapshot, err := snapshotCollection.ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
err = snapshotCollection.LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
}
|
||||
|
||||
var sourceRepo *debian.RemoteRepo
|
||||
|
||||
if snapshot.SourceKind == "repo" && len(snapshot.SourceIDs) == 1 {
|
||||
repoCollection := debian.NewRemoteRepoCollection(context.database)
|
||||
|
||||
sourceRepo, _ = repoCollection.ByUUID(snapshot.SourceIDs[0])
|
||||
}
|
||||
|
||||
component := cmd.Flag.Lookup("component").Value.String()
|
||||
if component == "" {
|
||||
if sourceRepo != nil && len(sourceRepo.Components) == 1 {
|
||||
component = sourceRepo.Components[0]
|
||||
} else {
|
||||
component = "main"
|
||||
}
|
||||
}
|
||||
|
||||
distribution := cmd.Flag.Lookup("distribution").Value.String()
|
||||
if distribution == "" {
|
||||
if sourceRepo != nil {
|
||||
distribution = sourceRepo.Distribution
|
||||
} else {
|
||||
return fmt.Errorf("unable to guess distribution name, please specify explicitly")
|
||||
}
|
||||
}
|
||||
|
||||
var architecturesList []string
|
||||
architectures := cmd.Flag.Lookup("architectures").Value.String()
|
||||
if architectures != "" {
|
||||
architecturesList = strings.Split(architectures, ",")
|
||||
}
|
||||
|
||||
signer := &utils.GpgSigner{}
|
||||
signer.SetKey(cmd.Flag.Lookup("gpg-key").Value.String())
|
||||
|
||||
published := debian.NewPublishedRepo(prefix, distribution, component, architecturesList, snapshot)
|
||||
|
||||
packageCollection := debian.NewPackageCollection(context.database)
|
||||
err = published.Publish(context.packageRepository, packageCollection, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if prefix != "" && !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
|
||||
fmt.Printf("\nSnapshot %s has been successfully published.\nPlease setup your webserver to serve directory '%s' with autoindexing.\n",
|
||||
snapshot.Name, context.packageRepository.PublicPath())
|
||||
fmt.Printf("Now you can add following line to apt sources:\n")
|
||||
fmt.Printf(" deb http://your-server/%s %s %s\n", prefix, distribution, component)
|
||||
fmt.Printf("Don't forget to add your GPG key to apt with apt-key.\n")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdPublishSnapshot() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlyPublishSnapshot,
|
||||
UsageLine: "snapshot",
|
||||
Short: "makes Debian repository out of snapshot",
|
||||
Long: `
|
||||
Publishes snapshot as Debian repository ready to be used by apt tools.
|
||||
|
||||
ex:
|
||||
$ aptly publish snapshot <name> [<prefix>]
|
||||
`,
|
||||
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("architectures", "", "list of architectures to publish (comma-separated)")
|
||||
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeCmdPublish() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "publish",
|
||||
Short: "manage published repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdPublishSnapshot(),
|
||||
},
|
||||
Flag: *flag.NewFlagSet("aptly-publish", flag.ExitOnError),
|
||||
}
|
||||
}
|
||||
-174
@@ -1,174 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
"github.com/smira/aptly/debian"
|
||||
)
|
||||
|
||||
func aptlySnapshotCreate(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
if len(args) < 4 || args[1] != "from" || args[2] != "mirror" {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
repoName, mirrorName := args[3], args[0]
|
||||
|
||||
repoCollection := debian.NewRemoteRepoCollection(context.database)
|
||||
repo, err := repoCollection.ByName(repoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
err = repoCollection.LoadComplete(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
snapshot, err := debian.NewSnapshotFromRepository(mirrorName, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create snapshot: %s", err)
|
||||
}
|
||||
|
||||
snapshotCollection := debian.NewSnapshotCollection(context.database)
|
||||
|
||||
err = snapshotCollection.Add(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add snapshot: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nSnapshot %s successfully created.\nYou can run 'aptly publish snapshot %s' to publish snapshot as Debian repository.\n", snapshot.Name, snapshot.Name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func aptlySnapshotList(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("List of snapshots:\n")
|
||||
|
||||
snapshotCollection := debian.NewSnapshotCollection(context.database)
|
||||
snapshotCollection.ForEach(func(snapshot *debian.Snapshot) error {
|
||||
fmt.Printf(" * %s\n", snapshot)
|
||||
return nil
|
||||
})
|
||||
|
||||
fmt.Printf("\nTo get more information about snapshot, run `aptly snapshot show <name>`.\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func aptlySnapshotShow(cmd *commander.Command, args []string) error {
|
||||
var err error
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
snapshotCollection := debian.NewSnapshotCollection(context.database)
|
||||
snapshot, err := snapshotCollection.ByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
err = snapshotCollection.LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to show: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s\n", snapshot.Name)
|
||||
fmt.Printf("Created At: %s\n", snapshot.CreatedAt.Format("2006-01-02 15:04:05 MST"))
|
||||
fmt.Printf("Description: %s\n", snapshot.Description)
|
||||
fmt.Printf("Number of packages: %d\n", snapshot.NumPackages())
|
||||
fmt.Printf("Packages:\n")
|
||||
|
||||
packageCollection := debian.NewPackageCollection(context.database)
|
||||
|
||||
err = snapshot.RefList().ForEach(func(key []byte) error {
|
||||
p, err := packageCollection.ByKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" %s\n", p)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func makeCmdSnapshotCreate() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotCreate,
|
||||
UsageLine: "create",
|
||||
Short: "creates snapshot out of any mirror",
|
||||
Long: `
|
||||
Create makes persistent immutable snapshot of repository mirror state in givent moment of time.
|
||||
|
||||
ex:
|
||||
$ aptly snapshot create <name> from mirror <mirror-name>
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-create", flag.ExitOnError),
|
||||
}
|
||||
|
||||
return cmd
|
||||
|
||||
}
|
||||
|
||||
func makeCmdSnapshotList() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotList,
|
||||
UsageLine: "list",
|
||||
Short: "lists snapshots",
|
||||
Long: `
|
||||
list shows full list of snapshots created.
|
||||
|
||||
ex:
|
||||
$ aptly snapshot list
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-list", flag.ExitOnError),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeCmdSnapshotShow() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: aptlySnapshotShow,
|
||||
UsageLine: "show",
|
||||
Short: "shows details about snapshot",
|
||||
Long: `
|
||||
shows shows full information about snapshot.
|
||||
|
||||
ex:
|
||||
$ aptly snapshot show <name>
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot-show", flag.ExitOnError),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeCmdSnapshot() *commander.Command {
|
||||
return &commander.Command{
|
||||
UsageLine: "snapshot",
|
||||
Short: "manage snapshots of repositories",
|
||||
Subcommands: []*commander.Command{
|
||||
makeCmdSnapshotCreate(),
|
||||
makeCmdSnapshotList(),
|
||||
makeCmdSnapshotShow(),
|
||||
//makeCmdSnapshotDestroy(),
|
||||
},
|
||||
Flag: *flag.NewFlagSet("aptly-snapshot", flag.ExitOnError),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/wsxiaoys/terminal/color"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
codePrint = iota
|
||||
codeProgress
|
||||
codeHideProgress
|
||||
codeStop
|
||||
codeFlush
|
||||
)
|
||||
|
||||
type printTask struct {
|
||||
code int
|
||||
message string
|
||||
reply chan bool
|
||||
}
|
||||
|
||||
// Progress is a progress displaying subroutine, it allows to show download and other operations progress
|
||||
// mixed with progress bar
|
||||
type Progress struct {
|
||||
stop chan bool
|
||||
stopped chan bool
|
||||
queue chan printTask
|
||||
bar *pb.ProgressBar
|
||||
barShown bool
|
||||
}
|
||||
|
||||
// Check interface
|
||||
var (
|
||||
_ aptly.Progress = (*Progress)(nil)
|
||||
)
|
||||
|
||||
// NewProgress creates new progress instance
|
||||
func NewProgress() *Progress {
|
||||
return &Progress{
|
||||
stopped: make(chan bool),
|
||||
queue: make(chan printTask, 100),
|
||||
}
|
||||
}
|
||||
|
||||
// Start makes progress start its work
|
||||
func (p *Progress) Start() {
|
||||
go p.worker()
|
||||
}
|
||||
|
||||
// Shutdown shuts down progress display
|
||||
func (p *Progress) Shutdown() {
|
||||
p.ShutdownBar()
|
||||
p.queue <- printTask{code: codeStop}
|
||||
<-p.stopped
|
||||
}
|
||||
|
||||
// Flush waits for all queued messages to be displayed
|
||||
func (p *Progress) Flush() {
|
||||
ch := make(chan bool)
|
||||
p.queue <- printTask{code: codeFlush, reply: ch}
|
||||
<-ch
|
||||
}
|
||||
|
||||
// InitBar starts progressbar for count bytes or count items
|
||||
func (p *Progress) InitBar(count int64, isBytes bool) {
|
||||
if p.bar != nil {
|
||||
panic("bar already initialized")
|
||||
}
|
||||
if RunningOnTerminal() {
|
||||
p.bar = pb.New(0)
|
||||
p.bar.Total = count
|
||||
p.bar.NotPrint = true
|
||||
p.bar.Callback = func(out string) {
|
||||
p.queue <- printTask{code: codeProgress, message: out}
|
||||
}
|
||||
|
||||
if isBytes {
|
||||
p.bar.SetUnits(pb.U_BYTES)
|
||||
p.bar.ShowSpeed = true
|
||||
}
|
||||
p.bar.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// ShutdownBar stops progress bar and hides it
|
||||
func (p *Progress) ShutdownBar() {
|
||||
if p.bar == nil {
|
||||
return
|
||||
}
|
||||
p.bar.Finish()
|
||||
p.bar = nil
|
||||
p.queue <- printTask{code: codeHideProgress}
|
||||
}
|
||||
|
||||
// Write is implementation of io.Writer to support updating of progress bar
|
||||
func (p *Progress) Write(s []byte) (int, error) {
|
||||
if p.bar != nil {
|
||||
p.bar.Add(len(s))
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
// AddBar increments progress for progress bar
|
||||
func (p *Progress) AddBar(count int) {
|
||||
if p.bar != nil {
|
||||
p.bar.Add(count)
|
||||
}
|
||||
}
|
||||
|
||||
// SetBar sets current position for progress bar
|
||||
func (p *Progress) SetBar(count int) {
|
||||
if p.bar != nil {
|
||||
p.bar.Set(count)
|
||||
}
|
||||
}
|
||||
|
||||
// Printf does printf but in safe manner: not overwriting progress bar
|
||||
func (p *Progress) Printf(msg string, a ...interface{}) {
|
||||
p.queue <- printTask{code: codePrint, message: fmt.Sprintf(msg, a...)}
|
||||
}
|
||||
|
||||
// ColoredPrintf does printf in colored way + newline
|
||||
func (p *Progress) ColoredPrintf(msg string, a ...interface{}) {
|
||||
if RunningOnTerminal() {
|
||||
p.queue <- printTask{code: codePrint, message: color.Sprintf(msg, a...) + "\n"}
|
||||
} else {
|
||||
// stip color marks
|
||||
var prev rune
|
||||
msg = strings.Map(func(r rune) rune {
|
||||
if prev == '@' {
|
||||
prev = 0
|
||||
if r == '@' {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}
|
||||
prev = r
|
||||
if r == '@' {
|
||||
return -1
|
||||
|
||||
}
|
||||
|
||||
return r
|
||||
}, msg)
|
||||
|
||||
p.Printf(msg+"\n", a...)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Progress) worker() {
|
||||
for {
|
||||
task := <-p.queue
|
||||
switch task.code {
|
||||
case codePrint:
|
||||
if p.barShown {
|
||||
fmt.Print("\r\033[2K")
|
||||
p.barShown = false
|
||||
}
|
||||
fmt.Print(task.message)
|
||||
case codeProgress:
|
||||
if p.bar != nil {
|
||||
fmt.Print("\r" + task.message)
|
||||
p.barShown = true
|
||||
}
|
||||
case codeHideProgress:
|
||||
if p.barShown {
|
||||
fmt.Print("\r\033[2K")
|
||||
p.barShown = false
|
||||
}
|
||||
case codeFlush:
|
||||
task.reply <- true
|
||||
case codeStop:
|
||||
p.stopped <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// +build !freebsd
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.crypto/ssh/terminal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// RunningOnTerminal checks whether stdout is terminal
|
||||
func RunningOnTerminal() bool {
|
||||
return terminal.IsTerminal(syscall.Stdout)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// +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
|
||||
}
|
||||
+105
-13
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// Errors for Storage
|
||||
@@ -18,12 +20,18 @@ var (
|
||||
type Storage interface {
|
||||
Get(key []byte) ([]byte, error)
|
||||
Put(key []byte, value []byte) error
|
||||
Delete(key []byte) error
|
||||
KeysByPrefix(prefix []byte) [][]byte
|
||||
FetchByPrefix(prefix []byte) [][]byte
|
||||
Close() error
|
||||
StartBatch()
|
||||
FinishBatch() error
|
||||
CompactDB() error
|
||||
}
|
||||
|
||||
type levelDB struct {
|
||||
db *leveldb.DB
|
||||
db *leveldb.DB
|
||||
batch *leveldb.Batch
|
||||
}
|
||||
|
||||
// Check interface
|
||||
@@ -44,6 +52,25 @@ func OpenDB(path string) (Storage, error) {
|
||||
return &levelDB{db: db}, nil
|
||||
}
|
||||
|
||||
// RecoverDB recovers LevelDB database from corruption
|
||||
func RecoverDB(path string) error {
|
||||
stor, err := storage.OpenFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := leveldb.Recover(stor, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.Close()
|
||||
stor.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get key value from database
|
||||
func (l *levelDB) Get(key []byte) ([]byte, error) {
|
||||
value, err := l.db.Get(key, nil)
|
||||
if err != nil {
|
||||
@@ -56,29 +83,94 @@ func (l *levelDB) Get(key []byte) ([]byte, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Put saves key to database, if key has the same value in DB already, it is not saved
|
||||
func (l *levelDB) Put(key []byte, value []byte) error {
|
||||
if l.batch != nil {
|
||||
l.batch.Put(key, value)
|
||||
return nil
|
||||
}
|
||||
old, err := l.db.Get(key, nil)
|
||||
if err != nil {
|
||||
if err != leveldb.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if bytes.Compare(old, value) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return l.db.Put(key, value, nil)
|
||||
}
|
||||
|
||||
func (l *levelDB) FetchByPrefix(prefix []byte) [][]byte {
|
||||
// Delete removes key from DB
|
||||
func (l *levelDB) Delete(key []byte) error {
|
||||
if l.batch != nil {
|
||||
l.batch.Delete(key)
|
||||
return nil
|
||||
}
|
||||
return l.db.Delete(key, nil)
|
||||
}
|
||||
|
||||
// KeysByPrefix returns all keys that start with prefix
|
||||
func (l *levelDB) KeysByPrefix(prefix []byte) [][]byte {
|
||||
result := make([][]byte, 0, 20)
|
||||
|
||||
iterator := l.db.NewIterator(nil)
|
||||
if iterator.Seek(prefix) {
|
||||
for bytes.HasPrefix(iterator.Key(), prefix) {
|
||||
val := iterator.Value()
|
||||
valc := make([]byte, len(val))
|
||||
copy(valc, val)
|
||||
result = append(result, valc)
|
||||
if !iterator.Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
iterator := l.db.NewIterator(nil, nil)
|
||||
defer iterator.Release()
|
||||
|
||||
for ok := iterator.Seek(prefix); ok && bytes.HasPrefix(iterator.Key(), prefix); ok = iterator.Next() {
|
||||
key := iterator.Key()
|
||||
keyc := make([]byte, len(key))
|
||||
copy(keyc, key)
|
||||
result = append(result, keyc)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FetchByPrefix returns all values with keys that start with prefix
|
||||
func (l *levelDB) FetchByPrefix(prefix []byte) [][]byte {
|
||||
result := make([][]byte, 0, 20)
|
||||
|
||||
iterator := l.db.NewIterator(nil, nil)
|
||||
defer iterator.Release()
|
||||
|
||||
for ok := iterator.Seek(prefix); ok && bytes.HasPrefix(iterator.Key(), prefix); ok = iterator.Next() {
|
||||
val := iterator.Value()
|
||||
valc := make([]byte, len(val))
|
||||
copy(valc, val)
|
||||
result = append(result, valc)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Close finishes DB work
|
||||
func (l *levelDB) Close() error {
|
||||
return l.db.Close()
|
||||
}
|
||||
|
||||
// StartBatch starts batch processing of keys
|
||||
//
|
||||
// All subsequent Get, Put and Delete would work on batch
|
||||
func (l *levelDB) StartBatch() {
|
||||
if l.batch != nil {
|
||||
panic("batch already started")
|
||||
}
|
||||
l.batch = new(leveldb.Batch)
|
||||
}
|
||||
|
||||
// FinishBatch finalizes the batch, saving operations
|
||||
func (l *levelDB) FinishBatch() error {
|
||||
if l.batch == nil {
|
||||
panic("no batch")
|
||||
}
|
||||
err := l.db.Write(l.batch, nil)
|
||||
l.batch = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// CompactDB compacts database by merging layers
|
||||
func (l *levelDB) CompactDB() error {
|
||||
return l.db.CompactRange(util.Range{})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ func Test(t *testing.T) {
|
||||
}
|
||||
|
||||
type LevelDBSuite struct {
|
||||
db Storage
|
||||
path string
|
||||
db Storage
|
||||
}
|
||||
|
||||
var _ = Suite(&LevelDBSuite{})
|
||||
@@ -19,7 +20,8 @@ var _ = Suite(&LevelDBSuite{})
|
||||
func (s *LevelDBSuite) SetUpTest(c *C) {
|
||||
var err error
|
||||
|
||||
s.db, err = OpenDB(c.MkDir())
|
||||
s.path = c.MkDir()
|
||||
s.db, err = OpenDB(s.path)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
@@ -28,6 +30,29 @@ func (s *LevelDBSuite) TearDownTest(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestRecoverDB(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
value = []byte("value")
|
||||
)
|
||||
|
||||
err := s.db.Put(key, value)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
err = s.db.Close()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
err = RecoverDB(s.path)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
s.db, err = OpenDB(s.path)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
result, err := s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(result, DeepEquals, value)
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestGetPut(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
@@ -45,19 +70,88 @@ func (s *LevelDBSuite) TestGetPut(c *C) {
|
||||
c.Assert(result, DeepEquals, value)
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestFetchByPrefix(c *C) {
|
||||
func (s *LevelDBSuite) TestDelete(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
value = []byte("value")
|
||||
)
|
||||
|
||||
err := s.db.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.db.Delete(key)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(key)
|
||||
c.Assert(err, ErrorMatches, "key not found")
|
||||
|
||||
err = s.db.Delete(key)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestByPrefix(c *C) {
|
||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
||||
|
||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||
|
||||
s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||
|
||||
s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
||||
c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x01}, {0x02}, {0x03}})
|
||||
c.Check(s.db.KeysByPrefix([]byte{0x80}), DeepEquals, [][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}})
|
||||
|
||||
c.Check(s.db.FetchByPrefix([]byte{0xa0}), DeepEquals, [][]byte{})
|
||||
c.Check(s.db.KeysByPrefix([]byte{0xa0}), DeepEquals, [][]byte{})
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestBatch(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
key2 = []byte("key2")
|
||||
value = []byte("value")
|
||||
value2 = []byte("value2")
|
||||
)
|
||||
|
||||
err := s.db.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.db.StartBatch()
|
||||
s.db.Put(key2, value2)
|
||||
s.db.Delete(key)
|
||||
|
||||
v, err := s.db.Get(key)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value)
|
||||
|
||||
_, err = s.db.Get(key2)
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
err = s.db.FinishBatch()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
v2, err := s.db.Get(key2)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v2, DeepEquals, value2)
|
||||
|
||||
_, err = s.db.Get(key)
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
c.Check(func() { s.db.FinishBatch() }, Panics, "no batch")
|
||||
|
||||
s.db.StartBatch()
|
||||
c.Check(func() { s.db.StartBatch() }, Panics, "batch already started")
|
||||
}
|
||||
|
||||
func (s *LevelDBSuite) TestCompactDB(c *C) {
|
||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||
|
||||
c.Check(s.db.CompactDB(), IsNil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
)
|
||||
|
||||
// CollectionFactory is a single place to generate all desired collections
|
||||
type CollectionFactory struct {
|
||||
db database.Storage
|
||||
packages *PackageCollection
|
||||
remoteRepos *RemoteRepoCollection
|
||||
snapshots *SnapshotCollection
|
||||
localRepos *LocalRepoCollection
|
||||
publishedRepos *PublishedRepoCollection
|
||||
}
|
||||
|
||||
// NewCollectionFactory creates new factory
|
||||
func NewCollectionFactory(db database.Storage) *CollectionFactory {
|
||||
return &CollectionFactory{db: db}
|
||||
}
|
||||
|
||||
// PackageCollection returns (or creates) new PackageCollection
|
||||
func (factory *CollectionFactory) PackageCollection() *PackageCollection {
|
||||
if factory.packages == nil {
|
||||
factory.packages = NewPackageCollection(factory.db)
|
||||
}
|
||||
|
||||
return factory.packages
|
||||
}
|
||||
|
||||
// RemoteRepoCollection returns (or creates) new RemoteRepoCollection
|
||||
func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection {
|
||||
if factory.remoteRepos == nil {
|
||||
factory.remoteRepos = NewRemoteRepoCollection(factory.db)
|
||||
}
|
||||
|
||||
return factory.remoteRepos
|
||||
}
|
||||
|
||||
// SnapshotCollection returns (or creates) new SnapshotCollection
|
||||
func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection {
|
||||
if factory.snapshots == nil {
|
||||
factory.snapshots = NewSnapshotCollection(factory.db)
|
||||
}
|
||||
|
||||
return factory.snapshots
|
||||
}
|
||||
|
||||
// LocalRepoCollection returns (or creates) new LocalRepoCollection
|
||||
func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection {
|
||||
if factory.localRepos == nil {
|
||||
factory.localRepos = NewLocalRepoCollection(factory.db)
|
||||
}
|
||||
|
||||
return factory.localRepos
|
||||
}
|
||||
|
||||
// PublishedRepoCollection returns (or creates) new PublishedRepoCollection
|
||||
func (factory *CollectionFactory) PublishedRepoCollection() *PublishedRepoCollection {
|
||||
if factory.publishedRepos == nil {
|
||||
factory.publishedRepos = NewPublishedRepoCollection(factory.db)
|
||||
}
|
||||
|
||||
return factory.publishedRepos
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"github.com/mkrautz/goar"
|
||||
"github.com/smira/aptly/utils"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetControlFileFromDeb reads control file from deb package
|
||||
func GetControlFileFromDeb(packageFile string) (Stanza, 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 control.tar.gz part")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read .deb archive: %s", err)
|
||||
}
|
||||
|
||||
if header.Name == "control.tar.gz" {
|
||||
ungzip, err := gzip.NewReader(library)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ungzip: %s", err)
|
||||
}
|
||||
defer ungzip.Close()
|
||||
|
||||
untar := tar.NewReader(ungzip)
|
||||
for {
|
||||
tarHeader, err := untar.Next()
|
||||
if err == io.EOF {
|
||||
return nil, fmt.Errorf("unable to find control file")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read .tar archive: %s", err)
|
||||
}
|
||||
|
||||
if tarHeader.Name == "./control" {
|
||||
reader := NewControlFileReader(untar)
|
||||
stanza, err := reader.ReadStanza()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stanza, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetControlFileFromDsc reads control file from dsc package
|
||||
func GetControlFileFromDsc(dscFile string, verifier utils.Verifier) (Stanza, error) {
|
||||
file, err := os.Open(dscFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
line, err := bufio.NewReader(file).ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file.Seek(0, 0)
|
||||
|
||||
var text *os.File
|
||||
|
||||
if strings.Index(line, "BEGIN PGP SIGN") != -1 {
|
||||
text, err = verifier.ExtractClearsigned(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer text.Close()
|
||||
} else {
|
||||
text = file
|
||||
}
|
||||
|
||||
reader := NewControlFileReader(text)
|
||||
stanza, err := reader.ReadStanza()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stanza, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type DebSuite struct {
|
||||
debFile, dscFile, dscFileNoSign string
|
||||
}
|
||||
|
||||
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.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")
|
||||
}
|
||||
|
||||
func (s *DebSuite) TestGetControlFileFromDeb(c *C) {
|
||||
_, err := GetControlFileFromDeb("/no/such/file")
|
||||
c.Check(err, ErrorMatches, ".*no such file or directory")
|
||||
|
||||
_, _File, _, _ := runtime.Caller(0)
|
||||
_, err = GetControlFileFromDeb(_File)
|
||||
c.Check(err, ErrorMatches, "unable to read .deb archive: ar: missing global header")
|
||||
|
||||
st, err := GetControlFileFromDeb(s.debFile)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(st["Version"], Equals, "1.49.0.1")
|
||||
c.Check(st["Package"], Equals, "libboost-program-options-dev")
|
||||
}
|
||||
|
||||
func (s *DebSuite) TestGetControlFileFromDsc(c *C) {
|
||||
verifier := &utils.GpgVerifier{}
|
||||
|
||||
_, err := GetControlFileFromDsc("/no/such/file", verifier)
|
||||
c.Check(err, ErrorMatches, ".*no such file or directory")
|
||||
|
||||
_, _File, _, _ := runtime.Caller(0)
|
||||
_, err = GetControlFileFromDsc(_File, verifier)
|
||||
c.Check(err, ErrorMatches, "malformed stanza syntax")
|
||||
|
||||
st, err := GetControlFileFromDsc(s.dscFile, verifier)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(st["Version"], Equals, "0.6.1-1.3")
|
||||
c.Check(st["Source"], Equals, "pyspi")
|
||||
|
||||
st, err = GetControlFileFromDsc(s.dscFileNoSign, verifier)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(st["Version"], Equals, "0.6.1-1.4")
|
||||
c.Check(st["Source"], Equals, "pyspi")
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package deb implements Debian-specific repository handling
|
||||
package deb
|
||||
@@ -0,0 +1,11 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Launch gocheck tests
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package debian
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
type Stanza map[string]string
|
||||
|
||||
// Canonical order of fields in stanza
|
||||
var canocialOrder = []string{"Package", "Version", "Installed-Size", "Priority", "Section", "Maintainer", "Architecture"}
|
||||
var canocialOrder = []string{"Package", "Origin", "Label", "Suite", "Version", "Installed-Size", "Priority", "Section", "Maintainer",
|
||||
"Architecture", "Codename", "Date", "Architectures", "Components", "Description", "MD5sum", "MD5Sum", "SHA1", "SHA256",
|
||||
"Archive", "Component"}
|
||||
|
||||
// Copy returns copy of Stanza
|
||||
func (s Stanza) Copy() (result Stanza) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package debian
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
+450
@@ -0,0 +1,450 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Dependency options
|
||||
const (
|
||||
// DepFollowSource pulls source packages when required
|
||||
DepFollowSource = 1 << iota
|
||||
// DepFollowSuggests pulls from suggests
|
||||
DepFollowSuggests
|
||||
// DepFollowRecommends pulls from recommends
|
||||
DepFollowRecommends
|
||||
// DepFollowAllVariants follows all variants if depends on "a | b"
|
||||
DepFollowAllVariants
|
||||
// DepFollowBuild pulls build dependencies
|
||||
DepFollowBuild
|
||||
)
|
||||
|
||||
// PackageList is list of unique (by key) packages
|
||||
//
|
||||
// It could be seen as repo snapshot, repo contents, result of filtering,
|
||||
// merge, etc.
|
||||
//
|
||||
// If indexed, PackageList starts supporting searching
|
||||
type PackageList struct {
|
||||
// Straight list of packages as map
|
||||
packages map[string]*Package
|
||||
// Has index been prepared?
|
||||
indexed bool
|
||||
// Indexed list of packages, sorted by name internally
|
||||
packagesIndex []*Package
|
||||
// Map of packages for each virtual package (provides)
|
||||
providesIndex map[string][]*Package
|
||||
}
|
||||
|
||||
// Verify interface
|
||||
var (
|
||||
_ sort.Interface = &PackageList{}
|
||||
)
|
||||
|
||||
// NewPackageList creates empty package list
|
||||
func NewPackageList() *PackageList {
|
||||
return &PackageList{packages: make(map[string]*Package, 1000)}
|
||||
}
|
||||
|
||||
// NewPackageListFromRefList loads packages list from PackageRefList
|
||||
func NewPackageListFromRefList(reflist *PackageRefList, collection *PackageCollection, progress aptly.Progress) (*PackageList, error) {
|
||||
// empty reflist
|
||||
if reflist == nil {
|
||||
return NewPackageList(), nil
|
||||
}
|
||||
|
||||
result := &PackageList{packages: make(map[string]*Package, reflist.Len())}
|
||||
|
||||
if progress != nil {
|
||||
progress.InitBar(int64(reflist.Len()), false)
|
||||
}
|
||||
|
||||
err := reflist.ForEach(func(key []byte) error {
|
||||
p, err2 := collection.ByKey(key)
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("unable to load package with key %s: %s", key, err2)
|
||||
}
|
||||
if progress != nil {
|
||||
progress.AddBar(1)
|
||||
}
|
||||
return result.Add(p)
|
||||
})
|
||||
|
||||
if progress != nil {
|
||||
progress.ShutdownBar()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Add appends package to package list, additionally checking for uniqueness
|
||||
func (l *PackageList) Add(p *Package) error {
|
||||
key := string(p.ShortKey(""))
|
||||
existing, ok := l.packages[key]
|
||||
if ok {
|
||||
if !existing.Equals(p) {
|
||||
return fmt.Errorf("conflict in package %s", p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
l.packages[key] = p
|
||||
|
||||
if l.indexed {
|
||||
for _, provides := range p.Provides {
|
||||
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
||||
}
|
||||
|
||||
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)
|
||||
copy(l.packagesIndex[i+1:], l.packagesIndex[i:])
|
||||
l.packagesIndex[i] = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForEach calls handler for each package in list
|
||||
func (l *PackageList) ForEach(handler func(*Package) error) error {
|
||||
var err error
|
||||
for _, p := range l.packages {
|
||||
err = handler(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// Append adds content from one package list to another
|
||||
func (l *PackageList) Append(pl *PackageList) error {
|
||||
if l.indexed {
|
||||
panic("Append not supported when indexed")
|
||||
}
|
||||
for k, p := range pl.packages {
|
||||
existing, ok := l.packages[k]
|
||||
if ok {
|
||||
if !existing.Equals(p) {
|
||||
return fmt.Errorf("conflict in package %s", p)
|
||||
}
|
||||
} else {
|
||||
l.packages[k] = p
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes package from the list, and updates index when required
|
||||
func (l *PackageList) Remove(p *Package) {
|
||||
delete(l.packages, string(p.ShortKey("")))
|
||||
if l.indexed {
|
||||
for _, provides := range p.Provides {
|
||||
for i, pkg := range l.providesIndex[provides] {
|
||||
if pkg.Equals(p) {
|
||||
// remove l.ProvidesIndex[provides][i] w/o preserving order
|
||||
l.providesIndex[provides][len(l.providesIndex[provides])-1], l.providesIndex[provides][i], l.providesIndex[provides] =
|
||||
nil, l.providesIndex[provides][len(l.providesIndex[provides])-1], l.providesIndex[provides][:len(l.providesIndex[provides])-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= p.Name })
|
||||
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == p.Name {
|
||||
if l.packagesIndex[i].Equals(p) {
|
||||
// remove l.packagesIndex[i] preserving order
|
||||
copy(l.packagesIndex[i:], l.packagesIndex[i+1:])
|
||||
l.packagesIndex[len(l.packagesIndex)-1] = nil
|
||||
l.packagesIndex = l.packagesIndex[:len(l.packagesIndex)-1]
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Architectures returns list of architectures present in packages and flag if source packages are present.
|
||||
//
|
||||
// If includeSource is true, meta-architecture "source" would be present in the list
|
||||
func (l *PackageList) Architectures(includeSource bool) (result []string) {
|
||||
result = make([]string, 0, 10)
|
||||
for _, pkg := range l.packages {
|
||||
if pkg.Architecture != "all" && (pkg.Architecture != "source" || includeSource) && !utils.StrSliceHasItem(result, pkg.Architecture) {
|
||||
result = append(result, pkg.Architecture)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// depSliceDeduplicate removes dups in slice of Dependencies
|
||||
func depSliceDeduplicate(s []Dependency) []Dependency {
|
||||
l := len(s)
|
||||
if l < 2 {
|
||||
return s
|
||||
}
|
||||
if l == 2 {
|
||||
if s[0] == s[1] {
|
||||
return s[0:1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
found := make(map[string]bool, l)
|
||||
j := 0
|
||||
for i, x := range s {
|
||||
h := x.Hash()
|
||||
if !found[h] {
|
||||
found[h] = true
|
||||
s[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return s[:j]
|
||||
}
|
||||
|
||||
// VerifyDependencies looks for missing dependencies in package list.
|
||||
//
|
||||
// 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) {
|
||||
missing := make([]Dependency, 0, 128)
|
||||
|
||||
if progress != nil {
|
||||
progress.InitBar(int64(l.Len())*int64(len(architectures)), false)
|
||||
}
|
||||
|
||||
for _, arch := range architectures {
|
||||
cache := make(map[string]bool, 2048)
|
||||
|
||||
for _, p := range l.packages {
|
||||
if progress != nil {
|
||||
progress.AddBar(1)
|
||||
}
|
||||
|
||||
if !p.MatchesArchitecture(arch) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dep := range p.GetDependencies(options) {
|
||||
variants, err := ParseDependencyVariants(dep)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process package %s: %s", p, err)
|
||||
}
|
||||
|
||||
variants = depSliceDeduplicate(variants)
|
||||
|
||||
variantsMissing := make([]Dependency, 0, len(variants))
|
||||
missingCount := 0
|
||||
|
||||
for _, dep := range variants {
|
||||
if dep.Architecture == "" {
|
||||
dep.Architecture = arch
|
||||
}
|
||||
|
||||
hash := dep.Hash()
|
||||
r, ok := cache[hash]
|
||||
if ok {
|
||||
if !r {
|
||||
missingCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if sources.Search(dep, false) == nil {
|
||||
variantsMissing = append(variantsMissing, dep)
|
||||
missingCount++
|
||||
} else {
|
||||
cache[hash] = true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
progress.ShutdownBar()
|
||||
}
|
||||
|
||||
return missing, nil
|
||||
}
|
||||
|
||||
// Swap swaps two packages in index
|
||||
func (l *PackageList) Swap(i, j int) {
|
||||
l.packagesIndex[i], l.packagesIndex[j] = l.packagesIndex[j], l.packagesIndex[i]
|
||||
}
|
||||
|
||||
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.lessPackages(l.packagesIndex[i], l.packagesIndex[j])
|
||||
}
|
||||
|
||||
// PrepareIndex prepares list for indexing
|
||||
func (l *PackageList) PrepareIndex() {
|
||||
l.packagesIndex = make([]*Package, l.Len())
|
||||
l.providesIndex = make(map[string][]*Package, 128)
|
||||
|
||||
i := 0
|
||||
for _, p := range l.packages {
|
||||
l.packagesIndex[i] = p
|
||||
i++
|
||||
|
||||
for _, provides := range p.Provides {
|
||||
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(l)
|
||||
|
||||
l.indexed = true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) {
|
||||
searchResults = append(searchResults, p)
|
||||
|
||||
if !allMatches {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
searchResults = append(searchResults, p)
|
||||
|
||||
if !allMatches {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filter filters package index by specified queries (ORed together), possibly pulling dependencies
|
||||
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")
|
||||
}
|
||||
|
||||
result := NewPackageList()
|
||||
|
||||
for _, query := range queries {
|
||||
result.Append(query.Query(l))
|
||||
}
|
||||
|
||||
if withDependencies {
|
||||
added := result.Len()
|
||||
|
||||
dependencySource := NewPackageList()
|
||||
if source != nil {
|
||||
dependencySource.Append(source)
|
||||
}
|
||||
dependencySource.Append(result)
|
||||
dependencySource.PrepareIndex()
|
||||
|
||||
// while some new dependencies were discovered
|
||||
for added > 0 {
|
||||
added = 0
|
||||
|
||||
// find missing dependencies
|
||||
missing, err := result.VerifyDependencies(dependencyOptions, architecturesList, dependencySource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to satisfy dependencies
|
||||
for _, dep := range missing {
|
||||
searchResults := l.Search(dep, false)
|
||||
if searchResults != nil {
|
||||
for _, p := range searchResults {
|
||||
result.Add(p)
|
||||
dependencySource.Add(p)
|
||||
added++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "launchpad.net/gocheck"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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
|
||||
p1, p2, p3, p4, p5, p6 *Package
|
||||
|
||||
// Mocked packages in list
|
||||
packages []*Package
|
||||
packages2 []*Package
|
||||
sourcePackages []*Package
|
||||
il *PackageList
|
||||
il2 *PackageList
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageListSuite{})
|
||||
|
||||
func (s *PackageListSuite) SetUpTest(c *C) {
|
||||
s.list = NewPackageList()
|
||||
|
||||
s.p1 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
s.p2 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
stanza := packageStanza.Copy()
|
||||
stanza["Package"] = "mars-invaders"
|
||||
s.p3 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Source"] = "unknown-planet"
|
||||
s.p4 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Package"] = "lonely-strangers"
|
||||
s.p5 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Version"] = "99.1"
|
||||
s.p6 = NewPackageFromControlFile(stanza)
|
||||
|
||||
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{}},
|
||||
}
|
||||
for _, p := range s.packages {
|
||||
s.il.Add(p)
|
||||
}
|
||||
s.il.PrepareIndex()
|
||||
|
||||
s.il2 = NewPackageList()
|
||||
s.packages2 = []*Package{
|
||||
&Package{Name: "mailer", Version: "3.5.8", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}},
|
||||
&Package{Name: "sendmail", Version: "1.0", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}},
|
||||
&Package{Name: "app", Version: "1.1-bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
&Package{Name: "app", Version: "1.1-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
&Package{Name: "app", Version: "1.2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}},
|
||||
&Package{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}},
|
||||
}
|
||||
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{}},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestAddLen(c *C) {
|
||||
c.Check(s.list.Len(), Equals, 0)
|
||||
c.Check(s.list.Add(s.p1), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 1)
|
||||
c.Check(s.list.Add(s.p2), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 1)
|
||||
c.Check(s.list.Add(s.p3), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 2)
|
||||
c.Check(s.list.Add(s.p4), ErrorMatches, "conflict in package.*")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestRemove(c *C) {
|
||||
c.Check(s.list.Add(s.p1), IsNil)
|
||||
c.Check(s.list.Add(s.p3), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 2)
|
||||
|
||||
s.list.Remove(s.p1)
|
||||
c.Check(s.list.Len(), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestAddWhenIndexed(c *C) {
|
||||
c.Check(s.list.Len(), Equals, 0)
|
||||
s.list.PrepareIndex()
|
||||
|
||||
c.Check(s.list.Add(&Package{Name: "a1st", Version: "1.0", Architecture: "i386", Provides: []string{"fa", "fb"}}), IsNil)
|
||||
c.Check(s.list.packagesIndex[0].Name, Equals, "a1st")
|
||||
c.Check(s.list.providesIndex["fa"][0].Name, Equals, "a1st")
|
||||
c.Check(s.list.providesIndex["fb"][0].Name, Equals, "a1st")
|
||||
|
||||
c.Check(s.list.Add(&Package{Name: "c3rd", Version: "1.0", Architecture: "i386", Provides: []string{"fa"}}), IsNil)
|
||||
c.Check(s.list.packagesIndex[0].Name, Equals, "a1st")
|
||||
c.Check(s.list.packagesIndex[1].Name, Equals, "c3rd")
|
||||
c.Check(s.list.providesIndex["fa"][0].Name, Equals, "a1st")
|
||||
c.Check(s.list.providesIndex["fa"][1].Name, Equals, "c3rd")
|
||||
c.Check(s.list.providesIndex["fb"][0].Name, Equals, "a1st")
|
||||
|
||||
c.Check(s.list.Add(&Package{Name: "b2nd", Version: "1.0", Architecture: "i386"}), IsNil)
|
||||
c.Check(s.list.packagesIndex[0].Name, Equals, "a1st")
|
||||
c.Check(s.list.packagesIndex[1].Name, Equals, "b2nd")
|
||||
c.Check(s.list.packagesIndex[2].Name, Equals, "c3rd")
|
||||
c.Check(s.list.providesIndex["fa"][0].Name, Equals, "a1st")
|
||||
c.Check(s.list.providesIndex["fa"][1].Name, Equals, "c3rd")
|
||||
c.Check(s.list.providesIndex["fb"][0].Name, Equals, "a1st")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestRemoveWhenIndexed(c *C) {
|
||||
s.il.Remove(s.packages[0])
|
||||
names := make([]string, s.il.Len())
|
||||
for i, p := range s.il.packagesIndex {
|
||||
names[i] = p.Name
|
||||
}
|
||||
c.Check(names, DeepEquals, []string{"aa", "app", "app", "app", "app", "data", "dpkg", "dpkg", "dpkg", "dpkg", "dpkg", "libx", "mailer"})
|
||||
|
||||
s.il.Remove(s.packages[4])
|
||||
names = make([]string, s.il.Len())
|
||||
for i, p := range s.il.packagesIndex {
|
||||
names[i] = p.Name
|
||||
}
|
||||
c.Check(names, DeepEquals, []string{"aa", "app", "app", "app", "app", "data", "dpkg", "dpkg", "dpkg", "dpkg", "dpkg", "libx"})
|
||||
c.Check(s.il.providesIndex["mail-agent"], DeepEquals, []*Package{})
|
||||
|
||||
s.il.Remove(s.packages[9])
|
||||
names = make([]string, s.il.Len())
|
||||
for i, p := range s.il.packagesIndex {
|
||||
names[i] = p.Name
|
||||
}
|
||||
c.Check(names, DeepEquals, []string{"aa", "app", "app", "app", "app", "data", "dpkg", "dpkg", "dpkg", "dpkg", "libx"})
|
||||
c.Check(s.il.providesIndex["package-installer"], HasLen, 2)
|
||||
|
||||
s.il.Remove(s.packages[1])
|
||||
names = make([]string, s.il.Len())
|
||||
for i, p := range s.il.packagesIndex {
|
||||
names[i] = p.Name
|
||||
}
|
||||
c.Check(names, DeepEquals, []string{"aa", "app", "app", "app", "app", "data", "dpkg", "dpkg", "dpkg", "libx"})
|
||||
c.Check(s.il.providesIndex["package-installer"], DeepEquals, []*Package{s.packages[11]})
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestForeach(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
|
||||
Len := 0
|
||||
err := s.list.ForEach(func(*Package) error {
|
||||
Len++
|
||||
return nil
|
||||
})
|
||||
|
||||
c.Check(Len, Equals, 2)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
e := errors.New("a")
|
||||
|
||||
err = s.list.ForEach(func(*Package) error {
|
||||
return e
|
||||
})
|
||||
|
||||
c.Check(err, Equals, e)
|
||||
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestIndex(c *C) {
|
||||
c.Check(len(s.il.providesIndex), Equals, 2)
|
||||
c.Check(len(s.il.providesIndex["mail-agent"]), Equals, 1)
|
||||
c.Check(len(s.il.providesIndex["package-installer"]), Equals, 3)
|
||||
c.Check(s.il.packagesIndex[0], Equals, s.packages[8])
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestAppend(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
|
||||
err := s.list.Append(s.il)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(s.list.Len(), Equals, 16)
|
||||
|
||||
list := NewPackageList()
|
||||
list.Add(s.p4)
|
||||
|
||||
err = s.list.Append(list)
|
||||
c.Check(err, ErrorMatches, "conflict.*")
|
||||
|
||||
s.list.PrepareIndex()
|
||||
c.Check(func() { s.list.Append(s.il) }, Panics, "Append not supported when indexed")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestSearch(c *C) {
|
||||
//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"}, 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"}, 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"}, 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"}, 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"}, 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"}, 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([]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())
|
||||
for _, p := range l.packages {
|
||||
list = append(list, p.String())
|
||||
}
|
||||
|
||||
sort.Strings(list)
|
||||
|
||||
return strings.Join(list, " ")
|
||||
}
|
||||
|
||||
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([]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([]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([]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([]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")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestVerifyDependencies(c *C) {
|
||||
missing, err := s.il.VerifyDependencies(0, []string{"i386"}, s.il, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(missing, DeepEquals, []Dependency{})
|
||||
|
||||
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"}})
|
||||
|
||||
missing, err = s.il.VerifyDependencies(0, []string{"arm"}, s.il, nil)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(missing, DeepEquals, []Dependency{})
|
||||
|
||||
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"}})
|
||||
|
||||
for _, p := range s.sourcePackages {
|
||||
s.il.Add(p)
|
||||
}
|
||||
|
||||
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"}})
|
||||
|
||||
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"}})
|
||||
|
||||
_, err = s.il.VerifyDependencies(0, []string{"i386", "amd64", "s390"}, s.il, nil)
|
||||
c.Check(err, ErrorMatches, "unable to process package app_1.0_s390:.*")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectures(c *C) {
|
||||
archs := s.il.Architectures(true)
|
||||
sort.Strings(archs)
|
||||
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390", "source"})
|
||||
|
||||
archs = s.il.Architectures(false)
|
||||
sort.Strings(archs)
|
||||
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390"})
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/ugorji/go/codec"
|
||||
"log"
|
||||
)
|
||||
|
||||
// LocalRepo is a collection of packages created locally
|
||||
type LocalRepo struct {
|
||||
// Permanent internal ID
|
||||
UUID string
|
||||
// User-assigned name
|
||||
Name string
|
||||
// Comment
|
||||
Comment string
|
||||
// DefaultDistribution
|
||||
DefaultDistribution string `codec:",omitempty"`
|
||||
// DefaultComponent
|
||||
DefaultComponent string `codec:",omitempty"`
|
||||
// "Snapshot" of current list of packages
|
||||
packageRefs *PackageRefList
|
||||
}
|
||||
|
||||
// NewLocalRepo creates new instance of Debian local repository
|
||||
func NewLocalRepo(name string, comment string) *LocalRepo {
|
||||
return &LocalRepo{
|
||||
UUID: uuid.New(),
|
||||
Name: name,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (repo *LocalRepo) String() string {
|
||||
if repo.Comment != "" {
|
||||
return fmt.Sprintf("[%s]: %s", repo.Name, repo.Comment)
|
||||
}
|
||||
return fmt.Sprintf("[%s]", repo.Name)
|
||||
}
|
||||
|
||||
// NumPackages return number of packages in local repo
|
||||
func (repo *LocalRepo) NumPackages() int {
|
||||
if repo.packageRefs == nil {
|
||||
return 0
|
||||
}
|
||||
return repo.packageRefs.Len()
|
||||
}
|
||||
|
||||
// RefList returns package list for repo
|
||||
func (repo *LocalRepo) RefList() *PackageRefList {
|
||||
return repo.packageRefs
|
||||
}
|
||||
|
||||
// UpdateRefList changes package list for local repo
|
||||
func (repo *LocalRepo) UpdateRefList(reflist *PackageRefList) {
|
||||
repo.packageRefs = reflist
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of LocalRepo
|
||||
func (repo *LocalRepo) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(repo)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Decode decodes msgpack representation into LocalRepo
|
||||
func (repo *LocalRepo) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
return decoder.Decode(repo)
|
||||
}
|
||||
|
||||
// Key is a unique id in DB
|
||||
func (repo *LocalRepo) Key() []byte {
|
||||
return []byte("L" + repo.UUID)
|
||||
}
|
||||
|
||||
// RefKey is a unique id for package reference list
|
||||
func (repo *LocalRepo) RefKey() []byte {
|
||||
return []byte("E" + repo.UUID)
|
||||
}
|
||||
|
||||
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
||||
type LocalRepoCollection struct {
|
||||
db database.Storage
|
||||
list []*LocalRepo
|
||||
}
|
||||
|
||||
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
||||
func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
|
||||
result := &LocalRepoCollection{
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("L"))
|
||||
result.list = make([]*LocalRepo, 0, len(blobs))
|
||||
|
||||
for _, blob := range blobs {
|
||||
r := &LocalRepo{}
|
||||
if err := r.Decode(blob); err != nil {
|
||||
log.Printf("Error decoding mirror: %s\n", err)
|
||||
} else {
|
||||
result.list = append(result.list, r)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add appends new repo to collection and saves it
|
||||
func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
||||
for _, r := range collection.list {
|
||||
if r.Name == repo.Name {
|
||||
return fmt.Errorf("local repo with name %s already exists", repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
err := collection.Update(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collection.list = append(collection.list, repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update stores updated information about repo in DB
|
||||
func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
||||
err := collection.db.Put(repo.Key(), repo.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.packageRefs != nil {
|
||||
err = collection.db.Put(repo.RefKey(), repo.packageRefs.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadComplete loads additional information for local repo
|
||||
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
||||
encoded, err := collection.db.Get(repo.RefKey())
|
||||
if err == database.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.packageRefs = &PackageRefList{}
|
||||
return repo.packageRefs.Decode(encoded)
|
||||
}
|
||||
|
||||
// ByName looks up repository by name
|
||||
func (collection *LocalRepoCollection) ByName(name string) (*LocalRepo, error) {
|
||||
for _, r := range collection.list {
|
||||
if r.Name == name {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("local repo with name %s not found", name)
|
||||
}
|
||||
|
||||
// ByUUID looks up repository by uuid
|
||||
func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
||||
for _, r := range collection.list {
|
||||
if r.UUID == uuid {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("local repo with uuid %s not found", uuid)
|
||||
}
|
||||
|
||||
// ForEach runs method for each repository
|
||||
func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) error {
|
||||
var err error
|
||||
for _, r := range collection.list {
|
||||
err = handler(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Len returns number of remote repos
|
||||
func (collection *LocalRepoCollection) Len() int {
|
||||
return len(collection.list)
|
||||
}
|
||||
|
||||
// Drop removes remote repo from collection
|
||||
func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
||||
repoPosition := -1
|
||||
|
||||
for i, r := range collection.list {
|
||||
if r == repo {
|
||||
repoPosition = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if repoPosition == -1 {
|
||||
panic("local repo not found!")
|
||||
}
|
||||
|
||||
collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list =
|
||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
||||
|
||||
err := collection.db.Delete(repo.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return collection.db.Delete(repo.RefKey())
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type LocalRepoSuite struct {
|
||||
db database.Storage
|
||||
list *PackageList
|
||||
reflist *PackageRefList
|
||||
repo *LocalRepo
|
||||
}
|
||||
|
||||
var _ = Suite(&LocalRepoSuite{})
|
||||
|
||||
func (s *LocalRepoSuite) SetUpTest(c *C) {
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.list = NewPackageList()
|
||||
s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"})
|
||||
s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"})
|
||||
|
||||
s.reflist = NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
s.repo = NewLocalRepo("lrepo", "Super repo")
|
||||
s.repo.packageRefs = s.reflist
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestString(c *C) {
|
||||
c.Check(NewLocalRepo("lrepo", "My first repo").String(), Equals, "[lrepo]: My first repo")
|
||||
c.Check(NewLocalRepo("lrepo2", "").String(), Equals, "[lrepo2]")
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestNumPackages(c *C) {
|
||||
c.Check(NewLocalRepo("lrepo", "My first repo").NumPackages(), Equals, 0)
|
||||
c.Check(s.repo.NumPackages(), Equals, 2)
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestRefList(c *C) {
|
||||
c.Check(NewLocalRepo("lrepo", "My first repo").RefList(), IsNil)
|
||||
c.Check(s.repo.RefList(), Equals, s.reflist)
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestUpdateRefList(c *C) {
|
||||
s.repo.UpdateRefList(nil)
|
||||
c.Check(s.repo.RefList(), IsNil)
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestEncodeDecode(c *C) {
|
||||
repo := &LocalRepo{}
|
||||
err := repo.Decode(s.repo.Encode())
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(repo.Name, Equals, s.repo.Name)
|
||||
c.Check(repo.Comment, Equals, s.repo.Comment)
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestKey(c *C) {
|
||||
c.Assert(len(s.repo.Key()), Equals, 37)
|
||||
c.Assert(s.repo.Key()[0], Equals, byte('L'))
|
||||
}
|
||||
|
||||
func (s *LocalRepoSuite) TestRefKey(c *C) {
|
||||
c.Assert(len(s.repo.RefKey()), Equals, 37)
|
||||
c.Assert(s.repo.RefKey()[0], Equals, byte('E'))
|
||||
c.Assert(s.repo.RefKey()[1:], DeepEquals, s.repo.Key()[1:])
|
||||
}
|
||||
|
||||
type LocalRepoCollectionSuite struct {
|
||||
db database.Storage
|
||||
collection *LocalRepoCollection
|
||||
list *PackageList
|
||||
reflist *PackageRefList
|
||||
}
|
||||
|
||||
var _ = Suite(&LocalRepoCollectionSuite{})
|
||||
|
||||
func (s *LocalRepoCollectionSuite) SetUpTest(c *C) {
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.collection = NewLocalRepoCollection(s.db)
|
||||
|
||||
s.list = NewPackageList()
|
||||
s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"})
|
||||
s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"})
|
||||
|
||||
s.reflist = NewPackageRefListFromPackageList(s.list)
|
||||
}
|
||||
|
||||
func (s *LocalRepoCollectionSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *LocalRepoCollectionSuite) TestAddByName(c *C) {
|
||||
r, err := s.collection.ByName("local1")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
repo := NewLocalRepo("local1", "Comment 1")
|
||||
c.Assert(s.collection.Add(repo), IsNil)
|
||||
c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists")
|
||||
|
||||
r, err = s.collection.ByName("local1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.String(), Equals, repo.String())
|
||||
|
||||
collection := NewLocalRepoCollection(s.db)
|
||||
r, err = collection.ByName("local1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.String(), Equals, repo.String())
|
||||
}
|
||||
|
||||
func (s *LocalRepoCollectionSuite) TestByUUID(c *C) {
|
||||
r, err := s.collection.ByUUID("some-uuid")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
repo := NewLocalRepo("local1", "Comment 1")
|
||||
c.Assert(s.collection.Add(repo), IsNil)
|
||||
|
||||
r, err = s.collection.ByUUID(repo.UUID)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.String(), Equals, repo.String())
|
||||
}
|
||||
|
||||
func (s *LocalRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
repo := NewLocalRepo("local1", "Comment 1")
|
||||
c.Assert(s.collection.Update(repo), IsNil)
|
||||
|
||||
collection := NewLocalRepoCollection(s.db)
|
||||
r, err := collection.ByName("local1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.packageRefs, IsNil)
|
||||
|
||||
repo.packageRefs = s.reflist
|
||||
c.Assert(s.collection.Update(repo), IsNil)
|
||||
|
||||
collection = NewLocalRepoCollection(s.db)
|
||||
r, err = collection.ByName("local1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.packageRefs, IsNil)
|
||||
c.Assert(r.NumPackages(), Equals, 0)
|
||||
c.Assert(s.collection.LoadComplete(r), IsNil)
|
||||
c.Assert(r.NumPackages(), Equals, 2)
|
||||
}
|
||||
|
||||
func (s *LocalRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||
repo := NewLocalRepo("local1", "Comment 1")
|
||||
s.collection.Add(repo)
|
||||
|
||||
count := 0
|
||||
err := s.collection.ForEach(func(*LocalRepo) error {
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
c.Assert(count, Equals, 1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.collection.Len(), Equals, 1)
|
||||
|
||||
e := errors.New("c")
|
||||
|
||||
err = s.collection.ForEach(func(*LocalRepo) error {
|
||||
return e
|
||||
})
|
||||
c.Assert(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *LocalRepoCollectionSuite) TestDrop(c *C) {
|
||||
repo1 := NewLocalRepo("local1", "Comment 1")
|
||||
s.collection.Add(repo1)
|
||||
|
||||
repo2 := NewLocalRepo("local2", "Comment 2")
|
||||
s.collection.Add(repo2)
|
||||
|
||||
r1, _ := s.collection.ByUUID(repo1.UUID)
|
||||
c.Check(r1, Equals, repo1)
|
||||
|
||||
err := s.collection.Drop(repo1)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByUUID(repo1.UUID)
|
||||
c.Check(err, ErrorMatches, "local repo .* not found")
|
||||
|
||||
collection := NewLocalRepoCollection(s.db)
|
||||
_, err = collection.ByName("local1")
|
||||
c.Check(err, ErrorMatches, "local repo .* not found")
|
||||
|
||||
r2, _ := collection.ByName("local2")
|
||||
c.Check(r2.String(), Equals, repo2.String())
|
||||
|
||||
c.Check(func() { s.collection.Drop(repo1) }, Panics, "local repo not found!")
|
||||
}
|
||||
+577
@@ -0,0 +1,577 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Package is single instance of Debian package
|
||||
type Package struct {
|
||||
// Basic package properties
|
||||
Name string
|
||||
Version string
|
||||
Architecture string
|
||||
// If this source package, this field holds "real" architecture value,
|
||||
// while Architecture would be equal to "source"
|
||||
SourceArchitecture string
|
||||
// For binary package, name of source package
|
||||
Source string
|
||||
// List of virtual packages this package provides
|
||||
Provides []string
|
||||
// Is this source package
|
||||
IsSource bool
|
||||
// Hash of files section
|
||||
FilesHash uint64
|
||||
// Is this >= 0.6 package?
|
||||
V06Plus bool
|
||||
// Offload fields
|
||||
deps *PackageDependencies
|
||||
extra *Stanza
|
||||
files *PackageFiles
|
||||
// Mother collection
|
||||
collection *PackageCollection
|
||||
}
|
||||
|
||||
// NewPackageFromControlFile creates Package from parsed Debian control file
|
||||
func NewPackageFromControlFile(input Stanza) *Package {
|
||||
result := &Package{
|
||||
Name: input["Package"],
|
||||
Version: input["Version"],
|
||||
Architecture: input["Architecture"],
|
||||
Source: input["Source"],
|
||||
V06Plus: true,
|
||||
}
|
||||
|
||||
delete(input, "Package")
|
||||
delete(input, "Version")
|
||||
delete(input, "Architecture")
|
||||
delete(input, "Source")
|
||||
|
||||
filesize, _ := strconv.ParseInt(input["Size"], 10, 64)
|
||||
|
||||
result.UpdateFiles(PackageFiles{PackageFile{
|
||||
Filename: filepath.Base(input["Filename"]),
|
||||
downloadPath: filepath.Dir(input["Filename"]),
|
||||
Checksums: utils.ChecksumInfo{
|
||||
Size: filesize,
|
||||
MD5: strings.TrimSpace(input["MD5sum"]),
|
||||
SHA1: strings.TrimSpace(input["SHA1"]),
|
||||
SHA256: strings.TrimSpace(input["SHA256"]),
|
||||
},
|
||||
}})
|
||||
|
||||
delete(input, "Filename")
|
||||
delete(input, "MD5sum")
|
||||
delete(input, "SHA1")
|
||||
delete(input, "SHA256")
|
||||
delete(input, "Size")
|
||||
|
||||
depends := &PackageDependencies{}
|
||||
depends.Depends = parseDependencies(input, "Depends")
|
||||
depends.PreDepends = parseDependencies(input, "Pre-Depends")
|
||||
depends.Suggests = parseDependencies(input, "Suggests")
|
||||
depends.Recommends = parseDependencies(input, "Recommends")
|
||||
result.deps = depends
|
||||
|
||||
result.Provides = parseDependencies(input, "Provides")
|
||||
|
||||
result.extra = &input
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NewSourcePackageFromControlFile creates Package from parsed Debian control file for source package
|
||||
func NewSourcePackageFromControlFile(input Stanza) (*Package, error) {
|
||||
result := &Package{
|
||||
IsSource: true,
|
||||
Name: input["Package"],
|
||||
Version: input["Version"],
|
||||
Architecture: "source",
|
||||
SourceArchitecture: input["Architecture"],
|
||||
V06Plus: true,
|
||||
}
|
||||
|
||||
delete(input, "Package")
|
||||
delete(input, "Version")
|
||||
delete(input, "Architecture")
|
||||
|
||||
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 })
|
||||
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
|
||||
}
|
||||
|
||||
result.UpdateFiles(files)
|
||||
|
||||
depends := &PackageDependencies{}
|
||||
depends.BuildDepends = parseDependencies(input, "Build-Depends")
|
||||
depends.BuildDependsInDep = parseDependencies(input, "Build-Depends-Indep")
|
||||
result.deps = depends
|
||||
|
||||
result.extra = &input
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Key returns unique key identifying package
|
||||
func (p *Package) Key(prefix string) []byte {
|
||||
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
|
||||
func (p *Package) String() string {
|
||||
return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture)
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
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]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MatchesArchitecture checks whether packages matches specified architecture
|
||||
func (p *Package) MatchesArchitecture(arch string) bool {
|
||||
if p.Architecture == "all" && arch != "source" {
|
||||
return true
|
||||
}
|
||||
|
||||
return p.Architecture == arch
|
||||
}
|
||||
|
||||
// MatchesDependency checks whether package matches specified dependency
|
||||
func (p *Package) MatchesDependency(dep Dependency) bool {
|
||||
if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) {
|
||||
return false
|
||||
}
|
||||
|
||||
if dep.Relation == VersionDontCare {
|
||||
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
|
||||
case VersionLess:
|
||||
return r < 0
|
||||
case VersionGreater:
|
||||
return r > 0
|
||||
case VersionLessOrEqual:
|
||||
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")
|
||||
}
|
||||
|
||||
// GetDependencies compiles list of dependenices by flags from options
|
||||
func (p *Package) GetDependencies(options int) (dependencies []string) {
|
||||
deps := p.Deps()
|
||||
|
||||
dependencies = make([]string, 0, 30)
|
||||
dependencies = append(dependencies, deps.Depends...)
|
||||
dependencies = append(dependencies, deps.PreDepends...)
|
||||
|
||||
if options&DepFollowRecommends == DepFollowRecommends {
|
||||
dependencies = append(dependencies, deps.Recommends...)
|
||||
}
|
||||
|
||||
if options&DepFollowSuggests == DepFollowSuggests {
|
||||
dependencies = append(dependencies, deps.Suggests...)
|
||||
}
|
||||
|
||||
if options&DepFollowBuild == DepFollowBuild {
|
||||
dependencies = append(dependencies, deps.BuildDepends...)
|
||||
dependencies = append(dependencies, deps.BuildDependsInDep...)
|
||||
}
|
||||
|
||||
if options&DepFollowSource == DepFollowSource {
|
||||
source := p.Source
|
||||
if source == "" {
|
||||
source = p.Name
|
||||
}
|
||||
if strings.Index(source, ")") != -1 {
|
||||
dependencies = append(dependencies, fmt.Sprintf("%s {source}", source))
|
||||
} else {
|
||||
dependencies = append(dependencies, fmt.Sprintf("%s (= %s) {source}", source, p.Version))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Extra returns Stanza of extra fields (it may load it from collection)
|
||||
func (p *Package) Extra() Stanza {
|
||||
if p.extra == nil {
|
||||
if p.collection == nil {
|
||||
panic("extra == nil && collection == nil")
|
||||
}
|
||||
p.extra = p.collection.loadExtra(p)
|
||||
}
|
||||
|
||||
return *p.extra
|
||||
}
|
||||
|
||||
// Deps returns parsed package dependencies (it may load it from collection)
|
||||
func (p *Package) Deps() *PackageDependencies {
|
||||
if p.deps == nil {
|
||||
if p.collection == nil {
|
||||
panic("deps == nil && collection == nil")
|
||||
}
|
||||
|
||||
p.deps = p.collection.loadDependencies(p)
|
||||
}
|
||||
|
||||
return p.deps
|
||||
}
|
||||
|
||||
// Files returns parsed files records (it may load it from collection)
|
||||
func (p *Package) Files() PackageFiles {
|
||||
if p.files == nil {
|
||||
if p.collection == nil {
|
||||
panic("files == nil && collection == nil")
|
||||
}
|
||||
|
||||
p.files = p.collection.loadFiles(p)
|
||||
}
|
||||
|
||||
return *p.files
|
||||
}
|
||||
|
||||
// UpdateFiles saves new state of files
|
||||
func (p *Package) UpdateFiles(files PackageFiles) {
|
||||
p.files = &files
|
||||
p.FilesHash = files.Hash()
|
||||
}
|
||||
|
||||
// Stanza creates original stanza from package
|
||||
func (p *Package) Stanza() (result Stanza) {
|
||||
result = p.Extra().Copy()
|
||||
result["Package"] = p.Name
|
||||
result["Version"] = p.Version
|
||||
|
||||
if p.IsSource {
|
||||
result["Architecture"] = p.SourceArchitecture
|
||||
} else {
|
||||
result["Architecture"] = p.Architecture
|
||||
result["Source"] = p.Source
|
||||
}
|
||||
|
||||
if p.IsSource {
|
||||
md5, sha1, sha256 := make([]string, 0), make([]string, 0), make([]string, 0)
|
||||
|
||||
for _, f := range p.Files() {
|
||||
if f.Checksums.MD5 != "" {
|
||||
md5 = append(md5, fmt.Sprintf(" %s %d %s\n", f.Checksums.MD5, f.Checksums.Size, f.Filename))
|
||||
}
|
||||
if f.Checksums.SHA1 != "" {
|
||||
sha1 = append(sha1, fmt.Sprintf(" %s %d %s\n", f.Checksums.SHA1, f.Checksums.Size, f.Filename))
|
||||
}
|
||||
if f.Checksums.SHA256 != "" {
|
||||
sha256 = append(sha256, fmt.Sprintf(" %s %d %s\n", f.Checksums.SHA256, f.Checksums.Size, f.Filename))
|
||||
}
|
||||
}
|
||||
|
||||
result["Files"] = strings.Join(md5, "")
|
||||
result["Checksums-Sha1"] = strings.Join(sha1, "")
|
||||
result["Checksums-Sha256"] = strings.Join(sha256, "")
|
||||
} else {
|
||||
f := p.Files()[0]
|
||||
result["Filename"] = f.DownloadURL()
|
||||
if f.Checksums.MD5 != "" {
|
||||
result["MD5sum"] = f.Checksums.MD5
|
||||
}
|
||||
if f.Checksums.SHA1 != "" {
|
||||
result["SHA1"] = " " + f.Checksums.SHA1
|
||||
}
|
||||
if f.Checksums.SHA256 != "" {
|
||||
result["SHA256"] = " " + f.Checksums.SHA256
|
||||
}
|
||||
result["Size"] = fmt.Sprintf("%d", f.Checksums.Size)
|
||||
}
|
||||
|
||||
deps := p.Deps()
|
||||
|
||||
if deps.Depends != nil {
|
||||
result["Depends"] = strings.Join(deps.Depends, ", ")
|
||||
}
|
||||
if deps.PreDepends != nil {
|
||||
result["Pre-Depends"] = strings.Join(deps.PreDepends, ", ")
|
||||
}
|
||||
if deps.Suggests != nil {
|
||||
result["Suggests"] = strings.Join(deps.Suggests, ", ")
|
||||
}
|
||||
if deps.Recommends != nil {
|
||||
result["Recommends"] = strings.Join(deps.Recommends, ", ")
|
||||
}
|
||||
if p.Provides != nil {
|
||||
result["Provides"] = strings.Join(p.Provides, ", ")
|
||||
}
|
||||
if deps.BuildDepends != nil {
|
||||
result["Build-Depends"] = strings.Join(deps.BuildDepends, ", ")
|
||||
}
|
||||
if deps.BuildDependsInDep != nil {
|
||||
result["Build-Depends-Indep"] = strings.Join(deps.BuildDependsInDep, ", ")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Equals compares two packages to be identical
|
||||
func (p *Package) Equals(p2 *Package) bool {
|
||||
return p.Name == p2.Name && p.Version == p2.Version && p.SourceArchitecture == p2.SourceArchitecture &&
|
||||
p.Architecture == p2.Architecture && p.Source == p2.Source && p.IsSource == p2.IsSource &&
|
||||
p.FilesHash == p2.FilesHash
|
||||
}
|
||||
|
||||
// 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 {
|
||||
poolDir, err := p.PoolDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, f := range p.Files() {
|
||||
sourcePath, err := packagePool.Path(f.Filename, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath := filepath.Join("pool", component, poolDir)
|
||||
publishedDirectory := filepath.Join(prefix, relPath)
|
||||
|
||||
err = publishedStorage.LinkFromPool(publishedDirectory, packagePool, sourcePath, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.IsSource {
|
||||
p.Extra()["Directory"] = relPath
|
||||
} else {
|
||||
p.Files()[i].downloadPath = relPath
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PoolDirectory returns directory in package pool of published repository for this package files
|
||||
func (p *Package) PoolDirectory() (string, error) {
|
||||
source := p.Source
|
||||
if source == "" {
|
||||
source = p.Name
|
||||
} else if pos := strings.Index(source, "("); pos != -1 {
|
||||
source = strings.TrimSpace(source[:pos])
|
||||
}
|
||||
|
||||
if len(source) < 2 {
|
||||
return "", fmt.Errorf("package source %s too short", source)
|
||||
}
|
||||
|
||||
var subdir string
|
||||
if strings.HasPrefix(source, "lib") {
|
||||
subdir = source[:4]
|
||||
} else {
|
||||
subdir = source[:1]
|
||||
|
||||
}
|
||||
|
||||
return filepath.Join(subdir, source), nil
|
||||
}
|
||||
|
||||
// PackageDownloadTask is a element of download queue for the package
|
||||
type PackageDownloadTask struct {
|
||||
RepoURI string
|
||||
DestinationPath string
|
||||
Checksums utils.ChecksumInfo
|
||||
}
|
||||
|
||||
// DownloadList returns list of missing package files for download in format
|
||||
// [[srcpath, dstpath]]
|
||||
func (p *Package) DownloadList(packagePool aptly.PackagePool) (result []PackageDownloadTask, err error) {
|
||||
result = make([]PackageDownloadTask, 0, 1)
|
||||
|
||||
for _, f := range p.Files() {
|
||||
poolPath, err := packagePool.Path(f.Filename, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verified, err := f.Verify(packagePool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !verified {
|
||||
result = append(result, PackageDownloadTask{RepoURI: f.DownloadURL(), DestinationPath: poolPath, Checksums: f.Checksums})
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// VerifyFiles verifies that all package files have neen correctly downloaded
|
||||
func (p *Package) VerifyFiles(packagePool aptly.PackagePool) (result bool, err error) {
|
||||
result = true
|
||||
|
||||
for _, f := range p.Files() {
|
||||
result, err = f.Verify(packagePool)
|
||||
if err != nil || !result {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FilepathList returns list of paths to files in package repository
|
||||
func (p *Package) FilepathList(packagePool aptly.PackagePool) ([]string, error) {
|
||||
var err error
|
||||
result := make([]string, len(p.Files()))
|
||||
|
||||
for i, f := range p.Files() {
|
||||
result[i], err = packagePool.RelativePath(f.Filename, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/ugorji/go/codec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// PackageCollection does management of packages in DB
|
||||
type PackageCollection struct {
|
||||
db database.Storage
|
||||
encodeBuffer bytes.Buffer
|
||||
codecHandle *codec.MsgpackHandle
|
||||
}
|
||||
|
||||
// NewPackageCollection creates new PackageCollection and binds it to database
|
||||
func NewPackageCollection(db database.Storage) *PackageCollection {
|
||||
return &PackageCollection{
|
||||
db: db,
|
||||
codecHandle: &codec.MsgpackHandle{},
|
||||
}
|
||||
}
|
||||
|
||||
// oldPackage is Package struct for aptly < 0.4 with all fields in one struct
|
||||
// It is used to decode old aptly DBs
|
||||
type oldPackage struct {
|
||||
IsSource bool
|
||||
Name string
|
||||
Version string
|
||||
Architecture string
|
||||
SourceArchitecture string
|
||||
Source string
|
||||
Provides []string
|
||||
Depends []string
|
||||
BuildDepends []string
|
||||
BuildDependsInDep []string
|
||||
PreDepends []string
|
||||
Suggests []string
|
||||
Recommends []string
|
||||
Files []PackageFile
|
||||
Extra Stanza
|
||||
}
|
||||
|
||||
// ByKey find package in DB by its key
|
||||
func (collection *PackageCollection) ByKey(key []byte) (*Package, error) {
|
||||
encoded, err := collection.db.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Package{}
|
||||
|
||||
if len(encoded) > 2 && (encoded[0] != 0xc1 || encoded[1] != 0x1) {
|
||||
oldp := &oldPackage{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(oldp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Name = oldp.Name
|
||||
p.Version = oldp.Version
|
||||
p.Architecture = oldp.Architecture
|
||||
p.IsSource = oldp.IsSource
|
||||
p.SourceArchitecture = oldp.SourceArchitecture
|
||||
p.Source = oldp.Source
|
||||
p.Provides = oldp.Provides
|
||||
|
||||
p.deps = &PackageDependencies{
|
||||
Depends: oldp.Depends,
|
||||
BuildDepends: oldp.BuildDepends,
|
||||
BuildDependsInDep: oldp.BuildDependsInDep,
|
||||
PreDepends: oldp.PreDepends,
|
||||
Suggests: oldp.Suggests,
|
||||
Recommends: oldp.Recommends,
|
||||
}
|
||||
|
||||
p.extra = &oldp.Extra
|
||||
for i := range oldp.Files {
|
||||
oldp.Files[i].Filename = filepath.Base(oldp.Files[i].Filename)
|
||||
}
|
||||
p.UpdateFiles(PackageFiles(oldp.Files))
|
||||
|
||||
// Save in new format
|
||||
err = collection.Update(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
decoder := codec.NewDecoderBytes(encoded[2:], collection.codecHandle)
|
||||
err = decoder.Decode(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p.collection = collection
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// loadExtra loads Stanza with all the xtra information about the package
|
||||
func (collection *PackageCollection) loadExtra(p *Package) *Stanza {
|
||||
encoded, err := collection.db.Get(p.Key("xE"))
|
||||
if err != nil {
|
||||
panic("unable to load extra")
|
||||
}
|
||||
|
||||
stanza := &Stanza{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(stanza)
|
||||
if err != nil {
|
||||
panic("unable to decode extra")
|
||||
}
|
||||
|
||||
return stanza
|
||||
}
|
||||
|
||||
// loadDependencies loads dependencies for the package
|
||||
func (collection *PackageCollection) loadDependencies(p *Package) *PackageDependencies {
|
||||
encoded, err := collection.db.Get(p.Key("xD"))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to load deps: %s, %s", p, err))
|
||||
}
|
||||
|
||||
deps := &PackageDependencies{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(deps)
|
||||
if err != nil {
|
||||
panic("unable to decode deps")
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
|
||||
// loadFiles loads additional PackageFiles record
|
||||
func (collection *PackageCollection) loadFiles(p *Package) *PackageFiles {
|
||||
encoded, err := collection.db.Get(p.Key("xF"))
|
||||
if err != nil {
|
||||
panic("unable to load files")
|
||||
}
|
||||
|
||||
files := &PackageFiles{}
|
||||
|
||||
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
|
||||
err = decoder.Decode(files)
|
||||
if err != nil {
|
||||
panic("unable to decode files")
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
// Update adds or updates information about package in DB checking for conficts first
|
||||
func (collection *PackageCollection) Update(p *Package) error {
|
||||
encoder := codec.NewEncoder(&collection.encodeBuffer, collection.codecHandle)
|
||||
|
||||
collection.encodeBuffer.Reset()
|
||||
collection.encodeBuffer.WriteByte(0xc1)
|
||||
collection.encodeBuffer.WriteByte(0x1)
|
||||
err := encoder.Encode(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key(""), collection.encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Encode offloaded fields one by one
|
||||
if p.files != nil {
|
||||
collection.encodeBuffer.Reset()
|
||||
err = encoder.Encode(*p.files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xF"), collection.encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if p.deps != nil {
|
||||
collection.encodeBuffer.Reset()
|
||||
err = encoder.Encode(*p.deps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xD"), collection.encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.deps = nil
|
||||
}
|
||||
|
||||
if p.extra != nil {
|
||||
collection.encodeBuffer.Reset()
|
||||
err = encoder.Encode(*p.extra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collection.db.Put(p.Key("xE"), collection.encodeBuffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.extra = nil
|
||||
}
|
||||
|
||||
p.collection = collection
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllPackageRefs returns list of all packages as PackageRefList
|
||||
func (collection *PackageCollection) AllPackageRefs() *PackageRefList {
|
||||
return &PackageRefList{Refs: collection.db.KeysByPrefix([]byte("P"))}
|
||||
}
|
||||
|
||||
// DeleteByKey deletes package in DB by key
|
||||
func (collection *PackageCollection) DeleteByKey(key []byte) error {
|
||||
for _, key := range [][]byte{key, append([]byte("xF"), key...), append([]byte("xD"), key...), append([]byte("xE"), key...)} {
|
||||
err := collection.db.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type PackageCollectionSuite struct {
|
||||
collection *PackageCollection
|
||||
p *Package
|
||||
db database.Storage
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageCollectionSuite{})
|
||||
|
||||
func (s *PackageCollectionSuite) SetUpTest(c *C) {
|
||||
s.p = NewPackageFromControlFile(packageStanza.Copy())
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.collection = NewPackageCollection(s.db)
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestUpdate(c *C) {
|
||||
// package doesn't exist, update ok
|
||||
err := s.collection.Update(s.p)
|
||||
c.Assert(err, IsNil)
|
||||
res, err := s.collection.ByKey(s.p.Key(""))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(res.Equals(s.p), Equals, true)
|
||||
|
||||
// same package, ok
|
||||
p2 := NewPackageFromControlFile(packageStanza.Copy())
|
||||
err = s.collection.Update(p2)
|
||||
c.Assert(err, IsNil)
|
||||
res, err = s.collection.ByKey(p2.Key(""))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(res.Equals(s.p), Equals, true)
|
||||
|
||||
// change some metadata
|
||||
p2.Source = "lala"
|
||||
err = s.collection.Update(p2)
|
||||
c.Assert(err, IsNil)
|
||||
res, err = s.collection.ByKey(p2.Key(""))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(res.Equals(s.p), Equals, false)
|
||||
c.Assert(res.Equals(p2), Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestByKey(c *C) {
|
||||
err := s.collection.Update(s.p)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
p2, err := s.collection.ByKey(s.p.Key(""))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p2.Equals(s.p), Equals, true)
|
||||
|
||||
c.Check(p2.GetDependencies(0), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)"})
|
||||
c.Check(p2.Extra()["Priority"], Equals, "extra")
|
||||
c.Check(p2.Files()[0].Filename, Equals, "alien-arena-common_7.40-2_i386.deb")
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestByKeyOld_0_3(c *C) {
|
||||
key := []byte("Pi386 vmware-view-open-client 4.5.0-297975+dfsg-4+b1")
|
||||
s.db.Put(key, old_0_3_Package)
|
||||
|
||||
p, err := s.collection.ByKey(key)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.Name, Equals, "vmware-view-open-client")
|
||||
c.Check(p.Version, Equals, "4.5.0-297975+dfsg-4+b1")
|
||||
c.Check(p.Architecture, Equals, "i386")
|
||||
c.Check(p.Files(), DeepEquals, PackageFiles{
|
||||
PackageFile{Filename: "vmware-view-open-client_4.5.0-297975+dfsg-4+b1_i386.deb",
|
||||
Checksums: utils.ChecksumInfo{
|
||||
Size: 520080,
|
||||
MD5: "9c61b54e2638a18f955a695b9162d6af",
|
||||
SHA1: "5b7c99e64a70f4f509bfa3a674088ff9cef68163",
|
||||
SHA256: "4a9e4b2d9b3db13f9a29e522f3ffbb34eee96fc6f34a0647042ab1b5b0f2e04d"}}})
|
||||
c.Check(p.GetDependencies(0), DeepEquals, []string{"libatk1.0-0 (>= 1.12.4)", "libboost-signals1.49.0 (>= 1.49.0-1)",
|
||||
"libc6 (>= 2.3.6-6~)", "libcairo2 (>= 1.2.4)", "libcurl3 (>= 7.18.0)", "libfontconfig1 (>= 2.8.0)", "libfreetype6 (>= 2.2.1)",
|
||||
"libgcc1 (>= 1:4.1.1)", "libgdk-pixbuf2.0-0 (>= 2.22.0)", "libglib2.0-0 (>= 2.24.0)", "libgtk2.0-0 (>= 2.24.0)",
|
||||
"libicu48 (>= 4.8-1)", "libpango1.0-0 (>= 1.14.0)", "libssl1.0.0 (>= 1.0.0)", "libstdc++6 (>= 4.6)", "libx11-6",
|
||||
"libxml2 (>= 2.7.4)", "rdesktop"})
|
||||
c.Check(p.Extra()["Priority"], Equals, "optional")
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestAllPackageRefs(c *C) {
|
||||
err := s.collection.Update(s.p)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
refs := s.collection.AllPackageRefs()
|
||||
c.Check(refs.Len(), Equals, 1)
|
||||
c.Check(refs.Refs[0], DeepEquals, s.p.Key(""))
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestDeleteByKey(c *C) {
|
||||
err := s.collection.Update(s.p)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(s.p.Key(""))
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(s.p.Key("xD"))
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(s.p.Key("xE"))
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(s.p.Key("xF"))
|
||||
c.Check(err, IsNil)
|
||||
|
||||
err = s.collection.DeleteByKey(s.p.Key(""))
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByKey(s.p.Key(""))
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
_, err = s.db.Get(s.p.Key(""))
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
_, err = s.db.Get(s.p.Key("xD"))
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
_, err = s.db.Get(s.p.Key("xE"))
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
_, err = s.db.Get(s.p.Key("xF"))
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
}
|
||||
|
||||
// This is old package (pre-0.4) that would habe to be converted
|
||||
var old_0_3_Package = []byte{0x8f, 0xac, 0x41, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0xa4, 0x69, 0x33, 0x38, 0x36,
|
||||
0xac, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0xc0, 0xb1, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65,
|
||||
0x70, 0x65, 0x6e, 0x64, 0x73, 0x49, 0x6e, 0x44, 0x65, 0x70, 0xc0, 0xa7, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0xdc, 0x0, 0x12,
|
||||
0xb7, 0x6c, 0x69, 0x62, 0x61, 0x74, 0x6b, 0x31, 0x2e, 0x30, 0x2d, 0x30, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x31, 0x2e, 0x31, 0x32, 0x2e,
|
||||
0x34, 0x29, 0xda, 0x0, 0x24, 0x6c, 0x69, 0x62, 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x31,
|
||||
0x2e, 0x34, 0x39, 0x2e, 0x30, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x31, 0x2e, 0x34, 0x39, 0x2e, 0x30, 0x2d, 0x31, 0x29, 0xb3, 0x6c, 0x69,
|
||||
0x62, 0x63, 0x36, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x32, 0x2e, 0x33, 0x2e, 0x36, 0x2d, 0x36, 0x7e, 0x29, 0xb4, 0x6c, 0x69, 0x62, 0x63,
|
||||
0x61, 0x69, 0x72, 0x6f, 0x32, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x31, 0x2e, 0x32, 0x2e, 0x34, 0x29, 0xb4, 0x6c, 0x69, 0x62, 0x63, 0x75,
|
||||
0x72, 0x6c, 0x33, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x37, 0x2e, 0x31, 0x38, 0x2e, 0x30, 0x29, 0xb9, 0x6c, 0x69, 0x62, 0x66, 0x6f, 0x6e,
|
||||
0x74, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x31, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x29, 0xb7, 0x6c, 0x69,
|
||||
0x62, 0x66, 0x72, 0x65, 0x65, 0x74, 0x79, 0x70, 0x65, 0x36, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x32, 0x2e, 0x32, 0x2e, 0x31, 0x29, 0xb4,
|
||||
0x6c, 0x69, 0x62, 0x67, 0x63, 0x63, 0x31, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x31, 0x3a, 0x34, 0x2e, 0x31, 0x2e, 0x31, 0x29, 0xbe, 0x6c,
|
||||
0x69, 0x62, 0x67, 0x64, 0x6b, 0x2d, 0x70, 0x69, 0x78, 0x62, 0x75, 0x66, 0x32, 0x2e, 0x30, 0x2d, 0x30, 0x20, 0x28, 0x3e, 0x3d, 0x20,
|
||||
0x32, 0x2e, 0x32, 0x32, 0x2e, 0x30, 0x29, 0xb8, 0x6c, 0x69, 0x62, 0x67, 0x6c, 0x69, 0x62, 0x32, 0x2e, 0x30, 0x2d, 0x30, 0x20, 0x28,
|
||||
0x3e, 0x3d, 0x20, 0x32, 0x2e, 0x32, 0x34, 0x2e, 0x30, 0x29, 0xb7, 0x6c, 0x69, 0x62, 0x67, 0x74, 0x6b, 0x32, 0x2e, 0x30, 0x2d, 0x30,
|
||||
0x20, 0x28, 0x3e, 0x3d, 0x20, 0x32, 0x2e, 0x32, 0x34, 0x2e, 0x30, 0x29, 0xb3, 0x6c, 0x69, 0x62, 0x69, 0x63, 0x75, 0x34, 0x38, 0x20,
|
||||
0x28, 0x3e, 0x3d, 0x20, 0x34, 0x2e, 0x38, 0x2d, 0x31, 0x29, 0xb9, 0x6c, 0x69, 0x62, 0x70, 0x61, 0x6e, 0x67, 0x6f, 0x31, 0x2e, 0x30,
|
||||
0x2d, 0x30, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x31, 0x2e, 0x31, 0x34, 0x2e, 0x30, 0x29, 0xb6, 0x6c, 0x69, 0x62, 0x73, 0x73, 0x6c, 0x31,
|
||||
0x2e, 0x30, 0x2e, 0x30, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x29, 0xb3, 0x6c, 0x69, 0x62, 0x73, 0x74, 0x64,
|
||||
0x63, 0x2b, 0x2b, 0x36, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x34, 0x2e, 0x36, 0x29, 0xa8, 0x6c, 0x69, 0x62, 0x78, 0x31, 0x31, 0x2d, 0x36,
|
||||
0xb2, 0x6c, 0x69, 0x62, 0x78, 0x6d, 0x6c, 0x32, 0x20, 0x28, 0x3e, 0x3d, 0x20, 0x32, 0x2e, 0x37, 0x2e, 0x34, 0x29, 0xa8, 0x72, 0x64,
|
||||
0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0xa5, 0x45, 0x78, 0x74, 0x72, 0x61, 0x88, 0xa3, 0x54, 0x61, 0x67, 0xbd, 0x72, 0x6f, 0x6c, 0x65,
|
||||
0x3a, 0x3a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2c, 0x20, 0x75, 0x69, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x3a, 0x3a,
|
||||
0x67, 0x74, 0x6b, 0xa8, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0xa8, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0xaa,
|
||||
0x4d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0xda, 0x0, 0x28, 0x44, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x20, 0x51, 0x41,
|
||||
0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x3c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x40, 0x71, 0x61, 0x2e, 0x64, 0x65,
|
||||
0x62, 0x69, 0x61, 0x6e, 0x2e, 0x6f, 0x72, 0x67, 0x3e, 0xa8, 0x48, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, 0x65, 0xda, 0x0, 0x30, 0x68,
|
||||
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x70, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x63, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0xaf, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x64, 0x35, 0xda, 0x0, 0x20,
|
||||
0x62, 0x34, 0x34, 0x64, 0x34, 0x39, 0x62, 0x34, 0x37, 0x61, 0x65, 0x30, 0x35, 0x35, 0x32, 0x63, 0x62, 0x66, 0x61, 0x64, 0x32, 0x31,
|
||||
0x30, 0x64, 0x65, 0x32, 0x31, 0x63, 0x64, 0x65, 0x31, 0x39, 0xa7, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0xab, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x69, 0x62, 0x2f, 0x78, 0x31, 0x31, 0xae, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2d, 0x53, 0x69, 0x7a,
|
||||
0x65, 0xa4, 0x31, 0x34, 0x35, 0x39, 0xab, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0xb9, 0x20, 0x56, 0x4d,
|
||||
0x77, 0x61, 0x72, 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0xa,
|
||||
0xa5, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x91, 0x82, 0xa9, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x73, 0x84, 0xa3, 0x4d, 0x44,
|
||||
0x35, 0xda, 0x0, 0x20, 0x39, 0x63, 0x36, 0x31, 0x62, 0x35, 0x34, 0x65, 0x32, 0x36, 0x33, 0x38, 0x61, 0x31, 0x38, 0x66, 0x39, 0x35,
|
||||
0x35, 0x61, 0x36, 0x39, 0x35, 0x62, 0x39, 0x31, 0x36, 0x32, 0x64, 0x36, 0x61, 0x66, 0xa4, 0x53, 0x48, 0x41, 0x31, 0xda, 0x0, 0x28,
|
||||
0x35, 0x62, 0x37, 0x63, 0x39, 0x39, 0x65, 0x36, 0x34, 0x61, 0x37, 0x30, 0x66, 0x34, 0x66, 0x35, 0x30, 0x39, 0x62, 0x66, 0x61, 0x33,
|
||||
0x61, 0x36, 0x37, 0x34, 0x30, 0x38, 0x38, 0x66, 0x66, 0x39, 0x63, 0x65, 0x66, 0x36, 0x38, 0x31, 0x36, 0x33, 0xa6, 0x53, 0x48, 0x41,
|
||||
0x32, 0x35, 0x36, 0xda, 0x0, 0x40, 0x34, 0x61, 0x39, 0x65, 0x34, 0x62, 0x32, 0x64, 0x39, 0x62, 0x33, 0x64, 0x62, 0x31, 0x33, 0x66,
|
||||
0x39, 0x61, 0x32, 0x39, 0x65, 0x35, 0x32, 0x32, 0x66, 0x33, 0x66, 0x66, 0x62, 0x62, 0x33, 0x34, 0x65, 0x65, 0x65, 0x39, 0x36, 0x66,
|
||||
0x63, 0x36, 0x66, 0x33, 0x34, 0x61, 0x30, 0x36, 0x34, 0x37, 0x30, 0x34, 0x32, 0x61, 0x62, 0x31, 0x62, 0x35, 0x62, 0x30, 0x66, 0x32,
|
||||
0x65, 0x30, 0x34, 0x64, 0xa4, 0x53, 0x69, 0x7a, 0x65, 0xce, 0x0, 0x7, 0xef, 0x90, 0xa8, 0x46, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0xda, 0x0, 0x5e, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2f, 0x76, 0x2f, 0x76, 0x6d, 0x77,
|
||||
0x61, 0x72, 0x65, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x76,
|
||||
0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x5f, 0x34, 0x2e, 0x35, 0x2e, 0x30, 0x2d, 0x32, 0x39, 0x37, 0x39, 0x37, 0x35, 0x2b, 0x64, 0x66, 0x73, 0x67, 0x2d, 0x34, 0x2b, 0x62,
|
||||
0x31, 0x5f, 0x69, 0x33, 0x38, 0x36, 0x2e, 0x64, 0x65, 0x62, 0xa8, 0x49, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0xc2, 0xa4, 0x4e,
|
||||
0x61, 0x6d, 0x65, 0xb7, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x63,
|
||||
0x6c, 0x69, 0x65, 0x6e, 0x74, 0xaa, 0x50, 0x72, 0x65, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0xc0, 0xa8, 0x50, 0x72, 0x6f, 0x76,
|
||||
0x69, 0x64, 0x65, 0x73, 0xc0, 0xaa, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x73, 0xc0, 0xa6, 0x53, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0xda, 0x0, 0x2d, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x2d,
|
||||
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x28, 0x34, 0x2e, 0x35, 0x2e, 0x30, 0x2d, 0x32, 0x39, 0x37, 0x39, 0x37, 0x35, 0x2b, 0x64,
|
||||
0x66, 0x73, 0x67, 0x2d, 0x34, 0x29, 0xb2, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74,
|
||||
0x75, 0x72, 0x65, 0xa0, 0xa8, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x73, 0xc0, 0xa7, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0xb6, 0x34, 0x2e, 0x35, 0x2e, 0x30, 0x2d, 0x32, 0x39, 0x37, 0x39, 0x37, 0x35, 0x2b, 0x64, 0x66, 0x73, 0x67, 0x2d, 0x34, 0x2b, 0x62, 0x31}
|
||||
@@ -0,0 +1,30 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PackageDependencies are various parsed dependencies
|
||||
type PackageDependencies struct {
|
||||
Depends []string
|
||||
BuildDepends []string
|
||||
BuildDependsInDep []string
|
||||
PreDepends []string
|
||||
Suggests []string
|
||||
Recommends []string
|
||||
}
|
||||
|
||||
func parseDependencies(input Stanza, key string) []string {
|
||||
value, ok := input[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(input, key)
|
||||
|
||||
result := strings.Split(value, ",")
|
||||
for i := range result {
|
||||
result[i] = strings.TrimSpace(result[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/utils"
|
||||
"hash/fnv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PackageFile is a single file entry in package
|
||||
type PackageFile struct {
|
||||
// Filename is name of file for the package (without directory)
|
||||
Filename string
|
||||
// Hashes for the file
|
||||
Checksums utils.ChecksumInfo
|
||||
// Temporary field used while downloading, stored relative path on the mirror
|
||||
downloadPath string
|
||||
}
|
||||
|
||||
// Verify that package file is present and correct
|
||||
func (f *PackageFile) Verify(packagePool aptly.PackagePool) (bool, error) {
|
||||
poolPath, err := packagePool.Path(f.Filename, f.Checksums.MD5)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
st, err := os.Stat(poolPath)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// verify size
|
||||
// TODO: verify checksum if configured
|
||||
return st.Size() == f.Checksums.Size, nil
|
||||
}
|
||||
|
||||
// DownloadURL return relative URL to package download location
|
||||
func (f *PackageFile) DownloadURL() string {
|
||||
return filepath.Join(f.downloadPath, f.Filename)
|
||||
}
|
||||
|
||||
// PackageFiles is collection of package files
|
||||
type PackageFiles []PackageFile
|
||||
|
||||
// Hash compute hash of all file items, sorting them first
|
||||
func (files PackageFiles) Hash() uint64 {
|
||||
sort.Sort(files)
|
||||
|
||||
h := fnv.New64a()
|
||||
|
||||
for _, f := range files {
|
||||
h.Write([]byte(f.Filename))
|
||||
binary.Write(h, binary.BigEndian, f.Checksums.Size)
|
||||
h.Write([]byte(f.Checksums.MD5))
|
||||
h.Write([]byte(f.Checksums.SHA1))
|
||||
h.Write([]byte(f.Checksums.SHA256))
|
||||
}
|
||||
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
// Len returns number of files
|
||||
func (files PackageFiles) Len() int {
|
||||
return len(files)
|
||||
}
|
||||
|
||||
// Swap swaps elements
|
||||
func (files PackageFiles) Swap(i, j int) {
|
||||
files[i], files[j] = files[j], files[i]
|
||||
}
|
||||
|
||||
// Less compares by filename
|
||||
func (files PackageFiles) Less(i, j int) bool {
|
||||
return files[i].Filename < files[j].Filename
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type PackageFilesSuite struct {
|
||||
files PackageFiles
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageFilesSuite{})
|
||||
|
||||
func (s *PackageFilesSuite) SetUpTest(c *C) {
|
||||
s.files = PackageFiles{PackageFile{
|
||||
Filename: "alien-arena-common_7.40-2_i386.deb",
|
||||
downloadPath: "pool/contrib/a/alien-arena",
|
||||
Checksums: utils.ChecksumInfo{
|
||||
Size: 187518,
|
||||
MD5: "1e8cba92c41420aa7baa8a5718d67122",
|
||||
SHA1: "46955e48cad27410a83740a21d766ce362364024",
|
||||
SHA256: "eb4afb9885cba6dc70cccd05b910b2dbccc02c5900578be5e99f0d3dbf9d76a5",
|
||||
}}}
|
||||
}
|
||||
|
||||
func (s *PackageFilesSuite) TestVerify(c *C) {
|
||||
packagePool := files.NewPackagePool(c.MkDir())
|
||||
poolPath, _ := packagePool.Path(s.files[0].Filename, s.files[0].Checksums.MD5)
|
||||
|
||||
result, err := s.files[0].Verify(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(result, Equals, false)
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(poolPath), 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
file, err := os.Create(poolPath)
|
||||
c.Assert(err, IsNil)
|
||||
file.WriteString("abcde")
|
||||
file.Close()
|
||||
|
||||
result, err = s.files[0].Verify(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(result, Equals, false)
|
||||
|
||||
s.files[0].Checksums.Size = 5
|
||||
result, err = s.files[0].Verify(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(result, Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageFilesSuite) TestDownloadURL(c *C) {
|
||||
c.Check(s.files[0].DownloadURL(), Equals, "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb")
|
||||
}
|
||||
|
||||
func (s *PackageFilesSuite) TestHash(c *C) {
|
||||
c.Check(s.files.Hash(), Equals, uint64(0xc8901eedd79ac51b))
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type PackageSuite struct {
|
||||
stanza Stanza
|
||||
sourceStanza Stanza
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageSuite{})
|
||||
|
||||
func (s *PackageSuite) SetUpTest(c *C) {
|
||||
s.stanza = packageStanza.Copy()
|
||||
|
||||
buf := bytes.NewBufferString(sourcePackageMeta)
|
||||
s.sourceStanza, _ = NewControlFileReader(buf).ReadStanza()
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestNewFromPara(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.IsSource, Equals, false)
|
||||
c.Check(p.Name, Equals, "alien-arena-common")
|
||||
c.Check(p.Version, Equals, "7.40-2")
|
||||
c.Check(p.Architecture, Equals, "i386")
|
||||
c.Check(p.Provides, DeepEquals, []string(nil))
|
||||
c.Check(p.Files(), HasLen, 1)
|
||||
c.Check(p.Files()[0].Filename, Equals, "alien-arena-common_7.40-2_i386.deb")
|
||||
c.Check(p.Files()[0].downloadPath, Equals, "pool/contrib/a/alien-arena")
|
||||
c.Check(p.Files()[0].Checksums.Size, Equals, int64(187518))
|
||||
c.Check(p.Files()[0].Checksums.MD5, Equals, "1e8cba92c41420aa7baa8a5718d67122")
|
||||
c.Check(p.deps.Depends, DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)"})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestNewSourceFromPara(c *C) {
|
||||
p, err := NewSourcePackageFromControlFile(s.sourceStanza)
|
||||
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.IsSource, Equals, true)
|
||||
c.Check(p.Name, Equals, "access-modifier-checker")
|
||||
c.Check(p.Version, Equals, "1.0-4")
|
||||
c.Check(p.Architecture, Equals, "source")
|
||||
c.Check(p.SourceArchitecture, Equals, "all")
|
||||
c.Check(p.Provides, IsNil)
|
||||
c.Check(p.deps.BuildDepends, DeepEquals, []string{"cdbs", "debhelper (>= 7)", "default-jdk", "maven-debian-helper"})
|
||||
c.Check(p.deps.BuildDependsInDep, DeepEquals, []string{"default-jdk-doc", "junit (>= 3.8.1)", "libannotation-indexer-java (>= 1.3)", "libannotation-indexer-java-doc", "libasm3-java", "libmaven-install-plugin-java", "libmaven-javadoc-plugin-java", "libmaven-scm-java", "libmaven2-core-java", "libmaven2-core-java-doc", "libmetainf-services-java", "libmetainf-services-java-doc", "libmaven-plugin-tools-java (>= 2.8)"})
|
||||
c.Check(p.Files(), HasLen, 3)
|
||||
|
||||
c.Check(p.Files()[0].Filename, Equals, "access-modifier-checker_1.0-4.debian.tar.gz")
|
||||
c.Check(p.Files()[0].downloadPath, Equals, "pool/main/a/access-modifier-checker")
|
||||
|
||||
c.Check(p.Files()[1].Filename, Equals, "access-modifier-checker_1.0-4.dsc")
|
||||
c.Check(p.Files()[1].downloadPath, Equals, "pool/main/a/access-modifier-checker")
|
||||
c.Check(p.Files()[1].Checksums.Size, Equals, int64(3))
|
||||
c.Check(p.Files()[1].Checksums.MD5, Equals, "900150983cd24fb0d6963f7d28e17f72")
|
||||
c.Check(p.Files()[1].Checksums.SHA1, Equals, "a9993e364706816aba3e25717850c26c9cd0d89d")
|
||||
c.Check(p.Files()[1].Checksums.SHA256, Equals, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
|
||||
c.Check(p.Files()[2].Filename, Equals, "access-modifier-checker_1.0.orig.tar.gz")
|
||||
c.Check(p.Files()[2].downloadPath, Equals, "pool/main/a/access-modifier-checker")
|
||||
c.Check(p.Files()[2].Checksums.Size, Equals, int64(4))
|
||||
c.Check(p.Files()[2].Checksums.MD5, Equals, "e2fc714c4727ee9395f324cd2e7f331f")
|
||||
c.Check(p.Files()[2].Checksums.SHA1, Equals, "81fe8bfe87576c3ecb22426f8e57847382917acf")
|
||||
c.Check(p.Files()[2].Checksums.SHA256, Equals, "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589")
|
||||
|
||||
c.Check(p.deps.Depends, IsNil)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestWithProvides(c *C) {
|
||||
s.stanza["Provides"] = "arena"
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.Name, Equals, "alien-arena-common")
|
||||
c.Check(p.Provides, DeepEquals, []string{"arena"})
|
||||
|
||||
st := p.Stanza()
|
||||
c.Check(st["Provides"], Equals, "arena")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestKey(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.Key(""), DeepEquals, []byte("Pi386 alien-arena-common 7.40-2 c8901eedd79ac51b"))
|
||||
c.Check(p.Key("xD"), DeepEquals, []byte("xDPi386 alien-arena-common 7.40-2 c8901eedd79ac51b"))
|
||||
|
||||
p.V06Plus = false
|
||||
c.Check(p.Key(""), DeepEquals, []byte("Pi386 alien-arena-common 7.40-2"))
|
||||
c.Check(p.Key("xD"), DeepEquals, []byte("xDPi386 alien-arena-common 7.40-2"))
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestShortKey(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.ShortKey(""), DeepEquals, []byte("Pi386 alien-arena-common 7.40-2"))
|
||||
c.Check(p.ShortKey("xD"), DeepEquals, []byte("xDPi386 alien-arena-common 7.40-2"))
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestStanza(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza.Copy())
|
||||
stanza := p.Stanza()
|
||||
|
||||
c.Assert(stanza, DeepEquals, s.stanza)
|
||||
|
||||
p, _ = NewSourcePackageFromControlFile(s.sourceStanza.Copy())
|
||||
stanza = p.Stanza()
|
||||
|
||||
c.Assert(stanza, DeepEquals, s.sourceStanza)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestString(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
c.Assert(p.String(), Equals, "alien-arena-common_7.40-2_i386")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestGetField(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza.Copy())
|
||||
|
||||
stanza2 := s.stanza.Copy()
|
||||
delete(stanza2, "Source")
|
||||
stanza2["Provides"] = "app, game"
|
||||
p2 := NewPackageFromControlFile(stanza2)
|
||||
|
||||
stanza3 := s.stanza.Copy()
|
||||
stanza3["Source"] = "alien-arena (3.5)"
|
||||
p3 := NewPackageFromControlFile(stanza3)
|
||||
|
||||
p4, _ := NewSourcePackageFromControlFile(s.sourceStanza.Copy())
|
||||
|
||||
c.Check(p.GetField("$Source"), Equals, "alien-arena")
|
||||
c.Check(p2.GetField("$Source"), Equals, "alien-arena-common")
|
||||
c.Check(p3.GetField("$Source"), Equals, "alien-arena")
|
||||
c.Check(p4.GetField("$Source"), Equals, "")
|
||||
|
||||
c.Check(p.GetField("$SourceVersion"), Equals, "7.40-2")
|
||||
c.Check(p2.GetField("$SourceVersion"), Equals, "7.40-2")
|
||||
c.Check(p3.GetField("$SourceVersion"), Equals, "3.5")
|
||||
c.Check(p4.GetField("$SourceVersion"), Equals, "")
|
||||
|
||||
c.Check(p.GetField("$Architecture"), Equals, "i386")
|
||||
c.Check(p4.GetField("$Architecture"), Equals, "source")
|
||||
|
||||
c.Check(p.GetField("$PackageType"), Equals, "deb")
|
||||
c.Check(p4.GetField("$PackageType"), Equals, "source")
|
||||
|
||||
c.Check(p.GetField("Name"), Equals, "alien-arena-common")
|
||||
c.Check(p4.GetField("Name"), Equals, "access-modifier-checker")
|
||||
|
||||
c.Check(p.GetField("Architecture"), Equals, "i386")
|
||||
c.Check(p4.GetField("Architecture"), Equals, "all")
|
||||
|
||||
c.Check(p.GetField("Version"), Equals, "7.40-2")
|
||||
|
||||
c.Check(p.GetField("Source"), Equals, "alien-arena")
|
||||
c.Check(p2.GetField("Source"), Equals, "")
|
||||
c.Check(p3.GetField("Source"), Equals, "alien-arena (3.5)")
|
||||
c.Check(p4.GetField("Source"), Equals, "")
|
||||
|
||||
c.Check(p.GetField("Depends"), Equals, "libc6 (>= 2.7), alien-arena-data (>= 7.40)")
|
||||
|
||||
c.Check(p.GetField("Provides"), Equals, "")
|
||||
c.Check(p2.GetField("Provides"), Equals, "app, game")
|
||||
|
||||
c.Check(p.GetField("Section"), Equals, "contrib/games")
|
||||
c.Check(p.GetField("Priority"), Equals, "extra")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestEquals(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
p2 := NewPackageFromControlFile(packageStanza.Copy())
|
||||
c.Check(p.Equals(p2), Equals, true)
|
||||
|
||||
p2.deps.Depends = []string{"package1"}
|
||||
c.Check(p.Equals(p2), Equals, true) // strange, but Equals doesn't check deep
|
||||
|
||||
p2 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
files := p2.Files()
|
||||
files[0].Checksums.MD5 = "abcdefabcdef"
|
||||
p2.UpdateFiles(files)
|
||||
c.Check(p.Equals(p2), Equals, false)
|
||||
|
||||
so, _ := NewSourcePackageFromControlFile(s.sourceStanza.Copy())
|
||||
so2, _ := NewSourcePackageFromControlFile(s.sourceStanza.Copy())
|
||||
|
||||
c.Check(so.Equals(so2), Equals, true)
|
||||
|
||||
files = so2.Files()
|
||||
files[2].Checksums.MD5 = "abcde"
|
||||
so2.UpdateFiles(files)
|
||||
c.Check(so.Equals(so2), Equals, false)
|
||||
|
||||
so2, _ = NewSourcePackageFromControlFile(s.sourceStanza.Copy())
|
||||
files = so2.Files()
|
||||
files[1].Filename = "other.deb"
|
||||
so2.UpdateFiles(files)
|
||||
c.Check(so.Equals(so2), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestMatchesArchitecture(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
c.Check(p.MatchesArchitecture("i386"), Equals, true)
|
||||
c.Check(p.MatchesArchitecture("amd64"), Equals, false)
|
||||
|
||||
s.stanza = packageStanza.Copy()
|
||||
s.stanza["Architecture"] = "all"
|
||||
p = NewPackageFromControlFile(s.stanza)
|
||||
c.Check(p.MatchesArchitecture("i386"), Equals, true)
|
||||
c.Check(p.MatchesArchitecture("amd64"), Equals, true)
|
||||
c.Check(p.MatchesArchitecture("source"), Equals, false)
|
||||
|
||||
p, _ = NewSourcePackageFromControlFile(s.sourceStanza)
|
||||
c.Check(p.MatchesArchitecture("source"), Equals, true)
|
||||
c.Check(p.MatchesArchitecture("amd64"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestMatchesDependency(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
// exact match
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40-2"}), Equals, true)
|
||||
|
||||
// exact match, same version, no revision specified
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40"}), Equals, false)
|
||||
|
||||
// different name
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena", Architecture: "i386", Relation: VersionEqual, Version: "7.40-2"}), Equals, false)
|
||||
|
||||
// different version
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionEqual, Version: "7.40-3"}), Equals, false)
|
||||
|
||||
// different arch
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "amd64", Relation: VersionEqual, Version: "7.40-2"}), Equals, false)
|
||||
|
||||
// empty arch
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "", Relation: VersionEqual, Version: "7.40-2"}), Equals, true)
|
||||
|
||||
// version don't care
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionDontCare, Version: ""}), Equals, true)
|
||||
|
||||
// >
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreater, Version: "7.40-2"}), Equals, false)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreater, Version: "7.40-1"}), Equals, true)
|
||||
|
||||
// <
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLess, Version: "7.40-2"}), Equals, false)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLess, Version: "7.40-3"}), Equals, true)
|
||||
|
||||
// >=
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreaterOrEqual, Version: "7.40-2"}), Equals, true)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionGreaterOrEqual, Version: "7.40-3"}), Equals, false)
|
||||
|
||||
// <=
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLessOrEqual, Version: "7.40-2"}), Equals, true)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionLessOrEqual, Version: "7.40-1"}), Equals, false)
|
||||
|
||||
// %
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionPatternMatch, Version: "7.40-*"}), Equals, true)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionPatternMatch, Version: "7.40-[2]"}), Equals, true)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionPatternMatch, Version: "7.40-[2"}), Equals, false)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionPatternMatch, Version: "7.40-[34]"}), Equals, false)
|
||||
|
||||
// ~
|
||||
c.Check(
|
||||
p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionRegexp, Version: "7\\.40-.*",
|
||||
Regexp: regexp.MustCompile("7\\.40-.*")}), Equals, true)
|
||||
c.Check(
|
||||
p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionRegexp, Version: "7\\.40-.*",
|
||||
Regexp: regexp.MustCompile("40")}), Equals, true)
|
||||
c.Check(
|
||||
p.MatchesDependency(Dependency{Pkg: "alien-arena-common", Architecture: "i386", Relation: VersionRegexp, Version: "7\\.40-.*",
|
||||
Regexp: regexp.MustCompile("39-.*")}), Equals, false)
|
||||
|
||||
// Provides
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, false)
|
||||
p.Provides = []string{"fun", "game"}
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Relation: VersionDontCare}), Equals, true)
|
||||
c.Check(p.MatchesDependency(Dependency{Pkg: "game", Architecture: "amd64", Relation: VersionDontCare}), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestGetDependencies(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
c.Check(p.GetDependencies(0), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)"})
|
||||
c.Check(p.GetDependencies(DepFollowSuggests), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)", "alien-arena-mars"})
|
||||
c.Check(p.GetDependencies(DepFollowSuggests|DepFollowRecommends), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)", "aliean-arena-luna", "alien-arena-mars"})
|
||||
|
||||
c.Check(p.GetDependencies(DepFollowSource), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)", "alien-arena (= 7.40-2) {source}"})
|
||||
p.Source = "alien-arena (7.40-3)"
|
||||
c.Check(p.GetDependencies(DepFollowSource), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)", "alien-arena (7.40-3) {source}"})
|
||||
p.Source = ""
|
||||
c.Check(p.GetDependencies(DepFollowSource), DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)", "dpkg (>= 1.6)", "alien-arena-common (= 7.40-2) {source}"})
|
||||
|
||||
p, _ = NewSourcePackageFromControlFile(s.sourceStanza)
|
||||
c.Check(p.GetDependencies(0), DeepEquals, []string{})
|
||||
c.Check(p.GetDependencies(DepFollowBuild), DeepEquals, []string{"cdbs", "debhelper (>= 7)", "default-jdk", "maven-debian-helper", "default-jdk-doc", "junit (>= 3.8.1)", "libannotation-indexer-java (>= 1.3)", "libannotation-indexer-java-doc", "libasm3-java", "libmaven-install-plugin-java", "libmaven-javadoc-plugin-java", "libmaven-scm-java", "libmaven2-core-java", "libmaven2-core-java-doc", "libmetainf-services-java", "libmetainf-services-java-doc", "libmaven-plugin-tools-java (>= 2.8)"})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestPoolDirectory(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
dir, err := p.PoolDirectory()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(dir, Equals, "a/alien-arena")
|
||||
|
||||
p = NewPackageFromControlFile(packageStanza.Copy())
|
||||
p.Source = ""
|
||||
dir, err = p.PoolDirectory()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(dir, Equals, "a/alien-arena-common")
|
||||
|
||||
p = NewPackageFromControlFile(packageStanza.Copy())
|
||||
p.Source = "libarena"
|
||||
dir, err = p.PoolDirectory()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(dir, Equals, "liba/libarena")
|
||||
|
||||
p = NewPackageFromControlFile(packageStanza.Copy())
|
||||
p.Source = "gcc-defaults (1.77)"
|
||||
dir, err = p.PoolDirectory()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(dir, Equals, "g/gcc-defaults")
|
||||
|
||||
p = NewPackageFromControlFile(packageStanza.Copy())
|
||||
p.Source = "l"
|
||||
_, err = p.PoolDirectory()
|
||||
c.Check(err, ErrorMatches, ".* too short")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestLinkFromPool(c *C) {
|
||||
packagePool := files.NewPackagePool(c.MkDir())
|
||||
publishedStorage := files.NewPublishedStorage(c.MkDir())
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
poolPath, _ := packagePool.Path(p.Files()[0].Filename, p.Files()[0].Checksums.MD5)
|
||||
err := os.MkdirAll(filepath.Dir(poolPath), 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
file, err := os.Create(poolPath)
|
||||
c.Assert(err, IsNil)
|
||||
file.Close()
|
||||
|
||||
err = p.LinkFromPool(publishedStorage, packagePool, "", "non-free")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.Files()[0].Filename, Equals, "alien-arena-common_7.40-2_i386.deb")
|
||||
c.Check(p.Files()[0].downloadPath, Equals, "pool/non-free/a/alien-arena")
|
||||
|
||||
p.IsSource = true
|
||||
err = p.LinkFromPool(publishedStorage, packagePool, "", "non-free")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(p.Extra()["Directory"], Equals, "pool/non-free/a/alien-arena")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestFilepathList(c *C) {
|
||||
packagePool := files.NewPackagePool(c.MkDir())
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
list, err := p.FilepathList(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(list, DeepEquals, []string{"1e/8c/alien-arena-common_7.40-2_i386.deb"})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestDownloadList(c *C) {
|
||||
packagePool := files.NewPackagePool(c.MkDir())
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
p.Files()[0].Checksums.Size = 5
|
||||
poolPath, _ := packagePool.Path(p.Files()[0].Filename, p.Files()[0].Checksums.MD5)
|
||||
|
||||
list, err := p.DownloadList(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(list, DeepEquals, []PackageDownloadTask{
|
||||
PackageDownloadTask{
|
||||
RepoURI: "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
||||
DestinationPath: poolPath,
|
||||
Checksums: utils.ChecksumInfo{Size: 5,
|
||||
MD5: "1e8cba92c41420aa7baa8a5718d67122",
|
||||
SHA1: "46955e48cad27410a83740a21d766ce362364024",
|
||||
SHA256: "eb4afb9885cba6dc70cccd05b910b2dbccc02c5900578be5e99f0d3dbf9d76a5"}}})
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(poolPath), 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
file, err := os.Create(poolPath)
|
||||
c.Assert(err, IsNil)
|
||||
file.WriteString("abcde")
|
||||
file.Close()
|
||||
|
||||
list, err = p.DownloadList(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(list, DeepEquals, []PackageDownloadTask{})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestVerifyFiles(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
packagePool := files.NewPackagePool(c.MkDir())
|
||||
poolPath, _ := packagePool.Path(p.Files()[0].Filename, p.Files()[0].Checksums.MD5)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(poolPath), 0755)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
file, err := os.Create(poolPath)
|
||||
c.Assert(err, IsNil)
|
||||
file.WriteString("abcde")
|
||||
file.Close()
|
||||
|
||||
result, err := p.VerifyFiles(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(result, Equals, false)
|
||||
|
||||
p.Files()[0].Checksums.Size = 5
|
||||
|
||||
result, err = p.VerifyFiles(packagePool)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(result, Equals, true)
|
||||
}
|
||||
|
||||
var packageStanza = Stanza{"Source": "alien-arena", "Pre-Depends": "dpkg (>= 1.6)", "Suggests": "alien-arena-mars", "Recommends": "aliean-arena-luna", "Depends": "libc6 (>= 2.7), alien-arena-data (>= 7.40)", "Filename": "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb", "SHA1": " 46955e48cad27410a83740a21d766ce362364024", "SHA256": " eb4afb9885cba6dc70cccd05b910b2dbccc02c5900578be5e99f0d3dbf9d76a5", "Priority": "extra", "Maintainer": "Debian Games Team <pkg-games-devel@lists.alioth.debian.org>", "Description": "Common files for Alien Arena client and server ALIEN ARENA is a standalone 3D first person online deathmatch shooter\n crafted from the original source code of Quake II and Quake III, released\n by id Software under the GPL license. With features including 32 bit\n graphics, new particle engine and effects, light blooms, reflective water,\n hi resolution textures and skins, hi poly models, stain maps, ALIEN ARENA\n pushes the envelope of graphical beauty rivaling today's top games.\n .\n This package installs the common files for Alien Arena.\n", "Homepage": "http://red.planetarena.org", "Tag": "role::app-data, role::shared-lib, special::auto-inst-parts", "Installed-Size": "456", "Version": "7.40-2", "Replaces": "alien-arena (<< 7.33-1)", "Size": "187518", "MD5sum": "1e8cba92c41420aa7baa8a5718d67122", "Package": "alien-arena-common", "Section": "contrib/games", "Architecture": "i386"}
|
||||
|
||||
const sourcePackageMeta = `Package: access-modifier-checker
|
||||
Binary: libaccess-modifier-checker-java, libaccess-modifier-checker-java-doc
|
||||
Version: 1.0-4
|
||||
Maintainer: Debian Java Maintainers <pkg-java-maintainers@lists.alioth.debian.org>
|
||||
Uploaders: James Page <james.page@ubuntu.com>
|
||||
Build-Depends: cdbs, debhelper (>= 7), default-jdk, maven-debian-helper
|
||||
Build-Depends-Indep: default-jdk-doc, junit (>= 3.8.1), libannotation-indexer-java (>= 1.3), libannotation-indexer-java-doc, libasm3-java, libmaven-install-plugin-java, libmaven-javadoc-plugin-java, libmaven-scm-java, libmaven2-core-java, libmaven2-core-java-doc, libmetainf-services-java, libmetainf-services-java-doc, libmaven-plugin-tools-java (>= 2.8)
|
||||
Architecture: all
|
||||
Standards-Version: 3.9.3
|
||||
Format: 3.0 (quilt)
|
||||
Files:
|
||||
ab56b4d92b40713acc5af89985d4b786 5 access-modifier-checker_1.0-4.debian.tar.gz
|
||||
900150983cd24fb0d6963f7d28e17f72 3 access-modifier-checker_1.0-4.dsc
|
||||
e2fc714c4727ee9395f324cd2e7f331f 4 access-modifier-checker_1.0.orig.tar.gz
|
||||
Dm-Upload-Allowed: yes
|
||||
Vcs-Browser: http://git.debian.org/?p=pkg-java/access-modifier-checker.git
|
||||
Vcs-Git: git://git.debian.org/git/pkg-java/access-modifier-checker.git
|
||||
Checksums-Sha1:
|
||||
03de6c570bfe24bfc328ccd7ca46b76eadaf4334 5 access-modifier-checker_1.0-4.debian.tar.gz
|
||||
a9993e364706816aba3e25717850c26c9cd0d89d 3 access-modifier-checker_1.0-4.dsc
|
||||
81fe8bfe87576c3ecb22426f8e57847382917acf 4 access-modifier-checker_1.0.orig.tar.gz
|
||||
Checksums-Sha256:
|
||||
36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c 5 access-modifier-checker_1.0-4.debian.tar.gz
|
||||
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3 access-modifier-checker_1.0-4.dsc
|
||||
88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589 4 access-modifier-checker_1.0.orig.tar.gz
|
||||
Homepage: https://github.com/kohsuke/access-modifier
|
||||
Package-List:
|
||||
libaccess-modifier-checker-java deb java optional
|
||||
libaccess-modifier-checker-java-doc deb doc optional
|
||||
Directory: pool/main/a/access-modifier-checker
|
||||
Priority: source
|
||||
Section: java
|
||||
`
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smira/aptly/utils"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ppaRegexp = regexp.MustCompile("^ppa:([^/]+)/(.+)$")
|
||||
|
||||
// ParsePPA converts ppa URL like ppa:user/ppa-name to full HTTP url
|
||||
func ParsePPA(ppaURL string, config *utils.ConfigStructure) (url string, distribution string, components []string, err error) {
|
||||
matches := ppaRegexp.FindStringSubmatch(ppaURL)
|
||||
if matches == nil {
|
||||
err = fmt.Errorf("unable to parse ppa URL: %v", ppaURL)
|
||||
return
|
||||
}
|
||||
|
||||
distributorID := config.PpaDistributorID
|
||||
if distributorID == "" {
|
||||
distributorID, err = getDistributorID()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to figure out Distributor ID: %s, please set config option ppaDistributorID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
codename := config.PpaCodename
|
||||
if codename == "" {
|
||||
codename, err = getCodename()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to figure out Codename: %s, please set config option ppaCodename", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
distribution = codename
|
||||
components = []string{"main"}
|
||||
url = fmt.Sprintf("http://ppa.launchpad.net/%s/%s/%s", matches[1], matches[2], distributorID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getCodename() (string, error) {
|
||||
out, err := exec.Command("lsb_release", "-sc").Output()
|
||||
return strings.TrimSpace(string(out)), err
|
||||
}
|
||||
|
||||
func getDistributorID() (string, error) {
|
||||
out, err := exec.Command("lsb_release", "-si").Output()
|
||||
return strings.ToLower(strings.TrimSpace(string(out))), err
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/utils"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type PpaSuite struct {
|
||||
config utils.ConfigStructure
|
||||
}
|
||||
|
||||
var _ = Suite(&PpaSuite{})
|
||||
|
||||
func (s *PpaSuite) TestParsePPA(c *C) {
|
||||
_, _, _, err := ParsePPA("ppa:dedeed", &s.config)
|
||||
c.Check(err, ErrorMatches, "unable to parse ppa URL.*")
|
||||
|
||||
s.config.PpaDistributorID = "debian"
|
||||
s.config.PpaCodename = "wheezy"
|
||||
|
||||
url, distribution, components, err := ParsePPA("ppa:user/project", &s.config)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(url, Equals, "http://ppa.launchpad.net/user/project/debian")
|
||||
c.Check(distribution, Equals, "wheezy")
|
||||
c.Check(components, DeepEquals, []string{"main"})
|
||||
}
|
||||
+1089
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,805 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/ugorji/go/codec"
|
||||
"io/ioutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type pathExistsChecker struct {
|
||||
*CheckerInfo
|
||||
}
|
||||
|
||||
var PathExists = &pathExistsChecker{
|
||||
&CheckerInfo{Name: "PathExists", Params: []string{"path"}},
|
||||
}
|
||||
|
||||
func (checker *pathExistsChecker) Check(params []interface{}, names []string) (result bool, error string) {
|
||||
_, err := os.Stat(params[0].(string))
|
||||
return err == nil, ""
|
||||
}
|
||||
|
||||
type NullSigner struct{}
|
||||
|
||||
func (n *NullSigner) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NullSigner) SetKey(keyRef string) {
|
||||
}
|
||||
|
||||
func (n *NullSigner) SetKeyRing(keyring, secretKeyring string) {
|
||||
}
|
||||
|
||||
func (n *NullSigner) DetachedSign(source string, destination string) error {
|
||||
return ioutil.WriteFile(destination, []byte{}, 0644)
|
||||
}
|
||||
|
||||
func (n *NullSigner) ClearSign(source string, destination string) error {
|
||||
return ioutil.WriteFile(destination, []byte{}, 0644)
|
||||
}
|
||||
|
||||
type FakeStorageProvider struct {
|
||||
storages map[string]aptly.PublishedStorage
|
||||
}
|
||||
|
||||
func (p *FakeStorageProvider) GetPublishedStorage(name string) aptly.PublishedStorage {
|
||||
storage, ok := p.storages[name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown storage: %#v", name))
|
||||
}
|
||||
return storage
|
||||
}
|
||||
|
||||
type PublishedRepoSuite struct {
|
||||
PackageListMixinSuite
|
||||
repo, repo2, repo3, repo4, repo5 *PublishedRepo
|
||||
root, root2 string
|
||||
provider *FakeStorageProvider
|
||||
publishedStorage, publishedStorage2 *files.PublishedStorage
|
||||
packagePool aptly.PackagePool
|
||||
localRepo *LocalRepo
|
||||
snapshot, snapshot2 *Snapshot
|
||||
db database.Storage
|
||||
factory *CollectionFactory
|
||||
packageCollection *PackageCollection
|
||||
}
|
||||
|
||||
var _ = Suite(&PublishedRepoSuite{})
|
||||
|
||||
func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
||||
s.SetUpPackages()
|
||||
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.factory = NewCollectionFactory(s.db)
|
||||
|
||||
s.root = c.MkDir()
|
||||
s.publishedStorage = files.NewPublishedStorage(s.root)
|
||||
s.root2 = c.MkDir()
|
||||
s.publishedStorage2 = files.NewPublishedStorage(s.root2)
|
||||
s.provider = &FakeStorageProvider{map[string]aptly.PublishedStorage{
|
||||
"": s.publishedStorage,
|
||||
"files:other": s.publishedStorage2}}
|
||||
s.packagePool = files.NewPackagePool(s.root)
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
repo.packageRefs = s.reflist
|
||||
s.factory.RemoteRepoCollection().Add(repo)
|
||||
|
||||
s.localRepo = NewLocalRepo("local1", "comment1")
|
||||
s.localRepo.packageRefs = s.reflist
|
||||
s.factory.LocalRepoCollection().Add(s.localRepo)
|
||||
|
||||
s.snapshot, _ = NewSnapshotFromRepository("snap", repo)
|
||||
s.factory.SnapshotCollection().Add(s.snapshot)
|
||||
|
||||
s.snapshot2, _ = NewSnapshotFromRepository("snap", repo)
|
||||
s.factory.SnapshotCollection().Add(s.snapshot2)
|
||||
|
||||
s.packageCollection = s.factory.PackageCollection()
|
||||
s.packageCollection.Update(s.p1)
|
||||
s.packageCollection.Update(s.p2)
|
||||
s.packageCollection.Update(s.p3)
|
||||
|
||||
s.repo, _ = NewPublishedRepo("", "ppa", "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
||||
|
||||
s.repo2, _ = NewPublishedRepo("", "ppa", "maverick", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
||||
|
||||
s.repo3, _ = NewPublishedRepo("", "linux", "natty", nil, []string{"main", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
|
||||
s.repo4, _ = NewPublishedRepo("", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
||||
|
||||
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "maverick", []string{"source"}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
||||
|
||||
poolPath, _ := s.packagePool.Path(s.p1.Files()[0].Filename, s.p1.Files()[0].Checksums.MD5)
|
||||
err := os.MkdirAll(filepath.Dir(poolPath), 0755)
|
||||
f, err := os.Create(poolPath)
|
||||
c.Assert(err, IsNil)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) {
|
||||
c.Check(s.repo.sourceItems["main"].snapshot, Equals, s.snapshot)
|
||||
c.Check(s.repo.SourceKind, Equals, "snapshot")
|
||||
c.Check(s.repo.Sources["main"], Equals, s.snapshot.UUID)
|
||||
c.Check(s.repo.Components(), DeepEquals, []string{"main"})
|
||||
|
||||
c.Check(s.repo2.sourceItems["main"].localRepo, Equals, s.localRepo)
|
||||
c.Check(s.repo2.SourceKind, Equals, "local")
|
||||
c.Check(s.repo2.Sources["main"], Equals, s.localRepo.UUID)
|
||||
c.Check(s.repo2.sourceItems["main"].packageRefs.Len(), Equals, 3)
|
||||
c.Check(s.repo2.Components(), DeepEquals, []string{"main"})
|
||||
|
||||
c.Check(s.repo.RefList("main").Len(), Equals, 3)
|
||||
c.Check(s.repo2.RefList("main").Len(), Equals, 3)
|
||||
|
||||
c.Check(s.repo3.Sources, DeepEquals, map[string]string{"main": s.snapshot.UUID, "contrib": s.snapshot2.UUID})
|
||||
c.Check(s.repo3.SourceKind, Equals, "snapshot")
|
||||
c.Check(s.repo3.sourceItems["main"].snapshot, Equals, s.snapshot)
|
||||
c.Check(s.repo3.sourceItems["contrib"].snapshot, Equals, s.snapshot2)
|
||||
c.Check(s.repo3.Components(), DeepEquals, []string{"contrib", "main"})
|
||||
|
||||
c.Check(s.repo3.RefList("main").Len(), Equals, 3)
|
||||
c.Check(s.repo3.RefList("contrib").Len(), Equals, 3)
|
||||
|
||||
c.Check(func() { NewPublishedRepo("", ".", "a", nil, nil, nil, s.factory) }, PanicMatches, "publish with empty sources")
|
||||
c.Check(func() {
|
||||
NewPublishedRepo("", ".", "a", nil, []string{"main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
}, PanicMatches, "sources and components should be equal in size")
|
||||
c.Check(func() {
|
||||
NewPublishedRepo("", ".", "a", nil, []string{"main", "contrib"}, []interface{}{s.localRepo, s.snapshot2}, s.factory)
|
||||
}, PanicMatches, "interface conversion:.*")
|
||||
|
||||
_, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
c.Check(err, ErrorMatches, "duplicate component name: main")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) {
|
||||
|
||||
for _, t := range []struct {
|
||||
prefix string
|
||||
expected string
|
||||
errorExpected string
|
||||
}{
|
||||
{
|
||||
prefix: "ppa",
|
||||
expected: "ppa",
|
||||
},
|
||||
{
|
||||
prefix: "",
|
||||
expected: ".",
|
||||
},
|
||||
{
|
||||
prefix: "/",
|
||||
expected: ".",
|
||||
},
|
||||
{
|
||||
prefix: "//",
|
||||
expected: ".",
|
||||
},
|
||||
{
|
||||
prefix: "//ppa/",
|
||||
expected: "ppa",
|
||||
},
|
||||
{
|
||||
prefix: "ppa/..",
|
||||
expected: ".",
|
||||
},
|
||||
{
|
||||
prefix: "ppa/ubuntu/",
|
||||
expected: "ppa/ubuntu",
|
||||
},
|
||||
{
|
||||
prefix: "ppa/../ubuntu/",
|
||||
expected: "ubuntu",
|
||||
},
|
||||
{
|
||||
prefix: "../ppa/",
|
||||
errorExpected: "invalid prefix .*",
|
||||
},
|
||||
{
|
||||
prefix: "../ppa/../ppa/",
|
||||
errorExpected: "invalid prefix .*",
|
||||
},
|
||||
{
|
||||
prefix: "ppa/dists",
|
||||
errorExpected: "invalid prefix .*",
|
||||
},
|
||||
{
|
||||
prefix: "ppa/pool",
|
||||
errorExpected: "invalid prefix .*",
|
||||
},
|
||||
} {
|
||||
repo, err := NewPublishedRepo("", t.prefix, "squeeze", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
||||
if t.errorExpected != "" {
|
||||
c.Check(err, ErrorMatches, t.errorExpected)
|
||||
} else {
|
||||
c.Check(repo.Prefix, Equals, t.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) {
|
||||
repo, err := NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.snapshot}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "squeeze")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "wheezy", nil, []string{""}, []interface{}{s.snapshot}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "wheezy")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"non-free"}, []interface{}{s.snapshot}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "squeeze")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"non-free"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "squeeze", nil, []string{""}, []interface{}{s.localRepo}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "squeeze")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"main"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
||||
c.Check(err, ErrorMatches, "unable to guess distribution name, please specify explicitly")
|
||||
|
||||
s.localRepo.DefaultDistribution = "precise"
|
||||
s.localRepo.DefaultComponent = "contrib"
|
||||
s.factory.LocalRepoCollection().Update(s.localRepo)
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "precise")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"contrib"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Distribution, Equals, "squeeze")
|
||||
c.Check(repo.Components(), DeepEquals, []string{"contrib", "main"})
|
||||
|
||||
repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", ""}, []interface{}{s.snapshot, s.snapshot2}, s.factory)
|
||||
c.Check(err, ErrorMatches, "duplicate component name: main")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublish(c *C) {
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.repo.Architectures, DeepEquals, []string{"i386"})
|
||||
|
||||
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cfr := NewControlFileReader(rf)
|
||||
st, err := cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(st["Origin"], Equals, "ppa squeeze")
|
||||
c.Check(st["Components"], Equals, "main")
|
||||
c.Check(st["Architectures"], Equals, "i386")
|
||||
|
||||
pf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Packages"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cfr = NewControlFileReader(pf)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
st, err = cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(st["Filename"], Equals, "pool/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb")
|
||||
}
|
||||
|
||||
st, err = cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(st, IsNil)
|
||||
|
||||
drf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cfr = NewControlFileReader(drf)
|
||||
st, err = cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(st["Archive"], Equals, "squeeze")
|
||||
c.Check(st["Architecture"], Equals, "i386")
|
||||
|
||||
_, err = os.Stat(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb"))
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) {
|
||||
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/main/binary-i386/Release"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishLocalSourceRepo(c *C) {
|
||||
err := s.repo4.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/main/source/Release"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishOtherStorage(c *C) {
|
||||
err := s.repo5.Publish(s.packagePool, s.provider, s.factory, nil, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/maverick/Release"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"), Not(PathExists))
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestString(c *C) {
|
||||
c.Check(s.repo.String(), Equals,
|
||||
"ppa/squeeze [] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||
c.Check(s.repo2.String(), Equals,
|
||||
"ppa/maverick [] publishes {main: [local1]: comment1}")
|
||||
repo, _ := NewPublishedRepo("", "", "squeeze", []string{"s390"}, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
||||
c.Check(repo.String(), Equals,
|
||||
"./squeeze [s390] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||
repo, _ = NewPublishedRepo("", "", "squeeze", []string{"i386", "amd64"}, []string{"main"}, []interface{}{s.snapshot}, s.factory)
|
||||
c.Check(repo.String(), Equals,
|
||||
"./squeeze [i386, amd64] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||
repo.Origin = "myorigin"
|
||||
c.Check(repo.String(), Equals,
|
||||
"./squeeze (origin: myorigin) [i386, amd64] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||
repo.Label = "mylabel"
|
||||
c.Check(repo.String(), Equals,
|
||||
"./squeeze (origin: myorigin, label: mylabel) [i386, amd64] publishes {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||
c.Check(s.repo3.String(), Equals,
|
||||
"linux/natty [] publishes {contrib: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}, {main: [snap]: Snapshot from mirror [yandex]: http://mirror.yandex.ru/debian/ squeeze}")
|
||||
c.Check(s.repo5.String(), Equals,
|
||||
"files:other:ppa/maverick [source] publishes {main: [local1]: comment1}")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestKey(c *C) {
|
||||
c.Check(s.repo.Key(), DeepEquals, []byte("Uppa>>squeeze"))
|
||||
c.Check(s.repo5.Key(), DeepEquals, []byte("Ufiles:other:ppa>>maverick"))
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestRefKey(c *C) {
|
||||
c.Check(s.repo.RefKey(""), DeepEquals, []byte("E"+s.repo.UUID))
|
||||
c.Check(s.repo.RefKey("main"), DeepEquals, []byte("E"+s.repo.UUID+"main"))
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestEncodeDecode(c *C) {
|
||||
encoded := s.repo.Encode()
|
||||
repo := &PublishedRepo{}
|
||||
err := repo.Decode(encoded)
|
||||
|
||||
s.repo.sourceItems = nil
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(repo, DeepEquals, s.repo)
|
||||
|
||||
encoded2 := s.repo2.Encode()
|
||||
repo2 := &PublishedRepo{}
|
||||
err = repo2.Decode(encoded2)
|
||||
|
||||
s.repo2.sourceItems = nil
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(repo2, DeepEquals, s.repo2)
|
||||
}
|
||||
|
||||
type PublishedRepoCollectionSuite struct {
|
||||
PackageListMixinSuite
|
||||
db database.Storage
|
||||
factory *CollectionFactory
|
||||
snapshotCollection *SnapshotCollection
|
||||
collection *PublishedRepoCollection
|
||||
snap1, snap2 *Snapshot
|
||||
localRepo *LocalRepo
|
||||
repo1, repo2, repo3, repo4, repo5 *PublishedRepo
|
||||
}
|
||||
|
||||
var _ = Suite(&PublishedRepoCollectionSuite{})
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) SetUpTest(c *C) {
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.factory = NewCollectionFactory(s.db)
|
||||
|
||||
s.snapshotCollection = s.factory.SnapshotCollection()
|
||||
|
||||
s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1")
|
||||
s.snap2 = NewSnapshotFromPackageList("snap2", []*Snapshot{}, NewPackageList(), "desc2")
|
||||
|
||||
s.snapshotCollection.Add(s.snap1)
|
||||
s.snapshotCollection.Add(s.snap2)
|
||||
|
||||
s.localRepo = NewLocalRepo("local1", "comment1")
|
||||
s.factory.LocalRepoCollection().Add(s.localRepo)
|
||||
|
||||
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
||||
s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main", "contrib"}, []interface{}{s.snap2, s.snap1}, s.factory)
|
||||
s.repo3, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap2}, s.factory)
|
||||
s.repo4, _ = NewPublishedRepo("", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
||||
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "precise", []string{}, []string{"main"}, []interface{}{s.localRepo}, s.factory)
|
||||
|
||||
s.collection = s.factory.PublishedRepoCollection()
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestAddByStoragePrefixDistribution(c *C) {
|
||||
r, err := s.collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
c.Assert(s.collection.Add(s.repo1), IsNil)
|
||||
c.Assert(s.collection.Add(s.repo1), ErrorMatches, ".*already exists")
|
||||
c.Assert(s.collection.CheckDuplicate(s.repo2), IsNil)
|
||||
c.Assert(s.collection.Add(s.repo2), IsNil)
|
||||
c.Assert(s.collection.Add(s.repo3), ErrorMatches, ".*already exists")
|
||||
c.Assert(s.collection.CheckDuplicate(s.repo3), Equals, s.repo1)
|
||||
c.Assert(s.collection.Add(s.repo4), IsNil)
|
||||
c.Assert(s.collection.Add(s.repo5), IsNil)
|
||||
|
||||
r, err = s.collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.collection.LoadComplete(r, s.factory)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.String(), Equals, s.repo1.String())
|
||||
|
||||
collection := NewPublishedRepoCollection(s.db)
|
||||
r, err = collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.collection.LoadComplete(r, s.factory)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.String(), Equals, s.repo1.String())
|
||||
|
||||
r, err = s.collection.ByStoragePrefixDistribution("files:other", "ppa", "precise")
|
||||
c.Check(r.String(), Equals, s.repo5.String())
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestByUUID(c *C) {
|
||||
r, err := s.collection.ByUUID(s.repo1.UUID)
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
c.Assert(s.collection.Add(s.repo1), IsNil)
|
||||
|
||||
r, err = s.collection.ByUUID(s.repo1.UUID)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.collection.LoadComplete(r, s.factory)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.String(), Equals, s.repo1.String())
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
c.Assert(s.collection.Update(s.repo1), IsNil)
|
||||
c.Assert(s.collection.Update(s.repo4), IsNil)
|
||||
|
||||
collection := NewPublishedRepoCollection(s.db)
|
||||
r, err := collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.sourceItems["main"].snapshot, IsNil)
|
||||
c.Assert(s.collection.LoadComplete(r, s.factory), IsNil)
|
||||
c.Assert(r.Sources["main"], Equals, s.repo1.sourceItems["main"].snapshot.UUID)
|
||||
c.Assert(r.RefList("main").Len(), Equals, 0)
|
||||
|
||||
r, err = collection.ByStoragePrefixDistribution("", "ppa", "precise")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(r.sourceItems["main"].localRepo, IsNil)
|
||||
c.Assert(s.collection.LoadComplete(r, s.factory), IsNil)
|
||||
c.Assert(r.sourceItems["main"].localRepo.UUID, Equals, s.repo4.sourceItems["main"].localRepo.UUID)
|
||||
c.Assert(r.sourceItems["main"].packageRefs.Len(), Equals, 0)
|
||||
c.Assert(r.RefList("main").Len(), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestLoadPre0_6(c *C) {
|
||||
type oldPublishedRepo struct {
|
||||
UUID string
|
||||
Prefix string
|
||||
Distribution string
|
||||
Origin string
|
||||
Label string
|
||||
Architectures []string
|
||||
SourceKind string
|
||||
Component string
|
||||
SourceUUID string `codec:"SnapshotUUID"`
|
||||
}
|
||||
|
||||
old := oldPublishedRepo{
|
||||
UUID: s.repo1.UUID,
|
||||
Prefix: "ppa",
|
||||
Distribution: "anaconda",
|
||||
Architectures: []string{"i386"},
|
||||
SourceKind: "local",
|
||||
Component: "contrib",
|
||||
SourceUUID: s.localRepo.UUID,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(&old)
|
||||
|
||||
c.Assert(s.db.Put(s.repo1.Key(), buf.Bytes()), IsNil)
|
||||
c.Assert(s.db.Put(s.repo1.RefKey(""), s.localRepo.RefList().Encode()), IsNil)
|
||||
|
||||
collection := NewPublishedRepoCollection(s.db)
|
||||
repo, err := collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(repo.Component, Equals, "")
|
||||
c.Check(repo.SourceUUID, Equals, "")
|
||||
c.Check(repo.Sources, DeepEquals, map[string]string{"contrib": s.localRepo.UUID})
|
||||
|
||||
c.Check(collection.LoadComplete(repo, s.factory), IsNil)
|
||||
c.Check(repo.sourceItems["contrib"].localRepo.UUID, Equals, s.localRepo.UUID)
|
||||
c.Check(repo.RefList("contrib").Len(), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||
s.collection.Add(s.repo1)
|
||||
|
||||
count := 0
|
||||
err := s.collection.ForEach(func(*PublishedRepo) error {
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
c.Assert(count, Equals, 1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.collection.Len(), Equals, 1)
|
||||
|
||||
e := errors.New("c")
|
||||
|
||||
err = s.collection.ForEach(func(*PublishedRepo) error {
|
||||
return e
|
||||
})
|
||||
c.Assert(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestBySnapshot(c *C) {
|
||||
c.Check(s.collection.Add(s.repo1), IsNil)
|
||||
c.Check(s.collection.Add(s.repo2), IsNil)
|
||||
|
||||
c.Check(s.collection.BySnapshot(s.snap1), DeepEquals, []*PublishedRepo{s.repo1, s.repo2})
|
||||
c.Check(s.collection.BySnapshot(s.snap2), DeepEquals, []*PublishedRepo{s.repo2})
|
||||
}
|
||||
|
||||
func (s *PublishedRepoCollectionSuite) TestByLocalRepo(c *C) {
|
||||
c.Check(s.collection.Add(s.repo1), IsNil)
|
||||
c.Check(s.collection.Add(s.repo4), IsNil)
|
||||
c.Check(s.collection.Add(s.repo5), IsNil)
|
||||
|
||||
c.Check(s.collection.ByLocalRepo(s.localRepo), DeepEquals, []*PublishedRepo{s.repo4, s.repo5})
|
||||
}
|
||||
|
||||
type PublishedRepoRemoveSuite struct {
|
||||
PackageListMixinSuite
|
||||
db database.Storage
|
||||
factory *CollectionFactory
|
||||
snapshotCollection *SnapshotCollection
|
||||
collection *PublishedRepoCollection
|
||||
root, root2 string
|
||||
provider *FakeStorageProvider
|
||||
publishedStorage, publishedStorage2 *files.PublishedStorage
|
||||
snap1 *Snapshot
|
||||
repo1, repo2, repo3, repo4, repo5 *PublishedRepo
|
||||
}
|
||||
|
||||
var _ = Suite(&PublishedRepoRemoveSuite{})
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) SetUpTest(c *C) {
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.factory = NewCollectionFactory(s.db)
|
||||
|
||||
s.snapshotCollection = s.factory.SnapshotCollection()
|
||||
|
||||
s.snap1 = NewSnapshotFromPackageList("snap1", []*Snapshot{}, NewPackageList(), "desc1")
|
||||
|
||||
s.snapshotCollection.Add(s.snap1)
|
||||
|
||||
s.repo1, _ = NewPublishedRepo("", "ppa", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
||||
s.repo2, _ = NewPublishedRepo("", "", "anaconda", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
||||
s.repo3, _ = NewPublishedRepo("", "ppa", "meduza", []string{}, []string{"main"}, []interface{}{s.snap1}, s.factory)
|
||||
s.repo4, _ = NewPublishedRepo("", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory)
|
||||
s.repo5, _ = NewPublishedRepo("files:other", "ppa", "osminog", []string{}, []string{"contrib"}, []interface{}{s.snap1}, s.factory)
|
||||
|
||||
s.collection = s.factory.PublishedRepoCollection()
|
||||
s.collection.Add(s.repo1)
|
||||
s.collection.Add(s.repo2)
|
||||
s.collection.Add(s.repo3)
|
||||
s.collection.Add(s.repo4)
|
||||
s.collection.Add(s.repo5)
|
||||
|
||||
s.root = c.MkDir()
|
||||
s.publishedStorage = files.NewPublishedStorage(s.root)
|
||||
s.publishedStorage.MkDir("ppa/dists/anaconda")
|
||||
s.publishedStorage.MkDir("ppa/dists/meduza")
|
||||
s.publishedStorage.MkDir("ppa/dists/osminog")
|
||||
s.publishedStorage.MkDir("ppa/pool/main")
|
||||
s.publishedStorage.MkDir("ppa/pool/contrib")
|
||||
s.publishedStorage.MkDir("dists/anaconda")
|
||||
s.publishedStorage.MkDir("pool/main")
|
||||
|
||||
s.root2 = c.MkDir()
|
||||
s.publishedStorage2 = files.NewPublishedStorage(s.root2)
|
||||
s.publishedStorage2.MkDir("ppa/dists/osminog")
|
||||
s.publishedStorage2.MkDir("ppa/pool/contrib")
|
||||
|
||||
s.provider = &FakeStorageProvider{map[string]aptly.PublishedStorage{
|
||||
"": s.publishedStorage,
|
||||
"files:other": s.publishedStorage2}}
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesOnlyDist(c *C) {
|
||||
s.repo1.RemoveFiles(s.provider, false, []string{}, nil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPool(c *C) {
|
||||
s.repo1.RemoveFiles(s.provider, false, []string{"main"}, nil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithTwoPools(c *C) {
|
||||
s.repo1.RemoveFiles(s.provider, false, []string{"main", "contrib"}, nil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefix(c *C) {
|
||||
s.repo1.RemoveFiles(s.provider, true, []string{"main"}, nil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveFilesWithPrefixRoot(c *C) {
|
||||
s.repo2.RemoveFiles(s.provider, true, []string{"main"}, nil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveRepo1and2(c *C) {
|
||||
err := s.collection.Remove(s.provider, "", "ppa", "anaconda", s.factory, nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
collection := NewPublishedRepoCollection(s.db)
|
||||
_, err = collection.ByStoragePrefixDistribution("", "ppa", "anaconda")
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
|
||||
err = s.collection.Remove(s.provider, "", "ppa", "anaconda", s.factory, nil)
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
err = s.collection.Remove(s.provider, "", "ppa", "meduza", s.factory, nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveRepo3(c *C) {
|
||||
err := s.collection.Remove(s.provider, "", ".", "anaconda", s.factory, nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByStoragePrefixDistribution("", ".", "anaconda")
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
collection := NewPublishedRepoCollection(s.db)
|
||||
_, err = collection.ByStoragePrefixDistribution("", ".", "anaconda")
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoRemoveSuite) TestRemoveRepo5(c *C) {
|
||||
err := s.collection.Remove(s.provider, "files:other", "ppa", "osminog", s.factory, nil)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByStoragePrefixDistribution("files:other", "ppa", "osminog")
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
collection := NewPublishedRepoCollection(s.db)
|
||||
_, err = collection.ByStoragePrefixDistribution("files:other", "ppa", "osminog")
|
||||
c.Check(err, ErrorMatches, ".*not found")
|
||||
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/anaconda"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/meduza"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/osminog"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/main"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/pool/contrib"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "dists/"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "pool/"), PathExists)
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/dists/osminog"), Not(PathExists))
|
||||
c.Check(filepath.Join(s.publishedStorage2.PublicPath(), "ppa/pool/contrib"), Not(PathExists))
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PackageQuery is interface of predicate on Package
|
||||
type PackageQuery interface {
|
||||
// Matches calculates match of condition against package
|
||||
Matches(pkg *Package) bool
|
||||
// Fast returns if search strategy is possible for this query
|
||||
Fast() bool
|
||||
// Query performs search on package list
|
||||
Query(list *PackageList) *PackageList
|
||||
// String interface
|
||||
String() string
|
||||
}
|
||||
|
||||
// OrQuery is L | R
|
||||
type OrQuery struct {
|
||||
L, R PackageQuery
|
||||
}
|
||||
|
||||
// AndQuery is L , R
|
||||
type AndQuery struct {
|
||||
L, R PackageQuery
|
||||
}
|
||||
|
||||
// NotQuery is ! Q
|
||||
type NotQuery struct {
|
||||
Q PackageQuery
|
||||
}
|
||||
|
||||
// FieldQuery is generic request against field
|
||||
type FieldQuery struct {
|
||||
Field string
|
||||
Relation int
|
||||
Value string
|
||||
Regexp *regexp.Regexp `codec:"-"`
|
||||
}
|
||||
|
||||
// PkgQuery is search request against specific package
|
||||
type PkgQuery struct {
|
||||
Pkg string
|
||||
Version string
|
||||
Arch string
|
||||
}
|
||||
|
||||
// DependencyQuery is generic Debian-dependency like query
|
||||
type DependencyQuery struct {
|
||||
Dep Dependency
|
||||
}
|
||||
|
||||
// Matches if any of L, R matches
|
||||
func (q *OrQuery) Matches(pkg *Package) bool {
|
||||
return q.L.Matches(pkg) || q.R.Matches(pkg)
|
||||
}
|
||||
|
||||
// Fast is true only if both parts are fast
|
||||
func (q *OrQuery) Fast() bool {
|
||||
return q.L.Fast() && q.R.Fast()
|
||||
}
|
||||
|
||||
// Query strategy depends on nodes
|
||||
func (q *OrQuery) Query(list *PackageList) (result *PackageList) {
|
||||
if q.Fast() {
|
||||
result = q.L.Query(list)
|
||||
result.Append(q.R.Query(list))
|
||||
} else {
|
||||
result = list.Scan(q)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *OrQuery) String() string {
|
||||
return fmt.Sprintf("(%s) | (%s)", q.L, q.R)
|
||||
}
|
||||
|
||||
// Matches if both of L, R matches
|
||||
func (q *AndQuery) Matches(pkg *Package) bool {
|
||||
return q.L.Matches(pkg) && q.R.Matches(pkg)
|
||||
}
|
||||
|
||||
// Fast is true if any of the parts are fast
|
||||
func (q *AndQuery) Fast() bool {
|
||||
return q.L.Fast() || q.R.Fast()
|
||||
}
|
||||
|
||||
// Query strategy depends on nodes
|
||||
func (q *AndQuery) Query(list *PackageList) (result *PackageList) {
|
||||
if !q.Fast() {
|
||||
result = list.Scan(q)
|
||||
} else {
|
||||
if q.L.Fast() {
|
||||
result = q.L.Query(list)
|
||||
result = result.Scan(q.R)
|
||||
} else {
|
||||
result = q.R.Query(list)
|
||||
result = result.Scan(q.L)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *AndQuery) String() string {
|
||||
return fmt.Sprintf("(%s), (%s)", q.L, q.R)
|
||||
}
|
||||
|
||||
// Matches if not matches
|
||||
func (q *NotQuery) Matches(pkg *Package) bool {
|
||||
return !q.Q.Matches(pkg)
|
||||
}
|
||||
|
||||
// Fast is false
|
||||
func (q *NotQuery) Fast() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Query strategy is scan always
|
||||
func (q *NotQuery) Query(list *PackageList) (result *PackageList) {
|
||||
result = list.Scan(q)
|
||||
return
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *NotQuery) String() string {
|
||||
return fmt.Sprintf("!(%s)", q.Q)
|
||||
}
|
||||
|
||||
// Matches on generic field
|
||||
func (q *FieldQuery) Matches(pkg *Package) bool {
|
||||
if q.Field == "$Version" {
|
||||
return pkg.MatchesDependency(Dependency{Pkg: pkg.Name, Relation: q.Relation, Version: q.Value, Regexp: q.Regexp})
|
||||
}
|
||||
if q.Field == "$Architecture" && q.Relation == VersionEqual {
|
||||
return pkg.MatchesArchitecture(q.Value)
|
||||
}
|
||||
|
||||
field := pkg.GetField(q.Field)
|
||||
|
||||
switch q.Relation {
|
||||
case VersionDontCare:
|
||||
return field != ""
|
||||
case VersionEqual:
|
||||
return field == q.Value
|
||||
case VersionGreater:
|
||||
return field > q.Value
|
||||
case VersionGreaterOrEqual:
|
||||
return field >= q.Value
|
||||
case VersionLess:
|
||||
return field < q.Value
|
||||
case VersionLessOrEqual:
|
||||
return field <= q.Value
|
||||
case VersionPatternMatch:
|
||||
matched, err := filepath.Match(q.Value, field)
|
||||
return err == nil && matched
|
||||
case VersionRegexp:
|
||||
if q.Regexp == nil {
|
||||
q.Regexp = regexp.MustCompile(q.Value)
|
||||
}
|
||||
return q.Regexp.FindStringIndex(field) != nil
|
||||
|
||||
}
|
||||
panic("unknown relation")
|
||||
}
|
||||
|
||||
// Query runs iteration through list
|
||||
func (q *FieldQuery) Query(list *PackageList) (result *PackageList) {
|
||||
result = list.Scan(q)
|
||||
return
|
||||
}
|
||||
|
||||
// Fast depends on the query
|
||||
func (q *FieldQuery) Fast() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *FieldQuery) String() string {
|
||||
escape := func(val string) string {
|
||||
if strings.IndexAny(val, "()|,!{} \t\n") != -1 {
|
||||
return "'" + strings.Replace(strings.Replace(val, "\\", "\\\\", -1), "'", "\\'", -1) + "'"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
var op string
|
||||
switch q.Relation {
|
||||
case VersionEqual:
|
||||
op = "="
|
||||
case VersionGreater:
|
||||
op = ">>"
|
||||
case VersionLess:
|
||||
op = "<<"
|
||||
case VersionRegexp:
|
||||
op = "~"
|
||||
case VersionPatternMatch:
|
||||
op = "%"
|
||||
case VersionGreaterOrEqual:
|
||||
op = ">="
|
||||
case VersionLessOrEqual:
|
||||
op = "<="
|
||||
}
|
||||
return fmt.Sprintf("%s (%s %s)", escape(q.Field), op, escape(q.Value))
|
||||
}
|
||||
|
||||
// Matches on dependency condition
|
||||
func (q *DependencyQuery) Matches(pkg *Package) bool {
|
||||
return pkg.MatchesDependency(q.Dep)
|
||||
}
|
||||
|
||||
// Fast is always true for dependency query
|
||||
func (q *DependencyQuery) Fast() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Query runs PackageList.Search
|
||||
func (q *DependencyQuery) Query(list *PackageList) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
for _, pkg := range list.Search(q.Dep, true) {
|
||||
result.Add(pkg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *DependencyQuery) String() string {
|
||||
return q.Dep.String()
|
||||
}
|
||||
|
||||
// Matches on specific properties
|
||||
func (q *PkgQuery) Matches(pkg *Package) bool {
|
||||
return pkg.Name == q.Pkg && pkg.Version == q.Version && pkg.Architecture == q.Arch
|
||||
}
|
||||
|
||||
// Fast is always true for package query
|
||||
func (q *PkgQuery) Fast() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Query looks up specific package
|
||||
func (q *PkgQuery) Query(list *PackageList) (result *PackageList) {
|
||||
result = NewPackageList()
|
||||
|
||||
pkg := list.packages["P"+q.Arch+" "+q.Pkg+" "+q.Version]
|
||||
if pkg != nil {
|
||||
result.Add(pkg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (q *PkgQuery) String() string {
|
||||
return fmt.Sprintf("%s_%s_%s", q.Pkg, q.Version, q.Arch)
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/ugorji/go/codec"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PackageRefList is a list of keys of packages, this is basis for snapshot
|
||||
// and similar stuff
|
||||
//
|
||||
// Refs are sorted in lexicographical order
|
||||
type PackageRefList struct {
|
||||
// List of package keys
|
||||
Refs [][]byte
|
||||
}
|
||||
|
||||
// Verify interface
|
||||
var (
|
||||
_ sort.Interface = &PackageRefList{}
|
||||
)
|
||||
|
||||
// NewPackageRefList creates empty PackageRefList
|
||||
func NewPackageRefList() *PackageRefList {
|
||||
return &PackageRefList{}
|
||||
}
|
||||
|
||||
// NewPackageRefListFromPackageList creates PackageRefList from PackageList
|
||||
func NewPackageRefListFromPackageList(list *PackageList) *PackageRefList {
|
||||
reflist := &PackageRefList{}
|
||||
reflist.Refs = make([][]byte, list.Len())
|
||||
|
||||
i := 0
|
||||
for _, p := range list.packages {
|
||||
reflist.Refs[i] = p.Key("")
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Sort(reflist)
|
||||
|
||||
return reflist
|
||||
}
|
||||
|
||||
// Len returns number of refs
|
||||
func (l *PackageRefList) Len() int {
|
||||
return len(l.Refs)
|
||||
}
|
||||
|
||||
// Swap swaps two refs
|
||||
func (l *PackageRefList) Swap(i, j int) {
|
||||
l.Refs[i], l.Refs[j] = l.Refs[j], l.Refs[i]
|
||||
}
|
||||
|
||||
// Compare compares two refs in lexographical order
|
||||
func (l *PackageRefList) Less(i, j int) bool {
|
||||
return bytes.Compare(l.Refs[i], l.Refs[j]) < 0
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of PackageRefList
|
||||
func (l *PackageRefList) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(l)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Decode decodes msgpack representation into PackageRefLit
|
||||
func (l *PackageRefList) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
return decoder.Decode(l)
|
||||
}
|
||||
|
||||
// ForEach calls handler for each package ref in list
|
||||
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
||||
var err error
|
||||
for _, p := range l.Refs {
|
||||
err = handler(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Substract returns all packages in l that are not in r
|
||||
func (l *PackageRefList) Substract(r *PackageRefList) *PackageRefList {
|
||||
result := &PackageRefList{Refs: make([][]byte, 0, 128)}
|
||||
|
||||
// pointer to left and right reflists
|
||||
il, ir := 0, 0
|
||||
// length of reflists
|
||||
ll, lr := l.Len(), r.Len()
|
||||
|
||||
for il < ll || ir < lr {
|
||||
if il == ll {
|
||||
// left list exhausted, we got the result
|
||||
break
|
||||
}
|
||||
if ir == lr {
|
||||
// right list exhausted, append what is left to result
|
||||
result.Refs = append(result.Refs, l.Refs[il:]...)
|
||||
break
|
||||
}
|
||||
|
||||
rel := bytes.Compare(l.Refs[il], r.Refs[ir])
|
||||
if rel == 0 {
|
||||
// r contains entry from l, so we skip it
|
||||
il++
|
||||
ir++
|
||||
} else if rel < 0 {
|
||||
// item il is not in r, append
|
||||
result.Refs = append(result.Refs, l.Refs[il])
|
||||
il++
|
||||
} else {
|
||||
// skip over to next item in r
|
||||
ir++
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// PackageDiff is a difference between two packages in a list.
|
||||
//
|
||||
// If left & right are present, difference is in package version
|
||||
// If left is nil, package is present only in right
|
||||
// If right is nil, package is present only in left
|
||||
type PackageDiff struct {
|
||||
Left, Right *Package
|
||||
}
|
||||
|
||||
// PackageDiffs is a list of PackageDiff records
|
||||
type PackageDiffs []PackageDiff
|
||||
|
||||
// Diff calculates difference between two reflists
|
||||
func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageCollection) (result PackageDiffs, err error) {
|
||||
result = make(PackageDiffs, 0, 128)
|
||||
|
||||
// pointer to left and right reflists
|
||||
il, ir := 0, 0
|
||||
// length of reflists
|
||||
ll, lr := l.Len(), r.Len()
|
||||
// cached loaded packages on the left & right
|
||||
pl, pr := (*Package)(nil), (*Package)(nil)
|
||||
|
||||
// until we reached end of both lists
|
||||
for il < ll || ir < lr {
|
||||
// if we've exhausted left list, pull the rest from the right
|
||||
if il == ll {
|
||||
pr, err = packageCollection.ByKey(r.Refs[ir])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, PackageDiff{Left: nil, Right: pr})
|
||||
ir++
|
||||
continue
|
||||
}
|
||||
// if we've exhausted right list, pull the rest from the left
|
||||
if ir == lr {
|
||||
pl, err = packageCollection.ByKey(l.Refs[il])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, PackageDiff{Left: pl, Right: nil})
|
||||
il++
|
||||
continue
|
||||
}
|
||||
|
||||
// refs on both sides are present, load them
|
||||
rl, rr := l.Refs[il], r.Refs[ir]
|
||||
// compare refs
|
||||
rel := bytes.Compare(rl, rr)
|
||||
|
||||
if rel == 0 {
|
||||
// refs are identical, so are packages, advance pointer
|
||||
il++
|
||||
ir++
|
||||
pl, pr = nil, nil
|
||||
} else {
|
||||
// load pl & pr if they haven't been loaded before
|
||||
if pl == nil {
|
||||
pl, err = packageCollection.ByKey(rl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if pr == nil {
|
||||
pr, err = packageCollection.ByKey(rr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise pl or pr is missing on one of the sides
|
||||
if rel < 0 {
|
||||
// compaction: +(,A) -(B,) --> !(A,B)
|
||||
if len(result) > 0 && result[len(result)-1].Left == nil && result[len(result)-1].Right.Name == pl.Name &&
|
||||
result[len(result)-1].Right.Architecture == pl.Architecture {
|
||||
result[len(result)-1] = PackageDiff{Left: pl, Right: result[len(result)-1].Right}
|
||||
} else {
|
||||
result = append(result, PackageDiff{Left: pl, Right: nil})
|
||||
}
|
||||
il++
|
||||
pl = nil
|
||||
} else {
|
||||
// compaction: -(A,) +(,B) --> !(A,B)
|
||||
if len(result) > 0 && result[len(result)-1].Right == nil && result[len(result)-1].Left.Name == pr.Name &&
|
||||
result[len(result)-1].Left.Architecture == pr.Architecture {
|
||||
result[len(result)-1] = PackageDiff{Left: result[len(result)-1].Left, Right: pr}
|
||||
} else {
|
||||
result = append(result, PackageDiff{Left: nil, Right: pr})
|
||||
}
|
||||
ir++
|
||||
pr = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Merge merges reflist r into current reflist. If overrideMatching, merge
|
||||
// replaces matching packages (by architecture/name) with reference from r.
|
||||
// Otherwise, all packages are saved.
|
||||
func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result *PackageRefList) {
|
||||
var overriddenArch, overridenName []byte
|
||||
|
||||
// pointer to left and right reflists
|
||||
il, ir := 0, 0
|
||||
// length of reflists
|
||||
ll, lr := l.Len(), r.Len()
|
||||
|
||||
result = &PackageRefList{}
|
||||
result.Refs = make([][]byte, 0, ll+lr)
|
||||
|
||||
// until we reached end of both lists
|
||||
for il < ll || ir < lr {
|
||||
// if we've exhausted left list, pull the rest from the right
|
||||
if il == ll {
|
||||
result.Refs = append(result.Refs, r.Refs[ir:]...)
|
||||
break
|
||||
}
|
||||
// if we've exhausted right list, pull the rest from the left
|
||||
if ir == lr {
|
||||
result.Refs = append(result.Refs, l.Refs[il:]...)
|
||||
break
|
||||
}
|
||||
|
||||
// refs on both sides are present, load them
|
||||
rl, rr := l.Refs[il], r.Refs[ir]
|
||||
// compare refs
|
||||
rel := bytes.Compare(rl, rr)
|
||||
|
||||
if rel == 0 {
|
||||
// refs are identical, so are packages, advance pointer
|
||||
result.Refs = append(result.Refs, l.Refs[il])
|
||||
il++
|
||||
ir++
|
||||
overridenName = nil
|
||||
overriddenArch = nil
|
||||
} else {
|
||||
if overrideMatching {
|
||||
partsL := bytes.Split(rl, []byte(" "))
|
||||
archL, nameL := partsL[0][1:], partsL[1]
|
||||
|
||||
partsR := bytes.Split(rr, []byte(" "))
|
||||
archR, nameR := partsR[0][1:], partsR[1]
|
||||
|
||||
if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) {
|
||||
// this package has already been overriden on the right
|
||||
il++
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) {
|
||||
// override with package from the right
|
||||
result.Refs = append(result.Refs, r.Refs[ir])
|
||||
il++
|
||||
ir++
|
||||
overriddenArch = archL
|
||||
overridenName = nameL
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise append smallest of two
|
||||
if rel < 0 {
|
||||
result.Refs = append(result.Refs, l.Refs[il])
|
||||
il++
|
||||
} else {
|
||||
result.Refs = append(result.Refs, r.Refs[ir])
|
||||
ir++
|
||||
overridenName = nil
|
||||
overriddenArch = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FilterLatestRefs takes in a reflist with potentially multiples of the same
|
||||
// packages and reduces it to only the latest of each package. The operations
|
||||
// are done in-place. This implements a "latest wins" approach which can be used
|
||||
// while merging two or more snapshots together.
|
||||
func FilterLatestRefs(r *PackageRefList) {
|
||||
var (
|
||||
lastArch, lastName, lastVer []byte
|
||||
arch, name, ver []byte
|
||||
parts [][]byte
|
||||
)
|
||||
|
||||
for i := 0; i < len(r.Refs); i++ {
|
||||
parts = bytes.Split(r.Refs[i][1:], []byte(" "))
|
||||
arch, name, ver = parts[0], parts[1], parts[2]
|
||||
|
||||
if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) {
|
||||
// Two packages are identical, check version and only one wins
|
||||
vres := CompareVersions(string(ver), string(lastVer))
|
||||
|
||||
// Remove the older refs from the result
|
||||
if vres > 0 {
|
||||
// ver[i] > ver[i-1], remove element i-1
|
||||
r.Refs = append(r.Refs[:i-1], r.Refs[i:]...)
|
||||
} else {
|
||||
// ver[i] < ver[i-1], remove element i
|
||||
r.Refs = append(r.Refs[:i], r.Refs[i+1:]...)
|
||||
arch, name, ver = lastArch, lastName, lastVer
|
||||
}
|
||||
|
||||
// Compensate for the reduced set
|
||||
i -= 1
|
||||
}
|
||||
|
||||
lastArch, lastName, lastVer = arch, name, ver
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type PackageRefListSuite struct {
|
||||
// Simple list with "real" packages from stanzas
|
||||
list *PackageList
|
||||
p1, p2, p3, p4, p5, p6 *Package
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageRefListSuite{})
|
||||
|
||||
func toStrSlice(reflist *PackageRefList) (result []string) {
|
||||
result = make([]string, reflist.Len())
|
||||
for i, r := range reflist.Refs {
|
||||
result[i] = string(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) SetUpTest(c *C) {
|
||||
s.list = NewPackageList()
|
||||
|
||||
s.p1 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
s.p2 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
stanza := packageStanza.Copy()
|
||||
stanza["Package"] = "mars-invaders"
|
||||
s.p3 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Source"] = "unknown-planet"
|
||||
s.p4 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Package"] = "lonely-strangers"
|
||||
s.p5 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Version"] = "99.1"
|
||||
s.p6 = NewPackageFromControlFile(stanza)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestNewPackageListFromRefList(c *C) {
|
||||
db, _ := database.OpenDB(c.MkDir())
|
||||
coll := NewPackageCollection(db)
|
||||
coll.Update(s.p1)
|
||||
coll.Update(s.p3)
|
||||
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
_, err := NewPackageListFromRefList(reflist, coll, nil)
|
||||
c.Assert(err, ErrorMatches, "unable to load package with key.*")
|
||||
|
||||
coll.Update(s.p5)
|
||||
coll.Update(s.p6)
|
||||
|
||||
list, err := NewPackageListFromRefList(reflist, coll, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(list.Len(), Equals, 4)
|
||||
c.Check(list.Add(s.p4), ErrorMatches, "conflict in package.*")
|
||||
|
||||
list, err = NewPackageListFromRefList(nil, coll, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(list.Len(), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestNewPackageRefList(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
c.Assert(reflist.Len(), Equals, 4)
|
||||
c.Check(reflist.Refs[0], DeepEquals, []byte(s.p1.Key("")))
|
||||
c.Check(reflist.Refs[1], DeepEquals, []byte(s.p6.Key("")))
|
||||
c.Check(reflist.Refs[2], DeepEquals, []byte(s.p5.Key("")))
|
||||
c.Check(reflist.Refs[3], DeepEquals, []byte(s.p3.Key("")))
|
||||
|
||||
reflist = NewPackageRefList()
|
||||
c.Check(reflist.Len(), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestPackageRefListEncodeDecode(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
reflist2 := &PackageRefList{}
|
||||
err := reflist2.Decode(reflist.Encode())
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(reflist2.Len(), Equals, reflist.Len())
|
||||
c.Check(reflist2.Refs, DeepEquals, reflist.Refs)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestPackageRefListForeach(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
Len := 0
|
||||
err := reflist.ForEach(func([]byte) error {
|
||||
Len++
|
||||
return nil
|
||||
})
|
||||
|
||||
c.Check(Len, Equals, 4)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
e := errors.New("b")
|
||||
|
||||
err = reflist.ForEach(func([]byte) error {
|
||||
return e
|
||||
})
|
||||
|
||||
c.Check(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestSubstract(c *C) {
|
||||
r1 := []byte("r1")
|
||||
r2 := []byte("r2")
|
||||
r3 := []byte("r3")
|
||||
r4 := []byte("r4")
|
||||
r5 := []byte("r5")
|
||||
|
||||
empty := &PackageRefList{Refs: [][]byte{}}
|
||||
l1 := &PackageRefList{Refs: [][]byte{r1, r2, r3, r4}}
|
||||
l2 := &PackageRefList{Refs: [][]byte{r1, r3}}
|
||||
l3 := &PackageRefList{Refs: [][]byte{r2, r4}}
|
||||
l4 := &PackageRefList{Refs: [][]byte{r4, r5}}
|
||||
l5 := &PackageRefList{Refs: [][]byte{r1, r2, r3}}
|
||||
|
||||
c.Check(l1.Substract(empty), DeepEquals, l1)
|
||||
c.Check(l1.Substract(l2), DeepEquals, l3)
|
||||
c.Check(l1.Substract(l3), DeepEquals, l2)
|
||||
c.Check(l1.Substract(l4), DeepEquals, l5)
|
||||
c.Check(empty.Substract(l1), DeepEquals, empty)
|
||||
c.Check(l2.Substract(l3), DeepEquals, l2)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestDiff(c *C) {
|
||||
db, _ := database.OpenDB(c.MkDir())
|
||||
coll := NewPackageCollection(db)
|
||||
|
||||
packages := []*Package{
|
||||
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0
|
||||
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
|
||||
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
|
||||
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
|
||||
&Package{Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6
|
||||
}
|
||||
|
||||
for _, p := range packages {
|
||||
coll.Update(p)
|
||||
}
|
||||
|
||||
listA := NewPackageList()
|
||||
listA.Add(packages[0])
|
||||
listA.Add(packages[1])
|
||||
listA.Add(packages[2])
|
||||
listA.Add(packages[3])
|
||||
listA.Add(packages[6])
|
||||
|
||||
listB := NewPackageList()
|
||||
listB.Add(packages[0])
|
||||
listB.Add(packages[2])
|
||||
listB.Add(packages[4])
|
||||
listB.Add(packages[5])
|
||||
|
||||
reflistA := NewPackageRefListFromPackageList(listA)
|
||||
reflistB := NewPackageRefListFromPackageList(listB)
|
||||
|
||||
diffAA, err := reflistA.Diff(reflistA, coll)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(diffAA, HasLen, 0)
|
||||
|
||||
diffAB, err := reflistA.Diff(reflistB, coll)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(diffAB, HasLen, 4)
|
||||
|
||||
c.Check(diffAB[0].Left, IsNil)
|
||||
c.Check(diffAB[0].Right.String(), Equals, "app_1.1~bp2_amd64")
|
||||
|
||||
c.Check(diffAB[1].Left.String(), Equals, "app_1.1~bp1_i386")
|
||||
c.Check(diffAB[1].Right.String(), Equals, "app_1.1~bp2_i386")
|
||||
|
||||
c.Check(diffAB[2].Left.String(), Equals, "dpkg_1.7_i386")
|
||||
c.Check(diffAB[2].Right, IsNil)
|
||||
|
||||
c.Check(diffAB[3].Left.String(), Equals, "xyz_3.0_sparc")
|
||||
c.Check(diffAB[3].Right, IsNil)
|
||||
|
||||
diffBA, err := reflistB.Diff(reflistA, coll)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(diffBA, HasLen, 4)
|
||||
|
||||
c.Check(diffBA[0].Right, IsNil)
|
||||
c.Check(diffBA[0].Left.String(), Equals, "app_1.1~bp2_amd64")
|
||||
|
||||
c.Check(diffBA[1].Right.String(), Equals, "app_1.1~bp1_i386")
|
||||
c.Check(diffBA[1].Left.String(), Equals, "app_1.1~bp2_i386")
|
||||
|
||||
c.Check(diffBA[2].Right.String(), Equals, "dpkg_1.7_i386")
|
||||
c.Check(diffBA[2].Left, IsNil)
|
||||
|
||||
c.Check(diffBA[3].Right.String(), Equals, "xyz_3.0_sparc")
|
||||
c.Check(diffBA[3].Left, IsNil)
|
||||
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestMerge(c *C) {
|
||||
db, _ := database.OpenDB(c.MkDir())
|
||||
coll := NewPackageCollection(db)
|
||||
|
||||
packages := []*Package{
|
||||
&Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0
|
||||
&Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1
|
||||
&Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2
|
||||
&Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4
|
||||
&Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5
|
||||
&Package{Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6
|
||||
&Package{Name: "xyz", Version: "1.0", Architecture: "sparc"}, //7
|
||||
}
|
||||
|
||||
for _, p := range packages {
|
||||
coll.Update(p)
|
||||
}
|
||||
|
||||
listA := NewPackageList()
|
||||
listA.Add(packages[0])
|
||||
listA.Add(packages[1])
|
||||
listA.Add(packages[2])
|
||||
listA.Add(packages[3])
|
||||
listA.Add(packages[7])
|
||||
|
||||
listB := NewPackageList()
|
||||
listB.Add(packages[0])
|
||||
listB.Add(packages[2])
|
||||
listB.Add(packages[4])
|
||||
listB.Add(packages[5])
|
||||
listB.Add(packages[6])
|
||||
|
||||
reflistA := NewPackageRefListFromPackageList(listA)
|
||||
reflistB := NewPackageRefListFromPackageList(listB)
|
||||
|
||||
mergeAB := reflistA.Merge(reflistB, true)
|
||||
mergeBA := reflistB.Merge(reflistA, true)
|
||||
|
||||
c.Check(toStrSlice(mergeAB), DeepEquals,
|
||||
[]string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp2", "Pi386 dpkg 1.0", "Pi386 lib 1.0", "Psparc xyz 1.0"})
|
||||
c.Check(toStrSlice(mergeBA), DeepEquals,
|
||||
[]string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp1", "Pi386 dpkg 1.7", "Pi386 lib 1.0", "Psparc xyz 1.0"})
|
||||
|
||||
mergeABall := reflistA.Merge(reflistB, false)
|
||||
mergeBAall := reflistB.Merge(reflistA, false)
|
||||
|
||||
c.Check(mergeABall, DeepEquals, mergeBAall)
|
||||
c.Check(toStrSlice(mergeBAall), DeepEquals,
|
||||
[]string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp1", "Pi386 app 1.1~bp2", "Pi386 dpkg 1.0", "Pi386 dpkg 1.7", "Pi386 lib 1.0", "Psparc xyz 1.0"})
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) {
|
||||
packages := []*Package{
|
||||
&Package{Name: "lib", Version: "1.0", Architecture: "i386"},
|
||||
&Package{Name: "lib", Version: "1.2~bp1", Architecture: "i386"},
|
||||
&Package{Name: "lib", Version: "1.2", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.2", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.3", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.5", Architecture: "i386"},
|
||||
&Package{Name: "dpkg", Version: "1.6", Architecture: "i386"},
|
||||
}
|
||||
|
||||
rl := NewPackageList()
|
||||
rl.Add(packages[0])
|
||||
rl.Add(packages[1])
|
||||
rl.Add(packages[2])
|
||||
rl.Add(packages[3])
|
||||
rl.Add(packages[4])
|
||||
rl.Add(packages[5])
|
||||
rl.Add(packages[6])
|
||||
rl.Add(packages[7])
|
||||
|
||||
result := NewPackageRefListFromPackageList(rl)
|
||||
FilterLatestRefs(result)
|
||||
|
||||
c.Check(toStrSlice(result), DeepEquals,
|
||||
[]string{"Pi386 dpkg 1.6", "Pi386 lib 1.2"})
|
||||
}
|
||||
+654
@@ -0,0 +1,654 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/http"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/ugorji/go/codec"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RemoteRepo represents remote (fetchable) Debian repository.
|
||||
//
|
||||
// Repostitory could be filtered when fetching by components, architectures
|
||||
type RemoteRepo struct {
|
||||
// Permanent internal ID
|
||||
UUID string
|
||||
// User-assigned name
|
||||
Name string
|
||||
// Root of Debian archive, URL
|
||||
ArchiveRoot string
|
||||
// Distribution name, e.g. squeeze
|
||||
Distribution string
|
||||
// List of components to fetch, if empty, then fetch all components
|
||||
Components []string
|
||||
// List of architectures to fetch, if empty, then fetch all architectures
|
||||
Architectures []string
|
||||
// Should we download sources?
|
||||
DownloadSources bool
|
||||
// Meta-information about repository
|
||||
Meta Stanza
|
||||
// Last update date
|
||||
LastDownloadDate time.Time
|
||||
// Checksums for release files
|
||||
ReleaseFiles map[string]utils.ChecksumInfo
|
||||
// Filter for packages
|
||||
Filter string
|
||||
// FilterWithDeps to include dependencies from filter query
|
||||
FilterWithDeps bool
|
||||
// "Snapshot" of current list of packages
|
||||
packageRefs *PackageRefList
|
||||
// Parsed archived root
|
||||
archiveRootURL *url.URL
|
||||
}
|
||||
|
||||
// NewRemoteRepo creates new instance of Debian remote repository with specified params
|
||||
func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string,
|
||||
architectures []string, downloadSources bool) (*RemoteRepo, error) {
|
||||
result := &RemoteRepo{
|
||||
UUID: uuid.New(),
|
||||
Name: name,
|
||||
ArchiveRoot: archiveRoot,
|
||||
Distribution: distribution,
|
||||
Components: components,
|
||||
Architectures: architectures,
|
||||
DownloadSources: downloadSources,
|
||||
}
|
||||
|
||||
err := result.prepare()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(result.Distribution, "/") || strings.HasPrefix(result.Distribution, ".") {
|
||||
// flat repo
|
||||
if !strings.HasPrefix(result.Distribution, ".") {
|
||||
result.Distribution = "./" + result.Distribution
|
||||
}
|
||||
result.Architectures = nil
|
||||
if len(result.Components) > 0 {
|
||||
return nil, fmt.Errorf("components aren't supported for flat repos")
|
||||
}
|
||||
result.Components = nil
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *RemoteRepo) prepare() error {
|
||||
var err error
|
||||
|
||||
// Add final / to URL
|
||||
if !strings.HasSuffix(repo.ArchiveRoot, "/") {
|
||||
repo.ArchiveRoot = repo.ArchiveRoot + "/"
|
||||
}
|
||||
|
||||
repo.archiveRootURL, err = url.Parse(repo.ArchiveRoot)
|
||||
return err
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (repo *RemoteRepo) String() string {
|
||||
srcFlag := ""
|
||||
if repo.DownloadSources {
|
||||
srcFlag = " [src]"
|
||||
}
|
||||
distribution := repo.Distribution
|
||||
if distribution == "" {
|
||||
distribution = "./"
|
||||
}
|
||||
return fmt.Sprintf("[%s]: %s %s%s", repo.Name, repo.ArchiveRoot, distribution, srcFlag)
|
||||
}
|
||||
|
||||
// IsFlat determines if repository is flat
|
||||
func (repo *RemoteRepo) IsFlat() bool {
|
||||
// aptly < 0.5.1 had Distribution = "" for flat repos
|
||||
// aptly >= 0.5.1 had Distribution = "./[path]/" for flat repos
|
||||
return repo.Distribution == "" || (strings.HasPrefix(repo.Distribution, ".") && strings.HasSuffix(repo.Distribution, "/"))
|
||||
}
|
||||
|
||||
// NumPackages return number of packages retrived from remote repo
|
||||
func (repo *RemoteRepo) NumPackages() int {
|
||||
if repo.packageRefs == nil {
|
||||
return 0
|
||||
}
|
||||
return repo.packageRefs.Len()
|
||||
}
|
||||
|
||||
// RefList returns package list for repo
|
||||
func (repo *RemoteRepo) RefList() *PackageRefList {
|
||||
return repo.packageRefs
|
||||
}
|
||||
|
||||
// ReleaseURL returns URL to Release* files in repo root
|
||||
func (repo *RemoteRepo) ReleaseURL(name string) *url.URL {
|
||||
var path *url.URL
|
||||
|
||||
if !repo.IsFlat() {
|
||||
path = &url.URL{Path: fmt.Sprintf("dists/%s/%s", repo.Distribution, name)}
|
||||
} else {
|
||||
path = &url.URL{Path: filepath.Join(repo.Distribution, name)}
|
||||
}
|
||||
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// FlatBinaryURL returns URL to Packages files for flat repo
|
||||
func (repo *RemoteRepo) FlatBinaryURL() *url.URL {
|
||||
path := &url.URL{Path: filepath.Join(repo.Distribution, "Packages")}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// FlatSourcesURL returns URL to Sources files for flat repo
|
||||
func (repo *RemoteRepo) FlatSourcesURL() *url.URL {
|
||||
path := &url.URL{Path: filepath.Join(repo.Distribution, "Sources")}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// BinaryURL returns URL of Packages files for given component and
|
||||
// architecture
|
||||
func (repo *RemoteRepo) BinaryURL(component string, architecture string) *url.URL {
|
||||
path := &url.URL{Path: fmt.Sprintf("dists/%s/%s/binary-%s/Packages", repo.Distribution, component, architecture)}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// SourcesURL returns URL of Sources files for given component
|
||||
func (repo *RemoteRepo) SourcesURL(component string) *url.URL {
|
||||
path := &url.URL{Path: fmt.Sprintf("dists/%s/%s/source/Sources", repo.Distribution, component)}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// PackageURL returns URL of package file relative to repository root
|
||||
// architecture
|
||||
func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
|
||||
path := &url.URL{Path: filename}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// Fetch updates information about repository
|
||||
func (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier utils.Verifier) error {
|
||||
var (
|
||||
release, inrelease, releasesig *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
if verifier == nil {
|
||||
// 0. Just download release file to temporary URL
|
||||
release, err = http.DownloadTemp(d, repo.ReleaseURL("Release").String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 1. try InRelease file
|
||||
inrelease, err = http.DownloadTemp(d, repo.ReleaseURL("InRelease").String())
|
||||
if err != nil {
|
||||
goto splitsignature
|
||||
}
|
||||
defer inrelease.Close()
|
||||
|
||||
err = verifier.VerifyClearsigned(inrelease)
|
||||
if err != nil {
|
||||
goto splitsignature
|
||||
}
|
||||
|
||||
inrelease.Seek(0, 0)
|
||||
|
||||
release, err = verifier.ExtractClearsigned(inrelease)
|
||||
if err != nil {
|
||||
goto splitsignature
|
||||
}
|
||||
|
||||
goto ok
|
||||
|
||||
splitsignature:
|
||||
// 2. try Release + Release.gpg
|
||||
release, err = http.DownloadTemp(d, repo.ReleaseURL("Release").String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
releasesig, err = http.DownloadTemp(d, repo.ReleaseURL("Release.gpg").String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = verifier.VerifyDetachedSignature(releasesig, release)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = release.Seek(0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ok:
|
||||
|
||||
defer release.Close()
|
||||
|
||||
sreader := NewControlFileReader(release)
|
||||
stanza, err := sreader.ReadStanza()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !repo.IsFlat() {
|
||||
architectures := strings.Split(stanza["Architectures"], " ")
|
||||
if len(repo.Architectures) == 0 {
|
||||
repo.Architectures = architectures
|
||||
} else {
|
||||
err = utils.StringsIsSubset(repo.Architectures, architectures,
|
||||
fmt.Sprintf("architecture %%s not available in repo %s", repo))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
components := strings.Split(stanza["Components"], " ")
|
||||
for i := range components {
|
||||
components[i] = path.Base(components[i])
|
||||
}
|
||||
if len(repo.Components) == 0 {
|
||||
repo.Components = components
|
||||
} else {
|
||||
err = utils.StringsIsSubset(repo.Components, components,
|
||||
fmt.Sprintf("component %%s not available in repo %s", repo))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repo.ReleaseFiles = make(map[string]utils.ChecksumInfo)
|
||||
|
||||
parseSums := func(field string, setter func(sum *utils.ChecksumInfo, data string)) error {
|
||||
for _, line := range strings.Split(stanza[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)
|
||||
}
|
||||
|
||||
var size int64
|
||||
size, err = strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse size: %s", err)
|
||||
}
|
||||
|
||||
sum := repo.ReleaseFiles[parts[2]]
|
||||
|
||||
sum.Size = size
|
||||
setter(&sum, parts[0])
|
||||
|
||||
repo.ReleaseFiles[parts[2]] = sum
|
||||
}
|
||||
|
||||
delete(stanza, field)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = parseSums("MD5Sum", func(sum *utils.ChecksumInfo, data string) { sum.MD5 = data })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = parseSums("SHA1", func(sum *utils.ChecksumInfo, data string) { sum.SHA1 = data })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = parseSums("SHA256", func(sum *utils.ChecksumInfo, data string) { sum.SHA256 = data })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.Meta = stanza
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download downloads all repo files
|
||||
func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory,
|
||||
packagePool aptly.PackagePool, ignoreMismatch bool, dependencyOptions int, filterQuery PackageQuery) error {
|
||||
list := NewPackageList()
|
||||
|
||||
progress.Printf("Downloading & parsing package files...\n")
|
||||
|
||||
// Download and parse all Packages & Source files
|
||||
packagesURLs := [][]string{}
|
||||
|
||||
if repo.IsFlat() {
|
||||
packagesURLs = append(packagesURLs, []string{repo.FlatBinaryURL().String(), "binary"})
|
||||
if repo.DownloadSources {
|
||||
packagesURLs = append(packagesURLs, []string{repo.FlatSourcesURL().String(), "source"})
|
||||
}
|
||||
} else {
|
||||
for _, component := range repo.Components {
|
||||
for _, architecture := range repo.Architectures {
|
||||
packagesURLs = append(packagesURLs, []string{repo.BinaryURL(component, architecture).String(), "binary"})
|
||||
}
|
||||
if repo.DownloadSources {
|
||||
packagesURLs = append(packagesURLs, []string{repo.SourcesURL(component).String(), "source"})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, info := range packagesURLs {
|
||||
url, kind := info[0], info[1]
|
||||
packagesReader, packagesFile, err := http.DownloadTryCompression(d, url, repo.ReleaseFiles, ignoreMismatch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer packagesFile.Close()
|
||||
|
||||
stat, _ := packagesFile.Stat()
|
||||
progress.InitBar(stat.Size(), true)
|
||||
|
||||
sreader := NewControlFileReader(packagesReader)
|
||||
|
||||
for {
|
||||
stanza, err := sreader.ReadStanza()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stanza == nil {
|
||||
break
|
||||
}
|
||||
|
||||
off, _ := packagesFile.Seek(0, 1)
|
||||
progress.SetBar(int(off))
|
||||
|
||||
var p *Package
|
||||
|
||||
if kind == "binary" {
|
||||
p = NewPackageFromControlFile(stanza)
|
||||
} else if kind == "source" {
|
||||
p, err = NewSourcePackageFromControlFile(stanza)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = list.Add(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = collectionFactory.PackageCollection().Update(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
progress.ShutdownBar()
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if repo.Filter != "" {
|
||||
progress.Printf("Applying filter...\n")
|
||||
|
||||
list.PrepareIndex()
|
||||
|
||||
emptyList := NewPackageList()
|
||||
emptyList.PrepareIndex()
|
||||
|
||||
origPackages := list.Len()
|
||||
list, err = list.Filter([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
progress.Printf("Packages filtered: %d -> %d.\n", origPackages, list.Len())
|
||||
}
|
||||
|
||||
progress.Printf("Building download queue...\n")
|
||||
|
||||
// Build download queue
|
||||
queued := make(map[string]PackageDownloadTask, list.Len())
|
||||
count := 0
|
||||
downloadSize := int64(0)
|
||||
|
||||
err = list.ForEach(func(p *Package) error {
|
||||
list, err2 := p.DownloadList(packagePool)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
p.files = nil
|
||||
|
||||
for _, task := range list {
|
||||
key := task.RepoURI + "-" + task.DestinationPath
|
||||
_, found := queued[key]
|
||||
if !found {
|
||||
count++
|
||||
downloadSize += task.Checksums.Size
|
||||
queued[key] = task
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build download queue: %s", err)
|
||||
}
|
||||
|
||||
repo.packageRefs = NewPackageRefListFromPackageList(list)
|
||||
// free up package list, we don't need it after this point
|
||||
list = nil
|
||||
|
||||
progress.Printf("Download queue: %d items (%s)\n", count, utils.HumanBytes(downloadSize))
|
||||
|
||||
progress.InitBar(downloadSize, true)
|
||||
|
||||
// Download all package files
|
||||
ch := make(chan error, len(queued))
|
||||
|
||||
for _, task := range queued {
|
||||
d.DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch)
|
||||
}
|
||||
|
||||
// We don't need queued after this point
|
||||
queued = nil
|
||||
|
||||
// Wait for all downloads to finish
|
||||
errors := make([]string, 0)
|
||||
|
||||
for count > 0 {
|
||||
err = <-ch
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
count--
|
||||
}
|
||||
|
||||
progress.ShutdownBar()
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("download errors:\n %s\n", strings.Join(errors, "\n "))
|
||||
}
|
||||
|
||||
repo.LastDownloadDate = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of RemoteRepo
|
||||
func (repo *RemoteRepo) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(repo)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Decode decodes msgpack representation into RemoteRepo
|
||||
func (repo *RemoteRepo) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
err := decoder.Decode(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repo.prepare()
|
||||
}
|
||||
|
||||
// Key is a unique id in DB
|
||||
func (repo *RemoteRepo) Key() []byte {
|
||||
return []byte("R" + repo.UUID)
|
||||
}
|
||||
|
||||
// RefKey is a unique id for package reference list
|
||||
func (repo *RemoteRepo) RefKey() []byte {
|
||||
return []byte("E" + repo.UUID)
|
||||
}
|
||||
|
||||
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
||||
type RemoteRepoCollection struct {
|
||||
db database.Storage
|
||||
list []*RemoteRepo
|
||||
}
|
||||
|
||||
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
||||
func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
|
||||
result := &RemoteRepoCollection{
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("R"))
|
||||
result.list = make([]*RemoteRepo, 0, len(blobs))
|
||||
|
||||
for _, blob := range blobs {
|
||||
r := &RemoteRepo{}
|
||||
if err := r.Decode(blob); err != nil {
|
||||
log.Printf("Error decoding mirror: %s\n", err)
|
||||
} else {
|
||||
result.list = append(result.list, r)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add appends new repo to collection and saves it
|
||||
func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
|
||||
for _, r := range collection.list {
|
||||
if r.Name == repo.Name {
|
||||
return fmt.Errorf("mirror with name %s already exists", repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
err := collection.Update(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collection.list = append(collection.list, repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update stores updated information about repo in DB
|
||||
func (collection *RemoteRepoCollection) Update(repo *RemoteRepo) error {
|
||||
err := collection.db.Put(repo.Key(), repo.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.packageRefs != nil {
|
||||
err = collection.db.Put(repo.RefKey(), repo.packageRefs.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadComplete loads additional information for remote repo
|
||||
func (collection *RemoteRepoCollection) LoadComplete(repo *RemoteRepo) error {
|
||||
encoded, err := collection.db.Get(repo.RefKey())
|
||||
if err == database.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.packageRefs = &PackageRefList{}
|
||||
return repo.packageRefs.Decode(encoded)
|
||||
}
|
||||
|
||||
// ByName looks up repository by name
|
||||
func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
|
||||
for _, r := range collection.list {
|
||||
if r.Name == name {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("mirror with name %s not found", name)
|
||||
}
|
||||
|
||||
// ByUUID looks up repository by uuid
|
||||
func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
|
||||
for _, r := range collection.list {
|
||||
if r.UUID == uuid {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("mirror with uuid %s not found", uuid)
|
||||
}
|
||||
|
||||
// ForEach runs method for each repository
|
||||
func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error) error {
|
||||
var err error
|
||||
for _, r := range collection.list {
|
||||
err = handler(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Len returns number of remote repos
|
||||
func (collection *RemoteRepoCollection) Len() int {
|
||||
return len(collection.list)
|
||||
}
|
||||
|
||||
// Drop removes remote repo from collection
|
||||
func (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error {
|
||||
repoPosition := -1
|
||||
|
||||
for i, r := range collection.list {
|
||||
if r == repo {
|
||||
repoPosition = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if repoPosition == -1 {
|
||||
panic("repo not found!")
|
||||
}
|
||||
|
||||
collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list =
|
||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
||||
|
||||
err := collection.db.Delete(repo.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return collection.db.Delete(repo.RefKey())
|
||||
}
|
||||
@@ -1,16 +1,44 @@
|
||||
package debian
|
||||
package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/smira/aptly/aptly"
|
||||
"github.com/smira/aptly/console"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/files"
|
||||
"github.com/smira/aptly/http"
|
||||
"github.com/smira/aptly/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Launch gocheck tests
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
type NullVerifier struct {
|
||||
}
|
||||
|
||||
func (n *NullVerifier) InitKeyring() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NullVerifier) AddKeyring(keyring string) {
|
||||
}
|
||||
|
||||
func (n *NullVerifier) VerifyDetachedSignature(signature, cleartext io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NullVerifier) VerifyClearsigned(clearsigned io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NullVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, err error) {
|
||||
text, _ = ioutil.TempFile("", "aptly-test")
|
||||
io.Copy(text, clearsigned)
|
||||
text.Seek(0, 0)
|
||||
os.Remove(text.Name())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type PackageListMixinSuite struct {
|
||||
@@ -40,68 +68,155 @@ func (s *PackageListMixinSuite) SetUpPackages() {
|
||||
type RemoteRepoSuite struct {
|
||||
PackageListMixinSuite
|
||||
repo *RemoteRepo
|
||||
downloader *utils.FakeDownloader
|
||||
flat *RemoteRepo
|
||||
downloader *http.FakeDownloader
|
||||
progress aptly.Progress
|
||||
db database.Storage
|
||||
packageCollection *PackageCollection
|
||||
packageRepo *Repository
|
||||
collectionFactory *CollectionFactory
|
||||
packagePool aptly.PackagePool
|
||||
}
|
||||
|
||||
var _ = Suite(&RemoteRepoSuite{})
|
||||
|
||||
func (s *RemoteRepoSuite) SetUpTest(c *C) {
|
||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{})
|
||||
s.downloader = utils.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
|
||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false)
|
||||
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false)
|
||||
s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
|
||||
s.progress = console.NewProgress()
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.packageCollection = NewPackageCollection(s.db)
|
||||
s.packageRepo = NewRepository(c.MkDir())
|
||||
s.collectionFactory = NewCollectionFactory(s.db)
|
||||
s.packagePool = files.NewPackagePool(c.MkDir())
|
||||
s.SetUpPackages()
|
||||
s.progress.Start()
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TearDownTest(c *C) {
|
||||
s.progress.Shutdown()
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestInvalidURL(c *C) {
|
||||
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{})
|
||||
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false)
|
||||
c.Assert(err, ErrorMatches, ".*hexadecimal escape in host.*")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFlatCreation(c *C) {
|
||||
c.Check(s.flat.IsFlat(), Equals, true)
|
||||
c.Check(s.flat.Distribution, Equals, "./")
|
||||
c.Check(s.flat.Architectures, IsNil)
|
||||
c.Check(s.flat.Components, IsNil)
|
||||
|
||||
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false)
|
||||
c.Check(flat2.IsFlat(), Equals, true)
|
||||
c.Check(flat2.Distribution, Equals, "./binary/")
|
||||
|
||||
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false)
|
||||
c.Check(err, ErrorMatches, "components aren't supported for flat repos")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestString(c *C) {
|
||||
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze")
|
||||
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./")
|
||||
|
||||
s.repo.DownloadSources = true
|
||||
s.flat.DownloadSources = true
|
||||
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src]")
|
||||
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./ [src]")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestNumPackages(c *C) {
|
||||
c.Check(s.repo.NumPackages(), Equals, 0)
|
||||
s.repo.packageRefs = s.reflist
|
||||
c.Check(s.repo.NumPackages(), Equals, 3)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestIsFlat(c *C) {
|
||||
c.Check(s.repo.IsFlat(), Equals, false)
|
||||
c.Check(s.flat.IsFlat(), Equals, true)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestRefList(c *C) {
|
||||
s.repo.packageRefs = s.reflist
|
||||
c.Check(s.repo.RefList(), Equals, s.reflist)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestReleaseURL(c *C) {
|
||||
c.Assert(s.repo.ReleaseURL().String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/Release")
|
||||
c.Assert(s.repo.ReleaseURL("Release").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/Release")
|
||||
c.Assert(s.repo.ReleaseURL("InRelease").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/InRelease")
|
||||
|
||||
c.Assert(s.flat.ReleaseURL("Release").String(), Equals, "http://repos.express42.com/virool/precise/Release")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestBinaryURL(c *C) {
|
||||
c.Assert(s.repo.BinaryURL("main", "amd64").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/binary-amd64/Packages")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestSourcesURL(c *C) {
|
||||
c.Assert(s.repo.SourcesURL("main").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFlatBinaryURL(c *C) {
|
||||
c.Assert(s.flat.FlatBinaryURL().String(), Equals, "http://repos.express42.com/virool/precise/Packages")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFlatSourcesURL(c *C) {
|
||||
c.Assert(s.flat.FlatSourcesURL().String(), Equals, "http://repos.express42.com/virool/precise/Sources")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestPackageURL(c *C) {
|
||||
c.Assert(s.repo.PackageURL("pool/main/0/0ad/0ad_0~r11863-2_i386.deb").String(), Equals,
|
||||
"http://mirror.yandex.ru/debian/pool/main/0/0ad/0ad_0~r11863-2_i386.deb")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetch(c *C) {
|
||||
err := s.repo.Fetch(s.downloader)
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.repo.Architectures, DeepEquals, []string{"amd64", "armel", "armhf", "i386", "powerpc"})
|
||||
c.Assert(s.repo.Components, DeepEquals, []string{"main"})
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
c.Check(s.repo.ReleaseFiles, HasLen, 39)
|
||||
c.Check(s.repo.ReleaseFiles["main/binary-i386/Packages.bz2"], DeepEquals,
|
||||
utils.ChecksumInfo{
|
||||
Size: 734,
|
||||
MD5: "7954ed80936429687122b554620c1b5b",
|
||||
SHA1: "95a463a0739bf9ff622c8d68f6e4598d400f5248",
|
||||
SHA256: "377890a26f99db55e117dfc691972dcbbb7d8be1630c8fc8297530c205377f2b"})
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchNullVerifier1(c *C) {
|
||||
downloader := http.NewFakeDownloader()
|
||||
downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", errors.New("404"))
|
||||
downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
|
||||
downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release.gpg", "GPG")
|
||||
|
||||
err := s.repo.Fetch(downloader, &NullVerifier{})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.repo.Architectures, DeepEquals, []string{"amd64", "armel", "armhf", "i386", "powerpc"})
|
||||
c.Assert(s.repo.Components, DeepEquals, []string{"main"})
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) {
|
||||
downloader := http.NewFakeDownloader()
|
||||
downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", exampleReleaseFile)
|
||||
|
||||
err := s.repo.Fetch(downloader, &NullVerifier{})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.repo.Architectures, DeepEquals, []string{"amd64", "armel", "armhf", "i386", "powerpc"})
|
||||
c.Assert(s.repo.Components, DeepEquals, []string{"main"})
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) {
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"})
|
||||
err := s.repo.Fetch(s.downloader)
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false)
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) {
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"})
|
||||
err := s.repo.Fetch(s.downloader)
|
||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false)
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, ErrorMatches, "component xyz not available in repo.*")
|
||||
}
|
||||
|
||||
@@ -128,7 +243,7 @@ func (s *RemoteRepoSuite) TestRefKey(c *C) {
|
||||
func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
s.repo.Architectures = []string{"i386"}
|
||||
|
||||
err := s.repo.Fetch(s.downloader)
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404"))
|
||||
@@ -136,20 +251,132 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
|
||||
err = s.repo.Download(s.downloader, s.packageCollection, s.packageRepo)
|
||||
err = s.repo.Download(s.progress, s.downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
c.Assert(s.repo.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.packageCollection.ByKey(s.repo.packageRefs.Refs[0])
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
poolPath, _ := s.packageRepo.PoolPath(pkg.Filename, pkg.HashMD5)
|
||||
c.Check(pkg.VerifyFile(poolPath), Equals, true)
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
s.repo.Architectures = []string{"i386"}
|
||||
s.repo.DownloadSources = true
|
||||
|
||||
err := s.repo.Fetch(s.downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404"))
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", errors.New("HTTP 404"))
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.bz2", errors.New("HTTP 404"))
|
||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", errors.New("HTTP 404"))
|
||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile)
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc", "abc")
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz", "abcd")
|
||||
s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz", "abcde")
|
||||
|
||||
err = s.repo.Download(s.progress, s.downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
c.Assert(s.repo.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
|
||||
pkg, err = s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[1])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err = pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "access-modifier-checker")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
||||
downloader := http.NewFakeDownloader()
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile)
|
||||
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404"))
|
||||
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404"))
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile)
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
|
||||
err := s.flat.Fetch(downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.flat.Download(s.progress, downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
c.Assert(s.flat.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
||||
s.flat.DownloadSources = true
|
||||
|
||||
downloader := http.NewFakeDownloader()
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile)
|
||||
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404"))
|
||||
downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404"))
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile)
|
||||
downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.bz2", errors.New("HTTP 404"))
|
||||
downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.gz", errors.New("HTTP 404"))
|
||||
downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile)
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz")
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc", "abc")
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz", "abcd")
|
||||
downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz", "abcde")
|
||||
|
||||
err := s.flat.Fetch(downloader, nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.flat.Download(s.progress, downloader, s.collectionFactory, s.packagePool, false, 0, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
c.Assert(s.flat.packageRefs, NotNil)
|
||||
|
||||
pkg, err := s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[0])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "amanda-client")
|
||||
|
||||
pkg, err = s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[1])
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err = pkg.VerifyFiles(s.packagePool)
|
||||
c.Check(result, Equals, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
c.Check(pkg.Name, Equals, "access-modifier-checker")
|
||||
}
|
||||
|
||||
type RemoteRepoCollectionSuite struct {
|
||||
PackageListMixinSuite
|
||||
db database.Storage
|
||||
@@ -172,7 +399,7 @@ func (s *RemoteRepoCollectionSuite) TestAddByName(c *C) {
|
||||
r, err := s.collection.ByName("yandex")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{})
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
c.Assert(s.collection.Add(repo), IsNil)
|
||||
c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists")
|
||||
|
||||
@@ -190,7 +417,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
||||
r, err := s.collection.ByUUID("some-uuid")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{})
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
c.Assert(s.collection.Add(repo), IsNil)
|
||||
|
||||
r, err = s.collection.ByUUID(repo.UUID)
|
||||
@@ -199,7 +426,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
||||
}
|
||||
|
||||
func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{})
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
c.Assert(s.collection.Update(repo), IsNil)
|
||||
|
||||
collection := NewRemoteRepoCollection(s.db)
|
||||
@@ -219,8 +446,8 @@ func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
c.Assert(r.NumPackages(), Equals, 3)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoCollectionSuite) TestForEach(c *C) {
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{})
|
||||
func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
s.collection.Add(repo)
|
||||
|
||||
count := 0
|
||||
@@ -231,6 +458,8 @@ func (s *RemoteRepoCollectionSuite) TestForEach(c *C) {
|
||||
c.Assert(count, Equals, 1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.collection.Len(), Equals, 1)
|
||||
|
||||
e := errors.New("c")
|
||||
|
||||
err = s.collection.ForEach(func(*RemoteRepo) error {
|
||||
@@ -239,6 +468,32 @@ func (s *RemoteRepoCollectionSuite) TestForEach(c *C) {
|
||||
c.Assert(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoCollectionSuite) TestDrop(c *C) {
|
||||
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
s.collection.Add(repo1)
|
||||
|
||||
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false)
|
||||
s.collection.Add(repo2)
|
||||
|
||||
r1, _ := s.collection.ByUUID(repo1.UUID)
|
||||
c.Check(r1, Equals, repo1)
|
||||
|
||||
err := s.collection.Drop(repo1)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByUUID(repo1.UUID)
|
||||
c.Check(err, ErrorMatches, "mirror .* not found")
|
||||
|
||||
collection := NewRemoteRepoCollection(s.db)
|
||||
_, err = collection.ByName("yandex")
|
||||
c.Check(err, ErrorMatches, "mirror .* not found")
|
||||
|
||||
r2, _ := collection.ByName("tyndex")
|
||||
c.Check(r2.String(), Equals, repo2.String())
|
||||
|
||||
c.Check(func() { s.collection.Drop(repo1) }, Panics, "repo not found!")
|
||||
}
|
||||
|
||||
const exampleReleaseFile = `Origin: LP-PPA-agenda-developers-daily
|
||||
Label: Agenda Daily Builds
|
||||
Suite: precise
|
||||
@@ -261,7 +516,7 @@ MD5Sum:
|
||||
c63d31e8e3a5650c29a7124e541d6c23 134 main/binary-armhf/Release
|
||||
4059d198768f9f8dc9372dc1c54bc3c3 14 main/binary-armhf/Packages.bz2
|
||||
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-armhf/Packages
|
||||
708fc548e709eea0dfd2d7edb6098829 1344 main/binary-i386/Packages
|
||||
c8d336856df67d509032bb54145c2f89 826 main/binary-i386/Packages
|
||||
92262f0668b265401291f0467bc93763 133 main/binary-i386/Release
|
||||
7954ed80936429687122b554620c1b5b 734 main/binary-i386/Packages.bz2
|
||||
e2eef4fe7d285b12c511adfa3a39069e 641 main/binary-i386/Packages.gz
|
||||
@@ -285,7 +540,7 @@ MD5Sum:
|
||||
4059d198768f9f8dc9372dc1c54bc3c3 14 main/debian-installer/binary-powerpc/Packages.bz2
|
||||
9d10bb61e59bd799891ae4fbcf447ec9 29 main/debian-installer/binary-powerpc/Packages.gz
|
||||
3481d65651306df1596dca9078c2506a 135 main/source/Release
|
||||
0531474bd4630bfcfd39048be830483d 1119 main/source/Sources
|
||||
0459b7e4512db5479cb982bac6e2f9a1 2003 main/source/Sources
|
||||
3d83a489f1bd3c04226aa6520b8a6d07 656 main/source/Sources.bz2
|
||||
b062b5b77094aeeb05ca8dbb1ecf68a9 592 main/source/Sources.gz
|
||||
SHA1:
|
||||
@@ -301,7 +556,7 @@ SHA1:
|
||||
585a452e27c2e7e047c49d4b0a7459d8c627aa08 134 main/binary-armhf/Release
|
||||
64a543afbb5f4bf728636bdcbbe7a2ed0804adc2 14 main/binary-armhf/Packages.bz2
|
||||
da39a3ee5e6b4b0d3255bfef95601890afd80709 0 main/binary-armhf/Packages
|
||||
2bfad956c2d2437924a8527970858c59823451b7 1344 main/binary-i386/Packages
|
||||
1d2f0cd7a3c9e687b853eb277e241cd712b6e3b1 826 main/binary-i386/Packages
|
||||
16020809662f9bda36eb516d0995658dd94d1ad5 133 main/binary-i386/Release
|
||||
95a463a0739bf9ff622c8d68f6e4598d400f5248 734 main/binary-i386/Packages.bz2
|
||||
bf8c0dec9665ba78311c97cae1755d4b2e60af76 641 main/binary-i386/Packages.gz
|
||||
@@ -325,7 +580,7 @@ SHA1:
|
||||
64a543afbb5f4bf728636bdcbbe7a2ed0804adc2 14 main/debian-installer/binary-powerpc/Packages.bz2
|
||||
3df6ca52b6e8ecfb4a8fac6b8e02c777e3c7960d 29 main/debian-installer/binary-powerpc/Packages.gz
|
||||
49cfec0c9b1df3a25e983a3ddf29d15b0e376e02 135 main/source/Release
|
||||
4987db83999b0a8bbbbeeb183f066cadb87a5fa5 1119 main/source/Sources
|
||||
6b92e0fc84307226172696fde59ca5f33f380b57 2003 main/source/Sources
|
||||
ecb8afea11030a5df46941cb8ec297ca24c85736 656 main/source/Sources.bz2
|
||||
923e71383969c91146f12fa8cd121397f2467a2e 592 main/source/Sources.gz
|
||||
SHA256:
|
||||
@@ -341,7 +596,7 @@ SHA256:
|
||||
d25382b633c4a1621f8df6ce86e5c63da2e506a377e05ae9453238bb18191540 134 main/binary-armhf/Release
|
||||
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/binary-armhf/Packages.bz2
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-armhf/Packages
|
||||
9cd4bad3462e795bad509a44bae48622f2e9c9e56aafc999419cc5221f087dc8 1344 main/binary-i386/Packages
|
||||
b1bb341bb613363ca29440c2eb9c08a9289de5458209990ec502ed27711a83a2 826 main/binary-i386/Packages
|
||||
e5aaceaac5ecb59143a4b4ed2bf700fe85d6cf08addd10cf2058bde697b7b219 133 main/binary-i386/Release
|
||||
377890a26f99db55e117dfc691972dcbbb7d8be1630c8fc8297530c205377f2b 734 main/binary-i386/Packages.bz2
|
||||
6361e8efc67d2e7c1a8db45388aec0311007c0a1bd96698623ddeb5ed0bdc914 641 main/binary-i386/Packages.gz
|
||||
@@ -365,7 +620,7 @@ SHA256:
|
||||
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/debian-installer/binary-powerpc/Packages.bz2
|
||||
825d493158fe0f50ca1acd70367aefa391170563af2e4ee9cedbcbe6796c8384 29 main/debian-installer/binary-powerpc/Packages.gz
|
||||
d683102993b6f11067ce86d73111f067e36a199e9dc1f4295c8b19c274dc9ef8 135 main/source/Release
|
||||
a8707486566f1623f0e50c0f8f61d93a93d79fb3043b6e1c407fc9f2afb002ce 1119 main/source/Sources
|
||||
45f868fd5d9efe611d67572ffcf96a00a5b9ec38ea5102753290c38c36b8c282 2003 main/source/Sources
|
||||
d178f1e310218d9f0f16c37d0780637f1cf3640a94a7fb0e24dc940c51b1e115 656 main/source/Sources.bz2
|
||||
080228b550da407fb8ac73fb30b37323468fd2b2de98dd56a324ee7d701f6103 592 main/source/Sources.gz`
|
||||
|
||||
@@ -385,7 +640,9 @@ Section: utils
|
||||
Priority: optional
|
||||
Filename: pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb
|
||||
Size: 3
|
||||
MD5sum: cdc997dc06126e18ea9ba843efed9811
|
||||
SHA1: 049ba341d520c447fa2e6a1f8c871b3dbbe00106
|
||||
SHA256: 4487115ca47fe9acd95355b9278f30e18c53f33c385252252d3d7948d650d1d0
|
||||
MD5sum: d16fb36f0911f878998c136191af705e
|
||||
SHA1: 66b27417d37e024c46526c2f6d358a754fc552f3
|
||||
SHA256: 3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282
|
||||
`
|
||||
|
||||
const exampleSourcesFile = sourcePackageMeta
|
||||
@@ -1,4 +1,4 @@
|
||||
package debian
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/ugorji/go/codec"
|
||||
"log"
|
||||
"time"
|
||||
@@ -46,6 +47,46 @@ func NewSnapshotFromRepository(name string, repo *RemoteRepo) (*Snapshot, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewSnapshotFromLocalRepo creates snapshot from current state of local repository
|
||||
func NewSnapshotFromLocalRepo(name string, repo *LocalRepo) (*Snapshot, error) {
|
||||
if repo.packageRefs == nil {
|
||||
return nil, errors.New("local repo doesn't have packages")
|
||||
}
|
||||
|
||||
return &Snapshot{
|
||||
UUID: uuid.New(),
|
||||
Name: name,
|
||||
CreatedAt: time.Now(),
|
||||
SourceKind: "local",
|
||||
SourceIDs: []string{repo.UUID},
|
||||
Description: fmt.Sprintf("Snapshot from local repo %s", repo),
|
||||
packageRefs: repo.packageRefs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewSnapshotFromPackageList creates snapshot from PackageList
|
||||
func NewSnapshotFromPackageList(name string, sources []*Snapshot, list *PackageList, description string) *Snapshot {
|
||||
return NewSnapshotFromRefList(name, sources, NewPackageRefListFromPackageList(list), description)
|
||||
}
|
||||
|
||||
// NewSnapshotFromRefList creates snapshot from PackageRefList
|
||||
func NewSnapshotFromRefList(name string, sources []*Snapshot, list *PackageRefList, description string) *Snapshot {
|
||||
sourceUUIDs := make([]string, len(sources))
|
||||
for i := range sources {
|
||||
sourceUUIDs[i] = sources[i].UUID
|
||||
}
|
||||
|
||||
return &Snapshot{
|
||||
UUID: uuid.New(),
|
||||
Name: name,
|
||||
CreatedAt: time.Now(),
|
||||
SourceKind: "snapshot",
|
||||
SourceIDs: sourceUUIDs,
|
||||
Description: description,
|
||||
packageRefs: list,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns string representation of snapshot
|
||||
func (s *Snapshot) String() string {
|
||||
return fmt.Sprintf("[%s]: %s", s.Name, s.Description)
|
||||
@@ -137,7 +178,10 @@ func (collection *SnapshotCollection) Update(snapshot *Snapshot) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return collection.db.Put(snapshot.RefKey(), snapshot.packageRefs.Encode())
|
||||
if snapshot.packageRefs != nil {
|
||||
return collection.db.Put(snapshot.RefKey(), snapshot.packageRefs.Encode())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadComplete loads additional information about snapshot
|
||||
@@ -161,6 +205,52 @@ func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) {
|
||||
return nil, fmt.Errorf("snapshot with name %s not found", name)
|
||||
}
|
||||
|
||||
// ByUUID looks up snapshot by UUID
|
||||
func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
|
||||
for _, s := range collection.list {
|
||||
if s.UUID == uuid {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("snapshot with uuid %s not found", uuid)
|
||||
}
|
||||
|
||||
// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
|
||||
func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
|
||||
result := make([]*Snapshot, 0)
|
||||
|
||||
for _, s := range collection.list {
|
||||
if s.SourceKind == "repo" && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
|
||||
func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
|
||||
result := make([]*Snapshot, 0)
|
||||
|
||||
for _, s := range collection.list {
|
||||
if s.SourceKind == "local" && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BySnapshotSource looks up snapshots that have specified snapshot as a source
|
||||
func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
|
||||
result := make([]*Snapshot, 0)
|
||||
|
||||
for _, s := range collection.list {
|
||||
if s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID) {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ForEach runs method for each snapshot
|
||||
func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) error {
|
||||
var err error
|
||||
@@ -172,3 +262,35 @@ func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Len returns number of snapshots in collection
|
||||
// ForEach runs method for each snapshot
|
||||
func (collection *SnapshotCollection) Len() int {
|
||||
return len(collection.list)
|
||||
}
|
||||
|
||||
// Drop removes snapshot from collection
|
||||
func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
|
||||
snapshotPosition := -1
|
||||
|
||||
for i, s := range collection.list {
|
||||
if s == snapshot {
|
||||
snapshotPosition = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if snapshotPosition == -1 {
|
||||
panic("snapshot not found!")
|
||||
}
|
||||
|
||||
collection.list[len(collection.list)-1], collection.list[snapshotPosition], collection.list =
|
||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
||||
|
||||
err := collection.db.Delete(snapshot.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return collection.db.Delete(snapshot.RefKey())
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type SnapshotSuite struct {
|
||||
PackageListMixinSuite
|
||||
repo *RemoteRepo
|
||||
}
|
||||
|
||||
var _ = Suite(&SnapshotSuite{})
|
||||
|
||||
func (s *SnapshotSuite) SetUpTest(c *C) {
|
||||
s.SetUpPackages()
|
||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
s.repo.packageRefs = s.reflist
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestNewSnapshotFromRepository(c *C) {
|
||||
snapshot, _ := NewSnapshotFromRepository("snap1", s.repo)
|
||||
c.Check(snapshot.Name, Equals, "snap1")
|
||||
c.Check(snapshot.NumPackages(), Equals, 3)
|
||||
c.Check(snapshot.RefList().Len(), Equals, 3)
|
||||
c.Check(snapshot.SourceKind, Equals, "repo")
|
||||
c.Check(snapshot.SourceIDs, DeepEquals, []string{s.repo.UUID})
|
||||
|
||||
s.repo.packageRefs = nil
|
||||
_, err := NewSnapshotFromRepository("snap2", s.repo)
|
||||
c.Check(err, ErrorMatches, ".*not updated")
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestNewSnapshotFromLocalRepo(c *C) {
|
||||
localRepo := NewLocalRepo("lala", "hoorah!")
|
||||
|
||||
_, err := NewSnapshotFromLocalRepo("snap2", localRepo)
|
||||
c.Check(err, ErrorMatches, "local repo doesn't have packages")
|
||||
|
||||
localRepo.UpdateRefList(s.reflist)
|
||||
snapshot, _ := NewSnapshotFromLocalRepo("snap1", localRepo)
|
||||
c.Check(snapshot.Name, Equals, "snap1")
|
||||
c.Check(snapshot.NumPackages(), Equals, 3)
|
||||
c.Check(snapshot.RefList().Len(), Equals, 3)
|
||||
c.Check(snapshot.SourceKind, Equals, "local")
|
||||
c.Check(snapshot.SourceIDs, DeepEquals, []string{localRepo.UUID})
|
||||
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestNewSnapshotFromPackageList(c *C) {
|
||||
snap, _ := NewSnapshotFromRepository("snap1", s.repo)
|
||||
|
||||
snapshot := NewSnapshotFromPackageList("snap2", []*Snapshot{snap}, s.list, "Pulled")
|
||||
c.Check(snapshot.Name, Equals, "snap2")
|
||||
c.Check(snapshot.NumPackages(), Equals, 3)
|
||||
c.Check(snapshot.SourceKind, Equals, "snapshot")
|
||||
c.Check(snapshot.SourceIDs, DeepEquals, []string{snap.UUID})
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestNewSnapshotFromRefList(c *C) {
|
||||
snap, _ := NewSnapshotFromRepository("snap1", s.repo)
|
||||
|
||||
snapshot := NewSnapshotFromRefList("snap2", []*Snapshot{snap}, s.reflist, "Merged")
|
||||
c.Check(snapshot.Name, Equals, "snap2")
|
||||
c.Check(snapshot.NumPackages(), Equals, 3)
|
||||
c.Check(snapshot.SourceKind, Equals, "snapshot")
|
||||
c.Check(snapshot.SourceIDs, DeepEquals, []string{snap.UUID})
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestKey(c *C) {
|
||||
snapshot, _ := NewSnapshotFromRepository("snap1", s.repo)
|
||||
c.Assert(len(snapshot.Key()), Equals, 37)
|
||||
c.Assert(snapshot.Key()[0], Equals, byte('S'))
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestRefKey(c *C) {
|
||||
snapshot, _ := NewSnapshotFromRepository("snap1", s.repo)
|
||||
c.Assert(len(snapshot.RefKey()), Equals, 37)
|
||||
c.Assert(snapshot.RefKey()[0], Equals, byte('E'))
|
||||
c.Assert(snapshot.RefKey()[1:], DeepEquals, snapshot.Key()[1:])
|
||||
}
|
||||
|
||||
func (s *SnapshotSuite) TestEncodeDecode(c *C) {
|
||||
snapshot, _ := NewSnapshotFromRepository("snap1", s.repo)
|
||||
s.repo.packageRefs = s.reflist
|
||||
|
||||
snapshot2 := &Snapshot{}
|
||||
c.Assert(snapshot2.Decode(snapshot.Encode()), IsNil)
|
||||
c.Assert(snapshot2.Name, Equals, snapshot.Name)
|
||||
c.Assert(snapshot2.packageRefs, IsNil)
|
||||
}
|
||||
|
||||
type SnapshotCollectionSuite struct {
|
||||
PackageListMixinSuite
|
||||
db database.Storage
|
||||
repo1, repo2 *RemoteRepo
|
||||
lrepo1, lrepo2 *LocalRepo
|
||||
snapshot1, snapshot2 *Snapshot
|
||||
snapshot3, snapshot4 *Snapshot
|
||||
collection *SnapshotCollection
|
||||
}
|
||||
|
||||
var _ = Suite(&SnapshotCollectionSuite{})
|
||||
|
||||
func (s *SnapshotCollectionSuite) SetUpTest(c *C) {
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.collection = NewSnapshotCollection(s.db)
|
||||
s.SetUpPackages()
|
||||
|
||||
s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false)
|
||||
s.repo1.packageRefs = s.reflist
|
||||
s.snapshot1, _ = NewSnapshotFromRepository("snap1", s.repo1)
|
||||
|
||||
s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false)
|
||||
s.repo2.packageRefs = s.reflist
|
||||
s.snapshot2, _ = NewSnapshotFromRepository("snap2", s.repo2)
|
||||
|
||||
s.lrepo1 = NewLocalRepo("local1", "")
|
||||
s.lrepo1.packageRefs = s.reflist
|
||||
s.snapshot3, _ = NewSnapshotFromLocalRepo("snap3", s.lrepo1)
|
||||
|
||||
s.lrepo2 = NewLocalRepo("local2", "")
|
||||
s.lrepo2.packageRefs = s.reflist
|
||||
s.snapshot4, _ = NewSnapshotFromLocalRepo("snap4", s.lrepo2)
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestAddByNameByUUID(c *C) {
|
||||
snapshot, err := s.collection.ByName("snap1")
|
||||
c.Assert(err, ErrorMatches, "*.not found")
|
||||
|
||||
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
||||
c.Assert(s.collection.Add(s.snapshot1), ErrorMatches, ".*already exists")
|
||||
|
||||
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
||||
|
||||
snapshot, err = s.collection.ByName("snap1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
||||
|
||||
collection := NewSnapshotCollection(s.db)
|
||||
snapshot, err = collection.ByName("snap1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
||||
|
||||
snapshot, err = collection.ByUUID(s.snapshot1.UUID)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||
c.Assert(s.collection.Update(s.snapshot1), IsNil)
|
||||
|
||||
collection := NewSnapshotCollection(s.db)
|
||||
snapshot, err := collection.ByName("snap1")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(snapshot.packageRefs, IsNil)
|
||||
|
||||
c.Assert(s.collection.LoadComplete(snapshot), IsNil)
|
||||
c.Assert(snapshot.NumPackages(), Equals, 3)
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestForEachAndLen(c *C) {
|
||||
s.collection.Add(s.snapshot1)
|
||||
s.collection.Add(s.snapshot2)
|
||||
|
||||
count := 0
|
||||
err := s.collection.ForEach(func(*Snapshot) error {
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
c.Assert(count, Equals, 2)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.collection.Len(), Equals, 2)
|
||||
|
||||
e := errors.New("d")
|
||||
err = s.collection.ForEach(func(*Snapshot) error {
|
||||
return e
|
||||
})
|
||||
c.Assert(err, Equals, e)
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
|
||||
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
||||
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
||||
|
||||
c.Check(s.collection.ByRemoteRepoSource(s.repo1), DeepEquals, []*Snapshot{s.snapshot1})
|
||||
c.Check(s.collection.ByRemoteRepoSource(s.repo2), DeepEquals, []*Snapshot{s.snapshot2})
|
||||
|
||||
repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false)
|
||||
|
||||
c.Check(s.collection.ByRemoteRepoSource(repo3), DeepEquals, []*Snapshot{})
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestFindByLocalRepoSource(c *C) {
|
||||
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
||||
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
||||
c.Assert(s.collection.Add(s.snapshot3), IsNil)
|
||||
c.Assert(s.collection.Add(s.snapshot4), IsNil)
|
||||
|
||||
c.Check(s.collection.ByLocalRepoSource(s.lrepo1), DeepEquals, []*Snapshot{s.snapshot3})
|
||||
c.Check(s.collection.ByLocalRepoSource(s.lrepo2), DeepEquals, []*Snapshot{s.snapshot4})
|
||||
|
||||
lrepo3 := NewLocalRepo("other", "")
|
||||
|
||||
c.Check(s.collection.ByLocalRepoSource(lrepo3), DeepEquals, []*Snapshot{})
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestFindSnapshotSource(c *C) {
|
||||
snapshot3 := NewSnapshotFromRefList("snap3", []*Snapshot{s.snapshot1, s.snapshot2}, s.reflist, "desc1")
|
||||
snapshot4 := NewSnapshotFromRefList("snap4", []*Snapshot{s.snapshot1}, s.reflist, "desc2")
|
||||
snapshot5 := NewSnapshotFromRefList("snap5", []*Snapshot{snapshot3}, s.reflist, "desc3")
|
||||
|
||||
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
||||
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
||||
c.Assert(s.collection.Add(snapshot3), IsNil)
|
||||
c.Assert(s.collection.Add(snapshot4), IsNil)
|
||||
c.Assert(s.collection.Add(snapshot5), IsNil)
|
||||
|
||||
c.Check(s.collection.BySnapshotSource(s.snapshot1), DeepEquals, []*Snapshot{snapshot3, snapshot4})
|
||||
c.Check(s.collection.BySnapshotSource(s.snapshot2), DeepEquals, []*Snapshot{snapshot3})
|
||||
c.Check(s.collection.BySnapshotSource(snapshot5), DeepEquals, []*Snapshot{})
|
||||
}
|
||||
|
||||
func (s *SnapshotCollectionSuite) TestDrop(c *C) {
|
||||
s.collection.Add(s.snapshot1)
|
||||
s.collection.Add(s.snapshot2)
|
||||
|
||||
snap, _ := s.collection.ByUUID(s.snapshot1.UUID)
|
||||
c.Check(snap, Equals, s.snapshot1)
|
||||
|
||||
err := s.collection.Drop(s.snapshot1)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.collection.ByUUID(s.snapshot1.UUID)
|
||||
c.Check(err, ErrorMatches, "snapshot .* not found")
|
||||
|
||||
collection := NewSnapshotCollection(s.db)
|
||||
|
||||
_, err = collection.ByUUID(s.snapshot1.UUID)
|
||||
c.Check(err, ErrorMatches, "snapshot .* not found")
|
||||
|
||||
c.Check(func() { s.collection.Drop(s.snapshot1) }, Panics, "snapshot not found!")
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Using documentation from: http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
||||
|
||||
// CompareVersions compares two package versions
|
||||
func CompareVersions(ver1, ver2 string) int {
|
||||
e1, u1, d1 := parseVersion(ver1)
|
||||
e2, u2, d2 := parseVersion(ver2)
|
||||
|
||||
r := compareVersionPart(e1, e2)
|
||||
if r != 0 {
|
||||
return r
|
||||
}
|
||||
|
||||
r = compareVersionPart(u1, u2)
|
||||
if r != 0 {
|
||||
return r
|
||||
}
|
||||
|
||||
return compareVersionPart(d1, d2)
|
||||
}
|
||||
|
||||
// parseVersions breaks down full version to components (possibly empty)
|
||||
func parseVersion(ver string) (epoch, upstream, debian string) {
|
||||
i := strings.LastIndex(ver, "-")
|
||||
if i != -1 {
|
||||
debian, ver = ver[i+1:], ver[:i]
|
||||
}
|
||||
|
||||
i = strings.Index(ver, ":")
|
||||
if i != -1 {
|
||||
epoch, ver = ver[:i], ver[i+1:]
|
||||
}
|
||||
|
||||
upstream = ver
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// compareLexicographic compares in "Debian lexicographic" way, see below compareVersionPart for details
|
||||
func compareLexicographic(s1, s2 string) int {
|
||||
i := 0
|
||||
l1, l2 := len(s1), len(s2)
|
||||
|
||||
for {
|
||||
if i == l1 && i == l2 {
|
||||
// s1 equal to s2
|
||||
break
|
||||
}
|
||||
|
||||
if i == l2 {
|
||||
// s1 is longer than s2
|
||||
if s1[i] == '~' {
|
||||
return -1 // s1 < s2
|
||||
}
|
||||
return 1 // s1 > s2
|
||||
}
|
||||
|
||||
if i == l1 {
|
||||
// s2 is longer than s1
|
||||
if s2[i] == '~' {
|
||||
return 1 // s1 > s2
|
||||
}
|
||||
return -1 // s1 < s2
|
||||
}
|
||||
|
||||
if s1[i] == s2[i] {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if s1[i] == '~' {
|
||||
return -1
|
||||
}
|
||||
|
||||
if s2[i] == '~' {
|
||||
return 1
|
||||
}
|
||||
|
||||
c1, c2 := unicode.IsLetter(rune(s1[i])), unicode.IsLetter(rune(s2[i]))
|
||||
if c1 && !c2 {
|
||||
return -1
|
||||
}
|
||||
if !c1 && c2 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if s1[i] < s2[i] {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// compareVersionPart compares parts of full version
|
||||
//
|
||||
// From Debian Policy Manual:
|
||||
//
|
||||
// "The strings are compared from left to right.
|
||||
//
|
||||
// First the initial part of each string consisting entirely of non-digit characters is
|
||||
// determined. These two parts (one of which may be empty) are compared lexically. If a
|
||||
// difference is found it is returned. The lexical comparison is a comparison of ASCII values
|
||||
// modified so that all the letters sort earlier than all the non-letters and so that a tilde
|
||||
// sorts before anything, even the end of a part. For example, the following parts are in sorted
|
||||
// order from earliest to latest: ~~, ~~a, ~, the empty part.
|
||||
//
|
||||
// Then the initial part of the remainder of each string which consists entirely of digit
|
||||
// characters is determined. The numerical values of these two parts are compared, and any difference
|
||||
// found is returned as the result of the comparison. For these purposes an empty string (which can only occur at
|
||||
// the end of one or both version strings being compared) counts as zero.
|
||||
|
||||
// These two steps (comparing and removing initial non-digit strings and initial digit strings) are
|
||||
// repeated until a difference is found or both strings are exhausted."
|
||||
func compareVersionPart(part1, part2 string) int {
|
||||
i1, i2 := 0, 0
|
||||
l1, l2 := len(part1), len(part2)
|
||||
|
||||
for {
|
||||
j1, j2 := i1, i2
|
||||
for j1 < l1 && !unicode.IsDigit(rune(part1[j1])) {
|
||||
j1++
|
||||
}
|
||||
|
||||
for j2 < l2 && !unicode.IsDigit(rune(part2[j2])) {
|
||||
j2++
|
||||
}
|
||||
|
||||
s1, s2 := part1[i1:j1], part2[i2:j2]
|
||||
r := compareLexicographic(s1, s2)
|
||||
if r != 0 {
|
||||
return r
|
||||
}
|
||||
|
||||
i1, i2 = j1, j2
|
||||
|
||||
for j1 < l1 && unicode.IsDigit(rune(part1[j1])) {
|
||||
j1++
|
||||
}
|
||||
|
||||
for j2 < l2 && unicode.IsDigit(rune(part2[j2])) {
|
||||
j2++
|
||||
}
|
||||
|
||||
s1, s2 = part1[i1:j1], part2[i2:j2]
|
||||
n1, _ := strconv.Atoi(s1)
|
||||
n2, _ := strconv.Atoi(s2)
|
||||
|
||||
if n1 < n2 {
|
||||
return -1
|
||||
}
|
||||
if n1 > n2 {
|
||||
return 1
|
||||
}
|
||||
|
||||
i1, i2 = j1, j2
|
||||
|
||||
if i1 == l1 && i2 == l2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Version relations
|
||||
const (
|
||||
VersionDontCare = iota
|
||||
VersionLess
|
||||
VersionLessOrEqual
|
||||
VersionEqual
|
||||
VersionGreaterOrEqual
|
||||
VersionGreater
|
||||
VersionPatternMatch
|
||||
VersionRegexp
|
||||
)
|
||||
|
||||
// Dependency is a parsed version of Debian dependency to package
|
||||
type Dependency struct {
|
||||
Pkg string
|
||||
Relation int
|
||||
Version string
|
||||
Architecture string
|
||||
Regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
// Hash calculates some predefined unique ID of Dependency
|
||||
func (d *Dependency) Hash() string {
|
||||
return fmt.Sprintf("%s:%s:%d:%s", d.Architecture, d.Pkg, d.Relation, d.Version)
|
||||
}
|
||||
|
||||
// String produces human-readable representation
|
||||
func (d *Dependency) String() string {
|
||||
var rel string
|
||||
switch d.Relation {
|
||||
case VersionEqual:
|
||||
rel = "="
|
||||
case VersionGreater:
|
||||
rel = ">>"
|
||||
case VersionLess:
|
||||
rel = "<<"
|
||||
case VersionGreaterOrEqual:
|
||||
rel = ">="
|
||||
case VersionLessOrEqual:
|
||||
rel = "<="
|
||||
case VersionPatternMatch:
|
||||
rel = "%"
|
||||
case VersionRegexp:
|
||||
rel = "~"
|
||||
case VersionDontCare:
|
||||
return fmt.Sprintf("%s [%s]", d.Pkg, d.Architecture)
|
||||
}
|
||||
return fmt.Sprintf("%s (%s %s) [%s]", d.Pkg, rel, d.Version, d.Architecture)
|
||||
}
|
||||
|
||||
// ParseDependencyVariants parses dependencies in format "pkg (>= 1.35) | other-package"
|
||||
func ParseDependencyVariants(variants string) (l []Dependency, err error) {
|
||||
parts := strings.Split(variants, "|")
|
||||
l = make([]Dependency, len(parts))
|
||||
|
||||
for i, part := range parts {
|
||||
l[i], err = ParseDependency(strings.TrimSpace(part))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseDependency parses dependency in format "pkg (>= 1.35) [arch]" into parts
|
||||
func ParseDependency(dep string) (d Dependency, err error) {
|
||||
if strings.HasSuffix(dep, "}") {
|
||||
i := strings.LastIndex(dep, "{")
|
||||
if i == -1 {
|
||||
err = fmt.Errorf("unable to parse dependency: %s", dep)
|
||||
return
|
||||
}
|
||||
d.Architecture = dep[i+1 : len(dep)-1]
|
||||
|
||||
dep = strings.TrimSpace(dep[:i])
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(dep, ")") {
|
||||
d.Pkg = strings.TrimSpace(dep)
|
||||
d.Relation = VersionDontCare
|
||||
return
|
||||
}
|
||||
|
||||
i := strings.Index(dep, "(")
|
||||
if i == -1 {
|
||||
err = fmt.Errorf("unable to parse dependency: %s", dep)
|
||||
return
|
||||
}
|
||||
|
||||
d.Pkg = strings.TrimSpace(dep[0:i])
|
||||
|
||||
rel := ""
|
||||
if dep[i+1] == '>' || dep[i+1] == '<' || dep[i+1] == '=' {
|
||||
rel += dep[i+1 : i+2]
|
||||
if dep[i+2] == '>' || dep[i+2] == '<' || dep[i+2] == '=' {
|
||||
rel += dep[i+2 : i+3]
|
||||
d.Version = strings.TrimSpace(dep[i+3 : len(dep)-1])
|
||||
} else {
|
||||
d.Version = strings.TrimSpace(dep[i+2 : len(dep)-1])
|
||||
}
|
||||
} else {
|
||||
d.Version = strings.TrimSpace(dep[i+1 : len(dep)-1])
|
||||
}
|
||||
|
||||
switch rel {
|
||||
case "<", "<=":
|
||||
d.Relation = VersionLessOrEqual
|
||||
case ">", ">=":
|
||||
d.Relation = VersionGreaterOrEqual
|
||||
case "<<":
|
||||
d.Relation = VersionLess
|
||||
case ">>":
|
||||
d.Relation = VersionGreater
|
||||
case "", "=":
|
||||
d.Relation = VersionEqual
|
||||
default:
|
||||
err = fmt.Errorf("relation unknown %#v in dependency %s", rel, dep)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type VersionSuite struct {
|
||||
stanza Stanza
|
||||
}
|
||||
|
||||
var _ = Suite(&VersionSuite{})
|
||||
|
||||
func (s *VersionSuite) TestParseVersion(c *C) {
|
||||
e, u, d := parseVersion("1.3.4")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", ""})
|
||||
|
||||
e, u, d = parseVersion("4:1.3:4")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3:4", ""})
|
||||
|
||||
e, u, d = parseVersion("1.3.4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", "1"})
|
||||
|
||||
e, u, d = parseVersion("1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3-pre4", "1"})
|
||||
|
||||
e, u, d = parseVersion("4:1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3-pre4", "1"})
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestCompareLexicographic(c *C) {
|
||||
c.Check(compareLexicographic("", ""), Equals, 0)
|
||||
c.Check(compareLexicographic("pre", "pre"), Equals, 0)
|
||||
|
||||
c.Check(compareLexicographic("pr", "pre"), Equals, -1)
|
||||
c.Check(compareLexicographic("pre", "pr"), Equals, 1)
|
||||
|
||||
c.Check(compareLexicographic("pra", "prb"), Equals, -1)
|
||||
c.Check(compareLexicographic("prb", "pra"), Equals, 1)
|
||||
|
||||
c.Check(compareLexicographic("prx", "pr+"), Equals, -1)
|
||||
c.Check(compareLexicographic("pr+", "prx"), Equals, 1)
|
||||
|
||||
c.Check(compareLexicographic("pr~", "pra"), Equals, -1)
|
||||
c.Check(compareLexicographic("pra", "pr~"), Equals, 1)
|
||||
|
||||
c.Check(compareLexicographic("~~", "~~a"), Equals, -1)
|
||||
c.Check(compareLexicographic("~~a", "~"), Equals, -1)
|
||||
c.Check(compareLexicographic("~", ""), Equals, -1)
|
||||
|
||||
c.Check(compareLexicographic("~~a", "~~"), Equals, 1)
|
||||
c.Check(compareLexicographic("~", "~~a"), Equals, 1)
|
||||
c.Check(compareLexicographic("", "~"), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestCompareVersionPart(c *C) {
|
||||
c.Check(compareVersionPart("", ""), Equals, 0)
|
||||
c.Check(compareVersionPart("pre", "pre"), Equals, 0)
|
||||
c.Check(compareVersionPart("12", "12"), Equals, 0)
|
||||
c.Check(compareVersionPart("1.3.5", "1.3.5"), Equals, 0)
|
||||
c.Check(compareVersionPart("1.3.5-pre1", "1.3.5-pre1"), Equals, 0)
|
||||
|
||||
c.Check(compareVersionPart("1.0~beta1~svn1245", "1.0~beta1"), Equals, -1)
|
||||
c.Check(compareVersionPart("1.0~beta1", "1.0"), Equals, -1)
|
||||
|
||||
c.Check(compareVersionPart("1.0~beta1", "1.0~beta1~svn1245"), Equals, 1)
|
||||
c.Check(compareVersionPart("1.0", "1.0~beta1"), Equals, 1)
|
||||
|
||||
c.Check(compareVersionPart("1.pr", "1.pre"), Equals, -1)
|
||||
c.Check(compareVersionPart("1.pre", "1.pr"), Equals, 1)
|
||||
|
||||
c.Check(compareVersionPart("1.pra", "1.prb"), Equals, -1)
|
||||
c.Check(compareVersionPart("1.prb", "1.pra"), Equals, 1)
|
||||
|
||||
c.Check(compareVersionPart("3.prx", "3.pr+"), Equals, -1)
|
||||
c.Check(compareVersionPart("3.pr+", "3.prx"), Equals, 1)
|
||||
|
||||
c.Check(compareVersionPart("3.pr~", "3.pra"), Equals, -1)
|
||||
c.Check(compareVersionPart("3.pra", "3.pr~"), Equals, 1)
|
||||
|
||||
c.Check(compareVersionPart("2~~", "2~~a"), Equals, -1)
|
||||
c.Check(compareVersionPart("2~~a", "2~"), Equals, -1)
|
||||
c.Check(compareVersionPart("2~", "2"), Equals, -1)
|
||||
|
||||
c.Check(compareVersionPart("2~~a", "2~~"), Equals, 1)
|
||||
c.Check(compareVersionPart("2~", "2~~a"), Equals, 1)
|
||||
c.Check(compareVersionPart("2", "2~"), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestCompareVersions(c *C) {
|
||||
c.Check(CompareVersions("3:1.0~beta1~svn1245-1", "3:1.0~beta1~svn1245-1"), Equals, 0)
|
||||
|
||||
c.Check(CompareVersions("1:1.0~beta1~svn1245-1", "3:1.0~beta1~svn1245-1"), Equals, -1)
|
||||
c.Check(CompareVersions("1:1.0~beta1~svn1245-1", "1.0~beta1~svn1245-1"), Equals, 1)
|
||||
c.Check(CompareVersions("1.0~beta1~svn1245-1", "1.0~beta1~svn1245-2"), Equals, -1)
|
||||
c.Check(CompareVersions("3:1.0~beta1~svn1245-1", "3:1.0~beta1-1"), Equals, -1)
|
||||
|
||||
c.Check(CompareVersions("1.0~beta1~svn1245", "1.0~beta1"), Equals, -1)
|
||||
c.Check(CompareVersions("1.0~beta1", "1.0"), Equals, -1)
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestParseDependency(c *C) {
|
||||
d, e := ParseDependency("dpkg (>= 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionGreaterOrEqual)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
c.Check(d.Architecture, Equals, "")
|
||||
|
||||
d, e = ParseDependency("dpkg(>>1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionGreater)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg(1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionEqual)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg ( 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionEqual)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg (> 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionGreaterOrEqual)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg (< 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionLessOrEqual)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg (= 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionEqual)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg (<< 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionLess)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg(>>1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionGreater)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
|
||||
d, e = ParseDependency("dpkg (>>1.6) {i386}")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionGreater)
|
||||
c.Check(d.Version, Equals, "1.6")
|
||||
c.Check(d.Architecture, Equals, "i386")
|
||||
|
||||
d, e = ParseDependency("dpkg{i386}")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionDontCare)
|
||||
c.Check(d.Version, Equals, "")
|
||||
c.Check(d.Architecture, Equals, "i386")
|
||||
|
||||
d, e = ParseDependency("dpkg ")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(d.Pkg, Equals, "dpkg")
|
||||
c.Check(d.Relation, Equals, VersionDontCare)
|
||||
c.Check(d.Version, Equals, "")
|
||||
|
||||
d, e = ParseDependency("dpkg(==1.6)")
|
||||
c.Check(e, ErrorMatches, "relation unknown.*")
|
||||
|
||||
d, e = ParseDependency("dpkg==1.6)")
|
||||
c.Check(e, ErrorMatches, "unable to parse.*")
|
||||
|
||||
d, e = ParseDependency("dpkg i386}")
|
||||
c.Check(e, ErrorMatches, "unable to parse.*")
|
||||
|
||||
d, e = ParseDependency("dpkg ) {i386}")
|
||||
c.Check(e, ErrorMatches, "unable to parse.*")
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestParseDependencyVariants(c *C) {
|
||||
l, e := ParseDependencyVariants("dpkg (>= 1.6)")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(l, HasLen, 1)
|
||||
c.Check(l[0].Pkg, Equals, "dpkg")
|
||||
c.Check(l[0].Relation, Equals, VersionGreaterOrEqual)
|
||||
c.Check(l[0].Version, Equals, "1.6")
|
||||
|
||||
l, e = ParseDependencyVariants("dpkg (>= 1.6) | mailer-agent")
|
||||
c.Check(e, IsNil)
|
||||
c.Check(l, HasLen, 2)
|
||||
c.Check(l[0].Pkg, Equals, "dpkg")
|
||||
c.Check(l[0].Relation, Equals, VersionGreaterOrEqual)
|
||||
c.Check(l[0].Version, Equals, "1.6")
|
||||
c.Check(l[1].Pkg, Equals, "mailer-agent")
|
||||
c.Check(l[1].Relation, Equals, VersionDontCare)
|
||||
|
||||
_, e = ParseDependencyVariants("dpkg(==1.6)")
|
||||
c.Check(e, ErrorMatches, "relation unknown.*")
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestDependencyString(c *C) {
|
||||
d, _ := ParseDependency("dpkg(>>1.6)")
|
||||
d.Architecture = "i386"
|
||||
c.Check(d.String(), Equals, "dpkg (>> 1.6) [i386]")
|
||||
|
||||
d, _ = ParseDependency("dpkg")
|
||||
d.Architecture = "i386"
|
||||
c.Check(d.String(), Equals, "dpkg [i386]")
|
||||
}
|
||||
Vendored
-120
@@ -1,120 +0,0 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/ugorji/go/codec"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PackageList is list of unique (by key) packages
|
||||
//
|
||||
// It could be seen as repo snapshot, repo contents, result of filtering,
|
||||
// merge, etc.
|
||||
type PackageList struct {
|
||||
packages map[string]*Package
|
||||
}
|
||||
|
||||
// NewPackageList creates empty package list
|
||||
func NewPackageList() *PackageList {
|
||||
return &PackageList{packages: make(map[string]*Package, 1000)}
|
||||
}
|
||||
|
||||
// Add appends package to package list, additionally checking for uniqueness
|
||||
func (l *PackageList) Add(p *Package) error {
|
||||
key := string(p.Key())
|
||||
existing, ok := l.packages[key]
|
||||
if ok {
|
||||
if !existing.Equals(p) {
|
||||
return fmt.Errorf("conflict in package %s: %#v != %#v", p, existing, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
l.packages[key] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForEach calls handler for each package in list
|
||||
func (l *PackageList) ForEach(handler func(*Package) error) error {
|
||||
var err error
|
||||
for _, p := range l.packages {
|
||||
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)
|
||||
}
|
||||
|
||||
// PackageRefList is a list of keys of packages, this is basis for snapshot
|
||||
// and similar stuff
|
||||
//
|
||||
// Refs are sorted in lexographical order
|
||||
type PackageRefList struct {
|
||||
// List of package keys
|
||||
Refs [][]byte
|
||||
}
|
||||
|
||||
// NewPackageRefListFromPackageList creates PackageRefList from PackageList
|
||||
func NewPackageRefListFromPackageList(list *PackageList) *PackageRefList {
|
||||
reflist := &PackageRefList{}
|
||||
reflist.Refs = make([][]byte, list.Len())
|
||||
|
||||
i := 0
|
||||
for _, p := range list.packages {
|
||||
reflist.Refs[i] = p.Key()
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Sort(reflist)
|
||||
|
||||
return reflist
|
||||
}
|
||||
|
||||
// Len returns number of refs
|
||||
func (l *PackageRefList) Len() int {
|
||||
return len(l.Refs)
|
||||
}
|
||||
|
||||
// Swap swaps two refs
|
||||
func (l *PackageRefList) Swap(i, j int) {
|
||||
l.Refs[i], l.Refs[j] = l.Refs[j], l.Refs[i]
|
||||
}
|
||||
|
||||
// Compare compares two refs in lexographical order
|
||||
func (l *PackageRefList) Less(i, j int) bool {
|
||||
return bytes.Compare(l.Refs[i], l.Refs[j]) < 0
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of PackageRefList
|
||||
func (l *PackageRefList) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(l)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Decode decodes msgpack representation into PackageRefLit
|
||||
func (l *PackageRefList) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
return decoder.Decode(l)
|
||||
}
|
||||
|
||||
// ForEach calls handler for each package ref in list
|
||||
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
||||
var err error
|
||||
for _, p := range l.Refs {
|
||||
err = handler(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
Vendored
-121
@@ -1,121 +0,0 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type PackageListSuite struct {
|
||||
list *PackageList
|
||||
p1, p2, p3, p4, p5, p6 *Package
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageListSuite{})
|
||||
|
||||
func (s *PackageListSuite) SetUpTest(c *C) {
|
||||
s.list = NewPackageList()
|
||||
|
||||
s.p1 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
s.p2 = NewPackageFromControlFile(packageStanza.Copy())
|
||||
stanza := packageStanza.Copy()
|
||||
stanza["Package"] = "mars-invaders"
|
||||
s.p3 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Size"] = "42"
|
||||
s.p4 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Package"] = "lonely-strangers"
|
||||
s.p5 = NewPackageFromControlFile(stanza)
|
||||
stanza = packageStanza.Copy()
|
||||
stanza["Version"] = "99.1"
|
||||
s.p6 = NewPackageFromControlFile(stanza)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestAddLen(c *C) {
|
||||
c.Check(s.list.Len(), Equals, 0)
|
||||
c.Check(s.list.Add(s.p1), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 1)
|
||||
c.Check(s.list.Add(s.p2), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 1)
|
||||
c.Check(s.list.Add(s.p3), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 2)
|
||||
c.Check(s.list.Add(s.p4), ErrorMatches, "conflict in package.*")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestForeach(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
|
||||
Len := 0
|
||||
err := s.list.ForEach(func(*Package) error {
|
||||
Len++
|
||||
return nil
|
||||
})
|
||||
|
||||
c.Check(Len, Equals, 2)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
e := errors.New("a")
|
||||
|
||||
err = s.list.ForEach(func(*Package) error {
|
||||
return e
|
||||
})
|
||||
|
||||
c.Check(err, Equals, e)
|
||||
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestNewPackageRefList(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
c.Assert(reflist.Len(), Equals, 4)
|
||||
c.Check(reflist.Refs[0], DeepEquals, []byte(s.p1.Key()))
|
||||
c.Check(reflist.Refs[1], DeepEquals, []byte(s.p6.Key()))
|
||||
c.Check(reflist.Refs[2], DeepEquals, []byte(s.p5.Key()))
|
||||
c.Check(reflist.Refs[3], DeepEquals, []byte(s.p3.Key()))
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestPackageRefListEncodeDecode(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
reflist2 := &PackageRefList{}
|
||||
err := reflist2.Decode(reflist.Encode())
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(reflist2.Len(), Equals, reflist.Len())
|
||||
c.Check(reflist2.Refs, DeepEquals, reflist.Refs)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestPackageRefListForeach(c *C) {
|
||||
s.list.Add(s.p1)
|
||||
s.list.Add(s.p3)
|
||||
s.list.Add(s.p5)
|
||||
s.list.Add(s.p6)
|
||||
|
||||
reflist := NewPackageRefListFromPackageList(s.list)
|
||||
|
||||
Len := 0
|
||||
err := reflist.ForEach(func([]byte) error {
|
||||
Len++
|
||||
return nil
|
||||
})
|
||||
|
||||
c.Check(Len, Equals, 4)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
e := errors.New("b")
|
||||
|
||||
err = reflist.ForEach(func([]byte) error {
|
||||
return e
|
||||
})
|
||||
|
||||
c.Check(err, Equals, e)
|
||||
}
|
||||
Vendored
-195
@@ -1,195 +0,0 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/ugorji/go/codec"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Package is single instance of Debian package
|
||||
//
|
||||
// TODO: support source & binary
|
||||
type Package struct {
|
||||
Name string
|
||||
Version string
|
||||
Filename string
|
||||
Filesize int64
|
||||
Architecture string
|
||||
Source string
|
||||
// Various dependencies
|
||||
Depends []string
|
||||
PreDepends []string
|
||||
Suggests []string
|
||||
Recommends []string
|
||||
// Hashsums of package contents
|
||||
HashMD5 string
|
||||
HashSHA1 string
|
||||
HashSHA256 string
|
||||
// Extra information from stanza
|
||||
Extra Stanza
|
||||
}
|
||||
|
||||
func parseDependencies(input Stanza, key string) []string {
|
||||
value, ok := input[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(input, key)
|
||||
|
||||
return strings.Split(value, ", ")
|
||||
}
|
||||
|
||||
// NewPackageFromControlFile creates Package from parsed Debian control file
|
||||
func NewPackageFromControlFile(input Stanza) *Package {
|
||||
result := &Package{
|
||||
Name: input["Package"],
|
||||
Version: input["Version"],
|
||||
Filename: input["Filename"],
|
||||
Architecture: input["Architecture"],
|
||||
Source: input["Source"],
|
||||
HashMD5: input["MD5sum"],
|
||||
HashSHA1: input["SHA1"],
|
||||
HashSHA256: input["SHA256"],
|
||||
}
|
||||
|
||||
delete(input, "Package")
|
||||
delete(input, "Version")
|
||||
delete(input, "Filename")
|
||||
delete(input, "Architecture")
|
||||
delete(input, "Source")
|
||||
delete(input, "MD5sum")
|
||||
delete(input, "SHA1")
|
||||
delete(input, "SHA256")
|
||||
|
||||
result.Filesize, _ = strconv.ParseInt(input["Size"], 10, 64)
|
||||
delete(input, "Size")
|
||||
|
||||
result.Depends = parseDependencies(input, "Depends")
|
||||
result.PreDepends = parseDependencies(input, "Pre-Depends")
|
||||
result.Suggests = parseDependencies(input, "Suggests")
|
||||
result.Recommends = parseDependencies(input, "Recommends")
|
||||
|
||||
result.Extra = input
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Key returns unique key identifying package
|
||||
func (p *Package) Key() []byte {
|
||||
return []byte("P" + p.Name + " " + p.Version + " " + p.Architecture)
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of Package
|
||||
func (p *Package) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(p)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Decode decodes msgpack representation into Package
|
||||
func (p *Package) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
return decoder.Decode(p)
|
||||
}
|
||||
|
||||
// String creates readable representation
|
||||
func (p *Package) String() string {
|
||||
return fmt.Sprintf("%s-%s_%s", p.Name, p.Version, p.Architecture)
|
||||
}
|
||||
|
||||
// Stanza creates original stanza from package
|
||||
func (p *Package) Stanza() (result Stanza) {
|
||||
result = p.Extra.Copy()
|
||||
result["Package"] = p.Name
|
||||
result["Version"] = p.Version
|
||||
result["Filename"] = p.Filename
|
||||
result["Architecture"] = p.Architecture
|
||||
result["Source"] = p.Source
|
||||
|
||||
if p.HashMD5 != "" {
|
||||
result["MD5sum"] = p.HashMD5
|
||||
}
|
||||
if p.HashSHA1 != "" {
|
||||
result["SHA1"] = p.HashSHA1
|
||||
}
|
||||
if p.HashSHA256 != "" {
|
||||
result["SHA256"] = p.HashSHA256
|
||||
}
|
||||
|
||||
if p.Depends != nil {
|
||||
result["Depends"] = strings.Join(p.Depends, ", ")
|
||||
}
|
||||
if p.PreDepends != nil {
|
||||
result["Pre-Depends"] = strings.Join(p.PreDepends, ", ")
|
||||
}
|
||||
if p.Suggests != nil {
|
||||
result["Suggests"] = strings.Join(p.Suggests, ", ")
|
||||
}
|
||||
if p.Recommends != nil {
|
||||
result["Recommends"] = strings.Join(p.Recommends, ", ")
|
||||
}
|
||||
|
||||
result["Size"] = fmt.Sprintf("%d", p.Filesize)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Equals compares two packages to be identical
|
||||
func (p *Package) Equals(p2 *Package) bool {
|
||||
return p.Name == p2.Name && p.Version == p2.Version && p.Filename == p2.Filename &&
|
||||
p.Architecture == p2.Architecture && utils.StrSlicesEqual(p.Depends, p2.Depends) &&
|
||||
utils.StrSlicesEqual(p.PreDepends, p2.PreDepends) && utils.StrSlicesEqual(p.Suggests, p2.Suggests) &&
|
||||
utils.StrSlicesEqual(p.Recommends, p2.Recommends) && utils.StrMapsEqual(p.Extra, p2.Extra) &&
|
||||
p.Filesize == p2.Filesize && p.HashMD5 == p2.HashMD5 && p.HashSHA1 == p2.HashSHA1 &&
|
||||
p.HashSHA256 == p2.HashSHA256 && p.Source == p2.Source
|
||||
}
|
||||
|
||||
// VerifyFile verifies integrity and existence of local files for the package
|
||||
func (p *Package) VerifyFile(filepath string) bool {
|
||||
st, err := os.Stat(filepath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return st.Size() == p.Filesize
|
||||
}
|
||||
|
||||
// PackageCollection does management of packages in DB
|
||||
type PackageCollection struct {
|
||||
db database.Storage
|
||||
}
|
||||
|
||||
// NewPackageCollection creates new PackageCollection and binds it to database
|
||||
func NewPackageCollection(db database.Storage) *PackageCollection {
|
||||
return &PackageCollection{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// ByKey find package in DB by its key
|
||||
func (collection *PackageCollection) ByKey(key []byte) (*Package, error) {
|
||||
encoded, err := collection.db.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Package{}
|
||||
err = p.Decode(encoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Update adds or updates information about package in DB
|
||||
func (collection *PackageCollection) Update(p *Package) error {
|
||||
return collection.db.Put(p.Key(), p.Encode())
|
||||
}
|
||||
Vendored
-96
@@ -1,96 +0,0 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
var packageStanza = Stanza{"Source": "alien-arena", "Depends": "libc6 (>= 2.7), alien-arena-data (>= 7.40)", "Filename": "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb", "SHA1": "46955e48cad27410a83740a21d766ce362364024", "SHA256": "eb4afb9885cba6dc70cccd05b910b2dbccc02c5900578be5e99f0d3dbf9d76a5", "Priority": "extra", "Maintainer": "Debian Games Team <pkg-games-devel@lists.alioth.debian.org>", "Description": "Common files for Alien Arena client and server ALIEN ARENA is a standalone 3D first person online deathmatch shooter\n crafted from the original source code of Quake II and Quake III, released\n by id Software under the GPL license. With features including 32 bit\n graphics, new particle engine and effects, light blooms, reflective water,\n hi resolution textures and skins, hi poly models, stain maps, ALIEN ARENA\n pushes the envelope of graphical beauty rivaling today's top games.\n .\n This package installs the common files for Alien Arena.\n", "Homepage": "http://red.planetarena.org", "Tag": "role::app-data, role::shared-lib, special::auto-inst-parts", "Installed-Size": "456", "Version": "7.40-2", "Replaces": "alien-arena (<< 7.33-1)", "Size": "187518", "MD5sum": "1e8cba92c41420aa7baa8a5718d67122", "Package": "alien-arena-common", "Section": "contrib/games", "Architecture": "i386"}
|
||||
|
||||
type PackageSuite struct {
|
||||
stanza Stanza
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageSuite{})
|
||||
|
||||
func (s *PackageSuite) SetUpTest(c *C) {
|
||||
s.stanza = packageStanza.Copy()
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestNewFromPara(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.Name, Equals, "alien-arena-common")
|
||||
c.Check(p.Version, Equals, "7.40-2")
|
||||
c.Check(p.Architecture, Equals, "i386")
|
||||
c.Check(p.Filename, Equals, "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb")
|
||||
c.Check(p.Depends, DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)"})
|
||||
c.Check(p.Suggests, IsNil)
|
||||
c.Check(p.Filesize, Equals, int64(187518))
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestKey(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
c.Check(p.Key(), DeepEquals, []byte("Palien-arena-common 7.40-2 i386"))
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestEncodeDecode(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
encoded := p.Encode()
|
||||
p2 := &Package{}
|
||||
err := p2.Decode(encoded)
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p2, DeepEquals, p)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestStanza(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza.Copy())
|
||||
stanza := p.Stanza()
|
||||
|
||||
c.Assert(stanza, DeepEquals, s.stanza)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestString(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
c.Assert(p.String(), Equals, "alien-arena-common-7.40-2_i386")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestEquals(c *C) {
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
|
||||
stanza2 := packageStanza.Copy()
|
||||
p2 := NewPackageFromControlFile(stanza2)
|
||||
c.Check(p.Equals(p2), Equals, true)
|
||||
|
||||
p2.Depends = []string{"package1"}
|
||||
c.Check(p.Equals(p2), Equals, false)
|
||||
}
|
||||
|
||||
type PackageCollectionSuite struct {
|
||||
collection *PackageCollection
|
||||
p *Package
|
||||
db database.Storage
|
||||
}
|
||||
|
||||
var _ = Suite(&PackageCollectionSuite{})
|
||||
|
||||
func (s *PackageCollectionSuite) SetUpTest(c *C) {
|
||||
s.p = NewPackageFromControlFile(packageStanza.Copy())
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
s.collection = NewPackageCollection(s.db)
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TearDownTest(c *C) {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestUpdateByKey(c *C) {
|
||||
err := s.collection.Update(s.p)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
p2, err := s.collection.ByKey(s.p.Key())
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p2.Equals(s.p), Equals, true)
|
||||
}
|
||||
Vendored
-212
@@ -1,212 +0,0 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/utils"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PublishedRepo is a published for http/ftp representation of snapshot as Debian repository
|
||||
type PublishedRepo struct {
|
||||
// Prefix & distribution should be unique across all published repositories
|
||||
Prefix string
|
||||
Distribution string
|
||||
Component string
|
||||
// Architectures is a list of all architectures published
|
||||
Architectures []string
|
||||
// Snapshot as a source of publishing
|
||||
SnapshotUUID string
|
||||
|
||||
snapshot *Snapshot
|
||||
}
|
||||
|
||||
// NewPublishedRepo creates new published repository
|
||||
func NewPublishedRepo(prefix string, distribution string, component string, architectures []string, snapshot *Snapshot) *PublishedRepo {
|
||||
return &PublishedRepo{
|
||||
Prefix: prefix,
|
||||
Distribution: distribution,
|
||||
Component: component,
|
||||
Architectures: architectures,
|
||||
SnapshotUUID: snapshot.UUID,
|
||||
snapshot: snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
// Publish publishes snapshot (repository) contents, links package files, generates Packages & Release files, signs them
|
||||
func (p *PublishedRepo) Publish(repo *Repository, packageCollection *PackageCollection, signer utils.Signer) error {
|
||||
err := repo.MkDir(filepath.Join(p.Prefix, "pool"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
basePath := filepath.Join(p.Prefix, "dists", p.Distribution)
|
||||
err = repo.MkDir(basePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load all packages
|
||||
list := NewPackageList()
|
||||
|
||||
err = p.snapshot.RefList().ForEach(func(key []byte) error {
|
||||
pkg, err := packageCollection.ByKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return list.Add(pkg)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load packages: %s", err)
|
||||
}
|
||||
|
||||
if list.Len() == 0 {
|
||||
return fmt.Errorf("repository is empty, can't publish")
|
||||
}
|
||||
|
||||
if p.Architectures == nil {
|
||||
p.Architectures = make([]string, 0, 10)
|
||||
list.ForEach(func(pkg *Package) error {
|
||||
if pkg.Architecture != "all" && !utils.StrSliceHasItem(p.Architectures, pkg.Architecture) {
|
||||
p.Architectures = append(p.Architectures, pkg.Architecture)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if len(p.Architectures) == 0 {
|
||||
return fmt.Errorf("unable to figure out list of architectures, please supply explicit list")
|
||||
}
|
||||
|
||||
generatedFiles := map[string]*utils.ChecksumInfo{}
|
||||
|
||||
// For all architectures, generate release file
|
||||
for _, arch := range p.Architectures {
|
||||
relativePath := filepath.Join(p.Component, fmt.Sprintf("binary-%s", arch), "Packages")
|
||||
err = repo.MkDir(filepath.Dir(filepath.Join(basePath, relativePath)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
packagesFile, err := repo.CreateFile(filepath.Join(basePath, relativePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to creates Packages file: %s", err)
|
||||
}
|
||||
|
||||
bufWriter := bufio.NewWriter(packagesFile)
|
||||
|
||||
err = list.ForEach(func(pkg *Package) error {
|
||||
if pkg.Architecture == arch || pkg.Architecture == "all" {
|
||||
source := pkg.Source
|
||||
if source == "" {
|
||||
source = pkg.Name
|
||||
}
|
||||
|
||||
path, err := repo.LinkFromPool(p.Prefix, p.Component, pkg.Filename, pkg.HashMD5, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg.Filename = path
|
||||
|
||||
err = pkg.Stanza().WriteTo(bufWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bufWriter.WriteByte('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to creates process packages: %s", err)
|
||||
}
|
||||
|
||||
err = bufWriter.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write Packages file: %s", err)
|
||||
}
|
||||
|
||||
err = utils.CompressFile(packagesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to compress Packages files: %s", err)
|
||||
}
|
||||
|
||||
packagesFile.Close()
|
||||
|
||||
checksumInfo, err := repo.ChecksumsForFile(filepath.Join(basePath, relativePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect checksums: %s", err)
|
||||
}
|
||||
generatedFiles[relativePath] = checksumInfo
|
||||
|
||||
checksumInfo, err = repo.ChecksumsForFile(filepath.Join(basePath, relativePath+".gz"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect checksums: %s", err)
|
||||
}
|
||||
generatedFiles[relativePath+".gz"] = checksumInfo
|
||||
|
||||
checksumInfo, err = repo.ChecksumsForFile(filepath.Join(basePath, relativePath+".bz2"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to collect checksums: %s", err)
|
||||
}
|
||||
generatedFiles[relativePath+".bz2"] = checksumInfo
|
||||
|
||||
}
|
||||
|
||||
release := make(Stanza)
|
||||
release["Origin"] = p.Prefix + " " + p.Distribution
|
||||
release["Label"] = p.Prefix + " " + p.Distribution
|
||||
release["Codename"] = p.Distribution
|
||||
release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST")
|
||||
release["Components"] = p.Component
|
||||
release["Architectures"] = strings.Join(p.Architectures, " ")
|
||||
release["Description"] = "Generated by aptly\n"
|
||||
release["MD5Sum"] = "\n"
|
||||
release["SHA1"] = "\n"
|
||||
release["SHA256"] = "\n"
|
||||
|
||||
for path, info := range generatedFiles {
|
||||
release["MD5Sum"] += fmt.Sprintf(" %s %8d %s\n", info.MD5, info.Size, path)
|
||||
release["SHA1"] += fmt.Sprintf(" %s %8d %s\n", info.SHA1, info.Size, path)
|
||||
release["SHA256"] += fmt.Sprintf(" %s %8d %s\n", info.SHA256, info.Size, path)
|
||||
}
|
||||
|
||||
releaseFile, err := repo.CreateFile(filepath.Join(basePath, "Release"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
bufWriter := bufio.NewWriter(releaseFile)
|
||||
|
||||
err = release.WriteTo(bufWriter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
err = bufWriter.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create Release file: %s", err)
|
||||
}
|
||||
|
||||
releaseFilename := releaseFile.Name()
|
||||
releaseFile.Close()
|
||||
|
||||
err = signer.DetachedSign(releaseFilename, releaseFilename+".gpg")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to sign Release file: %s", err)
|
||||
}
|
||||
|
||||
err = signer.ClearSign(releaseFilename, filepath.Join(filepath.Dir(releaseFilename), "InRelease"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to sign Release file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Vendored
-95
@@ -1,95 +0,0 @@
|
||||
package debian
|
||||
|
||||
import (
|
||||
"github.com/smira/aptly/database"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type NullSigner struct{}
|
||||
|
||||
func (n *NullSigner) SetKey(keyRef string) {
|
||||
|
||||
}
|
||||
|
||||
func (n *NullSigner) DetachedSign(source string, destination string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NullSigner) ClearSign(source string, destination string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PublishedRepoSuite struct {
|
||||
PackageListMixinSuite
|
||||
repo *PublishedRepo
|
||||
packageRepo *Repository
|
||||
db database.Storage
|
||||
packageCollection *PackageCollection
|
||||
}
|
||||
|
||||
var _ = Suite(&PublishedRepoSuite{})
|
||||
|
||||
func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
||||
s.SetUpPackages()
|
||||
|
||||
s.db, _ = database.OpenDB(c.MkDir())
|
||||
|
||||
s.packageRepo = NewRepository(c.MkDir())
|
||||
|
||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{})
|
||||
repo.packageRefs = s.reflist
|
||||
|
||||
snapshot, _ := NewSnapshotFromRepository("snap", repo)
|
||||
|
||||
s.repo = NewPublishedRepo("ppa", "squeeze", "main", nil, snapshot)
|
||||
|
||||
s.packageCollection = NewPackageCollection(s.db)
|
||||
s.packageCollection.Update(s.p1)
|
||||
s.packageCollection.Update(s.p2)
|
||||
s.packageCollection.Update(s.p3)
|
||||
|
||||
poolPath, _ := s.packageRepo.PoolPath(s.p1.Filename, s.p1.HashMD5)
|
||||
err := os.MkdirAll(filepath.Dir(poolPath), 0755)
|
||||
f, err := os.Create(poolPath)
|
||||
c.Assert(err, IsNil)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublish(c *C) {
|
||||
err := s.repo.Publish(s.packageRepo, s.packageCollection, &NullSigner{})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(s.repo.Architectures, DeepEquals, []string{"i386"})
|
||||
|
||||
rf, err := os.Open(filepath.Join(s.packageRepo.RootPath, "public/ppa/dists/squeeze/Release"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cfr := NewControlFileReader(rf)
|
||||
st, err := cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(st["Origin"], Equals, "ppa squeeze")
|
||||
c.Check(st["Components"], Equals, "main")
|
||||
c.Check(st["Architectures"], Equals, "i386")
|
||||
|
||||
pf, err := os.Open(filepath.Join(s.packageRepo.RootPath, "public/ppa/dists/squeeze/main/binary-i386/Packages"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cfr = NewControlFileReader(pf)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
st, err = cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(st["Filename"], Equals, "pool/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb")
|
||||
}
|
||||
|
||||
st, err = cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(st, IsNil)
|
||||
|
||||
_, err = os.Stat(filepath.Join(s.packageRepo.RootPath, "public/ppa/pool/main/a/alien-arena/alien-arena-common_7.40-2_i386.deb"))
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
Vendored
-362
@@ -1,362 +0,0 @@
|
||||
// Package debian implements Debian-specific repository handling
|
||||
package debian
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"fmt"
|
||||
"github.com/smira/aptly/database"
|
||||
"github.com/smira/aptly/utils"
|
||||
"github.com/ugorji/go/codec"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RemoteRepo represents remote (fetchable) Debian repository.
|
||||
//
|
||||
// Repostitory could be filtered when fetching by components, architectures
|
||||
// TODO: support flat format
|
||||
type RemoteRepo struct {
|
||||
// Permanent internal ID
|
||||
UUID string
|
||||
// User-assigned name
|
||||
Name string
|
||||
// Root of Debian archive, URL
|
||||
ArchiveRoot string
|
||||
// Distribution name, e.g. squeeze
|
||||
Distribution string
|
||||
// List of components to fetch, if empty, then fetch all components
|
||||
Components []string
|
||||
// List of architectures to fetch, if empty, then fetch all architectures
|
||||
Architectures []string
|
||||
// Meta-information about repository
|
||||
Meta Stanza
|
||||
// Last update date
|
||||
LastDownloadDate time.Time
|
||||
// "Snapshot" of current list of packages
|
||||
packageRefs *PackageRefList
|
||||
// Parsed archived root
|
||||
archiveRootURL *url.URL
|
||||
}
|
||||
|
||||
// NewRemoteRepo creates new instance of Debian remote repository with specified params
|
||||
func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string, architectures []string) (*RemoteRepo, error) {
|
||||
result := &RemoteRepo{
|
||||
UUID: uuid.New(),
|
||||
Name: name,
|
||||
ArchiveRoot: archiveRoot,
|
||||
Distribution: distribution,
|
||||
Components: components,
|
||||
Architectures: architectures,
|
||||
}
|
||||
|
||||
err := result.prepare()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *RemoteRepo) prepare() error {
|
||||
var err error
|
||||
repo.archiveRootURL, err = url.Parse(repo.ArchiveRoot)
|
||||
return err
|
||||
}
|
||||
|
||||
// String interface
|
||||
func (repo *RemoteRepo) String() string {
|
||||
return fmt.Sprintf("[%s]: %s %s", repo.Name, repo.ArchiveRoot, repo.Distribution)
|
||||
}
|
||||
|
||||
// NumPackages return number of packages retrived from remore repo
|
||||
func (repo *RemoteRepo) NumPackages() int {
|
||||
if repo.packageRefs == nil {
|
||||
return 0
|
||||
}
|
||||
return repo.packageRefs.Len()
|
||||
}
|
||||
|
||||
// ReleaseURL returns URL to Release file in repo root
|
||||
// TODO: InRelease, Release.gz, Release.bz2 handling
|
||||
func (repo *RemoteRepo) ReleaseURL() *url.URL {
|
||||
path := &url.URL{Path: fmt.Sprintf("dists/%s/Release", repo.Distribution)}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// BinaryURL returns URL of Packages file for given component and
|
||||
// architecture
|
||||
func (repo *RemoteRepo) BinaryURL(component string, architecture string) *url.URL {
|
||||
path := &url.URL{Path: fmt.Sprintf("dists/%s/%s/binary-%s/Packages", repo.Distribution, component, architecture)}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// PackageURL returns URL of package file relative to repository root
|
||||
// architecture
|
||||
func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
|
||||
path := &url.URL{Path: filename}
|
||||
return repo.archiveRootURL.ResolveReference(path)
|
||||
}
|
||||
|
||||
// Fetch updates information about repository
|
||||
func (repo *RemoteRepo) Fetch(d utils.Downloader) error {
|
||||
// Download release file to temporary URL
|
||||
release, err := utils.DownloadTemp(d, repo.ReleaseURL().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release.Close()
|
||||
|
||||
sreader := NewControlFileReader(release)
|
||||
stanza, err := sreader.ReadStanza()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
architectures := strings.Split(stanza["Architectures"], " ")
|
||||
if len(repo.Architectures) == 0 {
|
||||
repo.Architectures = architectures
|
||||
} else {
|
||||
err = utils.StringsIsSubset(repo.Architectures, architectures,
|
||||
fmt.Sprintf("architecture %%s not available in repo %s", repo))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
components := strings.Split(stanza["Components"], " ")
|
||||
if len(repo.Components) == 0 {
|
||||
repo.Components = components
|
||||
} else {
|
||||
err = utils.StringsIsSubset(repo.Components, components,
|
||||
fmt.Sprintf("component %%s not available in repo %s", repo))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
delete(stanza, "MD5Sum")
|
||||
delete(stanza, "SHA1")
|
||||
delete(stanza, "SHA256")
|
||||
repo.Meta = stanza
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download downloads all repo files
|
||||
func (repo *RemoteRepo) Download(d utils.Downloader, packageCollection *PackageCollection, packageRepo *Repository) error {
|
||||
list := NewPackageList()
|
||||
|
||||
// Download and parse all Release files
|
||||
for _, component := range repo.Components {
|
||||
for _, architecture := range repo.Architectures {
|
||||
packagesReader, packagesFile, err := utils.DownloadTryCompression(d, repo.BinaryURL(component, architecture).String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer packagesFile.Close()
|
||||
|
||||
sreader := NewControlFileReader(packagesReader)
|
||||
|
||||
for {
|
||||
stanza, err := sreader.ReadStanza()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stanza == nil {
|
||||
break
|
||||
}
|
||||
|
||||
p := NewPackageFromControlFile(stanza)
|
||||
|
||||
list.Add(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save package meta information to DB
|
||||
err := list.ForEach(func(p *Package) error {
|
||||
return packageCollection.Update(p)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save packages to db: %s", err)
|
||||
}
|
||||
|
||||
// Download all package files
|
||||
ch := make(chan error, list.Len())
|
||||
count := 0
|
||||
|
||||
err = list.ForEach(func(p *Package) error {
|
||||
poolPath, err := packageRepo.PoolPath(p.Filename, p.HashMD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !p.VerifyFile(poolPath) {
|
||||
d.Download(repo.PackageURL(p.Filename).String(), poolPath, ch)
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to download packages: %s", err)
|
||||
}
|
||||
|
||||
errors := make([]string, 0)
|
||||
|
||||
// Wait for all downloads to finish
|
||||
for count > 0 {
|
||||
err = <-ch
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
count--
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("download errors: %s", strings.Join(errors, ", "))
|
||||
}
|
||||
|
||||
repo.LastDownloadDate = time.Now()
|
||||
repo.packageRefs = NewPackageRefListFromPackageList(list)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode does msgpack encoding of RemoteRepo
|
||||
func (repo *RemoteRepo) Encode() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
encoder.Encode(repo)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Decode decodes msgpack representation into RemoteRepo
|
||||
func (repo *RemoteRepo) Decode(input []byte) error {
|
||||
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
||||
err := decoder.Decode(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repo.prepare()
|
||||
}
|
||||
|
||||
// Key is a unique id in DB
|
||||
func (repo *RemoteRepo) Key() []byte {
|
||||
return []byte("R" + repo.UUID)
|
||||
}
|
||||
|
||||
// RefKey is a unique id for package reference list
|
||||
func (repo *RemoteRepo) RefKey() []byte {
|
||||
return []byte("E" + repo.UUID)
|
||||
}
|
||||
|
||||
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
||||
type RemoteRepoCollection struct {
|
||||
db database.Storage
|
||||
list []*RemoteRepo
|
||||
}
|
||||
|
||||
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
||||
func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
|
||||
result := &RemoteRepoCollection{
|
||||
db: db,
|
||||
}
|
||||
|
||||
blobs := db.FetchByPrefix([]byte("R"))
|
||||
result.list = make([]*RemoteRepo, 0, len(blobs))
|
||||
|
||||
for _, blob := range blobs {
|
||||
r := &RemoteRepo{}
|
||||
if err := r.Decode(blob); err != nil {
|
||||
log.Printf("Error decoding mirror: %s\n", err)
|
||||
} else {
|
||||
result.list = append(result.list, r)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add appends new repo to collection and saves it
|
||||
func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
|
||||
for _, r := range collection.list {
|
||||
if r.Name == repo.Name {
|
||||
return fmt.Errorf("mirror with name %s already exists", repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
err := collection.Update(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collection.list = append(collection.list, repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update stores updated information about repo in DB
|
||||
func (collection *RemoteRepoCollection) Update(repo *RemoteRepo) error {
|
||||
err := collection.db.Put(repo.Key(), repo.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repo.packageRefs != nil {
|
||||
err = collection.db.Put(repo.RefKey(), repo.packageRefs.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadComplete loads additional information for remote repo
|
||||
func (collection *RemoteRepoCollection) LoadComplete(repo *RemoteRepo) error {
|
||||
encoded, err := collection.db.Get(repo.RefKey())
|
||||
if err == database.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.packageRefs = &PackageRefList{}
|
||||
return repo.packageRefs.Decode(encoded)
|
||||
}
|
||||
|
||||
// ByName looks up repository by name
|
||||
func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
|
||||
for _, r := range collection.list {
|
||||
if r.Name == name {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("mirror with name %s not found", name)
|
||||
}
|
||||
|
||||
// ByUUID looks up repository by uuid
|
||||
func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
|
||||
for _, r := range collection.list {
|
||||
if r.UUID == uuid {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("mirror with uuid %s not found", uuid)
|
||||
}
|
||||
|
||||
// ForEach runs method for each repository
|
||||
func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error) error {
|
||||
var err error
|
||||
for _, r := range collection.list {
|
||||
err = handler(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user