mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 20:26:54 +00:00
Compare commits
970 Commits
master
...
os-release
| Author | SHA1 | Date | |
|---|---|---|---|
| b58a22a7e1 | |||
| 8eba018b61 | |||
| 1f9557ac66 | |||
| 1c783567c0 | |||
| f3b2b8700f | |||
| 7be79767c1 | |||
| eaa1fc41c6 | |||
| cfdb954303 | |||
| d65ac42ca8 | |||
| 0780ddc498 | |||
| ab31d9500e | |||
| bab701c143 | |||
| 6a64865991 | |||
| bece5b82a8 | |||
| 26ae6d6b36 | |||
| ef49d127e3 | |||
| 8f36ded5b1 | |||
| 2726434f72 | |||
| cffd50752c | |||
| 253f975fdb | |||
| 9685b4767f | |||
| 9b1fb0d45b | |||
| 4102537cfd | |||
| ab1f03397e | |||
| e3fabf3ea1 | |||
| c3f42b29a8 | |||
| 365a0db0f0 | |||
| ec7c368ff0 | |||
| d8b4471a59 | |||
| 4fc97e40c9 | |||
| 8c1fa7f320 | |||
| 475be776b8 | |||
| d1064bd441 | |||
| 258357857b | |||
| 3668be932e | |||
| 6f3eb994b5 | |||
| cf1b5fe601 | |||
| e23d834a01 | |||
| b1bab9e1e7 | |||
| c56b02b284 | |||
| 8b90aefd14 | |||
| 84be89a273 | |||
| b34aa69e8f | |||
| ccf5b60c6f | |||
| b054a88af1 | |||
| 7b6bca5af1 | |||
| e2870bda43 | |||
| 2afdab4454 | |||
| 7ce9f02751 | |||
| 841205ec01 | |||
| 08a43b8b3b | |||
| 30b945fac9 | |||
| 71982d8e57 | |||
| 579cf43f0f | |||
| f9026e2b7b | |||
| 1ae4eeb813 | |||
| 98c1a412b4 | |||
| 835fbee9a2 | |||
| 60b5b0d380 | |||
| ee30af5ffb | |||
| e44876bf1f | |||
| 851a533a82 | |||
| b0315375f8 | |||
| 3a713d91ce | |||
| 28c66b0491 | |||
| dee57e4c3e | |||
| 9392de9dfe | |||
| 9e3db08363 | |||
| 96a09ea77c | |||
| 89e0990224 | |||
| c37c9df18a | |||
| cb2bd8bcec | |||
| 6f1b5946b1 | |||
| f256d8ad2d | |||
| d35a1f2868 | |||
| 1052ff674c | |||
| 58b36ba242 | |||
| e1804098b9 | |||
| ebfed4d474 | |||
| ff4026fa5b | |||
| 2ec4404ba0 | |||
| a00c0d795d | |||
| 080a906aac | |||
| f255155f6b | |||
| 7345050e4d | |||
| 7aab43e8d4 | |||
| add0d25029 | |||
| 176d8c5a14 | |||
| 9216b08536 | |||
| 0f00c36d65 | |||
| 32ead68e9f | |||
| 46f7ab3585 | |||
| 196af472a6 | |||
| e4dadeed88 | |||
| 42d643af55 | |||
| 946e8cde53 | |||
| 244d9c3370 | |||
| 17b0017cd0 | |||
| 919c3c1ec8 | |||
| 84488e228a | |||
| b22f89cb77 | |||
| 37c6c53b56 | |||
| 400ac8a66c | |||
| 2b4128e904 | |||
| adb37a1b39 | |||
| 389ce0aae6 | |||
| aaa8cae345 | |||
| 29714ee836 | |||
| d60883eb10 | |||
| ddf04ad113 | |||
| 899ca94ce8 | |||
| d550fbb80e | |||
| cc653b5d61 | |||
| 7a19164c17 | |||
| 22a0b0a4f9 | |||
| 22a0ea5550 | |||
| 3ad9cc996a | |||
| a0398fbd19 | |||
| bc503ca8ab | |||
| b48f4bf523 | |||
| 9c94dcea3d | |||
| 28ad7c588f | |||
| a17e53038d | |||
| 6ae6371d11 | |||
| 3057f0f0a7 | |||
| 2465ab8814 | |||
| 69949df242 | |||
| cfcddefacd | |||
| 06f0d03ced | |||
| 7078e7ddf4 | |||
| 4ea3ff7061 | |||
| ce607916e9 | |||
| 6d71f27760 | |||
| 9be87c9a96 | |||
| 3fc75c9739 | |||
| dd43c23812 | |||
| cc6aece1fd | |||
| 0f351ce364 | |||
| 2e3113aeef | |||
| 9ead1663c3 | |||
| b7d921e1b5 | |||
| e766a3505c | |||
| 39a71acf7e | |||
| 3856c5efa6 | |||
| 0bc748bba3 | |||
| d79860af87 | |||
| d955fdb9ac | |||
| 2dca1bc633 | |||
| 5ecbc9747f | |||
| a1d4b7dd0d | |||
| 8c63ce4f6b | |||
| cd426ebed7 | |||
| 844fb29619 | |||
| 9a13c2dc79 | |||
| 09ddb67dd4 | |||
| df79536769 | |||
| dc500630ec | |||
| c59e24ff12 | |||
| d08ca5c2b6 | |||
| ed68486f68 | |||
| 96965e2abd | |||
| eebf88ffd3 | |||
| 0083f6e984 | |||
| 11a24f19c2 | |||
| 4cc8490283 | |||
| 5794a0190d | |||
| 656cdbb0e9 | |||
| 151564d39b | |||
| 0b122dc55b | |||
| 6bacb32755 | |||
| 01ca4f621d | |||
| ec97bc9394 | |||
| 8a71e005b4 | |||
| c2d21fa9d3 | |||
| a82e3bc704 | |||
| cba4bbad4f | |||
| 011bfd666e | |||
| 504e67b867 | |||
| 67034e5003 | |||
| b0cbc02283 | |||
| 2d1414e8b0 | |||
| bfe252b781 | |||
| d7177c1224 | |||
| 26bd356078 | |||
| 08c5f52d70 | |||
| ab83cf37c9 | |||
| 79240b9eaf | |||
| fad2cc6a18 | |||
| a9e5cb0efb | |||
| 56ed0a6e8c | |||
| d5830d66e0 | |||
| 24174e8a84 | |||
| e38767e197 | |||
| d4d0718ebf | |||
| 4e899d64e0 | |||
| 9273c53d83 | |||
| a066b94ef0 | |||
| 32c3b99663 | |||
| ba973f2d74 | |||
| 0ff3123eba | |||
| c45ad0b02d | |||
| 70edf41c06 | |||
| 6e12d9fe62 | |||
| 3a24eb2de4 | |||
| 68de2d4834 | |||
| e4bf5cff49 | |||
| ad9706a7c5 | |||
| 8fee1c2020 | |||
| d475c4ddc1 | |||
| fb8217ee62 | |||
| 19c30447b5 | |||
| c6b02ad73b | |||
| dc9dd188cf | |||
| ed9434c5bb | |||
| ef4b053b90 | |||
| ec05d93c16 | |||
| 5c374c7137 | |||
| ae31aa10ab | |||
| d46fff7d8f | |||
| 72269acf29 | |||
| 4920d40093 | |||
| faa448e801 | |||
| b4d6b23838 | |||
| a6bbf16aa1 | |||
| 5f2e0864e4 | |||
| 99c247a46a | |||
| 1ce9be346b | |||
| 4d746658cc | |||
| 8d98b37031 | |||
| 8363f2f171 | |||
| 26701e2d9f | |||
| bf80adeee4 | |||
| e5f7fdccfb | |||
| 930b27a86b | |||
| a85e6a4d4a | |||
| ea3afdad61 | |||
| 44e1023a93 | |||
| 50f0906007 | |||
| d9a2fadb5e | |||
| 1e5165275f | |||
| 442b45628e | |||
| 9a3ab0358e | |||
| 215bfc73d2 | |||
| 7787d6dc28 | |||
| f112cd6b6e | |||
| 63f8db83aa | |||
| 52240c2d62 | |||
| dbc20693f7 | |||
| 5099255d87 | |||
| a2bc804936 | |||
| 90e733f0b1 | |||
| 2491f6c95a | |||
| a77d80d9d1 | |||
| dc04675f4a | |||
| 26ee5fea02 | |||
| 3b16cb15b4 | |||
| 96e1cda40f | |||
| c2df663851 | |||
| b69f4f3c13 | |||
| 19ff8e09ba | |||
| 3526993b2f | |||
| 06146b333c | |||
| 734c9d2f67 | |||
| b7083cf08b | |||
| 22d0be5797 | |||
| a095f82877 | |||
| d7ca0a48ac | |||
| 04e883c22b | |||
| 90a81f6af2 | |||
| a1620084c5 | |||
| ee7fdbe8b9 | |||
| 75bc0c064b | |||
| aaacde4fd2 | |||
| 1db161768c | |||
| c7e0531c3a | |||
| 14f43406a3 | |||
| e7ef8633d5 | |||
| a1d432301d | |||
| 103189e562 | |||
| a2f9fb7777 | |||
| b5b0da5fec | |||
| a705ca0173 | |||
| 6d9a84bcd0 | |||
| bccf5c756b | |||
| 5a94d2b5b9 | |||
| ee36b57c8f | |||
| b50a88cfd7 | |||
| 67e38cc54c | |||
| 4ab8514387 | |||
| d9f5f1954c | |||
| 01cd67e752 | |||
| bea2257f92 | |||
| 4c55464dd3 | |||
| 5ec323aaa2 | |||
| caf7d83c67 | |||
| 991589d518 | |||
| 4796b5c2f8 | |||
| 5bde858db5 | |||
| 246072e8c5 | |||
| cc411c054f | |||
| b5d914188e | |||
| 80469f8180 | |||
| 73e3d85c28 | |||
| 9123f77a78 | |||
| b3b9fe8c77 | |||
| 41c99cc621 | |||
| 683ee20562 | |||
| 79498b1c61 | |||
| 2b6af905fa | |||
| b44ef2b306 | |||
| 96559aaef2 | |||
| 0aee0b1f9e | |||
| c281f692c7 | |||
| 6654b0e05f | |||
| 9e58793207 | |||
| c3e5364813 | |||
| ffe32ac371 | |||
| f418f4864b | |||
| 6ed11b5374 | |||
| f5be0821cf | |||
| 52de250496 | |||
| a4eba15b75 | |||
| df6cd114a0 | |||
| e777f96e59 | |||
| 2b0927c9ac | |||
| c1783a1c2a | |||
| 4830f488d5 | |||
| 6fda30a437 | |||
| 618e00da81 | |||
| b290690d73 | |||
| 53d2429164 | |||
| 54f98f40a4 | |||
| b7249569e2 | |||
| 4a9d039721 | |||
| b08c6b1768 | |||
| 042d08a2a8 | |||
| a8175b751c | |||
| 124792a871 | |||
| 9cd84e405d | |||
| 1eecc1e7e5 | |||
| 77c592d26a | |||
| 96ad26a730 | |||
| fb04043ca5 | |||
| 281a48d092 | |||
| 899ecc11c5 | |||
| 4c1540c8d2 | |||
| 6d2d0a74e8 | |||
| e666dfff9b | |||
| ce0457a2c9 | |||
| 8e31036816 | |||
| a1c373197f | |||
| 2feebd95d3 | |||
| c559d8ebf7 | |||
| 292a1564dc | |||
| 8cee301874 | |||
| 73a6ac00ce | |||
| dd28ded711 | |||
| 22a9ba090e | |||
| 2ab94e11a2 | |||
| 4cb0790a62 | |||
| 0137e3eda2 | |||
| 2391233404 | |||
| 2606a2e4d3 | |||
| 28cff8bd03 | |||
| e0ef11a4c2 | |||
| 6a1a192e07 | |||
| 87de04005d | |||
| d295dc6652 | |||
| d929589c1f | |||
| 51e059e7e9 | |||
| 6b8b4feaa1 | |||
| c1c3475c6a | |||
| 7c19f660b3 | |||
| 670c48857a | |||
| aee0fcf413 | |||
| 931e6129d0 | |||
| 561005e5ca | |||
| b7009a4b3a | |||
| 7539013436 | |||
| 0fcd485626 | |||
| 69051ec0a5 | |||
| efc1ddd7e3 | |||
| e9c64b6d99 | |||
| 1529343a3f | |||
| f66a6f0346 | |||
| 806e4c4a02 | |||
| cc833c5e2b | |||
| 6a45bed9bf | |||
| 7db08b4e62 | |||
| e1d3cb89f1 | |||
| f41230b93a | |||
| aac5c44c10 | |||
| 582a24e937 | |||
| 33e9d70c82 | |||
| 80f2e3f0c6 | |||
| 0339ed787c | |||
| 2e557c5c9a | |||
| ec1d8d7f2a | |||
| 5eda566f93 | |||
| 482eca3099 | |||
| 072ac5e7f0 | |||
| fbd0cea90c | |||
| 883fe220ec | |||
| b3fe785c72 | |||
| 67eb810a60 | |||
| 4843323a46 | |||
| 14e8898648 | |||
| 0542939f40 | |||
| e86291edd8 | |||
| 5e061b5945 | |||
| e6ca006d86 | |||
| dc204b8786 | |||
| b78f66c472 | |||
| f04e8fdb9b | |||
| 3f04ab2fc7 | |||
| 2fcacf8ea3 | |||
| a6cef38936 | |||
| 545ab6bf48 | |||
| 139ea5b7f2 | |||
| 49066f621e | |||
| da0cf69147 | |||
| 24e7907bb1 | |||
| 0e17547af2 | |||
| 59b8a0f697 | |||
| c1713e7c5a | |||
| 01112989b7 | |||
| 08b117517d | |||
| 956fbccdaa | |||
| eb13311129 | |||
| 5a43beda91 | |||
| 662f55d11d | |||
| 134fefbaa2 | |||
| 2d43d93410 | |||
| 698e14902a | |||
| bbac09a097 | |||
| aa2e3247cb | |||
| d04fd5e0dc | |||
| d6f8365258 | |||
| 934d9d6323 | |||
| b8605a1107 | |||
| 830729bb94 | |||
| 970bc4b5c1 | |||
| 435e30e42f | |||
| 3fcf481361 | |||
| 77da5d8feb | |||
| cb0ce5bb76 | |||
| e4478caddf | |||
| 99d13e7e14 | |||
| 40b77af234 | |||
| b26bcfe9be | |||
| cf8688c235 | |||
| 289145ecc6 | |||
| 73f8b46d84 | |||
| a3b04de401 | |||
| 6d223febda | |||
| 94caae2477 | |||
| 39081eb3aa | |||
| 8ebd0c972a | |||
| 0e9b0806f5 | |||
| 826ebc9333 | |||
| 825f1a7e28 | |||
| f22674a5f0 | |||
| b7a009cc1c | |||
| e97e46b737 | |||
| f5c0eb9ffe | |||
| ba02bf31cb | |||
| ec25a44732 | |||
| 61634758c4 | |||
| 85244f80e3 | |||
| e5967d041d | |||
| 4f677df0d1 | |||
| d762dcd147 | |||
| 31b882e793 | |||
| c300978370 | |||
| 1dcb7d9e40 | |||
| 729c9a9782 | |||
| 36592a4bc2 | |||
| ae03e544e7 | |||
| da982cf551 | |||
| 144abf2a58 | |||
| 1fffc7a89d | |||
| 94e3b0f428 | |||
| 724b410c55 | |||
| 949270ba7c | |||
| 81c42d6c1e | |||
| 7e8c92ad72 | |||
| b08f071e18 | |||
| 2fb5d0c323 | |||
| 79c31f4b13 | |||
| 252af0ae2f | |||
| 8db945abb9 | |||
| 4e0eb91c55 | |||
| 992e464a80 | |||
| 499b76d2ec | |||
| 00e3277107 | |||
| 50852f3be3 | |||
| 9077c89689 | |||
| 8e5bb8fdd3 | |||
| 8781367c77 | |||
| 7eab3db324 | |||
| 213a0f9d86 | |||
| d6e7350b96 | |||
| 79dd76b602 | |||
| b50b928313 | |||
| 4d301f0059 | |||
| ab8a4102e1 | |||
| eff253dfaf | |||
| 74aeb0350e | |||
| 17062aea21 | |||
| 7335544fd0 | |||
| 4acf29a808 | |||
| 7669670f2d | |||
| c576310b6f | |||
| 4e62bd97d3 | |||
| 3571814dc5 | |||
| 1c71c1995f | |||
| 9324b154d2 | |||
| 486c12525f | |||
| da294050f3 | |||
| 457a066ff0 | |||
| 60f76bbfe1 | |||
| 1cab5a50d0 | |||
| f8f03a290c | |||
| 94464e3a5e | |||
| 6957b671bb | |||
| fcb9d8d899 | |||
| 2ed0739c7a | |||
| ba4c132491 | |||
| 39f2f15a35 | |||
| 02eed8d666 | |||
| c32d22e6e4 | |||
| 7d193ef1f3 | |||
| 166e082be3 | |||
| 3c3d232e0a | |||
| f0e904df8e | |||
| ff92f45be4 | |||
| ecedb3d81d | |||
| 80e31df3b6 | |||
| 6db1592066 | |||
| 07cfac3d2d | |||
| 5aa7c2317a | |||
| 7a1dbd17fc | |||
| 2ecc7290d6 | |||
| 59a000ec7f | |||
| 93bd57b0b2 | |||
| fada260ad2 | |||
| 7c52fcce85 | |||
| 2368d23665 | |||
| 5106532601 | |||
| 63e5891e4f | |||
| 1374ebcde0 | |||
| 02ae137b1d | |||
| 6d25eb6e7c | |||
| b5265fe7ad | |||
| b2b76894a1 | |||
| 27c76522de | |||
| 7d8b5fc3aa | |||
| 189d2c7985 | |||
| f6064cb42b | |||
| d0fdc3b5df | |||
| 6bd3e6357c | |||
| de41640435 | |||
| 608b3db5c8 | |||
| 5c94a9e800 | |||
| 7daeecd42d | |||
| 9a64f2bbb3 | |||
| 84b53c8d87 | |||
| a7e8f3616c | |||
| 26b1064967 | |||
| 23750fb802 | |||
| e65a7dabb9 | |||
| c8bc02ea35 | |||
| ccba7c3e14 | |||
| f2db5028cd | |||
| de8b5c46cc | |||
| f75206dd7b | |||
| 53e5f33279 | |||
| 1e514de2e4 | |||
| 5d630abbab | |||
| f2823f64a5 | |||
| 5505bcdd62 | |||
| 8c33c6c737 | |||
| 19613feddf | |||
| 029c6eacd6 | |||
| 22877e1b16 | |||
| 6fe38e3947 | |||
| 2458452a3b | |||
| ebe7ef9fc2 | |||
| 40e8560412 | |||
| a518612260 | |||
| 3a4775cb80 | |||
| b89fdc120e | |||
| c5b23337c3 | |||
| ddd0c47412 | |||
| d71ae45772 | |||
| ca5cc770b9 | |||
| a1573dffee | |||
| 793a409081 | |||
| 56b70bf84e | |||
| 24310b5b4a | |||
| 01670bec48 | |||
| 323c77e4e1 | |||
| 04b171a632 | |||
| 51fad49e67 | |||
| a077c46d14 | |||
| 40adb3fd2c | |||
| c0c941627a | |||
| 871daf5498 | |||
| 200fa6978c | |||
| 78de066a28 | |||
| 8d69e0eae0 | |||
| 8f8cc077d3 | |||
| ce092ed08e | |||
| 04e1015aa0 | |||
| 03dce24137 | |||
| ec4b71b64a | |||
| 8f7f0f8deb | |||
| 3e6991f35c | |||
| 70016198ce | |||
| da0ca83377 | |||
| 0a734182eb | |||
| b1d2f52bb2 | |||
| 8aef1a33eb | |||
| 6335c90d4a | |||
| d265f93f6e | |||
| 01c5cfcc56 | |||
| 34e41580f9 | |||
| 4d55877e27 | |||
| 141e91c480 | |||
| aa1780997f | |||
| a5879bb83b | |||
| 7acd3ca79d | |||
| b0dc11127e | |||
| 14e8949dcf | |||
| 9dc0973655 | |||
| dc17613297 | |||
| 04d76d898a | |||
| 9d8f9943e9 | |||
| 114612305c | |||
| 8a5ab83df8 | |||
| bfc785bf64 | |||
| 6353f3a7b5 | |||
| 44f47903a8 | |||
| 3efe5ff22e | |||
| a7e23cab8d | |||
| f97e6f2a37 | |||
| eff524e19c | |||
| 3b443abec2 | |||
| 108891069f | |||
| 5c73f14c62 | |||
| b69c13b1a6 | |||
| 985b7ec895 | |||
| a8eba11fb1 | |||
| da82b5fedb | |||
| 0c71c19239 | |||
| 732676fad5 | |||
| 7b4f73ee6c | |||
| 2daa5b1735 | |||
| d37674b44b | |||
| d572bab4a0 | |||
| 40edb5f97c | |||
| 0fea04d27e | |||
| 499ade0116 | |||
| f018c0430b | |||
| 72bb56618b | |||
| 587558f617 | |||
| ab39a4c981 | |||
| 7cf343d106 | |||
| 743dc1c791 | |||
| 124540c086 | |||
| 5746eb9501 | |||
| f05bacbe50 | |||
| 4a6e8c8f3c | |||
| 76b3b54314 | |||
| b616b8a17a | |||
| 4133401bf9 | |||
| e3c472426f | |||
| e41753678d | |||
| 148be8bd7e | |||
| ee6b81faf2 | |||
| c219311fd8 | |||
| acdc0f82c0 | |||
| 8d6796f8c6 | |||
| 7d5603e7cb | |||
| 07f1d68887 | |||
| 76c96da487 | |||
| 0b2091132a | |||
| 536353e87f | |||
| bf892efe36 | |||
| 16f60347bc | |||
| 0b8b8ed9e9 | |||
| 8d56108ffb | |||
| e61abac5fa | |||
| 3e212eec5e | |||
| 96f9efc063 | |||
| 45d7acf237 | |||
| 848792c42e | |||
| 3dbfb3d38c | |||
| 4026512a2c | |||
| 41646cb4e3 | |||
| 26bd5e3a2d | |||
| 72eb75a06c | |||
| c4d4318b82 | |||
| 249f45bcfe | |||
| 5d6ca225d1 | |||
| d09da26f06 | |||
| da67833e63 | |||
| 2bb6b13685 | |||
| cf6a53642a | |||
| d2e7ba42b1 | |||
| 52d7542195 | |||
| 4a707c9ad9 | |||
| a283a30fb0 | |||
| 4702d45599 | |||
| 96c735a724 | |||
| 828e4ac45a | |||
| 2b6b9a8018 | |||
| 57c6012cf0 | |||
| 66be81b148 | |||
| cfd3080a80 | |||
| 5e7895ee21 | |||
| e33b8b11af | |||
| cafee2eb45 | |||
| e5a26ec081 | |||
| 3b168b29f2 | |||
| f172847205 | |||
| b79782e951 | |||
| cdc91aaf7c | |||
| ffaee8fe78 | |||
| b51918fba1 | |||
| 1ffff4c8e2 | |||
| 4dfa996cc2 | |||
| 2d5d606fd3 | |||
| b0b3bc56d0 | |||
| f7d4b4fe2b | |||
| 9ab8c2d92e | |||
| 0ac28f5e48 | |||
| 9a8f1b7673 | |||
| 5cf3a6e010 | |||
| 3394fedf32 | |||
| 3be7146e14 | |||
| 5cbcbfc03c | |||
| a803148bba | |||
| 677a6eaf8f | |||
| 42a46ed1a4 | |||
| 4a2c3733bd | |||
| 3d4685e258 | |||
| b9a3660d1e | |||
| fb01c2ea3c | |||
| ac34ce0307 | |||
| 5b99591e18 | |||
| c1e59b94e2 | |||
| 74a8f2f668 | |||
| f9e9a622f0 | |||
| bfc4e38429 | |||
| 2c53643b33 | |||
| 814c19591d | |||
| 88b895111b | |||
| 259a0fb16c | |||
| a2f545c86d | |||
| 60db152b20 | |||
| 552c361f39 | |||
| 50d7fba775 | |||
| e148e763b8 | |||
| 0a5aac1ff6 | |||
| 7b1930063f | |||
| ea4d70bdfd | |||
| e0fee019ca | |||
| f2d978ccf9 | |||
| 8cfcd87380 | |||
| 54240f8da9 | |||
| b0d4e9d1ba | |||
| 606c6e8f57 | |||
| be082715c7 | |||
| 3b7cdfcd6b | |||
| 9c9b748ad8 | |||
| fc77ea787e | |||
| 6167c7bfb8 | |||
| 09e7dd03f8 | |||
| 9b648dfac6 | |||
| 2d21996eec | |||
| 96699e06dd | |||
| 59311faaed | |||
| 723f587b56 | |||
| e91f2c26ce | |||
| 5d6bb52976 | |||
| db2ac8655d | |||
| 0d873f6e70 | |||
| d3379946ec | |||
| 99b40d1e14 | |||
| 370ca6e401 | |||
| 386b7bc71b | |||
| 8ef7aaf885 | |||
| 58d6919183 | |||
| 2eedaf0389 | |||
| a90ed92df0 | |||
| 7ce270d4ea | |||
| bda964d085 | |||
| 2c6c07752c | |||
| fedecb8c91 | |||
| 628b9a41b0 | |||
| 11fda783f8 | |||
| 5e11cd43aa | |||
| d0e0c13b60 | |||
| 084f8060e2 | |||
| d25dada639 | |||
| ff61be9b04 | |||
| 621143a641 | |||
| 5575b93678 | |||
| 30164588d6 | |||
| 18d856f55a | |||
| 185544a4fe | |||
| b4841dd237 | |||
| dac92a8afb | |||
| 21cc0ac2c6 | |||
| 25eb101624 | |||
| 6b16c154f5 | |||
| 7efa0f37b2 | |||
| 611dbd8957 | |||
| 02e18c9ab3 | |||
| 6a55d95334 | |||
| 9d0954a83d | |||
| 69dde5c075 | |||
| 7fc4566c9d | |||
| e611a189cb | |||
| 6f15546f6b | |||
| 55a601e3f1 | |||
| 9fbef808c1 | |||
| c044dadade | |||
| f9320690d9 | |||
| bf2b8ec816 | |||
| 32964def83 | |||
| c8291f7107 | |||
| 3f631b0d94 | |||
| ec6d91ce43 | |||
| 4a5d40fdb7 | |||
| 50c355777e | |||
| 64faec8269 | |||
| bc48194ded | |||
| 23e68679f0 | |||
| f9e5a693ed | |||
| 335689118b | |||
| dc1cc46348 | |||
| 9746021f1a | |||
| 8de7fcaf7e | |||
| db54c79d7c | |||
| 22be1e443f | |||
| 7064fe283f | |||
| af14c6e7aa | |||
| 1ca88b2ca6 | |||
| a0d9ffd6f7 | |||
| 3781cde61f | |||
| 0e50d946a2 | |||
| bb1996ab8f | |||
| 947519bfb3 | |||
| b18d664692 | |||
| 65e43593f5 | |||
| c8dc729af4 | |||
| 08a2e475bc | |||
| 727f083a37 | |||
| e9e68fa2d2 | |||
| 40d3ce1063 | |||
| 072790bb3b | |||
| 012bdac6d6 | |||
| 6efe45990d | |||
| b78e4216a2 | |||
| 9f517fc5bb | |||
| 8855bf3500 | |||
| b154a6f68e | |||
| d2e9f6c0c1 | |||
| 72f355e466 | |||
| f790496d8b | |||
| 1caa9a7d16 | |||
| 3effe484e6 | |||
| 85433365cc | |||
| 587823af27 | |||
| a796ac5318 | |||
| 82fce53103 | |||
| 8b454dd79e | |||
| 5ff63b6946 | |||
| d333849459 | |||
| 0072896445 | |||
| 5d67fd5fb2 | |||
| 2724010364 | |||
| 3d8aae1308 | |||
| 34752e091d | |||
| 58ae808d42 | |||
| d4a123b20b | |||
| 5109071706 | |||
| 5c80c3fb3c | |||
| f70c592137 | |||
| 104d30f946 | |||
| e514fabfac | |||
| 8ff67b9c1a | |||
| e9602f3561 | |||
| bab424aa34 | |||
| fd5c33194c | |||
| 1cca5ee98c | |||
| f8308a4965 | |||
| 6733962a2c | |||
| 2022fa81ff | |||
| 9ca4aa4797 | |||
| 8d27417bfe | |||
| a58aa9d889 | |||
| 8845eb4345 | |||
| 4b4597ae17 | |||
| 25418010ef | |||
| 51662df329 | |||
| 634a848b8e | |||
| 5c22cb5741 | |||
| fd32c47112 | |||
| 87255b6721 | |||
| d5a705b600 | |||
| 9fdb626bfb | |||
| 1666fbe287 | |||
| 914905f452 | |||
| 0c70eb8381 | |||
| ab197f7ad2 | |||
| 1d52c53eea | |||
| 1947ab6c1e | |||
| 479fcbb1d0 | |||
| c486a3a6dc | |||
| eb5c81e799 | |||
| 451f4d0d3b | |||
| 2b6da0a965 | |||
| 3fdfbd43b6 | |||
| 00eaa7e1f5 | |||
| 4d648f924a | |||
| 3a539c6db9 | |||
| dc8a39036b | |||
| 5f5d73d515 | |||
| be7d346504 | |||
| 2968558f61 | |||
| a8d6b033e1 | |||
| c045af8332 | |||
| 46df9e4ec3 | |||
| a14a31ed40 | |||
| 12058f3a75 | |||
| 719886d435 | |||
| 57ab4a1a6c | |||
| 13d1f0c1a1 | |||
| 45368779f8 | |||
| b1053a7526 | |||
| be304ec688 | |||
| c1c4ccefcd | |||
| 49da029030 | |||
| 73d8af9319 | |||
| f4fb4796a1 | |||
| 23b33aef1e | |||
| 549c8ea96e | |||
| 6baf65d1a6 | |||
| 1cbf9047b3 | |||
| cd33b98041 | |||
| d9bf8163ad | |||
| 6990b76a69 | |||
| 915d6ec76d | |||
| 15aad5d3e6 | |||
| e787296c75 | |||
| 6549d6517b | |||
| 9c3aafd354 | |||
| 44da5c3de2 | |||
| 7795b1c697 | |||
| ffc8d4e204 | |||
| 7ca017eb62 | |||
| e451ddbf46 | |||
| b46c15e772 | |||
| 20bb065535 | |||
| c71a99af11 | |||
| 8b91b3cf77 | |||
| a12a781b5e |
+2
-1
@@ -30,8 +30,9 @@ docs/_build
|
|||||||
docs/_static
|
docs/_static
|
||||||
docs/_templates
|
docs/_templates
|
||||||
docs/changed-files
|
docs/changed-files
|
||||||
# generated by man/man/md2man-all.sh
|
# generated by man/md2man-all.sh
|
||||||
man/man1
|
man/man1
|
||||||
man/man5
|
man/man5
|
||||||
|
man/man8
|
||||||
pyenv
|
pyenv
|
||||||
vendor/pkg/
|
vendor/pkg/
|
||||||
|
|||||||
+39
-4
@@ -1,5 +1,40 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.8.3 (2015-10-12)
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
|
||||||
|
- Fix layer IDs lead to local graph poisoning (CVE-2014-8178)
|
||||||
|
- Fix manifest validation and parsing logic errors allow pull-by-digest validation bypass (CVE-2014-8179)
|
||||||
|
+ Add `--disable-legacy-registry` to prevent a daemon from using a v1 registry
|
||||||
|
|
||||||
|
## 1.8.2 (2015-09-10)
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
|
||||||
|
- Fixes rare edge case of handling GNU LongLink and LongName entries.
|
||||||
|
- Fix ^C on docker pull.
|
||||||
|
- Fix docker pull issues on client disconnection.
|
||||||
|
- Fix issue that caused the daemon to panic when loggers weren't configured properly.
|
||||||
|
- Fix goroutine leak pulling images from registry V2.
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
- Fix a bug mounting cgroups for docker daemons running inside docker containers.
|
||||||
|
- Initialize log configuration properly.
|
||||||
|
|
||||||
|
### Client:
|
||||||
|
|
||||||
|
- Handle `-q` flag in `docker ps` properly when there is a default format.
|
||||||
|
|
||||||
|
### Networking
|
||||||
|
|
||||||
|
- Fix several corner cases with netlink.
|
||||||
|
|
||||||
|
### Contrib
|
||||||
|
|
||||||
|
- Fix several issues with bash completion.
|
||||||
|
|
||||||
## 1.8.1 (2015-08-12)
|
## 1.8.1 (2015-08-12)
|
||||||
|
|
||||||
### Distribution
|
### Distribution
|
||||||
@@ -241,7 +276,7 @@
|
|||||||
#### Notable Features since 1.3.0
|
#### Notable Features since 1.3.0
|
||||||
+ Set key=value labels to the daemon (displayed in `docker info`), applied with
|
+ Set key=value labels to the daemon (displayed in `docker info`), applied with
|
||||||
new `-label` daemon flag
|
new `-label` daemon flag
|
||||||
+ Add support for `ENV` in Dockerfile of the form:
|
+ Add support for `ENV` in Dockerfile of the form:
|
||||||
`ENV name=value name2=value2...`
|
`ENV name=value name2=value2...`
|
||||||
+ New Overlayfs Storage Driver
|
+ New Overlayfs Storage Driver
|
||||||
+ `docker info` now returns an `ID` and `Name` field
|
+ `docker info` now returns an `ID` and `Name` field
|
||||||
@@ -703,7 +738,7 @@
|
|||||||
- Fix broken images API for version less than 1.7
|
- Fix broken images API for version less than 1.7
|
||||||
- Use the right encoding for all API endpoints which return JSON
|
- Use the right encoding for all API endpoints which return JSON
|
||||||
- Move remote api client to api/
|
- Move remote api client to api/
|
||||||
- Queue calls to the API using generic socket wait
|
- Queue calls to the API using generic socket wait
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
|
|
||||||
@@ -783,7 +818,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
- Do not add hostname when networking is disabled
|
- Do not add hostname when networking is disabled
|
||||||
* Return most recent image from the cache by date
|
* Return most recent image from the cache by date
|
||||||
- Return all errors from docker wait
|
- Return all errors from docker wait
|
||||||
* Add Content-Type Header "application/json" to GET /version and /info responses
|
* Add Content-Type Header "application/json" to GET /version and /info responses
|
||||||
|
|
||||||
#### Other
|
#### Other
|
||||||
|
|
||||||
@@ -811,7 +846,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
#### Runtime
|
#### Runtime
|
||||||
|
|
||||||
- Only get the image's rootfs when we need to calculate the image size
|
- Only get the image's rootfs when we need to calculate the image size
|
||||||
- Correctly handle unmapping UDP ports
|
- Correctly handle unmapping UDP ports
|
||||||
* Make CopyFileWithTar use a pipe instead of a buffer to save memory on docker build
|
* Make CopyFileWithTar use a pipe instead of a buffer to save memory on docker build
|
||||||
- Fix login message to say pull instead of push
|
- Fix login message to say pull instead of push
|
||||||
- Fix "docker load" help by removing "SOURCE" prompt and mentioning STDIN
|
- Fix "docker load" help by removing "SOURCE" prompt and mentioning STDIN
|
||||||
|
|||||||
+7
-4
@@ -25,7 +25,7 @@ The Docker maintainers take security seriously. If you discover a security
|
|||||||
issue, please bring it to their attention right away!
|
issue, please bring it to their attention right away!
|
||||||
|
|
||||||
Please **DO NOT** file a public issue, instead send your report privately to
|
Please **DO NOT** file a public issue, instead send your report privately to
|
||||||
[security@docker.com](mailto:security@docker.com),
|
[security@docker.com](mailto:security@docker.com).
|
||||||
|
|
||||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||||
We also like to send gifts—if you're into Docker schwag, make sure to let
|
We also like to send gifts—if you're into Docker schwag, make sure to let
|
||||||
@@ -42,14 +42,17 @@ and will thank you for it!
|
|||||||
Check that [our issue database](https://github.com/docker/docker/issues)
|
Check that [our issue database](https://github.com/docker/docker/issues)
|
||||||
doesn't already include that problem or suggestion before submitting an issue.
|
doesn't already include that problem or suggestion before submitting an issue.
|
||||||
If you find a match, add a quick "+1" or "I have this problem too." Doing this
|
If you find a match, add a quick "+1" or "I have this problem too." Doing this
|
||||||
helps prioritize the most common problems and requests.
|
helps prioritize the most common problems and requests. **DO NOT DO THAT** to
|
||||||
|
subscribe to the issue unless you have something meaningful to add to the
|
||||||
|
conversation. The best way to subscribe the issue is by clicking Subscribe
|
||||||
|
button in top right of the page.
|
||||||
|
|
||||||
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
|
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
|
||||||
etc). Please include:
|
etc). Please include:
|
||||||
|
|
||||||
* The output of `uname -a`.
|
* The output of `uname -a`.
|
||||||
* The output of `docker version`.
|
* The output of `docker version`.
|
||||||
* The output of `docker -D info`.
|
* The output of `docker info`.
|
||||||
|
|
||||||
Please also include the steps required to reproduce the problem if possible and
|
Please also include the steps required to reproduce the problem if possible and
|
||||||
applicable. This information will help us review and fix your issue faster.
|
applicable. This information will help us review and fix your issue faster.
|
||||||
@@ -224,7 +227,7 @@ high majority of submissions should have a single commit, so if in doubt: squash
|
|||||||
down to one.
|
down to one.
|
||||||
|
|
||||||
After every commit, [make sure the test suite passes]
|
After every commit, [make sure the test suite passes]
|
||||||
((https://docs.docker.com/project/test-and-docs/)). Include documentation
|
(https://docs.docker.com/project/test-and-docs/). Include documentation
|
||||||
changes in the same pull request so that a revert would remove all traces of
|
changes in the same pull request so that a revert would remove all traces of
|
||||||
the feature or fix.
|
the feature or fix.
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -46,8 +46,10 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libapparmor-dev \
|
libapparmor-dev \
|
||||||
libcap-dev \
|
libcap-dev \
|
||||||
libsqlite3-dev \
|
libsqlite3-dev \
|
||||||
|
libsystemd-journal-dev \
|
||||||
mercurial \
|
mercurial \
|
||||||
parallel \
|
parallel \
|
||||||
|
pkg-config \
|
||||||
python-mock \
|
python-mock \
|
||||||
python-pip \
|
python-pip \
|
||||||
python-websocket \
|
python-websocket \
|
||||||
@@ -81,7 +83,7 @@ RUN cd /usr/src/lxc \
|
|||||||
&& ldconfig
|
&& ldconfig
|
||||||
|
|
||||||
# Install Go
|
# Install Go
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.3
|
||||||
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/local -xz \
|
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/local -xz \
|
||||||
&& mkdir -p /go/bin
|
&& mkdir -p /go/bin
|
||||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# This file describes the standard way to build Docker, using docker
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# # Assemble the full dev environment. This is slow the first time.
|
||||||
|
# docker build -t docker -f Dockerfile.gccgo .
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM gcc:5.2
|
||||||
|
|
||||||
|
# Packaged dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
apparmor \
|
||||||
|
aufs-tools \
|
||||||
|
btrfs-tools \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
iptables \
|
||||||
|
net-tools \
|
||||||
|
libapparmor-dev \
|
||||||
|
libcap-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
mercurial \
|
||||||
|
parallel \
|
||||||
|
python-mock \
|
||||||
|
python-pip \
|
||||||
|
python-websocket \
|
||||||
|
--no-install-recommends
|
||||||
|
|
||||||
|
# Get lvm2 source for compiling statically
|
||||||
|
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
|
||||||
|
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||||
|
|
||||||
|
# Compile and install lvm2
|
||||||
|
RUN cd /usr/local/lvm2 \
|
||||||
|
&& ./configure --enable-static_link \
|
||||||
|
&& make device-mapper \
|
||||||
|
&& make install_device-mapper
|
||||||
|
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||||
|
|
||||||
|
# Install lxc
|
||||||
|
ENV LXC_VERSION 1.1.2
|
||||||
|
RUN mkdir -p /usr/src/lxc \
|
||||||
|
&& curl -sSL https://linuxcontainers.org/downloads/lxc/lxc-${LXC_VERSION}.tar.gz | tar -v -C /usr/src/lxc/ -xz --strip-components=1
|
||||||
|
RUN cd /usr/src/lxc \
|
||||||
|
&& ./configure \
|
||||||
|
&& make \
|
||||||
|
&& make install \
|
||||||
|
&& ldconfig
|
||||||
|
|
||||||
|
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||||
|
|
||||||
|
# Get the "docker-py" source so we can run their integration tests
|
||||||
|
ENV DOCKER_PY_COMMIT 139850f3f3b17357bab5ba3edfb745fb14043764
|
||||||
|
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||||
|
&& cd /docker-py \
|
||||||
|
&& git checkout -q $DOCKER_PY_COMMIT
|
||||||
|
|
||||||
|
# Add an unprivileged user to be used for tests which need it
|
||||||
|
RUN groupadd -r docker
|
||||||
|
RUN useradd --create-home --gid docker unprivilegeduser
|
||||||
|
|
||||||
|
VOLUME /var/lib/docker
|
||||||
|
WORKDIR /go/src/github.com/docker/docker
|
||||||
|
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||||
|
|
||||||
|
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||||
|
ENTRYPOINT ["hack/dind"]
|
||||||
|
|
||||||
|
# Upload docker source
|
||||||
|
COPY . /go/src/github.com/docker/docker
|
||||||
+27
-46
@@ -141,7 +141,7 @@ editor, and thus asking them to `git commit --amend -s` is not the best way forw
|
|||||||
In this case, maintainers can update the commits based on clause (c) of the DCO. The
|
In this case, maintainers can update the commits based on clause (c) of the DCO. The
|
||||||
most trivial way for a contributor to allow the maintainer to do this, is to add
|
most trivial way for a contributor to allow the maintainer to do this, is to add
|
||||||
a DCO signature in a Pull Requests's comment, or a maintainer can simply note that
|
a DCO signature in a Pull Requests's comment, or a maintainer can simply note that
|
||||||
the change is sufficiently trivial that it does not substantivly change the existing
|
the change is sufficiently trivial that it does not substantially change the existing
|
||||||
contribution - i.e., a spelling change.
|
contribution - i.e., a spelling change.
|
||||||
|
|
||||||
When you add someone's DCO, please also add your own to keep a log.
|
When you add someone's DCO, please also add your own to keep a log.
|
||||||
@@ -183,16 +183,6 @@ made through a pull request.
|
|||||||
# be approved by the chief architect.
|
# be approved by the chief architect.
|
||||||
"Chief Architect" = "shykes"
|
"Chief Architect" = "shykes"
|
||||||
|
|
||||||
# The Chief Operator is responsible for the day-to-day operations of the project including:
|
|
||||||
# - facilitating communications amongst all the contributors;
|
|
||||||
# - tracking release schedules;
|
|
||||||
# - managing the relationship with downstream distributions and upstream dependencies;
|
|
||||||
# - helping new contributors to get involved and become successful contributors and maintainers
|
|
||||||
#
|
|
||||||
# The role is also responsible for managing and measuring the success of the overall project
|
|
||||||
# and ensuring it is governed properly working in concert with the Docker Governance Advisory Board (DGAB).
|
|
||||||
"Chief Operator" = "spf13"
|
|
||||||
|
|
||||||
[Org.Operators]
|
[Org.Operators]
|
||||||
|
|
||||||
# The operators make sure the trains run on time. They are responsible for overall operations
|
# The operators make sure the trains run on time. They are responsible for overall operations
|
||||||
@@ -264,8 +254,6 @@ made through a pull request.
|
|||||||
#
|
#
|
||||||
# It is common for core maintainers to "branch out" to join or start a subsystem.
|
# It is common for core maintainers to "branch out" to join or start a subsystem.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
"calavera",
|
"calavera",
|
||||||
"crosbymichael",
|
"crosbymichael",
|
||||||
@@ -278,11 +266,11 @@ made through a pull request.
|
|||||||
"tibor",
|
"tibor",
|
||||||
"unclejack",
|
"unclejack",
|
||||||
"vbatts",
|
"vbatts",
|
||||||
|
"vdemeester",
|
||||||
"vieux",
|
"vieux",
|
||||||
"vishh"
|
"vishh"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[Org.Subsystems]
|
[Org.Subsystems]
|
||||||
|
|
||||||
# As the project grows, it gets separated into well-defined subsystems. Each subsystem
|
# As the project grows, it gets separated into well-defined subsystems. Each subsystem
|
||||||
@@ -336,12 +324,10 @@ made through a pull request.
|
|||||||
[Org.Subsystems.Documentation]
|
[Org.Subsystems.Documentation]
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
"fredlf",
|
|
||||||
"james",
|
"james",
|
||||||
"moxiegirl",
|
"moxiegirl",
|
||||||
"thaJeztah",
|
"thaJeztah",
|
||||||
"jamtur01",
|
"jamtur01",
|
||||||
"spf13",
|
|
||||||
"sven"
|
"sven"
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -502,11 +488,6 @@ made through a pull request.
|
|||||||
Email = "estesp@linux.vnet.ibm.com"
|
Email = "estesp@linux.vnet.ibm.com"
|
||||||
GitHub = "estesp"
|
GitHub = "estesp"
|
||||||
|
|
||||||
[people.fredlf]
|
|
||||||
Name = "Fred Lifton"
|
|
||||||
Email = "fred.lifton@docker.com"
|
|
||||||
GitHub = "fredlf"
|
|
||||||
|
|
||||||
[people.icecrime]
|
[people.icecrime]
|
||||||
Name = "Arnaud Porterie"
|
Name = "Arnaud Porterie"
|
||||||
Email = "arnaud@docker.com"
|
Email = "arnaud@docker.com"
|
||||||
@@ -522,6 +503,11 @@ made through a pull request.
|
|||||||
Email = "josh.hawn@docker.com"
|
Email = "josh.hawn@docker.com"
|
||||||
Github = "jlhawn"
|
Github = "jlhawn"
|
||||||
|
|
||||||
|
[people.jnagal]
|
||||||
|
Name = "Rohit Jnagal"
|
||||||
|
Email = "jnagal@google.com"
|
||||||
|
GitHub = "rjnagal"
|
||||||
|
|
||||||
[people.lk4d4]
|
[people.lk4d4]
|
||||||
Name = "Alexander Morozov"
|
Name = "Alexander Morozov"
|
||||||
Email = "lk4d4@docker.com"
|
Email = "lk4d4@docker.com"
|
||||||
@@ -532,6 +518,11 @@ made through a pull request.
|
|||||||
Email = "mary.anthony@docker.com"
|
Email = "mary.anthony@docker.com"
|
||||||
GitHub = "moxiegirl"
|
GitHub = "moxiegirl"
|
||||||
|
|
||||||
|
[people.mpatel]
|
||||||
|
Name = "Mrunal Patel"
|
||||||
|
Email = "mpatel@redhat.com"
|
||||||
|
GitHub = "mrunalp"
|
||||||
|
|
||||||
[people.nathanmccauley]
|
[people.nathanmccauley]
|
||||||
Name = "Nathan McCauley"
|
Name = "Nathan McCauley"
|
||||||
Email = "nathan.mccauley@docker.com"
|
Email = "nathan.mccauley@docker.com"
|
||||||
@@ -552,11 +543,6 @@ made through a pull request.
|
|||||||
Email = "solomon@docker.com"
|
Email = "solomon@docker.com"
|
||||||
GitHub = "shykes"
|
GitHub = "shykes"
|
||||||
|
|
||||||
[people.spf13]
|
|
||||||
Name = "Steve Francia"
|
|
||||||
Email = "steve.francia@gmail.com"
|
|
||||||
GitHub = "spf13"
|
|
||||||
|
|
||||||
[people.sven]
|
[people.sven]
|
||||||
Name = "Sven Dowideit"
|
Name = "Sven Dowideit"
|
||||||
Email = "SvenDowideit@home.org.au"
|
Email = "SvenDowideit@home.org.au"
|
||||||
@@ -582,37 +568,32 @@ made through a pull request.
|
|||||||
Email = "tibor@docker.com"
|
Email = "tibor@docker.com"
|
||||||
GitHub = "tiborvass"
|
GitHub = "tiborvass"
|
||||||
|
|
||||||
|
[people.unclejack]
|
||||||
|
Name = "Cristian Staretu"
|
||||||
|
Email = "cristian.staretu@gmail.com"
|
||||||
|
GitHub = "unclejack"
|
||||||
|
|
||||||
[people.vbatts]
|
[people.vbatts]
|
||||||
Name = "Vincent Batts"
|
Name = "Vincent Batts"
|
||||||
Email = "vbatts@redhat.com"
|
Email = "vbatts@redhat.com"
|
||||||
GitHub = "vbatts"
|
GitHub = "vbatts"
|
||||||
|
|
||||||
|
[people.vdemeester]
|
||||||
|
Name = "Vincent Demeester"
|
||||||
|
Email = "vincent@sbr.pm"
|
||||||
|
GitHub = "vdemeester"
|
||||||
|
|
||||||
[people.vieux]
|
[people.vieux]
|
||||||
Name = "Victor Vieux"
|
Name = "Victor Vieux"
|
||||||
Email = "vieux@docker.com"
|
Email = "vieux@docker.com"
|
||||||
GitHub = "vieux"
|
GitHub = "vieux"
|
||||||
|
|
||||||
[people.vmarmol]
|
|
||||||
Name = "Victor Marmol"
|
|
||||||
Email = "vmarmol@google.com"
|
|
||||||
GitHub = "vmarmol"
|
|
||||||
|
|
||||||
[people.jnagal]
|
|
||||||
Name = "Rohit Jnagal"
|
|
||||||
Email = "jnagal@google.com"
|
|
||||||
GitHub = "rjnagal"
|
|
||||||
|
|
||||||
[people.mpatel]
|
|
||||||
Name = "Mrunal Patel"
|
|
||||||
Email = "mpatel@redhat.com"
|
|
||||||
GitHub = "mrunalp"
|
|
||||||
|
|
||||||
[people.unclejack]
|
|
||||||
Name = "Cristian Staretu"
|
|
||||||
Email = "cristian.staretu@gmail.com"
|
|
||||||
GitHub = "unclejack"
|
|
||||||
|
|
||||||
[people.vishh]
|
[people.vishh]
|
||||||
Name = "Vishnu Kannan"
|
Name = "Vishnu Kannan"
|
||||||
Email = "vishnuk@google.com"
|
Email = "vishnuk@google.com"
|
||||||
GitHub = "vishh"
|
GitHub = "vishh"
|
||||||
|
|
||||||
|
[people.vmarmol]
|
||||||
|
Name = "Victor Marmol"
|
||||||
|
Email = "vmarmol@google.com"
|
||||||
|
GitHub = "vmarmol"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ DOCKER_ENVS := \
|
|||||||
-e DOCKER_DEBUG \
|
-e DOCKER_DEBUG \
|
||||||
-e DOCKER_EXECDRIVER \
|
-e DOCKER_EXECDRIVER \
|
||||||
-e DOCKER_EXPERIMENTAL \
|
-e DOCKER_EXPERIMENTAL \
|
||||||
|
-e DOCKER_REMAP_ROOT \
|
||||||
-e DOCKER_GRAPHDRIVER \
|
-e DOCKER_GRAPHDRIVER \
|
||||||
-e DOCKER_STORAGE_OPTS \
|
-e DOCKER_STORAGE_OPTS \
|
||||||
-e DOCKER_USERLANDPROXY \
|
-e DOCKER_USERLANDPROXY \
|
||||||
|
|||||||
+1
-1
@@ -130,7 +130,7 @@ we intend to take advantage of it in the Engine.
|
|||||||
## 2.1 Docker exec
|
## 2.1 Docker exec
|
||||||
|
|
||||||
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
|
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
|
||||||
*debugging* feature, as well as being strongly dependent on the the Runtime ingredient effort.
|
*debugging* feature, as well as being strongly dependent on the Runtime ingredient effort.
|
||||||
|
|
||||||
## 2.2 Dockerfile syntax
|
## 2.2 Dockerfile syntax
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker attach [OPTIONS] CONTAINER
|
// Usage: docker attach [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
|
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
|
||||||
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
||||||
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
||||||
|
|
||||||
|
|||||||
+24
-7
@@ -12,7 +12,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -35,6 +34,7 @@ import (
|
|||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const (
|
|||||||
//
|
//
|
||||||
// Usage: docker build [OPTIONS] PATH | URL | -
|
// Usage: docker build [OPTIONS] PATH | URL | -
|
||||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true)
|
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
|
||||||
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
||||||
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
||||||
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
||||||
@@ -58,12 +58,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
||||||
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
||||||
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
||||||
flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
||||||
flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||||
flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||||
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
||||||
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
||||||
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
||||||
|
flBuildArg := opts.NewListOpts(opts.ValidateEnv)
|
||||||
|
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
|
||||||
|
|
||||||
ulimits := make(map[string]*ulimit.Ulimit)
|
ulimits := make(map[string]*ulimit.Ulimit)
|
||||||
flUlimits := opts.NewUlimitOpt(&ulimits)
|
flUlimits := opts.NewUlimitOpt(&ulimits)
|
||||||
@@ -128,13 +130,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var includes = []string{"."}
|
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
excludes, err := utils.ReadDockerIgnore(path.Join(contextDir, ".dockerignore"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var excludes []string
|
||||||
|
if err == nil {
|
||||||
|
excludes, err = utils.ReadDockerIgnore(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||||
return fmt.Errorf("Error checking context: '%s'.", err)
|
return fmt.Errorf("Error checking context: '%s'.", err)
|
||||||
}
|
}
|
||||||
@@ -146,6 +154,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
// removed. The deamon will remove them for us, if needed, after it
|
// removed. The deamon will remove them for us, if needed, after it
|
||||||
// parses the Dockerfile. Ignore errors here, as they will have been
|
// parses the Dockerfile. Ignore errors here, as they will have been
|
||||||
// caught by ValidateContextDirectory above.
|
// caught by ValidateContextDirectory above.
|
||||||
|
var includes = []string{"."}
|
||||||
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
||||||
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
||||||
if keepThem1 || keepThem2 {
|
if keepThem1 || keepThem2 {
|
||||||
@@ -257,6 +266,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
}
|
}
|
||||||
v.Set("ulimits", string(ulimitsJSON))
|
v.Set("ulimits", string(ulimitsJSON))
|
||||||
|
|
||||||
|
// collect all the build-time environment variables for the container
|
||||||
|
buildArgs := runconfig.ConvertKVStringsToMap(flBuildArg.GetAll())
|
||||||
|
buildArgsJSON, err := json.Marshal(buildArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set("buildargs", string(buildArgsJSON))
|
||||||
|
|
||||||
headers := http.Header(make(map[string][]string))
|
headers := http.Header(make(map[string][]string))
|
||||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+7
-10
@@ -52,7 +52,7 @@ type DockerCli struct {
|
|||||||
outFd uintptr
|
outFd uintptr
|
||||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
// isTerminalIn indicates whether the client's STDIN is a TTY
|
||||||
isTerminalIn bool
|
isTerminalIn bool
|
||||||
// isTerminalOut dindicates whether the client's STDOUT is a TTY
|
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
||||||
isTerminalOut bool
|
isTerminalOut bool
|
||||||
// transport holds the client transport instance.
|
// transport holds the client transport instance.
|
||||||
transport *http.Transport
|
transport *http.Transport
|
||||||
@@ -105,21 +105,18 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
|
|||||||
|
|
||||||
switch len(hosts) {
|
switch len(hosts) {
|
||||||
case 0:
|
case 0:
|
||||||
defaultHost := os.Getenv("DOCKER_HOST")
|
hosts = []string{os.Getenv("DOCKER_HOST")}
|
||||||
if defaultHost == "" {
|
|
||||||
defaultHost = opts.DefaultHost
|
|
||||||
}
|
|
||||||
defaultHost, err := opts.ValidateHost(defaultHost)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hosts = []string{defaultHost}
|
|
||||||
case 1:
|
case 1:
|
||||||
// only accept one host to talk to
|
// only accept one host to talk to
|
||||||
default:
|
default:
|
||||||
return errors.New("Please specify only one -H")
|
return errors.New("Please specify only one -H")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var e error
|
||||||
|
if hosts[0], e = opts.ParseHost(hosts[0]); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
||||||
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||||
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true)
|
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, Cli.DockerCommands["commit"].Description, true)
|
||||||
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
||||||
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
||||||
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
||||||
|
|||||||
+2
-2
@@ -43,8 +43,8 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
|||||||
"cp",
|
"cp",
|
||||||
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
"Copy files/folders between a container and your host.\n",
|
Cli.DockerCommands["cp"].Description,
|
||||||
"Use '-' as the source to read a tar archive from stdin\n",
|
"\nUse '-' as the source to read a tar archive from stdin\n",
|
||||||
"and extract it to a directory destination in a container.\n",
|
"and extract it to a directory destination in a container.\n",
|
||||||
"Use '-' as the destination to stream a tar archive of a\n",
|
"Use '-' as the destination to stream a tar archive of a\n",
|
||||||
"container source to stdout.",
|
"container source to stdout.",
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
//
|
//
|
||||||
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||||
func (cli *DockerCli) CmdCreate(args ...string) error {
|
func (cli *DockerCli) CmdCreate(args ...string) error {
|
||||||
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true)
|
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
|
||||||
addTrustedFlags(cmd, true)
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker diff CONTAINER
|
// Usage: docker diff CONTAINER
|
||||||
func (cli *DockerCli) CmdDiff(args ...string) error {
|
func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||||
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true)
|
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, Cli.DockerCommands["diff"].Description, true)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker events [OPTIONS]
|
// Usage: docker events [OPTIONS]
|
||||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
cmd := Cli.Subcmd("events", nil, "Get real time events from the server", true)
|
cmd := Cli.Subcmd("events", nil, Cli.DockerCommands["events"].Description, true)
|
||||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||||
flFilter := opts.NewListOpts(nil)
|
flFilter := opts.NewListOpts(nil)
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
||||||
func (cli *DockerCli) CmdExec(args ...string) error {
|
func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", true)
|
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
|
||||||
|
|
||||||
execConfig, err := runconfig.ParseExec(cmd, args)
|
execConfig, err := runconfig.ParseExec(cmd, args)
|
||||||
// just in case the ParseExec does not exit
|
// just in case the ParseExec does not exit
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker export [OPTIONS] CONTAINER
|
// Usage: docker export [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||||
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", true)
|
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, Cli.DockerCommands["export"].Description, true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
|
|||||||
+27
-30
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
"github.com/docker/docker/pkg/promise"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
)
|
)
|
||||||
@@ -156,6 +155,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
req.Host = cli.addr
|
req.Host = cli.addr
|
||||||
|
|
||||||
dial, err := cli.dial()
|
dial, err := cli.dial()
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
|
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// When we set up a TCP connection for hijack, there could be long periods
|
// When we set up a TCP connection for hijack, there could be long periods
|
||||||
// of inactivity (a long running command with no output) that in certain
|
// of inactivity (a long running command with no output) that in certain
|
||||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||||
@@ -165,12 +171,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
tcpConn.SetKeepAlive(true)
|
tcpConn.SetKeepAlive(true)
|
||||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
clientconn := httputil.NewClientConn(dial, nil)
|
clientconn := httputil.NewClientConn(dial, nil)
|
||||||
defer clientconn.Close()
|
defer clientconn.Close()
|
||||||
|
|
||||||
@@ -184,8 +185,6 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
started <- rwc
|
started <- rwc
|
||||||
}
|
}
|
||||||
|
|
||||||
var receiveStdout chan error
|
|
||||||
|
|
||||||
var oldState *term.State
|
var oldState *term.State
|
||||||
|
|
||||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||||
@@ -196,19 +195,15 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
receiveStdout := make(chan error, 1)
|
||||||
if stdout != nil || stderr != nil {
|
if stdout != nil || stderr != nil {
|
||||||
receiveStdout = promise.Go(func() (err error) {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if in != nil {
|
if in != nil {
|
||||||
if setRawTerminal && cli.isTerminalIn {
|
if setRawTerminal && cli.isTerminalIn {
|
||||||
term.RestoreTerminal(cli.inFd, oldState)
|
term.RestoreTerminal(cli.inFd, oldState)
|
||||||
}
|
}
|
||||||
// For some reason this Close call blocks on darwin..
|
in.Close()
|
||||||
// As the client exists right after, simply discard the close
|
|
||||||
// until we find a better solution.
|
|
||||||
if runtime.GOOS != "darwin" {
|
|
||||||
in.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -219,11 +214,12 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
||||||
}
|
}
|
||||||
logrus.Debugf("[hijack] End of stdout")
|
logrus.Debugf("[hijack] End of stdout")
|
||||||
return err
|
receiveStdout <- err
|
||||||
})
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
sendStdin := promise.Go(func() error {
|
stdinDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
if in != nil {
|
if in != nil {
|
||||||
io.Copy(rwc, in)
|
io.Copy(rwc, in)
|
||||||
logrus.Debugf("[hijack] End of stdin")
|
logrus.Debugf("[hijack] End of stdin")
|
||||||
@@ -236,22 +232,23 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Discard errors due to pipe interruption
|
close(stdinDone)
|
||||||
return nil
|
}()
|
||||||
})
|
|
||||||
|
|
||||||
if stdout != nil || stderr != nil {
|
select {
|
||||||
if err := <-receiveStdout; err != nil {
|
case err := <-receiveStdout:
|
||||||
|
if err != nil {
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
logrus.Debugf("Error receiveStdout: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
case <-stdinDone:
|
||||||
|
if stdout != nil || stderr != nil {
|
||||||
if !cli.isTerminalIn {
|
if err := <-receiveStdout; err != nil {
|
||||||
if err := <-sendStdin; err != nil {
|
logrus.Debugf("Error receiveStdout: %s", err)
|
||||||
logrus.Debugf("Error sendStdin: %s", err)
|
return err
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker history [OPTIONS] IMAGE
|
// Usage: docker history [OPTIONS] IMAGE
|
||||||
func (cli *DockerCli) CmdHistory(args ...string) error {
|
func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||||
cmd := Cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", true)
|
cmd := Cli.Subcmd("history", []string{"IMAGE"}, Cli.DockerCommands["history"].Description, true)
|
||||||
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
|
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
@@ -57,9 +58,9 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *noTrunc {
|
if *noTrunc {
|
||||||
fmt.Fprintf(w, "%s\t", entry.CreatedBy)
|
fmt.Fprintf(w, "%s\t", strings.Replace(entry.CreatedBy, "\t", " ", -1))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45))
|
fmt.Fprintf(w, "%s\t", stringutils.Truncate(strings.Replace(entry.CreatedBy, "\t", " ", -1), 45))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *human {
|
if *human {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker images [OPTIONS] [REPOSITORY]
|
// Usage: docker images [OPTIONS] [REPOSITORY]
|
||||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, "List images", true)
|
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, Cli.DockerCommands["images"].Description, true)
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
||||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||||
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
|
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, Cli.DockerCommands["import"].Description, true)
|
||||||
flChanges := opts.NewListOpts(nil)
|
flChanges := opts.NewListOpts(nil)
|
||||||
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
||||||
message := cmd.String([]string{"m", "-message"}, "", "Set commit message for imported image")
|
message := cmd.String([]string{"m", "-message"}, "", "Set commit message for imported image")
|
||||||
|
|||||||
+14
-11
@@ -16,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker info
|
// Usage: docker info
|
||||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||||
cmd := Cli.Subcmd("info", nil, "Display system-wide information", true)
|
cmd := Cli.Subcmd("info", nil, Cli.DockerCommands["info"].Description, true)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -35,6 +35,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
|
|
||||||
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
||||||
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
||||||
|
ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion)
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
|
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
|
||||||
if info.DriverStatus != nil {
|
if info.DriverStatus != nil {
|
||||||
for _, pair := range info.DriverStatus {
|
for _, pair := range info.DriverStatus {
|
||||||
@@ -52,13 +53,13 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
|
|
||||||
if info.Debug {
|
if info.Debug {
|
||||||
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
|
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
|
||||||
fmt.Fprintf(cli.out, "File Descriptors: %d\n", info.NFd)
|
fmt.Fprintf(cli.out, " File Descriptors: %d\n", info.NFd)
|
||||||
fmt.Fprintf(cli.out, "Goroutines: %d\n", info.NGoroutines)
|
fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
|
||||||
fmt.Fprintf(cli.out, "System Time: %s\n", info.SystemTime)
|
fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
|
||||||
fmt.Fprintf(cli.out, "EventsListeners: %d\n", info.NEventsListener)
|
fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
|
||||||
fmt.Fprintf(cli.out, "Init SHA1: %s\n", info.InitSha1)
|
fmt.Fprintf(cli.out, " Init SHA1: %s\n", info.InitSha1)
|
||||||
fmt.Fprintf(cli.out, "Init Path: %s\n", info.InitPath)
|
fmt.Fprintf(cli.out, " Init Path: %s\n", info.InitPath)
|
||||||
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
fmt.Fprintf(cli.out, " Docker Root Dir: %s\n", info.DockerRootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
|
||||||
@@ -72,7 +73,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only output these warnings if the server supports these features
|
|
||||||
|
// Only output these warnings if the server does not support these features
|
||||||
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
||||||
if h.OS != "windows" {
|
if h.OS != "windows" {
|
||||||
if !info.MemoryLimit {
|
if !info.MemoryLimit {
|
||||||
@@ -100,8 +102,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.ExperimentalBuild {
|
ioutils.FprintfIfTrue(cli.out, "Experimental: %v\n", info.ExperimentalBuild)
|
||||||
fmt.Fprintf(cli.out, "Experimental: true\n")
|
if info.ClusterStore != "" {
|
||||||
|
fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -24,9 +25,10 @@ var funcMap = template.FuncMap{
|
|||||||
//
|
//
|
||||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
||||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||||
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true)
|
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
|
||||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
||||||
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
|
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
|
||||||
|
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -51,10 +53,15 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||||||
status := 0
|
status := 0
|
||||||
isImage := false
|
isImage := false
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
if *size {
|
||||||
|
v.Set("size", "1")
|
||||||
|
}
|
||||||
|
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
|
|
||||||
if *inspectType == "" || *inspectType == "container" {
|
if *inspectType == "" || *inspectType == "container" {
|
||||||
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
|
||||||
if err != nil && *inspectType == "container" {
|
if err != nil && *inspectType == "container" {
|
||||||
if strings.Contains(err.Error(), "No such") {
|
if strings.Contains(err.Error(), "No such") {
|
||||||
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdKill(args ...string) error {
|
func (cli *DockerCli) CmdKill(args ...string) error {
|
||||||
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", true)
|
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["kill"].Description, true)
|
||||||
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker load [OPTIONS]
|
// Usage: docker load [OPTIONS]
|
||||||
func (cli *DockerCli) CmdLoad(args ...string) error {
|
func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||||
cmd := Cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", true)
|
cmd := Cli.Subcmd("load", nil, Cli.DockerCommands["load"].Description, true)
|
||||||
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker login SERVER
|
// Usage: docker login SERVER
|
||||||
func (cli *DockerCli) CmdLogin(args ...string) error {
|
func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified \""+registry.IndexServer+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
var username, password, email string
|
var username, password, email string
|
||||||
@@ -64,7 +64,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
if username == "" {
|
if username == "" {
|
||||||
promptDefault("Username", authconfig.Username)
|
promptDefault("Username", authconfig.Username)
|
||||||
username = readInput(cli.in, cli.out)
|
username = readInput(cli.in, cli.out)
|
||||||
username = strings.Trim(username, " ")
|
username = strings.TrimSpace(username)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = authconfig.Username
|
username = authconfig.Username
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker logout [SERVER]
|
// Usage: docker logout [SERVER]
|
||||||
func (cli *DockerCli) CmdLogout(args ...string) error {
|
func (cli *DockerCli) CmdLogout(args ...string) error {
|
||||||
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified \""+registry.IndexServer+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+1
-6
@@ -2,7 +2,6 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// docker logs [OPTIONS] CONTAINER
|
// docker logs [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true)
|
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, Cli.DockerCommands["logs"].Description, true)
|
||||||
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
||||||
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
||||||
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
||||||
@@ -37,10 +36,6 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if logType := c.HostConfig.LogConfig.Type; logType != "json-file" {
|
|
||||||
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver (got: %s)", logType)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("stdout", "1")
|
v.Set("stdout", "1")
|
||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|||||||
+349
-8
@@ -1,16 +1,357 @@
|
|||||||
// +build experimental
|
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
nwclient "github.com/docker/libnetwork/client"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
|
"github.com/docker/docker/daemon/network"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdNetwork is used to create, display and configure network endpoints.
|
// CmdNetwork is the parent subcommand for all network commands
|
||||||
|
//
|
||||||
|
// Usage: docker network <COMMAND> [OPTIONS]
|
||||||
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
||||||
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
|
||||||
args = append([]string{"network"}, args...)
|
cmd.Require(flag.Min, 1)
|
||||||
return nCli.Cmd(os.Args[0], args...)
|
err := cmd.ParseFlags(args, true)
|
||||||
|
cmd.Usage()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdNetworkCreate creates a new network with a given name
|
||||||
|
//
|
||||||
|
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
|
||||||
|
func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
|
||||||
|
flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
|
||||||
|
flOpts := opts.NewMapOpts(nil, nil)
|
||||||
|
|
||||||
|
flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
|
||||||
|
flIpamSubnet := opts.NewListOpts(nil)
|
||||||
|
flIpamIPRange := opts.NewListOpts(nil)
|
||||||
|
flIpamGateway := opts.NewListOpts(nil)
|
||||||
|
flIpamAux := opts.NewMapOpts(nil, nil)
|
||||||
|
|
||||||
|
cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
|
||||||
|
cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
|
||||||
|
cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
|
||||||
|
cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
|
||||||
|
cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
|
||||||
|
|
||||||
|
cmd.Require(flag.Exact, 1)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct network create request body
|
||||||
|
nc := types.NetworkCreate{
|
||||||
|
Name: cmd.Arg(0),
|
||||||
|
Driver: *flDriver,
|
||||||
|
IPAM: network.IPAM{Driver: *flIpamDriver, Config: ipamCfg},
|
||||||
|
Options: flOpts.GetAll(),
|
||||||
|
CheckDuplicate: true,
|
||||||
|
}
|
||||||
|
obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var resp types.NetworkCreateResponse
|
||||||
|
err = json.Unmarshal(obj, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "%s\n", resp.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdNetworkRm deletes a network
|
||||||
|
//
|
||||||
|
// Usage: docker network rm <NETWORK-NAME | NETWORK-ID>
|
||||||
|
func (cli *DockerCli) CmdNetworkRm(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd("network rm", []string{"NETWORK"}, "Deletes a network", false)
|
||||||
|
cmd.Require(flag.Exact, 1)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, err = readBody(cli.call("DELETE", "/networks/"+cmd.Arg(0), nil, nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdNetworkConnect connects a container to a network
|
||||||
|
//
|
||||||
|
// Usage: docker network connect <NETWORK> <CONTAINER>
|
||||||
|
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
|
||||||
|
cmd.Require(flag.Exact, 2)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nc := types.NetworkConnect{Container: cmd.Arg(1)}
|
||||||
|
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/connect", nc, nil))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdNetworkDisconnect disconnects a container from a network
|
||||||
|
//
|
||||||
|
// Usage: docker network disconnect <NETWORK> <CONTAINER>
|
||||||
|
func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
|
||||||
|
cmd.Require(flag.Exact, 2)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nc := types.NetworkConnect{Container: cmd.Arg(1)}
|
||||||
|
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/disconnect", nc, nil))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdNetworkLs lists all the netorks managed by docker daemon
|
||||||
|
//
|
||||||
|
// Usage: docker network ls [OPTIONS]
|
||||||
|
func (cli *DockerCli) CmdNetworkLs(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
|
||||||
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||||
|
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
|
||||||
|
|
||||||
|
cmd.Require(flag.Exact, 0)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkResources []types.NetworkResource
|
||||||
|
err = json.Unmarshal(obj, &networkResources)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||||
|
|
||||||
|
// unless quiet (-q) is specified, print field titles
|
||||||
|
if !*quiet {
|
||||||
|
fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, networkResource := range networkResources {
|
||||||
|
ID := networkResource.ID
|
||||||
|
netName := networkResource.Name
|
||||||
|
if !*noTrunc {
|
||||||
|
ID = stringid.TruncateID(ID)
|
||||||
|
}
|
||||||
|
if *quiet {
|
||||||
|
fmt.Fprintln(wr, ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
driver := networkResource.Driver
|
||||||
|
fmt.Fprintf(wr, "%s\t%s\t%s\t",
|
||||||
|
ID,
|
||||||
|
netName,
|
||||||
|
driver)
|
||||||
|
fmt.Fprint(wr, "\n")
|
||||||
|
}
|
||||||
|
wr.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdNetworkInspect inspects the network object for more details
|
||||||
|
//
|
||||||
|
// Usage: docker network inspect <NETWORK>
|
||||||
|
// CmdNetworkInspect handles Network inspect UI
|
||||||
|
func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd("network inspect", []string{"NETWORK"}, "Displays detailed information on a network", false)
|
||||||
|
cmd.Require(flag.Exact, 1)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, _, err := readBody(cli.call("GET", "/networks/"+cmd.Arg(0), nil, nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
networkResource := &types.NetworkResource{}
|
||||||
|
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indented := new(bytes.Buffer)
|
||||||
|
if err := json.Indent(indented, obj, "", " "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consolidates the ipam configuration as a group from differnt related configurations
|
||||||
|
// user can configure network with multiple non-overlapping subnets and hence it is
|
||||||
|
// possible to corelate the various related parameters and consolidate them.
|
||||||
|
// consoidateIpam consolidates subnets, ip-ranges, gateways and auxilary addresses into
|
||||||
|
// structured ipam data.
|
||||||
|
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
|
||||||
|
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
|
||||||
|
return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
|
||||||
|
}
|
||||||
|
iData := map[string]*network.IPAMConfig{}
|
||||||
|
|
||||||
|
// Populate non-overlapping subnets into consolidation map
|
||||||
|
for _, s := range subnets {
|
||||||
|
for k := range iData {
|
||||||
|
ok1, err := subnetMatches(s, k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ok2, err := subnetMatches(k, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok1 || ok2 {
|
||||||
|
return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and add valid ip ranges
|
||||||
|
for _, r := range ranges {
|
||||||
|
match := false
|
||||||
|
for _, s := range subnets {
|
||||||
|
ok, err := subnetMatches(s, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iData[s].IPRange != "" {
|
||||||
|
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
|
||||||
|
}
|
||||||
|
d := iData[s]
|
||||||
|
d.IPRange = r
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return nil, fmt.Errorf("no matching subnet for range %s", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and add valid gateways
|
||||||
|
for _, g := range gateways {
|
||||||
|
match := false
|
||||||
|
for _, s := range subnets {
|
||||||
|
ok, err := subnetMatches(s, g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iData[s].Gateway != "" {
|
||||||
|
return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
|
||||||
|
}
|
||||||
|
d := iData[s]
|
||||||
|
d.Gateway = g
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return nil, fmt.Errorf("no matching subnet for gateway %s", g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and add aux-addresses
|
||||||
|
for key, aa := range auxaddrs {
|
||||||
|
match := false
|
||||||
|
for _, s := range subnets {
|
||||||
|
ok, err := subnetMatches(s, aa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
iData[s].AuxAddress[key] = aa
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idl := []network.IPAMConfig{}
|
||||||
|
for _, v := range iData {
|
||||||
|
idl = append(idl, *v)
|
||||||
|
}
|
||||||
|
return idl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func subnetMatches(subnet, data string) (bool, error) {
|
||||||
|
var (
|
||||||
|
ip net.IP
|
||||||
|
)
|
||||||
|
|
||||||
|
_, s, err := net.ParseCIDR(subnet)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(data, "/") {
|
||||||
|
ip, _, err = net.ParseCIDR(data)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ip = net.ParseIP(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Contains(ip), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func networkUsage() string {
|
||||||
|
networkCommands := map[string]string{
|
||||||
|
"create": "Create a network",
|
||||||
|
"connect": "Connect container to a network",
|
||||||
|
"disconnect": "Disconnect container from a network",
|
||||||
|
"inspect": "Display detailed network information",
|
||||||
|
"ls": "List all networks",
|
||||||
|
"rm": "Remove a network",
|
||||||
|
}
|
||||||
|
|
||||||
|
help := "Commands:\n"
|
||||||
|
|
||||||
|
for cmd, description := range networkCommands {
|
||||||
|
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
|
||||||
|
return help
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker pause CONTAINER [CONTAINER...]
|
// Usage: docker pause CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdPause(args ...string) error {
|
func (cli *DockerCli) CmdPause(args ...string) error {
|
||||||
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true)
|
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["pause"].Description, true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
||||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||||
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, Cli.DockerCommands["port"].Description, true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
psFilterArgs = filters.Args{}
|
psFilterArgs = filters.Args{}
|
||||||
v = url.Values{}
|
v = url.Values{}
|
||||||
|
|
||||||
cmd = Cli.Subcmd("ps", nil, "List containers", true)
|
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
|
||||||
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||||
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
||||||
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
||||||
|
|||||||
+3
-66
@@ -1,12 +1,9 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
@@ -65,7 +62,9 @@ func (c *containerContext) Image() string {
|
|||||||
return "<no image>"
|
return "<no image>"
|
||||||
}
|
}
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
return stringutils.Truncate(c.c.Image, 12)
|
if stringid.TruncateID(c.c.ImageID) == stringid.TruncateID(c.c.Image) {
|
||||||
|
return stringutils.Truncate(c.c.Image, 12)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return c.c.Image
|
return c.c.Image
|
||||||
}
|
}
|
||||||
@@ -152,68 +151,6 @@ func (c *containerContext) addHeader(header string) {
|
|||||||
c.header = append(c.header, strings.ToUpper(header))
|
c.header = append(c.header, strings.ToUpper(header))
|
||||||
}
|
}
|
||||||
|
|
||||||
func customFormat(ctx Context, containers []types.Container) {
|
|
||||||
var (
|
|
||||||
table bool
|
|
||||||
header string
|
|
||||||
format = ctx.Format
|
|
||||||
buffer = bytes.NewBufferString("")
|
|
||||||
)
|
|
||||||
|
|
||||||
if strings.HasPrefix(ctx.Format, tableKey) {
|
|
||||||
table = true
|
|
||||||
format = format[len(tableKey):]
|
|
||||||
}
|
|
||||||
|
|
||||||
format = strings.Trim(format, " ")
|
|
||||||
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
|
||||||
format = r.Replace(format)
|
|
||||||
|
|
||||||
if table && ctx.Size {
|
|
||||||
format += "\t{{.Size}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("").Parse(format)
|
|
||||||
if err != nil {
|
|
||||||
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range containers {
|
|
||||||
containerCtx := &containerContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
c: container,
|
|
||||||
}
|
|
||||||
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
|
||||||
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if table && len(header) == 0 {
|
|
||||||
header = containerCtx.fullHeader()
|
|
||||||
}
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if table {
|
|
||||||
if len(header) == 0 {
|
|
||||||
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
|
|
||||||
containerCtx := &containerContext{}
|
|
||||||
tmpl.Execute(bytes.NewBufferString(""), containerCtx)
|
|
||||||
header = containerCtx.fullHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
|
|
||||||
t.Write([]byte(header))
|
|
||||||
t.Write([]byte("\n"))
|
|
||||||
buffer.WriteTo(t)
|
|
||||||
t.Flush()
|
|
||||||
} else {
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripNamePrefix(ss []string) []string {
|
func stripNamePrefix(ss []string) []string {
|
||||||
for i, s := range ss {
|
for i, s := range ss {
|
||||||
ss[i] = s[1:]
|
ss[i] = s[1:]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -24,8 +23,29 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
call func() string
|
call func() string
|
||||||
}{
|
}{
|
||||||
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID},
|
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID},
|
||||||
|
{types.Container{ID: containerID}, false, containerID, idHeader, ctx.ID},
|
||||||
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
||||||
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
||||||
|
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
|
||||||
|
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
|
||||||
|
{types.Container{
|
||||||
|
Image: "a5a665ff33eced1e0803148700880edab4",
|
||||||
|
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"a5a665ff33ec",
|
||||||
|
imageHeader,
|
||||||
|
ctx.Image,
|
||||||
|
},
|
||||||
|
{types.Container{
|
||||||
|
Image: "a5a665ff33eced1e0803148700880edab4",
|
||||||
|
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"a5a665ff33eced1e0803148700880edab4",
|
||||||
|
imageHeader,
|
||||||
|
ctx.Image,
|
||||||
|
},
|
||||||
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
||||||
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
||||||
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
||||||
@@ -33,7 +53,9 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
||||||
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
||||||
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
||||||
|
{types.Container{}, true, "", labelsHeader, ctx.Labels},
|
||||||
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
||||||
|
{types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@@ -68,8 +90,8 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
||||||
ctx = containerContext{c: c, trunc: true}
|
ctx = containerContext{c: c1, trunc: true}
|
||||||
|
|
||||||
sid := ctx.Label("com.docker.swarm.swarm-id")
|
sid := ctx.Label("com.docker.swarm.swarm-id")
|
||||||
node := ctx.Label("com.docker.swarm.node_name")
|
node := ctx.Label("com.docker.swarm.node_name")
|
||||||
@@ -86,17 +108,19 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerPsFormatError(t *testing.T) {
|
c2 := types.Container{}
|
||||||
out := bytes.NewBufferString("")
|
ctx = containerContext{c: c2, trunc: true}
|
||||||
ctx := Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
label := ctx.Label("anything.really")
|
||||||
Output: out,
|
if label != "" {
|
||||||
|
t.Fatalf("Expected an empty string, was %s", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
customFormat(ctx, make([]types.Container, 0))
|
ctx = containerContext{c: c2, trunc: true}
|
||||||
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
|
fullHeader := ctx.fullHeader()
|
||||||
t.Fatalf("Expected format error, got `%v`\n", out.String())
|
if fullHeader != "" {
|
||||||
|
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
@@ -71,3 +76,65 @@ func tableFormat(ctx Context, containers []types.Container) {
|
|||||||
|
|
||||||
customFormat(ctx, containers)
|
customFormat(ctx, containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func customFormat(ctx Context, containers []types.Container) {
|
||||||
|
var (
|
||||||
|
table bool
|
||||||
|
header string
|
||||||
|
format = ctx.Format
|
||||||
|
buffer = bytes.NewBufferString("")
|
||||||
|
)
|
||||||
|
|
||||||
|
if strings.HasPrefix(ctx.Format, tableKey) {
|
||||||
|
table = true
|
||||||
|
format = format[len(tableKey):]
|
||||||
|
}
|
||||||
|
|
||||||
|
format = strings.Trim(format, " ")
|
||||||
|
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
||||||
|
format = r.Replace(format)
|
||||||
|
|
||||||
|
if table && ctx.Size {
|
||||||
|
format += "\t{{.Size}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("").Parse(format)
|
||||||
|
if err != nil {
|
||||||
|
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
containerCtx := &containerContext{
|
||||||
|
trunc: ctx.Trunc,
|
||||||
|
c: container,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
||||||
|
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if table && len(header) == 0 {
|
||||||
|
header = containerCtx.fullHeader()
|
||||||
|
}
|
||||||
|
buffer.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if table {
|
||||||
|
if len(header) == 0 {
|
||||||
|
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
|
||||||
|
containerCtx := &containerContext{}
|
||||||
|
tmpl.Execute(bytes.NewBufferString(""), containerCtx)
|
||||||
|
header = containerCtx.fullHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
|
||||||
|
t.Write([]byte(header))
|
||||||
|
t.Write([]byte("\n"))
|
||||||
|
buffer.WriteTo(t)
|
||||||
|
t.Flush()
|
||||||
|
} else {
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormat(t *testing.T) {
|
||||||
|
contexts := []struct {
|
||||||
|
context Context
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
// Errors
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "{{InvalidFunction}}",
|
||||||
|
},
|
||||||
|
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "{{nil}}",
|
||||||
|
},
|
||||||
|
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
// Table Format
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table",
|
||||||
|
},
|
||||||
|
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
|
containerID1 ubuntu "" 45 years ago foobar_baz
|
||||||
|
containerID2 ubuntu "" 45 years ago foobar_bar
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table {{.Image}}",
|
||||||
|
},
|
||||||
|
"IMAGE\nubuntu\nubuntu\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table {{.Image}}",
|
||||||
|
Size: true,
|
||||||
|
},
|
||||||
|
"IMAGE SIZE\nubuntu 0 B\nubuntu 0 B\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table {{.Image}}",
|
||||||
|
Quiet: true,
|
||||||
|
},
|
||||||
|
"IMAGE\nubuntu\nubuntu\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table",
|
||||||
|
Quiet: true,
|
||||||
|
},
|
||||||
|
"containerID1\ncontainerID2\n",
|
||||||
|
},
|
||||||
|
// Raw Format
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "raw",
|
||||||
|
},
|
||||||
|
`container_id: containerID1
|
||||||
|
image: ubuntu
|
||||||
|
command: ""
|
||||||
|
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||||
|
status:
|
||||||
|
names: foobar_baz
|
||||||
|
labels:
|
||||||
|
ports:
|
||||||
|
|
||||||
|
container_id: containerID2
|
||||||
|
image: ubuntu
|
||||||
|
command: ""
|
||||||
|
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||||
|
status:
|
||||||
|
names: foobar_bar
|
||||||
|
labels:
|
||||||
|
ports:
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "raw",
|
||||||
|
Size: true,
|
||||||
|
},
|
||||||
|
`container_id: containerID1
|
||||||
|
image: ubuntu
|
||||||
|
command: ""
|
||||||
|
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||||
|
status:
|
||||||
|
names: foobar_baz
|
||||||
|
labels:
|
||||||
|
ports:
|
||||||
|
size: 0 B
|
||||||
|
|
||||||
|
container_id: containerID2
|
||||||
|
image: ubuntu
|
||||||
|
command: ""
|
||||||
|
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||||
|
status:
|
||||||
|
names: foobar_bar
|
||||||
|
labels:
|
||||||
|
ports:
|
||||||
|
size: 0 B
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "raw",
|
||||||
|
Quiet: true,
|
||||||
|
},
|
||||||
|
"container_id: containerID1\ncontainer_id: containerID2\n",
|
||||||
|
},
|
||||||
|
// Custom Format
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "{{.Image}}",
|
||||||
|
},
|
||||||
|
"ubuntu\nubuntu\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "{{.Image}}",
|
||||||
|
Size: true,
|
||||||
|
},
|
||||||
|
"ubuntu\nubuntu\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, context := range contexts {
|
||||||
|
containers := []types.Container{
|
||||||
|
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu"},
|
||||||
|
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu"},
|
||||||
|
}
|
||||||
|
out := bytes.NewBufferString("")
|
||||||
|
context.context.Output = out
|
||||||
|
Format(context.context, containers)
|
||||||
|
actual := out.String()
|
||||||
|
if actual != context.expected {
|
||||||
|
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
||||||
|
}
|
||||||
|
// Clean buffer
|
||||||
|
out.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomFormatNoContainers(t *testing.T) {
|
||||||
|
out := bytes.NewBufferString("")
|
||||||
|
containers := []types.Container{}
|
||||||
|
|
||||||
|
contexts := []struct {
|
||||||
|
context Context
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "{{.Image}}",
|
||||||
|
Output: out,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table {{.Image}}",
|
||||||
|
Output: out,
|
||||||
|
},
|
||||||
|
"IMAGE\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "{{.Image}}",
|
||||||
|
Output: out,
|
||||||
|
Size: true,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context{
|
||||||
|
Format: "table {{.Image}}",
|
||||||
|
Output: out,
|
||||||
|
Size: true,
|
||||||
|
},
|
||||||
|
"IMAGE SIZE\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, context := range contexts {
|
||||||
|
customFormat(context.context, containers)
|
||||||
|
actual := out.String()
|
||||||
|
if actual != context.expected {
|
||||||
|
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
||||||
|
}
|
||||||
|
// Clean buffer
|
||||||
|
out.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
||||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true)
|
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true)
|
||||||
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
||||||
addTrustedFlags(cmd, true)
|
addTrustedFlags(cmd, true)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker push NAME[:TAG]
|
// Usage: docker push NAME[:TAG]
|
||||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true)
|
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, Cli.DockerCommands["push"].Description, true)
|
||||||
addTrustedFlags(cmd, false)
|
addTrustedFlags(cmd, false)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
@@ -11,13 +12,17 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rename OLD_NAME NEW_NAME
|
// Usage: docker rename OLD_NAME NEW_NAME
|
||||||
func (cli *DockerCli) CmdRename(args ...string) error {
|
func (cli *DockerCli) CmdRename(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true)
|
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, Cli.DockerCommands["rename"].Description, true)
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
oldName := cmd.Arg(0)
|
oldName := strings.TrimSpace(cmd.Arg(0))
|
||||||
newName := cmd.Arg(1)
|
newName := strings.TrimSpace(cmd.Arg(1))
|
||||||
|
|
||||||
|
if oldName == "" || newName == "" {
|
||||||
|
return fmt.Errorf("Error: Neither old nor new names may be empty")
|
||||||
|
}
|
||||||
|
|
||||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
|
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdRestart restarts one or more running containers.
|
// CmdRestart restarts one or more containers.
|
||||||
//
|
//
|
||||||
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||||
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", true)
|
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["restart"].Description, true)
|
||||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", true)
|
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["rm"].Description, true)
|
||||||
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
||||||
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
||||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
||||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true)
|
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["rmi"].Description, true)
|
||||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
||||||
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ func (cid *cidFile) Write(id string) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true)
|
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
|
||||||
addTrustedFlags(cmd, true)
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
|
|||||||
+13
-18
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
||||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||||
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["save"].Description+" (streamed to STDOUT by default)", true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
@@ -25,13 +25,14 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
output = cli.out
|
output = cli.out
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if *outfile == "" && cli.isTerminalOut {
|
||||||
|
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||||
|
}
|
||||||
if *outfile != "" {
|
if *outfile != "" {
|
||||||
output, err = os.Create(*outfile)
|
if output, err = os.Create(*outfile); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if cli.isTerminalOut {
|
|
||||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sopts := &streamOpts{
|
sopts := &streamOpts{
|
||||||
@@ -39,19 +40,13 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
out: output,
|
out: output,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cmd.Args()) == 1 {
|
v := url.Values{}
|
||||||
image := cmd.Arg(0)
|
for _, arg := range cmd.Args() {
|
||||||
if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
v.Add("names", arg)
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v := url.Values{}
|
|
||||||
for _, arg := range cmd.Args() {
|
|
||||||
v.Add("names", arg)
|
|
||||||
}
|
|
||||||
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
|
|||||||
//
|
//
|
||||||
// Usage: docker search [OPTIONS] TERM
|
// Usage: docker search [OPTIONS] TERM
|
||||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||||
cmd := Cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", true)
|
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
||||||
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
||||||
@@ -41,12 +41,13 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
|
|
||||||
// Resolve the Repository name from fqn to hostname + name
|
// Resolve the Repository name from fqn to hostname + name
|
||||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
|
||||||
|
indexInfo, err := registry.ParseIndexInfo(taglessRemote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search")
|
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, indexInfo, "search")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
||||||
for _, res := range results {
|
for _, res := range results {
|
||||||
if ((*automated || *trusted) && (!res.IsTrusted && !res.IsAutomated)) || (int(*stars) > res.StarCount) {
|
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) || (*trusted && !res.IsTrusted) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
desc := strings.Replace(res.Description, "\n", " ", -1)
|
desc := strings.Replace(res.Description, "\n", " ", -1)
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
// +build experimental
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
nwclient "github.com/docker/libnetwork/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdService is used to manage network services.
|
|
||||||
// service command is user to publish, attach and list a service from a container.
|
|
||||||
func (cli *DockerCli) CmdService(args ...string) error {
|
|
||||||
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
|
||||||
args = append([]string{"service"}, args...)
|
|
||||||
return nCli.Cmd(os.Args[0], args...)
|
|
||||||
}
|
|
||||||
+3
-2
@@ -32,6 +32,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|||||||
}
|
}
|
||||||
if sig == "" {
|
if sig == "" {
|
||||||
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
|
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
||||||
logrus.Debugf("Error sending signal: %s", err)
|
logrus.Debugf("Error sending signal: %s", err)
|
||||||
@@ -41,11 +42,11 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|||||||
return sigc
|
return sigc
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdStart starts one or more stopped containers.
|
// CmdStart starts one or more containers.
|
||||||
//
|
//
|
||||||
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, "Start one or more stopped containers", true)
|
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
|
||||||
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
||||||
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|||||||
+28
-8
@@ -56,15 +56,21 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var v *types.Stats
|
var v *types.StatsJSON
|
||||||
if err := dec.Decode(&v); err != nil {
|
if err := dec.Decode(&v); err != nil {
|
||||||
u <- err
|
u <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
|
||||||
|
var memPercent = 0.0
|
||||||
|
var cpuPercent = 0.0
|
||||||
|
|
||||||
|
// MemoryStats.Limit will never be 0 unless the container is not running and we havn't
|
||||||
|
// got any data from cgroup
|
||||||
|
if v.MemoryStats.Limit != 0 {
|
||||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||||
cpuPercent = 0.0
|
}
|
||||||
)
|
|
||||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
||||||
previousSystem = v.PreCPUStats.SystemUsage
|
previousSystem = v.PreCPUStats.SystemUsage
|
||||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
||||||
@@ -74,8 +80,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
s.Memory = float64(v.MemoryStats.Usage)
|
s.Memory = float64(v.MemoryStats.Usage)
|
||||||
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
||||||
s.MemoryPercentage = memPercent
|
s.MemoryPercentage = memPercent
|
||||||
s.NetworkRx = float64(v.Network.RxBytes)
|
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
||||||
s.NetworkTx = float64(v.Network.TxBytes)
|
|
||||||
s.BlockRead = float64(blkRead)
|
s.BlockRead = float64(blkRead)
|
||||||
s.BlockWrite = float64(blkWrite)
|
s.BlockWrite = float64(blkWrite)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
@@ -94,6 +99,11 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
s.CPUPercentage = 0
|
s.CPUPercentage = 0
|
||||||
s.Memory = 0
|
s.Memory = 0
|
||||||
s.MemoryPercentage = 0
|
s.MemoryPercentage = 0
|
||||||
|
s.MemoryLimit = 0
|
||||||
|
s.NetworkRx = 0
|
||||||
|
s.NetworkTx = 0
|
||||||
|
s.BlockRead = 0
|
||||||
|
s.BlockWrite = 0
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
case err := <-u:
|
case err := <-u:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -131,7 +141,7 @@ func (s *containerStats) Display(w io.Writer) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker stats CONTAINER [CONTAINER...]
|
// Usage: docker stats CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true)
|
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
|
||||||
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
@@ -192,7 +202,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
|
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||||
var (
|
var (
|
||||||
cpuPercent = 0.0
|
cpuPercent = 0.0
|
||||||
// calculate the change for the cpu usage of the container in between readings
|
// calculate the change for the cpu usage of the container in between readings
|
||||||
@@ -218,3 +228,13 @@ func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64)
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
||||||
|
var rx, tx float64
|
||||||
|
|
||||||
|
for _, v := range network {
|
||||||
|
rx += float64(v.RxBytes)
|
||||||
|
tx += float64(v.TxBytes)
|
||||||
|
}
|
||||||
|
return rx, tx
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -9,13 +9,13 @@ import (
|
|||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdStop stops one or more running containers.
|
// CmdStop stops one or more containers.
|
||||||
//
|
//
|
||||||
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
|
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
|
||||||
//
|
//
|
||||||
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||||
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stop"].Description+".\nSending SIGTERM and then SIGKILL after a grace period", true)
|
||||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||||
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", true)
|
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
|
||||||
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker top CONTAINER
|
// Usage: docker top CONTAINER
|
||||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||||
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true)
|
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, Cli.DockerCommands["top"].Description, true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+46
-23
@@ -15,7 +15,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -79,17 +78,19 @@ func (cli *DockerCli) certificateDirectory(server string) (string, error) {
|
|||||||
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trustServer(index *registry.IndexInfo) string {
|
func trustServer(index *registry.IndexInfo) (string, error) {
|
||||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||||
if !strings.HasPrefix(s, "https://") {
|
urlObj, err := url.Parse(s)
|
||||||
return "https://" + s
|
if err != nil || urlObj.Scheme != "https" {
|
||||||
|
return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
|
||||||
}
|
}
|
||||||
return s
|
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
if index.Official {
|
if index.Official {
|
||||||
return registry.NotaryServer
|
return registry.NotaryServer, nil
|
||||||
}
|
}
|
||||||
return "https://" + index.Name
|
return "https://" + index.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleCredentialStore struct {
|
type simpleCredentialStore struct {
|
||||||
@@ -101,9 +102,9 @@ func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
|
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
|
||||||
server := trustServer(repoInfo.Index)
|
server, err := trustServer(repoInfo.Index)
|
||||||
if !strings.HasPrefix(server, "https://") {
|
if err != nil {
|
||||||
return nil, errors.New("unsupported scheme: https required for trust server")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg = tlsconfig.ClientDefault
|
var cfg = tlsconfig.ClientDefault
|
||||||
@@ -144,15 +145,21 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := pingClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
challengeManager := auth.NewSimpleChallengeManager()
|
challengeManager := auth.NewSimpleChallengeManager()
|
||||||
if err := challengeManager.AddResponse(resp); err != nil {
|
|
||||||
return nil, err
|
resp, err := pingClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore error on ping to operate in offline mode
|
||||||
|
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Add response to the challenge manager to parse out
|
||||||
|
// authentication header and register authentication method
|
||||||
|
if err := challengeManager.AddResponse(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
creds := simpleCredentialStore{auth: authConfig}
|
creds := simpleCredentialStore{auth: authConfig}
|
||||||
@@ -178,16 +185,30 @@ func convertTarget(t client.Target) (target, error) {
|
|||||||
|
|
||||||
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
||||||
aliasMap := map[string]string{
|
aliasMap := map[string]string{
|
||||||
"root": "offline",
|
"root": "root",
|
||||||
"snapshot": "tagging",
|
"snapshot": "repository",
|
||||||
"targets": "tagging",
|
"targets": "repository",
|
||||||
}
|
}
|
||||||
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
|
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
|
||||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility with old env names. We should remove this in 1.10
|
||||||
|
if env["root"] == "" {
|
||||||
|
env["root"] = os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE")
|
||||||
|
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
if env["snapshot"] == "" || env["targets"] == "" {
|
||||||
|
env["snapshot"] = os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE")
|
||||||
|
env["targets"] = os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE")
|
||||||
|
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
||||||
if v := env[alias]; v != "" {
|
if v := env[alias]; v != "" {
|
||||||
return v, numAttempts > 1, nil
|
return v, numAttempts > 1, nil
|
||||||
@@ -248,6 +269,8 @@ func notaryError(err error) error {
|
|||||||
return fmt.Errorf("remote repository out-of-date: %v", err)
|
return fmt.Errorf("remote repository out-of-date: %v", err)
|
||||||
case trustmanager.ErrKeyNotFound:
|
case trustmanager.ErrKeyNotFound:
|
||||||
return fmt.Errorf("signing keys not found: %v", err)
|
return fmt.Errorf("signing keys not found: %v", err)
|
||||||
|
case *net.OpError:
|
||||||
|
return fmt.Errorf("error contacting notary server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unsetENV() {
|
||||||
|
os.Unsetenv("DOCKER_CONTENT_TRUST")
|
||||||
|
os.Unsetenv("DOCKER_CONTENT_TRUST_SERVER")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestENVTrustServer(t *testing.T) {
|
||||||
|
defer unsetENV()
|
||||||
|
indexInfo := ®istry.IndexInfo{Name: "testserver"}
|
||||||
|
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
|
||||||
|
t.Fatal("Failed to set ENV variable")
|
||||||
|
}
|
||||||
|
output, err := trustServer(indexInfo)
|
||||||
|
expectedStr := "https://notary-test.com:5000"
|
||||||
|
if err != nil || output != expectedStr {
|
||||||
|
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPENVTrustServer(t *testing.T) {
|
||||||
|
defer unsetENV()
|
||||||
|
indexInfo := ®istry.IndexInfo{Name: "testserver"}
|
||||||
|
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
|
||||||
|
t.Fatal("Failed to set ENV variable")
|
||||||
|
}
|
||||||
|
_, err := trustServer(indexInfo)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error with invalid scheme")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOfficialTrustServer(t *testing.T) {
|
||||||
|
indexInfo := ®istry.IndexInfo{Name: "testserver", Official: true}
|
||||||
|
output, err := trustServer(indexInfo)
|
||||||
|
if err != nil || output != registry.NotaryServer {
|
||||||
|
t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonOfficialTrustServer(t *testing.T) {
|
||||||
|
indexInfo := ®istry.IndexInfo{Name: "testserver", Official: false}
|
||||||
|
output, err := trustServer(indexInfo)
|
||||||
|
expectedStr := "https://" + indexInfo.Name
|
||||||
|
if err != nil || output != expectedStr {
|
||||||
|
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker unpause CONTAINER [CONTAINER...]
|
// Usage: docker unpause CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||||
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true)
|
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["unpause"].Description, true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+2
-1
@@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -96,7 +97,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if types.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
||||||
return serverResp, errConnectionFailed
|
return serverResp, errConnectionFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type versionData struct {
|
|||||||
//
|
//
|
||||||
// Usage: docker version
|
// Usage: docker version
|
||||||
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
||||||
cmd := Cli.Subcmd("version", nil, "Show the Docker version information.", true)
|
cmd := Cli.Subcmd("version", nil, Cli.DockerCommands["version"].Description, true)
|
||||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
|
|||||||
+14
-12
@@ -20,7 +20,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker volume <COMMAND> <OPTS>
|
// Usage: docker volume <COMMAND> <OPTS>
|
||||||
func (cli *DockerCli) CmdVolume(args ...string) error {
|
func (cli *DockerCli) CmdVolume(args ...string) error {
|
||||||
description := "Manage Docker volumes\n\nCommands:\n"
|
description := Cli.DockerCommands["volume"].Description + "\n\nCommands:\n"
|
||||||
commands := [][]string{
|
commands := [][]string{
|
||||||
{"create", "Create a volume"},
|
{"create", "Create a volume"},
|
||||||
{"inspect", "Return low-level information on a volume"},
|
{"inspect", "Return low-level information on a volume"},
|
||||||
@@ -32,12 +32,13 @@ func (cli *DockerCli) CmdVolume(args ...string) error {
|
|||||||
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
|
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
description += "\nRun 'docker volume COMMAND --help' for more information on a command."
|
description += "\nRun 'docker volume COMMAND --help' for more information on a command"
|
||||||
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, true)
|
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, false)
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
return cli.CmdVolumeLs(args...)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
err := cmd.ParseFlags(args, true)
|
||||||
|
cmd.Usage()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdVolumeLs outputs a list of Docker volumes.
|
// CmdVolumeLs outputs a list of Docker volumes.
|
||||||
@@ -102,15 +103,16 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
|
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
|
||||||
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
|
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
|
||||||
cmd := Cli.Subcmd("volume inspect", []string{"[VOLUME NAME]"}, "Return low-level information on a volume", true)
|
cmd := Cli.Subcmd("volume inspect", []string{"VOLUME [VOLUME...]"}, "Return low-level information on a volume", true)
|
||||||
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template.")
|
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
|
||||||
if err := cmd.Parse(args); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
|
if err := cmd.Parse(args); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
if *tmplStr != "" {
|
if *tmplStr != "" {
|
||||||
var err error
|
var err error
|
||||||
@@ -210,7 +212,7 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker volume rm VOLUME [VOLUME...]
|
// Usage: docker volume rm VOLUME [VOLUME...]
|
||||||
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
||||||
cmd := Cli.Subcmd("volume rm", []string{"[NAME]"}, "Remove a volume", true)
|
cmd := Cli.Subcmd("volume rm", []string{"VOLUME [VOLUME...]"}, "Remove a volume", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker wait CONTAINER [CONTAINER...]
|
// Usage: docker wait CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdWait(args ...string) error {
|
func (cli *DockerCli) CmdWait(args ...string) error {
|
||||||
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true)
|
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["wait"].Description, true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+28
-39
@@ -5,6 +5,7 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -17,7 +18,7 @@ import (
|
|||||||
// Common constants for daemon and client.
|
// Common constants for daemon and client.
|
||||||
const (
|
const (
|
||||||
// Version of Current REST API
|
// Version of Current REST API
|
||||||
Version version.Version = "1.21"
|
Version version.Version = "1.22"
|
||||||
|
|
||||||
// MinVersion represents Minimun REST API version supported
|
// MinVersion represents Minimun REST API version supported
|
||||||
MinVersion version.Version = "1.12"
|
MinVersion version.Version = "1.12"
|
||||||
@@ -37,22 +38,17 @@ func (r byPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].Priv
|
|||||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
||||||
// it's used by command 'docker ps'
|
// it's used by command 'docker ps'
|
||||||
func DisplayablePorts(ports []types.Port) string {
|
func DisplayablePorts(ports []types.Port) string {
|
||||||
var (
|
type portGroup struct {
|
||||||
result = []string{}
|
first int
|
||||||
hostMappings = []string{}
|
last int
|
||||||
firstInGroupMap map[string]int
|
}
|
||||||
lastInGroupMap map[string]int
|
groupMap := make(map[string]*portGroup)
|
||||||
)
|
var result []string
|
||||||
firstInGroupMap = make(map[string]int)
|
var hostMappings []string
|
||||||
lastInGroupMap = make(map[string]int)
|
|
||||||
sort.Sort(byPrivatePort(ports))
|
sort.Sort(byPrivatePort(ports))
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
var (
|
current := port.PrivatePort
|
||||||
current = port.PrivatePort
|
portKey := port.Type
|
||||||
portKey = port.Type
|
|
||||||
firstInGroup int
|
|
||||||
lastInGroup int
|
|
||||||
)
|
|
||||||
if port.IP != "" {
|
if port.IP != "" {
|
||||||
if port.PublicPort != current {
|
if port.PublicPort != current {
|
||||||
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||||
@@ -60,45 +56,38 @@ func DisplayablePorts(ports []types.Port) string {
|
|||||||
}
|
}
|
||||||
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
||||||
}
|
}
|
||||||
firstInGroup = firstInGroupMap[portKey]
|
group := groupMap[portKey]
|
||||||
lastInGroup = lastInGroupMap[portKey]
|
|
||||||
|
|
||||||
if firstInGroup == 0 {
|
if group == nil {
|
||||||
firstInGroupMap[portKey] = current
|
groupMap[portKey] = &portGroup{first: current, last: current}
|
||||||
lastInGroupMap[portKey] = current
|
continue
|
||||||
|
}
|
||||||
|
if current == (group.last + 1) {
|
||||||
|
group.last = current
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if current == (lastInGroup + 1) {
|
result = append(result, formGroup(portKey, group.first, group.last))
|
||||||
lastInGroupMap[portKey] = current
|
groupMap[portKey] = &portGroup{first: current, last: current}
|
||||||
continue
|
|
||||||
}
|
|
||||||
result = append(result, formGroup(portKey, firstInGroup, lastInGroup))
|
|
||||||
firstInGroupMap[portKey] = current
|
|
||||||
lastInGroupMap[portKey] = current
|
|
||||||
}
|
}
|
||||||
for portKey, firstInGroup := range firstInGroupMap {
|
for portKey, g := range groupMap {
|
||||||
result = append(result, formGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
|
result = append(result, formGroup(portKey, g.first, g.last))
|
||||||
}
|
}
|
||||||
result = append(result, hostMappings...)
|
result = append(result, hostMappings...)
|
||||||
return strings.Join(result, ", ")
|
return strings.Join(result, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func formGroup(key string, start, last int) string {
|
func formGroup(key string, start, last int) string {
|
||||||
var (
|
parts := strings.Split(key, "/")
|
||||||
group string
|
groupType := parts[0]
|
||||||
parts = strings.Split(key, "/")
|
var ip string
|
||||||
groupType = parts[0]
|
|
||||||
ip = ""
|
|
||||||
)
|
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
ip = parts[0]
|
ip = parts[0]
|
||||||
groupType = parts[1]
|
groupType = parts[1]
|
||||||
}
|
}
|
||||||
if start == last {
|
group := strconv.Itoa(start)
|
||||||
group = fmt.Sprintf("%d", start)
|
if start != last {
|
||||||
} else {
|
group = fmt.Sprintf("%s-%d", group, last)
|
||||||
group = fmt.Sprintf("%d-%d", start, last)
|
|
||||||
}
|
}
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var config *cliconfig.AuthConfig
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&config)
|
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
status, err := s.daemon.RegistryService.Auth(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, &types.AuthResponse{
|
|
||||||
Status: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,535 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.LessThan("1.20") && runtime.GOOS != "windows" {
|
|
||||||
return getContainersByNameDownlevel(w, s, vars["name"])
|
|
||||||
}
|
|
||||||
|
|
||||||
containerJSON, err := s.daemon.ContainerInspect(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, containerJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &daemon.ContainersConfig{
|
|
||||||
All: boolValue(r, "all"),
|
|
||||||
Size: boolValue(r, "size"),
|
|
||||||
Since: r.Form.Get("since"),
|
|
||||||
Before: r.Form.Get("before"),
|
|
||||||
Filters: r.Form.Get("filters"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
|
||||||
limit, err := strconv.Atoi(tmpLimit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Limit = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := s.daemon.Containers(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, containers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersStats(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
stream := boolValueOrDefault(r, "stream", true)
|
|
||||||
var out io.Writer
|
|
||||||
if !stream {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
out = w
|
|
||||||
} else {
|
|
||||||
out = ioutils.NewWriteFlusher(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotifier <-chan bool
|
|
||||||
if notifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotifier = notifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &daemon.ContainerStatsConfig{
|
|
||||||
Stream: stream,
|
|
||||||
OutStream: out,
|
|
||||||
Stop: closeNotifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerStats(vars["name"], config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate args here, because we can't return not StatusOK after job.Run() call
|
|
||||||
stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr")
|
|
||||||
if !(stdout || stderr) {
|
|
||||||
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
|
||||||
}
|
|
||||||
|
|
||||||
var since time.Time
|
|
||||||
if r.Form.Get("since") != "" {
|
|
||||||
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
since = time.Unix(s, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotifier <-chan bool
|
|
||||||
if notifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotifier = notifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := s.daemon.Get(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
outStream := ioutils.NewWriteFlusher(w)
|
|
||||||
// write an empty chunk of data (this is to ensure that the
|
|
||||||
// HTTP Response is sent immediately, even if the container has
|
|
||||||
// not yet produced any data)
|
|
||||||
outStream.Write(nil)
|
|
||||||
|
|
||||||
logsConfig := &daemon.ContainerLogsConfig{
|
|
||||||
Follow: boolValue(r, "follow"),
|
|
||||||
Timestamps: boolValue(r, "timestamps"),
|
|
||||||
Since: since,
|
|
||||||
Tail: r.Form.Get("tail"),
|
|
||||||
UseStdout: stdout,
|
|
||||||
UseStderr: stderr,
|
|
||||||
OutStream: outStream,
|
|
||||||
Stop: closeNotifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerLogs(c, logsConfig); err != nil {
|
|
||||||
fmt.Fprintf(w, "Error running logs job: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerExport(vars["name"], w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If contentLength is -1, we can assumed chunked encoding
|
|
||||||
// or more technically that the length is unknown
|
|
||||||
// https://golang.org/src/pkg/net/http/request.go#L139
|
|
||||||
// net/http otherwise seems to swallow any headers related to chunked encoding
|
|
||||||
// including r.TransferEncoding
|
|
||||||
// allow a nil body for backwards compatibility
|
|
||||||
var hostConfig *runconfig.HostConfig
|
|
||||||
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := runconfig.DecodeHostConfig(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostConfig = c
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil {
|
|
||||||
if err.Error() == "Container already started" {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersStop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
seconds, _ := strconv.Atoi(r.Form.Get("t"))
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil {
|
|
||||||
if err.Error() == "Container already stopped" {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersKill(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var sig uint64
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
// If we have a signal, look at it. Otherwise, do nothing
|
|
||||||
if sigStr := r.Form.Get("signal"); sigStr != "" {
|
|
||||||
// Check if we passed the signal as a number:
|
|
||||||
// The largest legal signal is 31, so let's parse on 5 bits
|
|
||||||
sigN, err := strconv.ParseUint(sigStr, 10, 5)
|
|
||||||
if err != nil {
|
|
||||||
// The signal is not a number, treat it as a string (either like
|
|
||||||
// "KILL" or like "SIGKILL")
|
|
||||||
syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Invalid signal: %s", sigStr)
|
|
||||||
}
|
|
||||||
sig = uint64(syscallSig)
|
|
||||||
} else {
|
|
||||||
sig = sigN
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig == 0 {
|
|
||||||
return fmt.Errorf("Invalid signal: %s", sigStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerKill(name, sig); err != nil {
|
|
||||||
_, isStopped := err.(daemon.ErrContainerNotRunning)
|
|
||||||
// Return error that's not caused because the container is stopped.
|
|
||||||
// Return error if the container is not running and the api is >= 1.20
|
|
||||||
// to keep backwards compatibility.
|
|
||||||
if version.GreaterThanOrEqualTo("1.20") || !isStopped {
|
|
||||||
return fmt.Errorf("Cannot kill container %s: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersRestart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, _ := strconv.Atoi(r.Form.Get("t"))
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersPause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerPause(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersUnpause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerUnpause(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{
|
|
||||||
StatusCode: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
changes, err := s.daemon.ContainerChanges(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, changes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersTop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, procList)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainerRename(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
newName := r.Form.Get("name")
|
|
||||||
if err := s.daemon.ContainerRename(name, newName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
warnings []string
|
|
||||||
name = r.Form.Get("name")
|
|
||||||
)
|
|
||||||
|
|
||||||
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
adjustCPUShares := version.LessThan("1.19")
|
|
||||||
|
|
||||||
container, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{
|
|
||||||
ID: container.ID,
|
|
||||||
Warnings: warnings,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) deleteContainers(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
config := &daemon.ContainerRmConfig{
|
|
||||||
ForceRemove: boolValue(r, "force"),
|
|
||||||
RemoveVolume: boolValue(r, "v"),
|
|
||||||
RemoveLink: boolValue(r, "link"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerRm(name, config); err != nil {
|
|
||||||
// Force a 404 for the empty string
|
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
|
|
||||||
return fmt.Errorf("no such id: \"\"")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerResize(vars["name"], height, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := s.daemon.Get(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
inStream, outStream, err := hijackServer(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer closeStreams(inStream, outStream)
|
|
||||||
|
|
||||||
if _, ok := r.Header["Upgrade"]; ok {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
|
|
||||||
InStream: inStream,
|
|
||||||
OutStream: outStream,
|
|
||||||
UseStdin: boolValue(r, "stdin"),
|
|
||||||
UseStdout: boolValue(r, "stdout"),
|
|
||||||
UseStderr: boolValue(r, "stderr"),
|
|
||||||
Logs: boolValue(r, "logs"),
|
|
||||||
Stream: boolValue(r, "stream"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerAttachWithLogs(cont, attachWithLogsConfig); err != nil {
|
|
||||||
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := s.daemon.Get(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
defer ws.Close()
|
|
||||||
|
|
||||||
wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
|
|
||||||
InStream: ws,
|
|
||||||
OutStream: ws,
|
|
||||||
ErrStream: ws,
|
|
||||||
Logs: boolValue(r, "logs"),
|
|
||||||
Stream: boolValue(r, "stream"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerWsAttachWithLogs(cont, wsAttachWithLogsConfig); err != nil {
|
|
||||||
logrus.Errorf("Error attaching websocket: %s", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ws := websocket.Server{Handler: h, Handshake: nil}
|
|
||||||
ws.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v := &types.Version{
|
|
||||||
Version: dockerversion.VERSION,
|
|
||||||
APIVersion: api.Version,
|
|
||||||
GitCommit: dockerversion.GITCOMMIT,
|
|
||||||
GoVersion: runtime.Version(),
|
|
||||||
Os: runtime.GOOS,
|
|
||||||
Arch: runtime.GOARCH,
|
|
||||||
BuildTime: dockerversion.BUILDTIME,
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.GreaterThanOrEqualTo("1.19") {
|
|
||||||
v.Experimental = utils.ExperimentalBuild()
|
|
||||||
}
|
|
||||||
|
|
||||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
|
||||||
v.KernelVersion = kernelVersion.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
info, err := s.daemon.SystemInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var since int64 = -1
|
|
||||||
if r.Form.Get("since") != "" {
|
|
||||||
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
since = s
|
|
||||||
}
|
|
||||||
|
|
||||||
var until int64 = -1
|
|
||||||
if r.Form.Get("until") != "" {
|
|
||||||
u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
until = u
|
|
||||||
}
|
|
||||||
|
|
||||||
timer := time.NewTimer(0)
|
|
||||||
timer.Stop()
|
|
||||||
if until > 0 {
|
|
||||||
dur := time.Unix(until, 0).Sub(time.Now())
|
|
||||||
timer = time.NewTimer(dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
ef, err := filters.FromParam(r.Form.Get("filters"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
isFiltered := func(field string, filter []string) bool {
|
|
||||||
if len(field) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(filter) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, v := range filter {
|
|
||||||
if v == field {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.Contains(field, ":") {
|
|
||||||
image := strings.Split(field, ":")
|
|
||||||
if image[0] == v {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
d := s.daemon
|
|
||||||
es := d.EventsService
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
outStream := ioutils.NewWriteFlusher(w)
|
|
||||||
outStream.Write(nil) // make sure response is sent immediately
|
|
||||||
enc := json.NewEncoder(outStream)
|
|
||||||
|
|
||||||
getContainerID := func(cn string) string {
|
|
||||||
c, err := d.Get(cn)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent := func(ev *jsonmessage.JSONMessage) error {
|
|
||||||
//incoming container filter can be name,id or partial id, convert and replace as a full container id
|
|
||||||
for i, cn := range ef["container"] {
|
|
||||||
ef["container"][i] = getContainerID(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) &&
|
|
||||||
isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return enc.Encode(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
current, l := es.Subscribe()
|
|
||||||
if since == -1 {
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
defer es.Evict(l)
|
|
||||||
for _, ev := range current {
|
|
||||||
if ev.Time < since {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := sendEvent(ev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotify <-chan bool
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotify = closeNotifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-l:
|
|
||||||
jev, ok := ev.(*jsonmessage.JSONMessage)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := sendEvent(jev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-timer.C:
|
|
||||||
return nil
|
|
||||||
case <-closeNotify:
|
|
||||||
logrus.Debug("Client disconnected, stop sending events")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func boolValue(r *http.Request, k string) bool {
|
|
||||||
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
|
|
||||||
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
|
||||||
}
|
|
||||||
|
|
||||||
// boolValueOrDefault returns the default bool passed if the query param is
|
|
||||||
// missing, otherwise it's just a proxy to boolValue above
|
|
||||||
func boolValueOrDefault(r *http.Request, k string, d bool) bool {
|
|
||||||
if _, ok := r.Form[k]; !ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
return boolValue(r, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func int64ValueOrZero(r *http.Request, k string) int64 {
|
|
||||||
val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
type archiveOptions struct {
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func archiveFormValues(r *http.Request, vars map[string]string) (archiveOptions, error) {
|
|
||||||
if vars == nil {
|
|
||||||
return archiveOptions{}, fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return archiveOptions{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
path := filepath.FromSlash(r.Form.Get("path"))
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case name == "":
|
|
||||||
return archiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
|
|
||||||
case path == "":
|
|
||||||
return archiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return archiveOptions{name, path}, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package httputils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BoolValue transforms a form value in different formats into a boolean type.
|
||||||
|
func BoolValue(r *http.Request, k string) bool {
|
||||||
|
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
|
||||||
|
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolValueOrDefault returns the default bool passed if the query param is
|
||||||
|
// missing, otherwise it's just a proxy to boolValue above
|
||||||
|
func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
|
||||||
|
if _, ok := r.Form[k]; !ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return BoolValue(r, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64ValueOrZero parses a form value into an int64 type.
|
||||||
|
// It returns 0 if the parsing fails.
|
||||||
|
func Int64ValueOrZero(r *http.Request, k string) int64 {
|
||||||
|
val, err := Int64ValueOrDefault(r, k, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64ValueOrDefault parses a form value into an int64 type. If there is an
|
||||||
|
// error, returns the error. If there is no value returns the default value.
|
||||||
|
func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) {
|
||||||
|
if r.Form.Get(field) != "" {
|
||||||
|
value, err := strconv.ParseInt(r.Form.Get(field), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveOptions stores archive information for different operations.
|
||||||
|
type ArchiveOptions struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
|
||||||
|
// It fails if the archive name and path are not in the request.
|
||||||
|
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
|
||||||
|
if err := ParseForm(r); err != nil {
|
||||||
|
return ArchiveOptions{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := vars["name"]
|
||||||
|
path := filepath.FromSlash(r.Form.Get("path"))
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case name == "":
|
||||||
|
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
|
||||||
|
case path == "":
|
||||||
|
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ArchiveOptions{name, path}, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package server
|
package httputils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -26,7 +26,7 @@ func TestBoolValue(t *testing.T) {
|
|||||||
r, _ := http.NewRequest("POST", "", nil)
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
r.Form = v
|
r.Form = v
|
||||||
|
|
||||||
a := boolValue(r, "test")
|
a := BoolValue(r, "test")
|
||||||
if a != e {
|
if a != e {
|
||||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ func TestBoolValue(t *testing.T) {
|
|||||||
|
|
||||||
func TestBoolValueOrDefault(t *testing.T) {
|
func TestBoolValueOrDefault(t *testing.T) {
|
||||||
r, _ := http.NewRequest("GET", "", nil)
|
r, _ := http.NewRequest("GET", "", nil)
|
||||||
if !boolValueOrDefault(r, "queryparam", true) {
|
if !BoolValueOrDefault(r, "queryparam", true) {
|
||||||
t.Fatal("Expected to get true default value, got false")
|
t.Fatal("Expected to get true default value, got false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ func TestBoolValueOrDefault(t *testing.T) {
|
|||||||
v.Set("param", "")
|
v.Set("param", "")
|
||||||
r, _ = http.NewRequest("GET", "", nil)
|
r, _ = http.NewRequest("GET", "", nil)
|
||||||
r.Form = v
|
r.Form = v
|
||||||
if boolValueOrDefault(r, "param", true) {
|
if BoolValueOrDefault(r, "param", true) {
|
||||||
t.Fatal("Expected not to get true")
|
t.Fatal("Expected not to get true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,9 +62,44 @@ func TestInt64ValueOrZero(t *testing.T) {
|
|||||||
r, _ := http.NewRequest("POST", "", nil)
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
r.Form = v
|
r.Form = v
|
||||||
|
|
||||||
a := int64ValueOrZero(r, "test")
|
a := Int64ValueOrZero(r, "test")
|
||||||
if a != e {
|
if a != e {
|
||||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInt64ValueOrDefault(t *testing.T) {
|
||||||
|
cases := map[string]int64{
|
||||||
|
"": -1,
|
||||||
|
"-1": -1,
|
||||||
|
"42": 42,
|
||||||
|
}
|
||||||
|
|
||||||
|
for c, e := range cases {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("test", c)
|
||||||
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
|
||||||
|
a, err := Int64ValueOrDefault(r, "test", -1)
|
||||||
|
if a != e {
|
||||||
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error should be nil, but received: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64ValueOrDefaultWithError(t *testing.T) {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("test", "invalid")
|
||||||
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
|
||||||
|
_, err := Int64ValueOrDefault(r, "test", -1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected an error.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package httputils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
|
"github.com/docker/docker/api"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIVersionKey is the client's requested API version.
|
||||||
|
const APIVersionKey = "api-version"
|
||||||
|
|
||||||
|
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
||||||
|
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
||||||
|
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||||
|
|
||||||
|
// HijackConnection interrupts the http response writer to get the
|
||||||
|
// underlying connection and operate with it.
|
||||||
|
func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||||
|
conn, _, err := w.(http.Hijacker).Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// Flush the options to make sure the client sets the raw mode
|
||||||
|
conn.Write([]byte{})
|
||||||
|
return conn, conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseStreams ensures that a list for http streams are properly closed.
|
||||||
|
func CloseStreams(streams ...interface{}) {
|
||||||
|
for _, stream := range streams {
|
||||||
|
if tcpc, ok := stream.(interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}); ok {
|
||||||
|
tcpc.CloseWrite()
|
||||||
|
} else if closer, ok := stream.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckForJSON makes sure that the request's Content-Type is application/json.
|
||||||
|
func CheckForJSON(r *http.Request) error {
|
||||||
|
ct := r.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// No Content-Type header is ok as long as there's no Body
|
||||||
|
if ct == "" {
|
||||||
|
if r.Body == nil || r.ContentLength == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise it better be json
|
||||||
|
if api.MatchesContentType(ct, "application/json") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseForm ensures the request form is parsed even with invalid content types.
|
||||||
|
// If we don't do this, POST method without Content-type (even with empty body) will fail.
|
||||||
|
func ParseForm(r *http.Request) error {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
|
||||||
|
func ParseMultipartForm(r *http.Request) error {
|
||||||
|
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteError decodes a specific docker error and sends it in the response.
|
||||||
|
func WriteError(w http.ResponseWriter, err error) {
|
||||||
|
if err == nil || w == nil {
|
||||||
|
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode := http.StatusInternalServerError
|
||||||
|
errMsg := err.Error()
|
||||||
|
|
||||||
|
// Based on the type of error we get we need to process things
|
||||||
|
// slightly differently to extract the error message.
|
||||||
|
// In the 'errcode.*' cases there are two different type of
|
||||||
|
// error that could be returned. errocode.ErrorCode is the base
|
||||||
|
// type of error object - it is just an 'int' that can then be
|
||||||
|
// used as the look-up key to find the message. errorcode.Error
|
||||||
|
// extends errorcode.Error by adding error-instance specific
|
||||||
|
// data, like 'details' or variable strings to be inserted into
|
||||||
|
// the message.
|
||||||
|
//
|
||||||
|
// Ideally, we should just be able to call err.Error() for all
|
||||||
|
// cases but the errcode package doesn't support that yet.
|
||||||
|
//
|
||||||
|
// Additionally, in both errcode cases, there might be an http
|
||||||
|
// status code associated with it, and if so use it.
|
||||||
|
switch err.(type) {
|
||||||
|
case errcode.ErrorCode:
|
||||||
|
daError, _ := err.(errcode.ErrorCode)
|
||||||
|
statusCode = daError.Descriptor().HTTPStatusCode
|
||||||
|
errMsg = daError.Message()
|
||||||
|
|
||||||
|
case errcode.Error:
|
||||||
|
// For reference, if you're looking for a particular error
|
||||||
|
// then you can do something like :
|
||||||
|
// import ( derr "github.com/docker/docker/errors" )
|
||||||
|
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
|
||||||
|
|
||||||
|
daError, _ := err.(errcode.Error)
|
||||||
|
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
|
||||||
|
errMsg = daError.Message
|
||||||
|
|
||||||
|
default:
|
||||||
|
// This part of will be removed once we've
|
||||||
|
// converted everything over to use the errcode package
|
||||||
|
|
||||||
|
// FIXME: this is brittle and should not be necessary.
|
||||||
|
// If we need to differentiate between different possible error types,
|
||||||
|
// we should create appropriate error types with clearly defined meaning
|
||||||
|
errStr := strings.ToLower(err.Error())
|
||||||
|
for keyword, status := range map[string]int{
|
||||||
|
"not found": http.StatusNotFound,
|
||||||
|
"no such": http.StatusNotFound,
|
||||||
|
"bad parameter": http.StatusBadRequest,
|
||||||
|
"conflict": http.StatusConflict,
|
||||||
|
"impossible": http.StatusNotAcceptable,
|
||||||
|
"wrong login/password": http.StatusUnauthorized,
|
||||||
|
"hasn't been activated": http.StatusForbidden,
|
||||||
|
} {
|
||||||
|
if strings.Contains(errStr, keyword) {
|
||||||
|
statusCode = status
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode == 0 {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
|
||||||
|
http.Error(w, errMsg, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
||||||
|
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(code)
|
||||||
|
return json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionFromContext returns an API version from the context using APIVersionKey.
|
||||||
|
// It panics if the context value does not have version.Version type.
|
||||||
|
func VersionFromContext(ctx context.Context) (ver version.Version) {
|
||||||
|
if ctx == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val := ctx.Value(APIVersionKey)
|
||||||
|
if val == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return val.(version.Version)
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
|
"github.com/docker/docker/errors"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// middleware is an adapter to allow the use of ordinary functions as Docker API filters.
|
||||||
|
// Any function that has the appropriate signature can be register as a middleware.
|
||||||
|
type middleware func(handler httputils.APIFunc) httputils.APIFunc
|
||||||
|
|
||||||
|
// loggingMiddleware logs each request when logging is enabled.
|
||||||
|
func (s *Server) loggingMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||||
|
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if s.cfg.Logging {
|
||||||
|
logrus.Infof("%s %s", r.Method, r.RequestURI)
|
||||||
|
}
|
||||||
|
return handler(ctx, w, r, vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
|
||||||
|
func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||||
|
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||||
|
dockerVersion := version.Version(s.cfg.Version)
|
||||||
|
|
||||||
|
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||||
|
|
||||||
|
// v1.20 onwards includes the GOOS of the client after the version
|
||||||
|
// such as Docker/1.7.0 (linux)
|
||||||
|
if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
|
||||||
|
userAgent[1] = strings.Split(userAgent[1], " ")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
|
||||||
|
logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handler(ctx, w, r, vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// corsMiddleware sets the CORS header expectations in the server.
|
||||||
|
func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||||
|
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
|
||||||
|
// otherwise, all head values will be passed to HTTP handler
|
||||||
|
corsHeaders := s.cfg.CorsHeaders
|
||||||
|
if corsHeaders == "" && s.cfg.EnableCors {
|
||||||
|
corsHeaders = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
if corsHeaders != "" {
|
||||||
|
writeCorsHeaders(w, r, corsHeaders)
|
||||||
|
}
|
||||||
|
return handler(ctx, w, r, vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// versionMiddleware checks the api version requirements before passing the request to the server handler.
|
||||||
|
func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||||
|
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
apiVersion := version.Version(vars["version"])
|
||||||
|
if apiVersion == "" {
|
||||||
|
apiVersion = api.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiVersion.GreaterThan(api.Version) {
|
||||||
|
return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, api.Version)
|
||||||
|
}
|
||||||
|
if apiVersion.LessThan(api.MinVersion) {
|
||||||
|
return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, api.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||||
|
ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
|
||||||
|
return handler(ctx, w, r, vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleWithGlobalMiddlwares wraps the handler function for a request with
|
||||||
|
// the server's global middlewares. The order of the middlewares is backwards,
|
||||||
|
// meaning that the first in the list will be evaludated last.
|
||||||
|
//
|
||||||
|
// Example: handleWithGlobalMiddlewares(s.getContainersName)
|
||||||
|
//
|
||||||
|
// s.loggingMiddleware(
|
||||||
|
// s.userAgentMiddleware(
|
||||||
|
// s.corsMiddleware(
|
||||||
|
// versionMiddleware(s.getContainersName)
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
|
||||||
|
middlewares := []middleware{
|
||||||
|
versionMiddleware,
|
||||||
|
s.corsMiddleware,
|
||||||
|
s.userAgentMiddleware,
|
||||||
|
s.loggingMiddleware,
|
||||||
|
}
|
||||||
|
|
||||||
|
h := handler
|
||||||
|
for _, m := range middlewares {
|
||||||
|
h = m(h)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionMiddleware(t *testing.T) {
|
||||||
|
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if httputils.VersionFromContext(ctx) == "" {
|
||||||
|
t.Fatalf("Expected version, got empty string")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h := versionMiddleware(handler)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "/containers/json", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := h(ctx, resp, req, map[string]string{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionMiddlewareWithErrors(t *testing.T) {
|
||||||
|
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if httputils.VersionFromContext(ctx) == "" {
|
||||||
|
t.Fatalf("Expected version, got empty string")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h := versionMiddleware(handler)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "/containers/json", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
vars := map[string]string{"version": "0.1"}
|
||||||
|
err := h(ctx, resp, req, vars)
|
||||||
|
if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeOldClientVersion {
|
||||||
|
t.Fatalf("Expected ErrorCodeOldClientVersion, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vars["version"] = "100000"
|
||||||
|
err = h(ctx, resp, req, vars)
|
||||||
|
if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeNewerClientVersion {
|
||||||
|
t.Fatalf("Expected ErrorCodeNewerClientVersion, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *router) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
var config *cliconfig.AuthConfig
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&config)
|
||||||
|
r.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status, err := s.daemon.AuthenticateToRegistry(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
derr "github.com/docker/docker/errors"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *router) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &daemon.ContainersConfig{
|
||||||
|
All: httputils.BoolValue(r, "all"),
|
||||||
|
Size: httputils.BoolValue(r, "size"),
|
||||||
|
Since: r.Form.Get("since"),
|
||||||
|
Before: r.Form.Get("before"),
|
||||||
|
Filters: r.Form.Get("filters"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
||||||
|
limit, err := strconv.Atoi(tmpLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.Limit = limit
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := s.daemon.Containers(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, containers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := httputils.BoolValueOrDefault(r, "stream", true)
|
||||||
|
var out io.Writer
|
||||||
|
if !stream {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
out = w
|
||||||
|
} else {
|
||||||
|
out = ioutils.NewWriteFlusher(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeNotifier <-chan bool
|
||||||
|
if notifier, ok := w.(http.CloseNotifier); ok {
|
||||||
|
closeNotifier = notifier.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &daemon.ContainerStatsConfig{
|
||||||
|
Stream: stream,
|
||||||
|
OutStream: out,
|
||||||
|
Stop: closeNotifier,
|
||||||
|
Version: httputils.VersionFromContext(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.daemon.ContainerStats(vars["name"], config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args are validated before the stream starts because when it starts we're
|
||||||
|
// sending HTTP 200 by writing an empty chunk of data to tell the client that
|
||||||
|
// daemon is going to stream. By sending this initial HTTP 200 we can't report
|
||||||
|
// any error after the stream starts (i.e. container not found, wrong parameters)
|
||||||
|
// with the appropriate status code.
|
||||||
|
stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
|
||||||
|
if !(stdout || stderr) {
|
||||||
|
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
var since time.Time
|
||||||
|
if r.Form.Get("since") != "" {
|
||||||
|
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
since = time.Unix(s, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeNotifier <-chan bool
|
||||||
|
if notifier, ok := w.(http.CloseNotifier); ok {
|
||||||
|
closeNotifier = notifier.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := vars["name"]
|
||||||
|
|
||||||
|
if !s.daemon.Exists(containerName) {
|
||||||
|
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
outStream := ioutils.NewWriteFlusher(w)
|
||||||
|
// write an empty chunk of data (this is to ensure that the
|
||||||
|
// HTTP Response is sent immediately, even if the container has
|
||||||
|
// not yet produced any data)
|
||||||
|
outStream.Write(nil)
|
||||||
|
|
||||||
|
logsConfig := &daemon.ContainerLogsConfig{
|
||||||
|
Follow: httputils.BoolValue(r, "follow"),
|
||||||
|
Timestamps: httputils.BoolValue(r, "timestamps"),
|
||||||
|
Since: since,
|
||||||
|
Tail: r.Form.Get("tail"),
|
||||||
|
UseStdout: stdout,
|
||||||
|
UseStderr: stderr,
|
||||||
|
OutStream: outStream,
|
||||||
|
Stop: closeNotifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerLogs(containerName, logsConfig); err != nil {
|
||||||
|
// The client may be expecting all of the data we're sending to
|
||||||
|
// be multiplexed, so send it through OutStream, which will
|
||||||
|
// have been set up to handle that if needed.
|
||||||
|
fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
return s.daemon.ContainerExport(vars["name"], w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
// If contentLength is -1, we can assumed chunked encoding
|
||||||
|
// or more technically that the length is unknown
|
||||||
|
// https://golang.org/src/pkg/net/http/request.go#L139
|
||||||
|
// net/http otherwise seems to swallow any headers related to chunked encoding
|
||||||
|
// including r.TransferEncoding
|
||||||
|
// allow a nil body for backwards compatibility
|
||||||
|
var hostConfig *runconfig.HostConfig
|
||||||
|
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
|
||||||
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := runconfig.DecodeHostConfig(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig = c
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds, _ := strconv.Atoi(r.Form.Get("t"))
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sig syscall.Signal
|
||||||
|
name := vars["name"]
|
||||||
|
|
||||||
|
// If we have a signal, look at it. Otherwise, do nothing
|
||||||
|
if sigStr := r.Form.Get("signal"); sigStr != "" {
|
||||||
|
var err error
|
||||||
|
if sig, err = signal.ParseSignal(sigStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil {
|
||||||
|
theErr, isDerr := err.(errcode.ErrorCoder)
|
||||||
|
isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning
|
||||||
|
|
||||||
|
// Return error that's not caused because the container is stopped.
|
||||||
|
// Return error if the container is not running and the api is >= 1.20
|
||||||
|
// to keep backwards compatibility.
|
||||||
|
version := httputils.VersionFromContext(ctx)
|
||||||
|
if version.GreaterThanOrEqualTo("1.20") || !isStopped {
|
||||||
|
return fmt.Errorf("Cannot kill container %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout, _ := strconv.Atoi(r.Form.Get("t"))
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerPause(vars["name"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerUnpause(vars["name"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{
|
||||||
|
StatusCode: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
changes, err := s.daemon.ContainerChanges(vars["name"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, procList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := vars["name"]
|
||||||
|
newName := r.Form.Get("name")
|
||||||
|
if err := s.daemon.ContainerRename(name, newName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := r.Form.Get("name")
|
||||||
|
|
||||||
|
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := httputils.VersionFromContext(ctx)
|
||||||
|
adjustCPUShares := version.LessThan("1.19")
|
||||||
|
|
||||||
|
ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusCreated, ccr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := vars["name"]
|
||||||
|
config := &daemon.ContainerRmConfig{
|
||||||
|
ForceRemove: httputils.BoolValue(r, "force"),
|
||||||
|
RemoveVolume: httputils.BoolValue(r, "v"),
|
||||||
|
RemoveLink: httputils.BoolValue(r, "link"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerRm(name, config); err != nil {
|
||||||
|
// Force a 404 for the empty string
|
||||||
|
if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
|
||||||
|
return fmt.Errorf("no such id: \"\"")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
width, err := strconv.Atoi(r.Form.Get("w"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.daemon.ContainerResize(vars["name"], height, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerName := vars["name"]
|
||||||
|
|
||||||
|
if !s.daemon.Exists(containerName) {
|
||||||
|
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
inStream, outStream, err := httputils.HijackConnection(w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer httputils.CloseStreams(inStream, outStream)
|
||||||
|
|
||||||
|
if _, ok := r.Header["Upgrade"]; ok {
|
||||||
|
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
|
||||||
|
InStream: inStream,
|
||||||
|
OutStream: outStream,
|
||||||
|
UseStdin: httputils.BoolValue(r, "stdin"),
|
||||||
|
UseStdout: httputils.BoolValue(r, "stdout"),
|
||||||
|
UseStderr: httputils.BoolValue(r, "stderr"),
|
||||||
|
Logs: httputils.BoolValue(r, "logs"),
|
||||||
|
Stream: httputils.BoolValue(r, "stream"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerAttachWithLogs(containerName, attachWithLogsConfig); err != nil {
|
||||||
|
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
containerName := vars["name"]
|
||||||
|
|
||||||
|
if !s.daemon.Exists(containerName) {
|
||||||
|
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
|
||||||
|
InStream: ws,
|
||||||
|
OutStream: ws,
|
||||||
|
ErrStream: ws,
|
||||||
|
Logs: httputils.BoolValue(r, "logs"),
|
||||||
|
Stream: httputils.BoolValue(r, "stream"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.daemon.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
|
||||||
|
logrus.Errorf("Error attaching websocket: %s", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ws := websocket.Server{Handler: h, Handshake: nil}
|
||||||
|
ws.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package server
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -9,17 +9,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/version"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// postContainersCopy is deprecated in favor of getContainersArchive.
|
// postContainersCopy is deprecated in favor of getContainersArchive.
|
||||||
func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,13 +65,13 @@ func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Heade
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
v, err := archiveFormValues(r, vars)
|
v, err := httputils.ArchiveFormValues(r, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := s.daemon.ContainerStatPath(v.name, v.path)
|
stat, err := s.daemon.ContainerStatPath(v.Name, v.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,13 +79,13 @@ func (s *Server) headContainersArchive(version version.Version, w http.ResponseW
|
|||||||
return setContainerPathStatHeader(stat, w.Header())
|
return setContainerPathStatHeader(stat, w.Header())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
v, err := archiveFormValues(r, vars)
|
v, err := httputils.ArchiveFormValues(r, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path)
|
tarArchive, stat, err := s.daemon.ContainerArchivePath(v.Name, v.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -104,12 +101,12 @@ func (s *Server) getContainersArchive(version version.Version, w http.ResponseWr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
v, err := archiveFormValues(r, vars)
|
v, err := httputils.ArchiveFormValues(r, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir")
|
noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir")
|
||||||
return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body)
|
return s.daemon.ContainerExtractToDir(v.Name, v.Path, noOverwriteDirNonDir, r.Body)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package server
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -8,30 +8,27 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter 'id'")
|
|
||||||
}
|
|
||||||
|
|
||||||
eConfig, err := s.daemon.ContainerExecInspect(vars["id"])
|
eConfig, err := s.daemon.ContainerExecInspect(vars["id"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, eConfig)
|
return httputils.WriteJSON(w, http.StatusOK, eConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := checkForJSON(r); err != nil {
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
@@ -53,14 +50,14 @@ func (s *Server) postContainerExecCreate(version version.Version, w http.Respons
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{
|
return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{
|
||||||
ID: id,
|
ID: id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
||||||
func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
@@ -74,14 +71,18 @@ func (s *Server) postContainerExecStart(version version.Version, w http.Response
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if exists, err := s.daemon.ExecExists(execName); !exists {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if !execStartCheck.Detach {
|
if !execStartCheck.Detach {
|
||||||
var err error
|
var err error
|
||||||
// Setting up the streaming http interface.
|
// Setting up the streaming http interface.
|
||||||
inStream, outStream, err = hijackServer(w)
|
inStream, outStream, err = httputils.HijackConnection(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer closeStreams(inStream, outStream)
|
defer httputils.CloseStreams(inStream, outStream)
|
||||||
|
|
||||||
if _, ok := r.Header["Upgrade"]; ok {
|
if _, ok := r.Header["Upgrade"]; ok {
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
||||||
@@ -101,19 +102,18 @@ func (s *Server) postContainerExecStart(version version.Version, w http.Response
|
|||||||
|
|
||||||
// Now run the user process in container.
|
// Now run the user process in container.
|
||||||
if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
|
if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
|
||||||
fmt.Fprintf(outStream, "Error running exec in container: %v\n", err)
|
if execStartCheck.Detach {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Errorf("Error running exec in container: %v\n", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postContainerExecResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1,39 +1,50 @@
|
|||||||
package server
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/builder"
|
"github.com/docker/docker/builder"
|
||||||
|
"github.com/docker/docker/builder/dockerfile"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
|
"github.com/docker/docker/daemon/daemonbuilder"
|
||||||
|
derr "github.com/docker/docker/errors"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
|
"github.com/docker/docker/graph/tags"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkForJSON(r); err != nil {
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cname := r.Form.Get("container")
|
cname := r.Form.Get("container")
|
||||||
|
|
||||||
pause := boolValue(r, "pause")
|
pause := httputils.BoolValue(r, "pause")
|
||||||
|
version := httputils.VersionFromContext(ctx)
|
||||||
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
||||||
pause = true
|
pause = true
|
||||||
}
|
}
|
||||||
@@ -43,7 +54,7 @@ func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *h
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
commitCfg := &builder.CommitConfig{
|
commitCfg := &dockerfile.CommitConfig{
|
||||||
Pause: pause,
|
Pause: pause,
|
||||||
Repo: r.Form.Get("repo"),
|
Repo: r.Form.Get("repo"),
|
||||||
Tag: r.Form.Get("tag"),
|
Tag: r.Form.Get("tag"),
|
||||||
@@ -53,19 +64,23 @@ func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *h
|
|||||||
Config: c,
|
Config: c,
|
||||||
}
|
}
|
||||||
|
|
||||||
imgID, err := builder.Commit(cname, s.daemon, commitCfg)
|
if !s.daemon.Exists(cname) {
|
||||||
|
return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgID, err := dockerfile.Commit(cname, s.daemon, commitCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
|
return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
|
||||||
ID: imgID,
|
ID: string(imgID),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an image from Pull or from Import
|
// Creates an image from Pull or from Import
|
||||||
func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +125,7 @@ func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter
|
|||||||
OutStream: output,
|
OutStream: output,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)
|
err = s.daemon.PullImage(image, tag, imagePullConfig)
|
||||||
} else { //import
|
} else { //import
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
repo, tag = parsers.ParseRepositoryTag(repo)
|
repo, tag = parsers.ParseRepositoryTag(repo)
|
||||||
@@ -122,12 +137,12 @@ func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter
|
|||||||
// generated from the download to be available to the output
|
// generated from the download to be available to the output
|
||||||
// stream processing below
|
// stream processing below
|
||||||
var newConfig *runconfig.Config
|
var newConfig *runconfig.Config
|
||||||
newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"])
|
newConfig, err = dockerfile.BuildFromConfig(&runconfig.Config{}, r.Form["changes"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig)
|
err = s.daemon.ImportImage(src, repo, tag, message, r.Body, output, newConfig)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !output.Flushed() {
|
if !output.Flushed() {
|
||||||
@@ -140,18 +155,14 @@ func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
metaHeaders := map[string][]string{}
|
metaHeaders := map[string][]string{}
|
||||||
for k, v := range r.Header {
|
for k, v := range r.Header {
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
if strings.HasPrefix(k, "X-Meta-") {
|
||||||
metaHeaders[k] = v
|
metaHeaders[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
authConfig := &cliconfig.AuthConfig{}
|
authConfig := &cliconfig.AuthConfig{}
|
||||||
@@ -182,7 +193,7 @@ func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter,
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil {
|
if err := s.daemon.PushImage(name, imagePushConfig); err != nil {
|
||||||
if !output.Flushed() {
|
if !output.Flushed() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -192,11 +203,8 @@ func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +218,7 @@ func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r
|
|||||||
names = r.Form["names"]
|
names = r.Form["names"]
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.daemon.Repositories().ImageExport(names, output); err != nil {
|
if err := s.daemon.ExportImage(names, output); err != nil {
|
||||||
if !output.Flushed() {
|
if !output.Flushed() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -220,17 +228,14 @@ func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
return s.daemon.Repositories().Load(r.Body, w)
|
return s.daemon.LoadImage(r.Body, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
|
||||||
@@ -238,35 +243,31 @@ func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r
|
|||||||
return fmt.Errorf("image name cannot be blank")
|
return fmt.Errorf("image name cannot be blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
force := boolValue(r, "force")
|
force := httputils.BoolValue(r, "force")
|
||||||
prune := !boolValue(r, "noprune")
|
prune := !httputils.BoolValue(r, "noprune")
|
||||||
|
|
||||||
list, err := s.daemon.ImageDelete(name, force, prune)
|
list, err := s.daemon.ImageDelete(name, force, prune)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, list)
|
return httputils.WriteJSON(w, http.StatusOK, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
imageInspect, err := s.daemon.LookupImage(vars["name"])
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
imageInspect, err := s.daemon.Repositories().Lookup(vars["name"])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, imageInspect)
|
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
var (
|
var (
|
||||||
authConfigs = map[string]cliconfig.AuthConfig{}
|
authConfigs = map[string]cliconfig.AuthConfig{}
|
||||||
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
||||||
buildConfig = builder.NewBuildConfig()
|
buildConfig = &dockerfile.Config{}
|
||||||
)
|
)
|
||||||
|
|
||||||
if authConfigsEncoded != "" {
|
if authConfigsEncoded != "" {
|
||||||
@@ -280,33 +281,54 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
|
version := httputils.VersionFromContext(ctx)
|
||||||
|
output := ioutils.NewWriteFlusher(w)
|
||||||
|
sf := streamformatter.NewJSONStreamFormatter()
|
||||||
|
errf := func(err error) error {
|
||||||
|
// Do not write the error in the http output if it's still empty.
|
||||||
|
// This prevents from writing a 200(OK) when there is an interal error.
|
||||||
|
if !output.Flushed() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("could not write error response: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
|
||||||
buildConfig.Remove = true
|
buildConfig.Remove = true
|
||||||
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
||||||
buildConfig.Remove = true
|
buildConfig.Remove = true
|
||||||
} else {
|
} else {
|
||||||
buildConfig.Remove = boolValue(r, "rm")
|
buildConfig.Remove = httputils.BoolValue(r, "rm")
|
||||||
}
|
}
|
||||||
if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
|
if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
|
||||||
buildConfig.Pull = true
|
buildConfig.Pull = true
|
||||||
}
|
}
|
||||||
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
repoName, tag := parsers.ParseRepositoryTag(r.FormValue("t"))
|
||||||
buildConfig.Stdout = output
|
if repoName != "" {
|
||||||
buildConfig.Context = r.Body
|
if err := registry.ValidateRepositoryName(repoName); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
if len(tag) > 0 {
|
||||||
|
if err := tags.ValidateTagName(tag); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildConfig.RemoteURL = r.FormValue("remote")
|
|
||||||
buildConfig.DockerfileName = r.FormValue("dockerfile")
|
buildConfig.DockerfileName = r.FormValue("dockerfile")
|
||||||
buildConfig.RepoName = r.FormValue("t")
|
buildConfig.Verbose = !httputils.BoolValue(r, "q")
|
||||||
buildConfig.SuppressOutput = boolValue(r, "q")
|
buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
|
||||||
buildConfig.NoCache = boolValue(r, "nocache")
|
buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
|
||||||
buildConfig.ForceRemove = boolValue(r, "forcerm")
|
buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
|
||||||
buildConfig.AuthConfigs = authConfigs
|
buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
|
||||||
buildConfig.MemorySwap = int64ValueOrZero(r, "memswap")
|
buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
|
||||||
buildConfig.Memory = int64ValueOrZero(r, "memory")
|
buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
|
||||||
buildConfig.CPUShares = int64ValueOrZero(r, "cpushares")
|
buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
|
||||||
buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod")
|
|
||||||
buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota")
|
|
||||||
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
|
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
|
||||||
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
|
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
|
||||||
buildConfig.CgroupParent = r.FormValue("cgroupparent")
|
buildConfig.CgroupParent = r.FormValue("cgroupparent")
|
||||||
@@ -315,12 +337,65 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
ulimitsJSON := r.FormValue("ulimits")
|
ulimitsJSON := r.FormValue("ulimits")
|
||||||
if ulimitsJSON != "" {
|
if ulimitsJSON != "" {
|
||||||
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
||||||
return err
|
return errf(err)
|
||||||
}
|
}
|
||||||
buildConfig.Ulimits = buildUlimits
|
buildConfig.Ulimits = buildUlimits
|
||||||
}
|
}
|
||||||
|
|
||||||
// Job cancellation. Note: not all job types support this.
|
var buildArgs = map[string]string{}
|
||||||
|
buildArgsJSON := r.FormValue("buildargs")
|
||||||
|
if buildArgsJSON != "" {
|
||||||
|
if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
buildConfig.BuildArgs = buildArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteURL := r.FormValue("remote")
|
||||||
|
|
||||||
|
// Currently, only used if context is from a remote url.
|
||||||
|
// The field `In` is set by DetectContextFromRemoteURL.
|
||||||
|
// Look at code in DetectContextFromRemoteURL for more information.
|
||||||
|
pReader := &progressreader.Config{
|
||||||
|
// TODO: make progressreader streamformatter-agnostic
|
||||||
|
Out: output,
|
||||||
|
Formatter: sf,
|
||||||
|
Size: r.ContentLength,
|
||||||
|
NewLines: true,
|
||||||
|
ID: "Downloading context",
|
||||||
|
Action: remoteURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
context builder.ModifiableContext
|
||||||
|
dockerfileName string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := context.Close(); err != nil {
|
||||||
|
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
|
||||||
|
defaultArchiver := &archive.Archiver{
|
||||||
|
Untar: chrootarchive.Untar,
|
||||||
|
UIDMaps: uidMaps,
|
||||||
|
GIDMaps: gidMaps,
|
||||||
|
}
|
||||||
|
docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver}
|
||||||
|
|
||||||
|
b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
|
||||||
|
b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
|
||||||
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||||
finished := make(chan struct{})
|
finished := make(chan struct{})
|
||||||
defer close(finished)
|
defer close(finished)
|
||||||
@@ -329,64 +404,62 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
case <-finished:
|
case <-finished:
|
||||||
case <-closeNotifier.CloseNotify():
|
case <-closeNotifier.CloseNotify():
|
||||||
logrus.Infof("Client disconnected, cancelling job: build")
|
logrus.Infof("Client disconnected, cancelling job: build")
|
||||||
buildConfig.Cancel()
|
b.Cancel()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := builder.Build(s.daemon, buildConfig); err != nil {
|
if len(dockerfileName) > 0 {
|
||||||
// Do not write the error in the http output if it's still empty.
|
b.DockerfileName = dockerfileName
|
||||||
// This prevents from writing a 200(OK) when there is an interal error.
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
w.Write(sf.FormatError(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imgID, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if repoName != "" {
|
||||||
|
if err := s.daemon.TagImage(repoName, tag, string(imgID), true); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: The filter parameter could just be a match filter
|
// FIXME: The filter parameter could just be a match filter
|
||||||
images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all"))
|
images, err := s.daemon.ListImages(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, images)
|
return httputils.WriteJSON(w, http.StatusOK, images)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
history, err := s.daemon.Repositories().History(name)
|
history, err := s.daemon.ImageHistory(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, history)
|
return httputils.WriteJSON(w, http.StatusOK, history)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := r.Form.Get("repo")
|
repo := r.Form.Get("repo")
|
||||||
tag := r.Form.Get("tag")
|
tag := r.Form.Get("tag")
|
||||||
force := boolValue(r, "force")
|
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil {
|
force := httputils.BoolValue(r, "force")
|
||||||
|
if err := s.daemon.TagImage(repo, tag, name, force); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
|
s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
|
||||||
@@ -394,8 +467,8 @@ func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
@@ -417,9 +490,9 @@ func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter,
|
|||||||
headers[k] = v
|
headers[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers)
|
query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeJSON(w, http.StatusOK, query.Results)
|
return httputils.WriteJSON(w, http.StatusOK, query.Results)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *router) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
v := &types.Version{
|
||||||
|
Version: dockerversion.VERSION,
|
||||||
|
APIVersion: api.Version,
|
||||||
|
GitCommit: dockerversion.GITCOMMIT,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
Os: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
BuildTime: dockerversion.BUILDTIME,
|
||||||
|
}
|
||||||
|
|
||||||
|
version := httputils.VersionFromContext(ctx)
|
||||||
|
|
||||||
|
if version.GreaterThanOrEqualTo("1.19") {
|
||||||
|
v.Experimental = utils.ExperimentalBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
||||||
|
v.KernelVersion = kernelVersion.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
info, err := s.daemon.SystemInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOutputEncoder(w http.ResponseWriter) *json.Encoder {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
outStream := ioutils.NewWriteFlusher(w)
|
||||||
|
// Write an empty chunk of data.
|
||||||
|
// This is to ensure that the HTTP status code is sent immediately,
|
||||||
|
// so that it will not block the receiver.
|
||||||
|
outStream.Write(nil)
|
||||||
|
return json.NewEncoder(outStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
since, err := httputils.Int64ValueOrDefault(r, "since", -1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
until, err := httputils.Int64ValueOrDefault(r, "until", -1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(0)
|
||||||
|
timer.Stop()
|
||||||
|
if until > 0 {
|
||||||
|
dur := time.Unix(until, 0).Sub(time.Now())
|
||||||
|
timer = time.NewTimer(dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
ef, err := filters.FromParam(r.Form.Get("filters"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := buildOutputEncoder(w)
|
||||||
|
d := s.daemon
|
||||||
|
es := d.EventsService
|
||||||
|
current, l := es.Subscribe()
|
||||||
|
defer es.Evict(l)
|
||||||
|
|
||||||
|
eventFilter := d.GetEventFilter(ef)
|
||||||
|
handleEvent := func(ev *jsonmessage.JSONMessage) error {
|
||||||
|
if eventFilter.Include(ev) {
|
||||||
|
if err := enc.Encode(ev); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if since == -1 {
|
||||||
|
current = nil
|
||||||
|
}
|
||||||
|
for _, ev := range current {
|
||||||
|
if ev.Time < since {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := handleEvent(ev); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeNotify <-chan bool
|
||||||
|
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||||
|
closeNotify = closeNotifier.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ev := <-l:
|
||||||
|
jev, ok := ev.(*jsonmessage.JSONMessage)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := handleEvent(jev); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-timer.C:
|
||||||
|
return nil
|
||||||
|
case <-closeNotify:
|
||||||
|
logrus.Debug("Client disconnected, stop sending events")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getContainersByName inspects containers configuration and serializes it as json.
|
||||||
|
func (s *router) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
displaySize := httputils.BoolValue(r, "size")
|
||||||
|
|
||||||
|
var json interface{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
version := httputils.VersionFromContext(ctx)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case version.LessThan("1.20"):
|
||||||
|
json, err = s.daemon.ContainerInspectPre120(vars["name"])
|
||||||
|
case version.Equal("1.20"):
|
||||||
|
json, err = s.daemon.ContainerInspect120(vars["name"])
|
||||||
|
default:
|
||||||
|
json, err = s.daemon.ContainerInspect(vars["name"], displaySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, json)
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
dkrouter "github.com/docker/docker/api/server/router"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// router is a docker router that talks with the local docker daemon.
|
||||||
|
type router struct {
|
||||||
|
daemon *daemon.Daemon
|
||||||
|
routes []dkrouter.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// localRoute defines an individual API route to connect with the docker daemon.
|
||||||
|
// It implements router.Route.
|
||||||
|
type localRoute struct {
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
handler httputils.APIFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns the APIFunc to let the server wrap it in middlewares
|
||||||
|
func (l localRoute) Handler() httputils.APIFunc {
|
||||||
|
return l.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the http method that the route responds to.
|
||||||
|
func (l localRoute) Method() string {
|
||||||
|
return l.method
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the subpath where the route responds to.
|
||||||
|
func (l localRoute) Path() string {
|
||||||
|
return l.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoute initialies a new local route for the reouter
|
||||||
|
func NewRoute(method, path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return localRoute{method, path, handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetRoute initializes a new route with the http method GET.
|
||||||
|
func NewGetRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return NewRoute("GET", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPostRoute initializes a new route with the http method POST.
|
||||||
|
func NewPostRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return NewRoute("POST", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPutRoute initializes a new route with the http method PUT.
|
||||||
|
func NewPutRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return NewRoute("PUT", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeleteRoute initializes a new route with the http method DELETE.
|
||||||
|
func NewDeleteRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return NewRoute("DELETE", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptionsRoute initializes a new route with the http method OPTIONS
|
||||||
|
func NewOptionsRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return NewRoute("OPTIONS", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeadRoute initializes a new route with the http method HEAD.
|
||||||
|
func NewHeadRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
||||||
|
return NewRoute("HEAD", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter initializes a local router with a new daemon.
|
||||||
|
func NewRouter(daemon *daemon.Daemon) dkrouter.Router {
|
||||||
|
r := &router{
|
||||||
|
daemon: daemon,
|
||||||
|
}
|
||||||
|
r.initRoutes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the list of routes registered in the router.
|
||||||
|
func (r *router) Routes() []dkrouter.Route {
|
||||||
|
return r.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// initRoutes initializes the routes in this router
|
||||||
|
func (r *router) initRoutes() {
|
||||||
|
r.routes = []dkrouter.Route{
|
||||||
|
// HEAD
|
||||||
|
NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
|
||||||
|
// OPTIONS
|
||||||
|
NewOptionsRoute("/", optionsHandler),
|
||||||
|
// GET
|
||||||
|
NewGetRoute("/_ping", pingHandler),
|
||||||
|
NewGetRoute("/events", r.getEvents),
|
||||||
|
NewGetRoute("/info", r.getInfo),
|
||||||
|
NewGetRoute("/version", r.getVersion),
|
||||||
|
NewGetRoute("/images/json", r.getImagesJSON),
|
||||||
|
NewGetRoute("/images/search", r.getImagesSearch),
|
||||||
|
NewGetRoute("/images/get", r.getImagesGet),
|
||||||
|
NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
|
||||||
|
NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
|
||||||
|
NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
|
||||||
|
NewGetRoute("/containers/json", r.getContainersJSON),
|
||||||
|
NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
|
||||||
|
NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
|
||||||
|
NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
|
||||||
|
NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
|
||||||
|
NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs),
|
||||||
|
NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats),
|
||||||
|
NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
|
||||||
|
NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
|
||||||
|
NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
|
||||||
|
NewGetRoute("/volumes", r.getVolumesList),
|
||||||
|
NewGetRoute("/volumes/{name:.*}", r.getVolumeByName),
|
||||||
|
// POST
|
||||||
|
NewPostRoute("/auth", r.postAuth),
|
||||||
|
NewPostRoute("/commit", r.postCommit),
|
||||||
|
NewPostRoute("/build", r.postBuild),
|
||||||
|
NewPostRoute("/images/create", r.postImagesCreate),
|
||||||
|
NewPostRoute("/images/load", r.postImagesLoad),
|
||||||
|
NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
|
||||||
|
NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
|
||||||
|
NewPostRoute("/containers/create", r.postContainersCreate),
|
||||||
|
NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
|
||||||
|
NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
|
||||||
|
NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
|
||||||
|
NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
|
||||||
|
NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
|
||||||
|
NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
|
||||||
|
NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
|
||||||
|
NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
|
||||||
|
NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
|
||||||
|
NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy),
|
||||||
|
NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
|
||||||
|
NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
|
||||||
|
NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
|
||||||
|
NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
|
||||||
|
NewPostRoute("/volumes", r.postVolumesCreate),
|
||||||
|
// PUT
|
||||||
|
NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
|
||||||
|
// DELETE
|
||||||
|
NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
|
||||||
|
NewDeleteRoute("/images/{name:.*}", r.deleteImages),
|
||||||
|
NewDeleteRoute("/volumes/{name:.*}", r.deleteVolumes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
_, err := w.Write([]byte{'O', 'K'})
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *router) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := s.daemon.VolumeInspect(vars["name"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req types.VolumeCreateRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusCreated, volume)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *router) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.daemon.VolumeRm(vars["name"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/server/router"
|
||||||
|
"github.com/docker/docker/api/server/router/local"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// networkRouter is a router to talk with the network controller
|
||||||
|
type networkRouter struct {
|
||||||
|
daemon *daemon.Daemon
|
||||||
|
routes []router.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter initializes a new network router
|
||||||
|
func NewRouter(d *daemon.Daemon) router.Router {
|
||||||
|
r := &networkRouter{
|
||||||
|
daemon: d,
|
||||||
|
}
|
||||||
|
r.initRoutes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the available routes to the network controller
|
||||||
|
func (r *networkRouter) Routes() []router.Route {
|
||||||
|
return r.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *networkRouter) initRoutes() {
|
||||||
|
r.routes = []router.Route{
|
||||||
|
// GET
|
||||||
|
local.NewGetRoute("/networks", r.getNetworksList),
|
||||||
|
local.NewGetRoute("/networks/{id:.*}", r.getNetwork),
|
||||||
|
// POST
|
||||||
|
local.NewPostRoute("/networks/create", r.postNetworkCreate),
|
||||||
|
local.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
|
||||||
|
local.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
|
||||||
|
// DELETE
|
||||||
|
local.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
"github.com/docker/docker/daemon/network"
|
||||||
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
|
"github.com/docker/libnetwork"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := r.Form.Get("filters")
|
||||||
|
netFilters, err := filters.FromParam(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
list := []*types.NetworkResource{}
|
||||||
|
var nameFilter, idFilter bool
|
||||||
|
var names, ids []string
|
||||||
|
if names, nameFilter = netFilters["name"]; nameFilter {
|
||||||
|
for _, name := range names {
|
||||||
|
if nw, err := n.daemon.GetNetwork(name, daemon.NetworkByName); err == nil {
|
||||||
|
list = append(list, buildNetworkResource(nw))
|
||||||
|
} else {
|
||||||
|
logrus.Errorf("failed to get network for filter=%s : %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ids, idFilter = netFilters["id"]; idFilter {
|
||||||
|
for _, id := range ids {
|
||||||
|
for _, nw := range n.daemon.GetNetworksByID(id) {
|
||||||
|
list = append(list, buildNetworkResource(nw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nameFilter && !idFilter {
|
||||||
|
nwList := n.daemon.GetNetworksByID("")
|
||||||
|
for _, nw := range nwList {
|
||||||
|
list = append(list, buildNetworkResource(nw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nw, err := n.daemon.FindNetwork(vars["id"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, buildNetworkResource(nw))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
var create types.NetworkCreate
|
||||||
|
var warning string
|
||||||
|
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nw, err := n.daemon.GetNetwork(create.Name, daemon.NetworkByName)
|
||||||
|
if _, ok := err.(libnetwork.ErrNoSuchNetwork); err != nil && !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if nw != nil {
|
||||||
|
if create.CheckDuplicate {
|
||||||
|
return libnetwork.NetworkNameError(create.Name)
|
||||||
|
}
|
||||||
|
warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
nw, err = n.daemon.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return httputils.WriteJSON(w, http.StatusCreated, &types.NetworkCreateResponse{
|
||||||
|
ID: nw.ID(),
|
||||||
|
Warning: warning,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
var connect types.NetworkConnect
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&connect); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nw, err := n.daemon.FindNetwork(vars["id"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.daemon.ConnectContainerToNetwork(connect.Container, nw.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
var disconnect types.NetworkDisconnect
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := httputils.CheckForJSON(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&disconnect); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nw, err := n.daemon.FindNetwork(vars["id"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.daemon.DisconnectContainerFromNetwork(disconnect.Container, nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nw, err := n.daemon.FindNetwork(vars["id"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nw.Delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
|
||||||
|
r := &types.NetworkResource{}
|
||||||
|
if nw == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Name = nw.Name()
|
||||||
|
r.ID = nw.ID()
|
||||||
|
r.Scope = nw.Info().Scope()
|
||||||
|
r.Driver = nw.Type()
|
||||||
|
r.Options = nw.Info().DriverOptions()
|
||||||
|
r.Containers = make(map[string]types.EndpointResource)
|
||||||
|
buildIpamResources(r, nw)
|
||||||
|
|
||||||
|
epl := nw.Endpoints()
|
||||||
|
for _, e := range epl {
|
||||||
|
sb := e.Info().Sandbox()
|
||||||
|
if sb == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Containers[sb.ContainerID()] = buildEndpointResource(e)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildIpamResources(r *types.NetworkResource, nw libnetwork.Network) {
|
||||||
|
id, ipv4conf, ipv6conf := nw.Info().IpamConfig()
|
||||||
|
|
||||||
|
r.IPAM.Driver = id
|
||||||
|
|
||||||
|
r.IPAM.Config = []network.IPAMConfig{}
|
||||||
|
for _, ip4 := range ipv4conf {
|
||||||
|
iData := network.IPAMConfig{}
|
||||||
|
iData.Subnet = ip4.PreferredPool
|
||||||
|
iData.IPRange = ip4.SubPool
|
||||||
|
iData.Gateway = ip4.Gateway
|
||||||
|
iData.AuxAddress = ip4.AuxAddresses
|
||||||
|
r.IPAM.Config = append(r.IPAM.Config, iData)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip6 := range ipv6conf {
|
||||||
|
iData := network.IPAMConfig{}
|
||||||
|
iData.Subnet = ip6.PreferredPool
|
||||||
|
iData.IPRange = ip6.SubPool
|
||||||
|
iData.Gateway = ip6.Gateway
|
||||||
|
iData.AuxAddress = ip6.AuxAddresses
|
||||||
|
r.IPAM.Config = append(r.IPAM.Config, iData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildEndpointResource(e libnetwork.Endpoint) types.EndpointResource {
|
||||||
|
er := types.EndpointResource{}
|
||||||
|
if e == nil {
|
||||||
|
return er
|
||||||
|
}
|
||||||
|
|
||||||
|
er.EndpointID = e.ID()
|
||||||
|
if iface := e.Info().Iface(); iface != nil {
|
||||||
|
if mac := iface.MacAddress(); mac != nil {
|
||||||
|
er.MacAddress = mac.String()
|
||||||
|
}
|
||||||
|
if ip := iface.Address(); ip != nil && len(ip.IP) > 0 {
|
||||||
|
er.IPv4Address = ip.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6 := iface.AddressIPv6(); ipv6 != nil && len(ipv6.IP) > 0 {
|
||||||
|
er.IPv6Address = ipv6.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return er
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import "github.com/docker/docker/api/server/httputils"
|
||||||
|
|
||||||
|
// Router defines an interface to specify a group of routes to add the the docker server.
|
||||||
|
type Router interface {
|
||||||
|
Routes() []Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route defines an individual API route in the docker server.
|
||||||
|
type Route interface {
|
||||||
|
// Handler returns the raw function to create the http handler.
|
||||||
|
Handler() httputils.APIFunc
|
||||||
|
// Method returns the http method that the route responds to.
|
||||||
|
Method() string
|
||||||
|
// Path returns the subpath where the route responds to.
|
||||||
|
Path() string
|
||||||
|
}
|
||||||
+104
-289
@@ -2,25 +2,27 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api/server/httputils"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/api/server/router"
|
||||||
|
"github.com/docker/docker/api/server/router/local"
|
||||||
|
"github.com/docker/docker/api/server/router/network"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/pkg/sockets"
|
"github.com/docker/docker/pkg/sockets"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/utils"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// versionMatcher defines a variable matcher to be parsed by the router
|
||||||
|
// when a request is about to be served.
|
||||||
|
const versionMatcher = "/v{version:[0-9.]+}"
|
||||||
|
|
||||||
// Config provides the configuration for the API server
|
// Config provides the configuration for the API server
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Logging bool
|
Logging bool
|
||||||
@@ -29,26 +31,39 @@ type Config struct {
|
|||||||
Version string
|
Version string
|
||||||
SocketGroup string
|
SocketGroup string
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
Addrs []Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server contains instance details for the server
|
// Server contains instance details for the server
|
||||||
type Server struct {
|
type Server struct {
|
||||||
daemon *daemon.Daemon
|
|
||||||
cfg *Config
|
cfg *Config
|
||||||
router *mux.Router
|
|
||||||
start chan struct{}
|
start chan struct{}
|
||||||
servers []serverCloser
|
servers []*HTTPServer
|
||||||
|
routers []router.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr contains string representation of address and its protocol (tcp, unix...).
|
||||||
|
type Addr struct {
|
||||||
|
Proto string
|
||||||
|
Addr string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new instance of the server based on the specified configuration.
|
// New returns a new instance of the server based on the specified configuration.
|
||||||
func New(cfg *Config) *Server {
|
// It allocates resources which will be needed for ServeAPI(ports, unix-sockets).
|
||||||
srv := &Server{
|
func New(cfg *Config) (*Server, error) {
|
||||||
|
s := &Server{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
start: make(chan struct{}),
|
start: make(chan struct{}),
|
||||||
}
|
}
|
||||||
r := createRouter(srv)
|
for _, addr := range cfg.Addrs {
|
||||||
srv.router = r
|
srv, err := s.newServer(addr.Proto, addr.Addr)
|
||||||
return srv
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Server created for HTTP on %s (%s)", addr.Proto, addr.Addr)
|
||||||
|
s.servers = append(s.servers, srv...)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes servers and thus stop receiving requests
|
// Close closes servers and thus stop receiving requests
|
||||||
@@ -60,39 +75,22 @@ func (s *Server) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverCloser interface {
|
// ServeAPI loops through all initialized servers and spawns goroutine
|
||||||
Serve() error
|
// with Serve() method for each.
|
||||||
Close() error
|
func (s *Server) ServeAPI() error {
|
||||||
}
|
var chErrors = make(chan error, len(s.servers))
|
||||||
|
for _, srv := range s.servers {
|
||||||
// ServeAPI loops through all of the protocols sent in to docker and spawns
|
go func(srv *HTTPServer) {
|
||||||
// off a go routine to setup a serving http.Server for each.
|
var err error
|
||||||
func (s *Server) ServeAPI(protoAddrs []string) error {
|
logrus.Infof("API listen on %s", srv.l.Addr())
|
||||||
var chErrors = make(chan error, len(protoAddrs))
|
if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
err = nil
|
||||||
for _, protoAddr := range protoAddrs {
|
}
|
||||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
chErrors <- err
|
||||||
if len(protoAddrParts) != 2 {
|
}(srv)
|
||||||
return fmt.Errorf("bad format, expected PROTO://ADDR")
|
|
||||||
}
|
|
||||||
srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.servers = append(s.servers, srv...)
|
|
||||||
|
|
||||||
for _, s := range srv {
|
|
||||||
logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
|
||||||
go func(s serverCloser) {
|
|
||||||
if err := s.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
chErrors <- err
|
|
||||||
}(s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(protoAddrs); i++ {
|
for i := 0; i < len(s.servers); i++ {
|
||||||
err := <-chErrors
|
err := <-chErrors
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -120,109 +118,6 @@ func (s *HTTPServer) Close() error {
|
|||||||
return s.l.Close()
|
return s.l.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPAPIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
|
||||||
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
|
||||||
type HTTPAPIFunc func(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
|
||||||
|
|
||||||
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
||||||
conn, _, err := w.(http.Hijacker).Hijack()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Flush the options to make sure the client sets the raw mode
|
|
||||||
conn.Write([]byte{})
|
|
||||||
return conn, conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeStreams(streams ...interface{}) {
|
|
||||||
for _, stream := range streams {
|
|
||||||
if tcpc, ok := stream.(interface {
|
|
||||||
CloseWrite() error
|
|
||||||
}); ok {
|
|
||||||
tcpc.CloseWrite()
|
|
||||||
} else if closer, ok := stream.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkForJSON makes sure that the request's Content-Type is application/json.
|
|
||||||
func checkForJSON(r *http.Request) error {
|
|
||||||
ct := r.Header.Get("Content-Type")
|
|
||||||
|
|
||||||
// No Content-Type header is ok as long as there's no Body
|
|
||||||
if ct == "" {
|
|
||||||
if r.Body == nil || r.ContentLength == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise it better be json
|
|
||||||
if api.MatchesContentType(ct, "application/json") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
|
||||||
}
|
|
||||||
|
|
||||||
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
|
||||||
func parseForm(r *http.Request) error {
|
|
||||||
if r == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMultipartForm(r *http.Request) error {
|
|
||||||
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpError(w http.ResponseWriter, err error) {
|
|
||||||
if err == nil || w == nil {
|
|
||||||
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
statusCode := http.StatusInternalServerError
|
|
||||||
// FIXME: this is brittle and should not be necessary.
|
|
||||||
// If we need to differentiate between different possible error types, we should
|
|
||||||
// create appropriate error types with clearly defined meaning.
|
|
||||||
errStr := strings.ToLower(err.Error())
|
|
||||||
for keyword, status := range map[string]int{
|
|
||||||
"not found": http.StatusNotFound,
|
|
||||||
"no such": http.StatusNotFound,
|
|
||||||
"bad parameter": http.StatusBadRequest,
|
|
||||||
"conflict": http.StatusConflict,
|
|
||||||
"impossible": http.StatusNotAcceptable,
|
|
||||||
"wrong login/password": http.StatusUnauthorized,
|
|
||||||
"hasn't been activated": http.StatusForbidden,
|
|
||||||
} {
|
|
||||||
if strings.Contains(errStr, keyword) {
|
|
||||||
statusCode = status
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error")
|
|
||||||
http.Error(w, err.Error(), statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeJSON writes the value v to the http response stream as json with standard
|
|
||||||
// json encoding.
|
|
||||||
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
return json.NewEncoder(w).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) optionsHandler(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
|
func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
|
||||||
logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
|
logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
|
||||||
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
|
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
|
||||||
@@ -230,11 +125,6 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string
|
|||||||
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
|
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ping(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
_, err := w.Write([]byte{'O', 'K'})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
|
func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
|
||||||
if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
|
if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
|
||||||
logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||||
@@ -248,153 +138,78 @@ func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeHTTPHandler(logging bool, localMethod string, localRoute string, handlerFunc HTTPAPIFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc {
|
func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// log the request
|
// log the handler call
|
||||||
logrus.Debugf("Calling %s %s", localMethod, localRoute)
|
logrus.Debugf("Calling %s %s", r.Method, r.URL.Path)
|
||||||
|
|
||||||
if logging {
|
// Define the context that we'll pass around to share info
|
||||||
logrus.Infof("%s %s", r.Method, r.RequestURI)
|
// like the docker-request-id.
|
||||||
|
//
|
||||||
|
// The 'context' will be used for global data that should
|
||||||
|
// apply to all requests. Data that is specific to the
|
||||||
|
// immediate function being called should still be passed
|
||||||
|
// as 'args' on the function call.
|
||||||
|
ctx := context.Background()
|
||||||
|
handlerFunc := s.handleWithGlobalMiddlewares(handler)
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
if vars == nil {
|
||||||
|
vars = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
if err := handlerFunc(ctx, w, r, vars); err != nil {
|
||||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.URL.Path, utils.GetErrorMessage(err))
|
||||||
|
httputils.WriteError(w, err)
|
||||||
// v1.20 onwards includes the GOOS of the client after the version
|
|
||||||
// such as Docker/1.7.0 (linux)
|
|
||||||
if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
|
|
||||||
userAgent[1] = strings.Split(userAgent[1], " ")[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
|
|
||||||
logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
version := version.Version(mux.Vars(r)["version"])
|
|
||||||
if version == "" {
|
|
||||||
version = api.Version
|
|
||||||
}
|
|
||||||
if corsHeaders != "" {
|
|
||||||
writeCorsHeaders(w, r, corsHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.GreaterThan(api.Version) {
|
|
||||||
http.Error(w, fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", version, api.Version).Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if version.LessThan(api.MinVersion) {
|
|
||||||
http.Error(w, fmt.Errorf("client is too old, minimum supported API version is %s, please upgrade your client to a newer version", api.MinVersion).Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
|
||||||
|
|
||||||
if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil {
|
|
||||||
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
|
|
||||||
httpError(w, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitRouters initializes a list of routers for the server.
|
||||||
|
// Sets those routers as Handler for each server.
|
||||||
|
func (s *Server) InitRouters(d *daemon.Daemon) {
|
||||||
|
s.addRouter(local.NewRouter(d))
|
||||||
|
s.addRouter(network.NewRouter(d))
|
||||||
|
for _, srv := range s.servers {
|
||||||
|
srv.srv.Handler = s.CreateMux()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRouter adds a new router to the server.
|
||||||
|
func (s *Server) addRouter(r router.Router) {
|
||||||
|
s.routers = append(s.routers, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMux initializes the main router the server uses.
|
||||||
// we keep enableCors just for legacy usage, need to be removed in the future
|
// we keep enableCors just for legacy usage, need to be removed in the future
|
||||||
func createRouter(s *Server) *mux.Router {
|
func (s *Server) CreateMux() *mux.Router {
|
||||||
r := mux.NewRouter()
|
m := mux.NewRouter()
|
||||||
if os.Getenv("DEBUG") != "" {
|
if os.Getenv("DEBUG") != "" {
|
||||||
profilerSetup(r, "/debug/")
|
profilerSetup(m, "/debug/")
|
||||||
}
|
|
||||||
m := map[string]map[string]HTTPAPIFunc{
|
|
||||||
"HEAD": {
|
|
||||||
"/containers/{name:.*}/archive": s.headContainersArchive,
|
|
||||||
},
|
|
||||||
"GET": {
|
|
||||||
"/_ping": s.ping,
|
|
||||||
"/events": s.getEvents,
|
|
||||||
"/info": s.getInfo,
|
|
||||||
"/version": s.getVersion,
|
|
||||||
"/images/json": s.getImagesJSON,
|
|
||||||
"/images/search": s.getImagesSearch,
|
|
||||||
"/images/get": s.getImagesGet,
|
|
||||||
"/images/{name:.*}/get": s.getImagesGet,
|
|
||||||
"/images/{name:.*}/history": s.getImagesHistory,
|
|
||||||
"/images/{name:.*}/json": s.getImagesByName,
|
|
||||||
"/containers/json": s.getContainersJSON,
|
|
||||||
"/containers/{name:.*}/export": s.getContainersExport,
|
|
||||||
"/containers/{name:.*}/changes": s.getContainersChanges,
|
|
||||||
"/containers/{name:.*}/json": s.getContainersByName,
|
|
||||||
"/containers/{name:.*}/top": s.getContainersTop,
|
|
||||||
"/containers/{name:.*}/logs": s.getContainersLogs,
|
|
||||||
"/containers/{name:.*}/stats": s.getContainersStats,
|
|
||||||
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
|
||||||
"/exec/{id:.*}/json": s.getExecByID,
|
|
||||||
"/containers/{name:.*}/archive": s.getContainersArchive,
|
|
||||||
"/volumes": s.getVolumesList,
|
|
||||||
"/volumes/{name:.*}": s.getVolumeByName,
|
|
||||||
},
|
|
||||||
"POST": {
|
|
||||||
"/auth": s.postAuth,
|
|
||||||
"/commit": s.postCommit,
|
|
||||||
"/build": s.postBuild,
|
|
||||||
"/images/create": s.postImagesCreate,
|
|
||||||
"/images/load": s.postImagesLoad,
|
|
||||||
"/images/{name:.*}/push": s.postImagesPush,
|
|
||||||
"/images/{name:.*}/tag": s.postImagesTag,
|
|
||||||
"/containers/create": s.postContainersCreate,
|
|
||||||
"/containers/{name:.*}/kill": s.postContainersKill,
|
|
||||||
"/containers/{name:.*}/pause": s.postContainersPause,
|
|
||||||
"/containers/{name:.*}/unpause": s.postContainersUnpause,
|
|
||||||
"/containers/{name:.*}/restart": s.postContainersRestart,
|
|
||||||
"/containers/{name:.*}/start": s.postContainersStart,
|
|
||||||
"/containers/{name:.*}/stop": s.postContainersStop,
|
|
||||||
"/containers/{name:.*}/wait": s.postContainersWait,
|
|
||||||
"/containers/{name:.*}/resize": s.postContainersResize,
|
|
||||||
"/containers/{name:.*}/attach": s.postContainersAttach,
|
|
||||||
"/containers/{name:.*}/copy": s.postContainersCopy,
|
|
||||||
"/containers/{name:.*}/exec": s.postContainerExecCreate,
|
|
||||||
"/exec/{name:.*}/start": s.postContainerExecStart,
|
|
||||||
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
|
||||||
"/containers/{name:.*}/rename": s.postContainerRename,
|
|
||||||
"/volumes": s.postVolumesCreate,
|
|
||||||
},
|
|
||||||
"PUT": {
|
|
||||||
"/containers/{name:.*}/archive": s.putContainersArchive,
|
|
||||||
},
|
|
||||||
"DELETE": {
|
|
||||||
"/containers/{name:.*}": s.deleteContainers,
|
|
||||||
"/images/{name:.*}": s.deleteImages,
|
|
||||||
"/volumes/{name:.*}": s.deleteVolumes,
|
|
||||||
},
|
|
||||||
"OPTIONS": {
|
|
||||||
"": s.optionsHandler,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
|
logrus.Debugf("Registering routers")
|
||||||
// otherwise, all head values will be passed to HTTP handler
|
for _, apiRouter := range s.routers {
|
||||||
corsHeaders := s.cfg.CorsHeaders
|
for _, r := range apiRouter.Routes() {
|
||||||
if corsHeaders == "" && s.cfg.EnableCors {
|
f := s.makeHTTPHandler(r.Handler())
|
||||||
corsHeaders = "*"
|
|
||||||
}
|
|
||||||
|
|
||||||
for method, routes := range m {
|
logrus.Debugf("Registering %s, %s", r.Method(), r.Path())
|
||||||
for route, fct := range routes {
|
m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f)
|
||||||
logrus.Debugf("Registering %s, %s", method, route)
|
m.Path(r.Path()).Methods(r.Method()).Handler(f)
|
||||||
// NOTE: scope issue, make sure the variables are local and won't be changed
|
|
||||||
localRoute := route
|
|
||||||
localFct := fct
|
|
||||||
localMethod := method
|
|
||||||
|
|
||||||
// build the handler function
|
|
||||||
f := makeHTTPHandler(s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version))
|
|
||||||
|
|
||||||
// add the new route
|
|
||||||
if localRoute == "" {
|
|
||||||
r.Methods(localMethod).HandlerFunc(f)
|
|
||||||
} else {
|
|
||||||
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
|
||||||
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptConnections allows clients to connect to the API server.
|
||||||
|
// Referenced Daemon is notified about this server, and waits for the
|
||||||
|
// daemon acknowledgement before the incoming connections are accepted.
|
||||||
|
func (s *Server) AcceptConnections() {
|
||||||
|
// close the lock so the listeners start accepting connections
|
||||||
|
select {
|
||||||
|
case <-s.start:
|
||||||
|
default:
|
||||||
|
close(s.start)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
// +build experimental,!windows
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
func (s *Server) registerSubRouter() {
|
|
||||||
httpHandler := s.daemon.NetworkAPIRouter()
|
|
||||||
|
|
||||||
subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
subrouter = s.router.PathPrefix("/networks").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
|
|
||||||
subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
subrouter = s.router.PathPrefix("/services").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// +build !experimental windows
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
func (s *Server) registerSubRouter() {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMiddlewares(t *testing.T) {
|
||||||
|
cfg := &Config{}
|
||||||
|
srv := &Server{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "/containers/json", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
localHandler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if httputils.VersionFromContext(ctx) == "" {
|
||||||
|
t.Fatalf("Expected version, got empty string")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerFunc := srv.handleWithGlobalMiddlewares(localHandler)
|
||||||
|
if err := handlerFunc(ctx, resp, req, map[string]string{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-49
@@ -8,16 +8,16 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/sockets"
|
"github.com/docker/docker/pkg/sockets"
|
||||||
"github.com/docker/libnetwork/portallocator"
|
"github.com/docker/libnetwork/portallocator"
|
||||||
|
|
||||||
systemdActivation "github.com/coreos/go-systemd/activation"
|
systemdActivation "github.com/coreos/go-systemd/activation"
|
||||||
systemdDaemon "github.com/coreos/go-systemd/daemon"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// newServer sets up the required serverClosers and does protocol specific checking.
|
// newServer sets up the required HTTPServers and does protocol specific checking.
|
||||||
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
// newServer does not set any muxers, you should set it later to Handler field
|
||||||
|
func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
ls []net.Listener
|
ls []net.Listener
|
||||||
@@ -28,10 +28,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// We don't want to start serving on these sockets until the
|
|
||||||
// daemon is initialized and installed. Otherwise required handlers
|
|
||||||
// won't be ready.
|
|
||||||
<-s.start
|
|
||||||
case "tcp":
|
case "tcp":
|
||||||
l, err := s.initTCPSocket(addr)
|
l, err := s.initTCPSocket(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,12 +43,11 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
||||||
}
|
}
|
||||||
var res []serverCloser
|
var res []*HTTPServer
|
||||||
for _, l := range ls {
|
for _, l := range ls {
|
||||||
res = append(res, &HTTPServer{
|
res = append(res, &HTTPServer{
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.router,
|
|
||||||
},
|
},
|
||||||
l,
|
l,
|
||||||
})
|
})
|
||||||
@@ -60,22 +55,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptConnections allows clients to connect to the API server.
|
|
||||||
// Referenced Daemon is notified about this server, and waits for the
|
|
||||||
// daemon acknowledgement before the incoming connections are accepted.
|
|
||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
|
||||||
// Tell the init daemon we are accepting requests
|
|
||||||
s.daemon = d
|
|
||||||
s.registerSubRouter()
|
|
||||||
go systemdDaemon.SdNotify("READY=1")
|
|
||||||
// close the lock so the listeners start accepting connections
|
|
||||||
select {
|
|
||||||
case <-s.start:
|
|
||||||
default:
|
|
||||||
close(s.start)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func allocateDaemonPort(addr string) error {
|
func allocateDaemonPort(addr string) error {
|
||||||
host, port, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,16 +82,6 @@ func allocateDaemonPort(addr string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContainersByNameDownlevel performs processing for pre 1.20 APIs. This
|
|
||||||
// is only relevant on non-Windows daemons.
|
|
||||||
func getContainersByNameDownlevel(w http.ResponseWriter, s *Server, namevar string) error {
|
|
||||||
containerJSONRaw, err := s.daemon.ContainerInspectPre120(namevar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, containerJSONRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// listenFD returns the specified socket activated files as a slice of
|
// listenFD returns the specified socket activated files as a slice of
|
||||||
// net.Listeners or all of the activated files if "*" is given.
|
// net.Listeners or all of the activated files if "*" is given.
|
||||||
func listenFD(addr string) ([]net.Listener, error) {
|
func listenFD(addr string) ([]net.Listener, error) {
|
||||||
@@ -122,24 +91,33 @@ func listenFD(addr string) ([]net.Listener, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if listeners == nil || len(listeners) == 0 {
|
if len(listeners) == 0 {
|
||||||
return nil, fmt.Errorf("No sockets found")
|
return nil, fmt.Errorf("No sockets found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// default to all fds just like unix:// and tcp://
|
// default to all fds just like unix:// and tcp://
|
||||||
if addr == "" {
|
if addr == "" || addr == "*" {
|
||||||
addr = "*"
|
|
||||||
}
|
|
||||||
|
|
||||||
fdNum, _ := strconv.Atoi(addr)
|
|
||||||
fdOffset := fdNum - 3
|
|
||||||
if (addr != "*") && (len(listeners) < int(fdOffset)+1) {
|
|
||||||
return nil, fmt.Errorf("Too few socket activated files passed in")
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr == "*" {
|
|
||||||
return listeners, nil
|
return listeners, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fdNum, err := strconv.Atoi(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse systemd address, should be number: %v", err)
|
||||||
|
}
|
||||||
|
fdOffset := fdNum - 3
|
||||||
|
if len(listeners) < int(fdOffset)+1 {
|
||||||
|
return nil, fmt.Errorf("Too few socket activated files passed in")
|
||||||
|
}
|
||||||
|
if listeners[fdOffset] == nil {
|
||||||
|
return nil, fmt.Errorf("failed to listen on systemd activated file at fd %d", fdOffset+3)
|
||||||
|
}
|
||||||
|
for i, ls := range listeners {
|
||||||
|
if i == fdOffset || ls == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := ls.Close(); err != nil {
|
||||||
|
logrus.Errorf("Failed to close systemd activated file at fd %d: %v", fdOffset+3, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return []net.Listener{listeners[fdOffset]}, nil
|
return []net.Listener{listeners[fdOffset]}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewServer sets up the required Server and does protocol specific checking.
|
// NewServer sets up the required Server and does protocol specific checking.
|
||||||
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
||||||
var (
|
var (
|
||||||
ls []net.Listener
|
ls []net.Listener
|
||||||
)
|
)
|
||||||
@@ -27,12 +25,11 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []serverCloser
|
var res []*HTTPServer
|
||||||
for _, l := range ls {
|
for _, l := range ls {
|
||||||
res = append(res, &HTTPServer{
|
res = append(res, &HTTPServer{
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.router,
|
|
||||||
},
|
},
|
||||||
l,
|
l,
|
||||||
})
|
})
|
||||||
@@ -41,24 +38,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptConnections allows router to start listening for the incoming requests.
|
|
||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
|
||||||
s.daemon = d
|
|
||||||
s.registerSubRouter()
|
|
||||||
// close the lock so the listeners start accepting connections
|
|
||||||
select {
|
|
||||||
case <-s.start:
|
|
||||||
default:
|
|
||||||
close(s.start)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func allocateDaemonPort(addr string) error {
|
func allocateDaemonPort(addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContainersByNameDownlevel performs processing for pre 1.20 APIs. This
|
|
||||||
// is only relevant on non-Windows daemons.
|
|
||||||
func getContainersByNameDownlevel(w http.ResponseWriter, s *Server, namevar string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getVolumesList(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getVolumeByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := s.daemon.VolumeInspect(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postVolumesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var req types.VolumeCreateRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusCreated, volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) deleteVolumes(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.daemon.VolumeRm(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+13
-6
@@ -89,10 +89,17 @@ type NetworkStats struct {
|
|||||||
|
|
||||||
// Stats is Ultimate struct aggregating all types of stats of one container
|
// Stats is Ultimate struct aggregating all types of stats of one container
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
Read time.Time `json:"read"`
|
Read time.Time `json:"read"`
|
||||||
Network NetworkStats `json:"network,omitempty"`
|
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
||||||
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
||||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
}
|
||||||
|
|
||||||
|
// StatsJSON is newly used Networks
|
||||||
|
type StatsJSON struct {
|
||||||
|
Stats
|
||||||
|
|
||||||
|
// Networks request version >=1.21
|
||||||
|
Networks map[string]NetworkStats `json:"networks,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-21
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ type GraphDriverData struct {
|
|||||||
// GET "/images/{name:.*}/json"
|
// GET "/images/{name:.*}/json"
|
||||||
type ImageInspect struct {
|
type ImageInspect struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
|
Tags []string
|
||||||
Parent string
|
Parent string
|
||||||
Comment string
|
Comment string
|
||||||
Created string
|
Created string
|
||||||
@@ -125,6 +127,7 @@ type Container struct {
|
|||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Names []string
|
Names []string
|
||||||
Image string
|
Image string
|
||||||
|
ImageID string
|
||||||
Command string
|
Command string
|
||||||
Created int64
|
Created int64
|
||||||
Ports []Port
|
Ports []Port
|
||||||
@@ -201,7 +204,7 @@ type Info struct {
|
|||||||
KernelVersion string
|
KernelVersion string
|
||||||
OperatingSystem string
|
OperatingSystem string
|
||||||
IndexServerAddress string
|
IndexServerAddress string
|
||||||
RegistryConfig interface{}
|
RegistryConfig *registry.ServiceConfig
|
||||||
InitSha1 string
|
InitSha1 string
|
||||||
InitPath string
|
InitPath string
|
||||||
NCPU int
|
NCPU int
|
||||||
@@ -213,6 +216,8 @@ type Info struct {
|
|||||||
Name string
|
Name string
|
||||||
Labels []string
|
Labels []string
|
||||||
ExperimentalBuild bool
|
ExperimentalBuild bool
|
||||||
|
ServerVersion string
|
||||||
|
ClusterStore string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecStartCheck is a temp struct used by execStart
|
// ExecStartCheck is a temp struct used by execStart
|
||||||
@@ -264,6 +269,8 @@ type ContainerJSONBase struct {
|
|||||||
ExecIDs []string
|
ExecIDs []string
|
||||||
HostConfig *runconfig.HostConfig
|
HostConfig *runconfig.HostConfig
|
||||||
GraphDriver GraphDriverData
|
GraphDriver GraphDriverData
|
||||||
|
SizeRw *int64 `json:",omitempty"`
|
||||||
|
SizeRootFs *int64 `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSON is newly used struct along with MountPoint
|
// ContainerJSON is newly used struct along with MountPoint
|
||||||
@@ -273,26 +280,6 @@ type ContainerJSON struct {
|
|||||||
Config *runconfig.Config
|
Config *runconfig.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSONPre120 is a backcompatibility struct along with ContainerConfig.
|
|
||||||
// Note this is not used by the Windows daemon.
|
|
||||||
type ContainerJSONPre120 struct {
|
|
||||||
*ContainerJSONBase
|
|
||||||
Volumes map[string]string
|
|
||||||
VolumesRW map[string]bool
|
|
||||||
Config *ContainerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerConfig is a backcompatibility struct used in ContainerJSONPre120
|
|
||||||
type ContainerConfig struct {
|
|
||||||
*runconfig.Config
|
|
||||||
|
|
||||||
// backward compatibility, they now live in HostConfig
|
|
||||||
Memory int64
|
|
||||||
MemorySwap int64
|
|
||||||
CPUShares int64 `json:"CpuShares"`
|
|
||||||
CPUSet string `json:"CpuSet"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountPoint represents a mount point configuration inside the container.
|
// MountPoint represents a mount point configuration inside the container.
|
||||||
type MountPoint struct {
|
type MountPoint struct {
|
||||||
Name string `json:",omitempty"`
|
Name string `json:",omitempty"`
|
||||||
@@ -323,3 +310,47 @@ type VolumeCreateRequest struct {
|
|||||||
Driver string // Driver is the name of the driver that should be used to create the volume
|
Driver string // Driver is the name of the driver that should be used to create the volume
|
||||||
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
|
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkResource is the body of the "get network" http response message
|
||||||
|
type NetworkResource struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
IPAM network.IPAM `json:"ipam"`
|
||||||
|
Containers map[string]EndpointResource `json:"containers"`
|
||||||
|
Options map[string]string `json:"options"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//EndpointResource contains network resources allocated and usd for a container in a network
|
||||||
|
type EndpointResource struct {
|
||||||
|
EndpointID string `json:"endpoint"`
|
||||||
|
MacAddress string `json:"mac_address"`
|
||||||
|
IPv4Address string `json:"ipv4_address"`
|
||||||
|
IPv6Address string `json:"ipv6_address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkCreate is the expected body of the "create network" http request message
|
||||||
|
type NetworkCreate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CheckDuplicate bool `json:"check_duplicate"`
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
IPAM network.IPAM `json:"ipam"`
|
||||||
|
Options map[string]string `json:"options"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkCreateResponse is the response message sent by the server for network create call
|
||||||
|
type NetworkCreateResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Warning string `json:"warning"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkConnect represents the data to be used to connect a container to the network
|
||||||
|
type NetworkConnect struct {
|
||||||
|
Container string `json:"container"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkDisconnect represents the data to be used to disconnect a container from the network
|
||||||
|
type NetworkDisconnect struct {
|
||||||
|
Container string `json:"container"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
## Legacy API type versions
|
||||||
|
|
||||||
|
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`.
|
||||||
|
|
||||||
|
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`.
|
||||||
|
|
||||||
|
### Package name conventions
|
||||||
|
|
||||||
|
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention:
|
||||||
|
|
||||||
|
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`.
|
||||||
|
2. We cannot use `_` because golint complains abount it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`.
|
||||||
|
|
||||||
|
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Package v1p19 provides specific API types for the API version 1, patch 19.
|
||||||
|
package v1p19
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContainerJSON is a backcompatibility struct for APIs prior to 1.20.
|
||||||
|
// Note this is not used by the Windows daemon.
|
||||||
|
type ContainerJSON struct {
|
||||||
|
*types.ContainerJSONBase
|
||||||
|
Volumes map[string]string
|
||||||
|
VolumesRW map[string]bool
|
||||||
|
Config *ContainerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerConfig is a backcompatibility struct for APIs prior to 1.20.
|
||||||
|
type ContainerConfig struct {
|
||||||
|
*runconfig.Config
|
||||||
|
|
||||||
|
// backward compatibility, they now live in HostConfig
|
||||||
|
VolumeDriver string
|
||||||
|
Memory int64
|
||||||
|
MemorySwap int64
|
||||||
|
CPUShares int64 `json:"CpuShares"`
|
||||||
|
CPUSet string `json:"Cpuset"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Package v1p20 provides specific API types for the API version 1, patch 20.
|
||||||
|
package v1p20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContainerJSON is a backcompatibility struct for the API 1.20
|
||||||
|
type ContainerJSON struct {
|
||||||
|
*types.ContainerJSONBase
|
||||||
|
Mounts []types.MountPoint
|
||||||
|
Config *ContainerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20
|
||||||
|
type ContainerConfig struct {
|
||||||
|
*runconfig.Config
|
||||||
|
|
||||||
|
// backward compatibility, they now live in HostConfig
|
||||||
|
VolumeDriver string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatsJSON is a backcompatibility struct used in Stats for API prior to 1.21
|
||||||
|
type StatsJSON struct {
|
||||||
|
types.Stats
|
||||||
|
Network types.NetworkStats `json:"network,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
// Package builder defines interfaces for any Docker builder to implement.
|
||||||
|
//
|
||||||
|
// Historically, only server-side Dockerfile interpreters existed.
|
||||||
|
// This package allows for other implementations of Docker builders.
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
// TODO: remove dependency on daemon
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
"github.com/docker/docker/image"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder abstracts a Docker builder whose only purpose is to build a Docker image referenced by an imageID.
|
||||||
|
type Builder interface {
|
||||||
|
// Build builds a Docker image referenced by an imageID string.
|
||||||
|
//
|
||||||
|
// Note: Tagging an image should not be done by a Builder, it should instead be done
|
||||||
|
// by the caller.
|
||||||
|
//
|
||||||
|
// TODO: make this return a reference instead of string
|
||||||
|
Build() (imageID string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context represents a file system tree.
|
||||||
|
type Context interface {
|
||||||
|
// Close allows to signal that the filesystem tree won't be used anymore.
|
||||||
|
// For Context implementations using a temporary directory, it is recommended to
|
||||||
|
// delete the temporary directory in Close().
|
||||||
|
Close() error
|
||||||
|
// Stat returns an entry corresponding to path if any.
|
||||||
|
// It is recommended to return an error if path was not found.
|
||||||
|
Stat(path string) (FileInfo, error)
|
||||||
|
// Open opens path from the context and returns a readable stream of it.
|
||||||
|
Open(path string) (io.ReadCloser, error)
|
||||||
|
// Walk walks the tree of the context with the function passed to it.
|
||||||
|
Walk(root string, walkFn WalkFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkFunc is the type of the function called for each file or directory visited by Context.Walk().
|
||||||
|
type WalkFunc func(path string, fi FileInfo, err error) error
|
||||||
|
|
||||||
|
// ModifiableContext represents a modifiable Context.
|
||||||
|
// TODO: remove this interface once we can get rid of Remove()
|
||||||
|
type ModifiableContext interface {
|
||||||
|
Context
|
||||||
|
// Remove deletes the entry specified by `path`.
|
||||||
|
// It is usual for directory entries to delete all its subentries.
|
||||||
|
Remove(path string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileInfo extends os.FileInfo to allow retrieving an absolute path to the file.
|
||||||
|
// TODO: remove this interface once pkg/archive exposes a walk function that Context can use.
|
||||||
|
type FileInfo interface {
|
||||||
|
os.FileInfo
|
||||||
|
Path() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathFileInfo is a convenience struct that implements the FileInfo interface.
|
||||||
|
type PathFileInfo struct {
|
||||||
|
os.FileInfo
|
||||||
|
// FilePath holds the absolute path to the file.
|
||||||
|
FilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the absolute path to the file.
|
||||||
|
func (fi PathFileInfo) Path() string {
|
||||||
|
return fi.FilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashed defines an extra method intended for implementations of os.FileInfo.
|
||||||
|
type Hashed interface {
|
||||||
|
// Hash returns the hash of a file.
|
||||||
|
Hash() string
|
||||||
|
SetHash(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashedFileInfo is a convenient struct that augments FileInfo with a field.
|
||||||
|
type HashedFileInfo struct {
|
||||||
|
FileInfo
|
||||||
|
// FileHash represents the hash of a file.
|
||||||
|
FileHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of a file.
|
||||||
|
func (fi HashedFileInfo) Hash() string {
|
||||||
|
return fi.FileHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHash sets the hash of a file.
|
||||||
|
func (fi *HashedFileInfo) SetHash(h string) {
|
||||||
|
fi.FileHash = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Docker abstracts calls to a Docker Daemon.
|
||||||
|
type Docker interface {
|
||||||
|
// TODO: use digest reference instead of name
|
||||||
|
|
||||||
|
// LookupImage looks up a Docker image referenced by `name`.
|
||||||
|
LookupImage(name string) (*image.Image, error)
|
||||||
|
// Pull tells Docker to pull image referenced by `name`.
|
||||||
|
Pull(name string) (*image.Image, error)
|
||||||
|
|
||||||
|
// TODO: move daemon.Container to its own package
|
||||||
|
|
||||||
|
// Container looks up a Docker container referenced by `id`.
|
||||||
|
Container(id string) (*daemon.Container, error)
|
||||||
|
// Create creates a new Docker container and returns potential warnings
|
||||||
|
// TODO: put warnings in the error
|
||||||
|
Create(*runconfig.Config, *runconfig.HostConfig) (*daemon.Container, []string, error)
|
||||||
|
// Remove removes a container specified by `id`.
|
||||||
|
Remove(id string, cfg *daemon.ContainerRmConfig) error
|
||||||
|
// Commit creates a new Docker image from an existing Docker container.
|
||||||
|
Commit(*daemon.Container, *daemon.ContainerCommitConfig) (*image.Image, error)
|
||||||
|
// Copy copies/extracts a source FileInfo to a destination path inside a container
|
||||||
|
// specified by a container object.
|
||||||
|
// TODO: make an Extract method instead of passing `decompress`
|
||||||
|
// TODO: do not pass a FileInfo, instead refactor the archive package to export a Walk function that can be used
|
||||||
|
// with Context.Walk
|
||||||
|
Copy(c *daemon.Container, destPath string, src FileInfo, decompress bool) error
|
||||||
|
|
||||||
|
// Retain retains an image avoiding it to be removed or overwritten until a corresponding Release() call.
|
||||||
|
// TODO: remove
|
||||||
|
Retain(sessionID, imgID string)
|
||||||
|
// Release releases a list of images that were retained for the time of a build.
|
||||||
|
// TODO: remove
|
||||||
|
Release(sessionID string, activeImages []string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageCache abstracts an image cache store.
|
||||||
|
// (parent image, child runconfig) -> child image
|
||||||
|
type ImageCache interface {
|
||||||
|
// GetCachedImage returns a reference to a cached image whose parent equals `parent`
|
||||||
|
// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
|
||||||
|
GetCachedImage(parentID string, cfg *runconfig.Config) (imageID string, err error)
|
||||||
|
}
|
||||||
@@ -1,536 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
// This file contains the dispatchers for each command. Note that
|
|
||||||
// `nullDispatch` is not actually a command, but support for commands we parse
|
|
||||||
// but do nothing with.
|
|
||||||
//
|
|
||||||
// See evaluator.go for a higher level discussion of the whole evaluator
|
|
||||||
// package.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/nat"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
|
||||||
// command to specify that no base image is to be used.
|
|
||||||
NoBaseImageSpecifier string = "scratch"
|
|
||||||
)
|
|
||||||
|
|
||||||
// dispatch with no layer / parsing. This is effectively not a command.
|
|
||||||
func nullDispatch(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ENV foo bar
|
|
||||||
//
|
|
||||||
// Sets the environment variable foo to bar, also makes interpolation
|
|
||||||
// in the dockerfile available from the next statement on via ${foo}.
|
|
||||||
//
|
|
||||||
func env(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("ENV requires at least one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args)%2 != 0 {
|
|
||||||
// should never get here, but just in case
|
|
||||||
return fmt.Errorf("Bad input to ENV, too many args")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO/FIXME/NOT USED
|
|
||||||
// Just here to show how to use the builder flags stuff within the
|
|
||||||
// context of a builder command. Will remove once we actually add
|
|
||||||
// a builder command to something!
|
|
||||||
/*
|
|
||||||
flBool1 := b.BuilderFlags.AddBool("bool1", false)
|
|
||||||
flStr1 := b.BuilderFlags.AddString("str1", "HI")
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Bool1:%v\n", flBool1)
|
|
||||||
fmt.Printf("Str1:%v\n", flStr1)
|
|
||||||
*/
|
|
||||||
|
|
||||||
commitStr := "ENV"
|
|
||||||
|
|
||||||
for j := 0; j < len(args); j++ {
|
|
||||||
// name ==> args[j]
|
|
||||||
// value ==> args[j+1]
|
|
||||||
newVar := args[j] + "=" + args[j+1] + ""
|
|
||||||
commitStr += " " + newVar
|
|
||||||
|
|
||||||
gotOne := false
|
|
||||||
for i, envVar := range b.Config.Env {
|
|
||||||
envParts := strings.SplitN(envVar, "=", 2)
|
|
||||||
if envParts[0] == args[j] {
|
|
||||||
b.Config.Env[i] = newVar
|
|
||||||
gotOne = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !gotOne {
|
|
||||||
b.Config.Env = append(b.Config.Env, newVar)
|
|
||||||
}
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.commit("", b.Config.Cmd, commitStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAINTAINER some text <maybe@an.email.address>
|
|
||||||
//
|
|
||||||
// Sets the maintainer metadata.
|
|
||||||
func maintainer(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return fmt.Errorf("MAINTAINER requires exactly one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.maintainer = args[0]
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LABEL some json data describing the image
|
|
||||||
//
|
|
||||||
// Sets the Label variable foo to bar,
|
|
||||||
//
|
|
||||||
func label(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("LABEL requires at least one argument")
|
|
||||||
}
|
|
||||||
if len(args)%2 != 0 {
|
|
||||||
// should never get here, but just in case
|
|
||||||
return fmt.Errorf("Bad input to LABEL, too many args")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
commitStr := "LABEL"
|
|
||||||
|
|
||||||
if b.Config.Labels == nil {
|
|
||||||
b.Config.Labels = map[string]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := 0; j < len(args); j++ {
|
|
||||||
// name ==> args[j]
|
|
||||||
// value ==> args[j+1]
|
|
||||||
newVar := args[j] + "=" + args[j+1] + ""
|
|
||||||
commitStr += " " + newVar
|
|
||||||
|
|
||||||
b.Config.Labels[args[j]] = args[j+1]
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
return b.commit("", b.Config.Cmd, commitStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ADD foo /path
|
|
||||||
//
|
|
||||||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
|
||||||
// exist here. If you do not wish to have this automatic handling, use COPY.
|
|
||||||
//
|
|
||||||
func add(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return fmt.Errorf("ADD requires at least two arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.runContextCommand(args, true, true, "ADD")
|
|
||||||
}
|
|
||||||
|
|
||||||
// COPY foo /path
|
|
||||||
//
|
|
||||||
// Same as 'ADD' but without the tar and remote url handling.
|
|
||||||
//
|
|
||||||
func dispatchCopy(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return fmt.Errorf("COPY requires at least two arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.runContextCommand(args, false, false, "COPY")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FROM imagename
|
|
||||||
//
|
|
||||||
// This sets the image the dockerfile will build on top of.
|
|
||||||
//
|
|
||||||
func from(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return fmt.Errorf("FROM requires one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
// Windows cannot support a container with no base image.
|
|
||||||
if name == NoBaseImageSpecifier {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return fmt.Errorf("Windows does not support FROM scratch")
|
|
||||||
}
|
|
||||||
b.image = ""
|
|
||||||
b.noBaseImage = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
image, err := b.Daemon.Repositories().LookupImage(name)
|
|
||||||
if b.Pull {
|
|
||||||
image, err = b.pullImage(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if b.Daemon.Graph().IsNotExist(err, name) {
|
|
||||||
image, err = b.pullImage(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// note that the top level err will still be !nil here if IsNotExist is
|
|
||||||
// not the error. This approach just simplifies the logic a bit.
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.processImageFrom(image)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ONBUILD RUN echo yo
|
|
||||||
//
|
|
||||||
// ONBUILD triggers run when the image is used in a FROM statement.
|
|
||||||
//
|
|
||||||
// ONBUILD handling has a lot of special-case functionality, the heading in
|
|
||||||
// evaluator.go and comments around dispatch() in the same file explain the
|
|
||||||
// special cases. search for 'OnBuild' in internals.go for additional special
|
|
||||||
// cases.
|
|
||||||
//
|
|
||||||
func onbuild(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("ONBUILD requires at least one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
|
||||||
switch triggerInstruction {
|
|
||||||
case "ONBUILD":
|
|
||||||
return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
|
||||||
case "MAINTAINER", "FROM":
|
|
||||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
|
|
||||||
|
|
||||||
b.Config.OnBuild = append(b.Config.OnBuild, original)
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WORKDIR /tmp
|
|
||||||
//
|
|
||||||
// Set the working directory for future RUN/CMD/etc statements.
|
|
||||||
//
|
|
||||||
func workdir(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return fmt.Errorf("WORKDIR requires exactly one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is from the Dockerfile and will not necessarily be in platform
|
|
||||||
// specific semantics, hence ensure it is converted.
|
|
||||||
workdir := filepath.FromSlash(args[0])
|
|
||||||
|
|
||||||
if !system.IsAbs(workdir) {
|
|
||||||
current := filepath.FromSlash(b.Config.WorkingDir)
|
|
||||||
workdir = filepath.Join(string(os.PathSeparator), current, workdir)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Config.WorkingDir = workdir
|
|
||||||
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUN some command yo
|
|
||||||
//
|
|
||||||
// run a command and commit the image. Args are automatically prepended with
|
|
||||||
// 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is
|
|
||||||
// only one argument. The difference in processing:
|
|
||||||
//
|
|
||||||
// RUN echo hi # sh -c echo hi (Linux)
|
|
||||||
// RUN echo hi # cmd /S /C echo hi (Windows)
|
|
||||||
// RUN [ "echo", "hi" ] # echo hi
|
|
||||||
//
|
|
||||||
func run(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if b.image == "" && !b.noBaseImage {
|
|
||||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
args = handleJSONArgs(args, attributes)
|
|
||||||
|
|
||||||
if !attributes["json"] {
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
args = append([]string{"/bin/sh", "-c"}, args...)
|
|
||||||
} else {
|
|
||||||
args = append([]string{"cmd", "/S /C"}, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
|
||||||
runCmd.SetOutput(ioutil.Discard)
|
|
||||||
runCmd.Usage = nil
|
|
||||||
|
|
||||||
config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := b.Config.Cmd
|
|
||||||
// set Cmd manually, this is special case only for Dockerfiles
|
|
||||||
b.Config.Cmd = config.Cmd
|
|
||||||
runconfig.Merge(b.Config, config)
|
|
||||||
|
|
||||||
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
|
|
||||||
|
|
||||||
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
|
|
||||||
|
|
||||||
hit, err := b.probeCache()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hit {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := b.create()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that we keep the container mounted until the commit
|
|
||||||
// to avoid unmounting and then mounting directly again
|
|
||||||
c.Mount()
|
|
||||||
defer c.Unmount()
|
|
||||||
|
|
||||||
err = b.run(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CMD foo
|
|
||||||
//
|
|
||||||
// Set the default command to run in the container (which may be empty).
|
|
||||||
// Argument handling is the same as RUN.
|
|
||||||
//
|
|
||||||
func cmd(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdSlice := handleJSONArgs(args, attributes)
|
|
||||||
|
|
||||||
if !attributes["json"] {
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
|
|
||||||
} else {
|
|
||||||
cmdSlice = append([]string{"cmd", "/S /C"}, cmdSlice...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Config.Cmd = stringutils.NewStrSlice(cmdSlice...)
|
|
||||||
|
|
||||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) != 0 {
|
|
||||||
b.cmdSet = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ENTRYPOINT /usr/sbin/nginx
|
|
||||||
//
|
|
||||||
// Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to
|
|
||||||
// /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx.
|
|
||||||
//
|
|
||||||
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
|
||||||
// is initialized at NewBuilder time instead of through argument parsing.
|
|
||||||
//
|
|
||||||
func entrypoint(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed := handleJSONArgs(args, attributes)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case attributes["json"]:
|
|
||||||
// ENTRYPOINT ["echo", "hi"]
|
|
||||||
b.Config.Entrypoint = stringutils.NewStrSlice(parsed...)
|
|
||||||
case len(parsed) == 0:
|
|
||||||
// ENTRYPOINT []
|
|
||||||
b.Config.Entrypoint = nil
|
|
||||||
default:
|
|
||||||
// ENTRYPOINT echo hi
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
b.Config.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0])
|
|
||||||
} else {
|
|
||||||
b.Config.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// when setting the entrypoint if a CMD was not explicitly set then
|
|
||||||
// set the command to nil
|
|
||||||
if !b.cmdSet {
|
|
||||||
b.Config.Cmd = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPOSE 6667/tcp 7000/tcp
|
|
||||||
//
|
|
||||||
// Expose ports for links and port mappings. This all ends up in
|
|
||||||
// b.Config.ExposedPorts for runconfig.
|
|
||||||
//
|
|
||||||
func expose(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
portsTab := args
|
|
||||||
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("EXPOSE requires at least one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Config.ExposedPorts == nil {
|
|
||||||
b.Config.ExposedPorts = make(nat.PortSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
ports, _, err := nat.ParsePortSpecs(portsTab)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// instead of using ports directly, we build a list of ports and sort it so
|
|
||||||
// the order is consistent. This prevents cache burst where map ordering
|
|
||||||
// changes between builds
|
|
||||||
portList := make([]string, len(ports))
|
|
||||||
var i int
|
|
||||||
for port := range ports {
|
|
||||||
if _, exists := b.Config.ExposedPorts[port]; !exists {
|
|
||||||
b.Config.ExposedPorts[port] = struct{}{}
|
|
||||||
}
|
|
||||||
portList[i] = string(port)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(portList)
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// USER foo
|
|
||||||
//
|
|
||||||
// Set the user to 'foo' for future commands and when running the
|
|
||||||
// ENTRYPOINT/CMD at container run time.
|
|
||||||
//
|
|
||||||
func user(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return fmt.Errorf("USER is not supported on Windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) != 1 {
|
|
||||||
return fmt.Errorf("USER requires exactly one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Config.User = args[0]
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
|
|
||||||
}
|
|
||||||
|
|
||||||
// VOLUME /foo
|
|
||||||
//
|
|
||||||
// Expose the volume /foo for use. Will also accept the JSON array form.
|
|
||||||
//
|
|
||||||
func volume(b *builder, args []string, attributes map[string]bool, original string) error {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return fmt.Errorf("VOLUME is not supported on Windows")
|
|
||||||
}
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("VOLUME requires at least one argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Config.Volumes == nil {
|
|
||||||
b.Config.Volumes = map[string]struct{}{}
|
|
||||||
}
|
|
||||||
for _, v := range args {
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if v == "" {
|
|
||||||
return fmt.Errorf("Volume specified can not be an empty string")
|
|
||||||
}
|
|
||||||
b.Config.Volumes[v] = struct{}{}
|
|
||||||
}
|
|
||||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package builder
|
package dockerfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package builder
|
package dockerfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package dockerfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/builder"
|
||||||
|
"github.com/docker/docker/builder/dockerfile/parser"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validCommitCommands = map[string]bool{
|
||||||
|
"cmd": true,
|
||||||
|
"entrypoint": true,
|
||||||
|
"env": true,
|
||||||
|
"expose": true,
|
||||||
|
"label": true,
|
||||||
|
"onbuild": true,
|
||||||
|
"user": true,
|
||||||
|
"volume": true,
|
||||||
|
"workdir": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuiltinAllowedBuildArgs is list of built-in allowed build args
|
||||||
|
var BuiltinAllowedBuildArgs = map[string]bool{
|
||||||
|
"HTTP_PROXY": true,
|
||||||
|
"http_proxy": true,
|
||||||
|
"HTTPS_PROXY": true,
|
||||||
|
"https_proxy": true,
|
||||||
|
"FTP_PROXY": true,
|
||||||
|
"ftp_proxy": true,
|
||||||
|
"NO_PROXY": true,
|
||||||
|
"no_proxy": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config constitutes the configuration for a Dockerfile builder.
|
||||||
|
type Config struct {
|
||||||
|
// only used if Dockerfile has to be extracted from Context
|
||||||
|
DockerfileName string
|
||||||
|
|
||||||
|
Verbose bool
|
||||||
|
UseCache bool
|
||||||
|
Remove bool
|
||||||
|
ForceRemove bool
|
||||||
|
Pull bool
|
||||||
|
BuildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
|
||||||
|
|
||||||
|
// resource constraints
|
||||||
|
// TODO: factor out to be reused with Run ?
|
||||||
|
|
||||||
|
Memory int64
|
||||||
|
MemorySwap int64
|
||||||
|
CPUShares int64
|
||||||
|
CPUPeriod int64
|
||||||
|
CPUQuota int64
|
||||||
|
CPUSetCpus string
|
||||||
|
CPUSetMems string
|
||||||
|
CgroupParent string
|
||||||
|
Ulimits []*ulimit.Ulimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder is a Dockerfile builder
|
||||||
|
// It implements the builder.Builder interface.
|
||||||
|
type Builder struct {
|
||||||
|
*Config
|
||||||
|
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
|
||||||
|
docker builder.Docker
|
||||||
|
context builder.Context
|
||||||
|
|
||||||
|
dockerfile *parser.Node
|
||||||
|
runConfig *runconfig.Config // runconfig for cmd, run, entrypoint etc.
|
||||||
|
flags *BFlags
|
||||||
|
tmpContainers map[string]struct{}
|
||||||
|
image string // imageID
|
||||||
|
noBaseImage bool
|
||||||
|
maintainer string
|
||||||
|
cmdSet bool
|
||||||
|
disableCommit bool
|
||||||
|
cacheBusted bool
|
||||||
|
cancelled chan struct{}
|
||||||
|
cancelOnce sync.Once
|
||||||
|
allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
|
||||||
|
|
||||||
|
// TODO: remove once docker.Commit can receive a tag
|
||||||
|
id string
|
||||||
|
activeImages []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
|
||||||
|
// If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
|
||||||
|
// will be read from the Context passed to Build().
|
||||||
|
func NewBuilder(config *Config, docker builder.Docker, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
|
||||||
|
if config == nil {
|
||||||
|
config = new(Config)
|
||||||
|
}
|
||||||
|
if config.BuildArgs == nil {
|
||||||
|
config.BuildArgs = make(map[string]string)
|
||||||
|
}
|
||||||
|
b = &Builder{
|
||||||
|
Config: config,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
docker: docker,
|
||||||
|
context: context,
|
||||||
|
runConfig: new(runconfig.Config),
|
||||||
|
tmpContainers: map[string]struct{}{},
|
||||||
|
cancelled: make(chan struct{}),
|
||||||
|
id: stringid.GenerateNonCryptoID(),
|
||||||
|
allowedBuildArgs: make(map[string]bool),
|
||||||
|
}
|
||||||
|
if dockerfile != nil {
|
||||||
|
b.dockerfile, err = parser.Parse(dockerfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build runs the Dockerfile builder from a context and a docker object that allows to make calls
|
||||||
|
// to Docker.
|
||||||
|
//
|
||||||
|
// This will (barring errors):
|
||||||
|
//
|
||||||
|
// * read the dockerfile from context
|
||||||
|
// * parse the dockerfile if not already parsed
|
||||||
|
// * walk the AST and execute it by dispatching to handlers. If Remove
|
||||||
|
// or ForceRemove is set, additional cleanup around containers happens after
|
||||||
|
// processing.
|
||||||
|
// * Print a happy message and return the image ID.
|
||||||
|
// * NOT tag the image, that is responsibility of the caller.
|
||||||
|
//
|
||||||
|
func (b *Builder) Build() (string, error) {
|
||||||
|
// TODO: remove once b.docker.Commit can take a tag parameter.
|
||||||
|
defer func() {
|
||||||
|
b.docker.Release(b.id, b.activeImages)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If Dockerfile was not parsed yet, extract it from the Context
|
||||||
|
if b.dockerfile == nil {
|
||||||
|
if err := b.readDockerfile(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortImgID string
|
||||||
|
for i, n := range b.dockerfile.Children {
|
||||||
|
select {
|
||||||
|
case <-b.cancelled:
|
||||||
|
logrus.Debug("Builder: build cancelled!")
|
||||||
|
fmt.Fprintf(b.Stdout, "Build cancelled")
|
||||||
|
return "", fmt.Errorf("Build cancelled")
|
||||||
|
default:
|
||||||
|
// Not cancelled yet, keep going...
|
||||||
|
}
|
||||||
|
if err := b.dispatch(i, n); err != nil {
|
||||||
|
if b.ForceRemove {
|
||||||
|
b.clearTmp()
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
shortImgID = stringid.TruncateID(b.image)
|
||||||
|
fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
|
||||||
|
if b.Remove {
|
||||||
|
b.clearTmp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there are any leftover build-args that were passed but not
|
||||||
|
// consumed during build. Return an error, if there are any.
|
||||||
|
leftoverArgs := []string{}
|
||||||
|
for arg := range b.BuildArgs {
|
||||||
|
if !b.isBuildArgAllowed(arg) {
|
||||||
|
leftoverArgs = append(leftoverArgs, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(leftoverArgs) > 0 {
|
||||||
|
return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.image == "" {
|
||||||
|
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
|
||||||
|
return b.image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel cancels an ongoing Dockerfile build.
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
b.cancelOnce.Do(func() {
|
||||||
|
close(b.cancelled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitConfig contains build configs for commit operation
|
||||||
|
type CommitConfig struct {
|
||||||
|
Pause bool
|
||||||
|
Repo string
|
||||||
|
Tag string
|
||||||
|
Author string
|
||||||
|
Comment string
|
||||||
|
Changes []string
|
||||||
|
Config *runconfig.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildFromConfig will do build directly from parameter 'changes', which comes
|
||||||
|
// from Dockerfile entries, it will:
|
||||||
|
// - call parse.Parse() to get AST root from Dockerfile entries
|
||||||
|
// - do build by calling builder.dispatch() to call all entries' handling routines
|
||||||
|
// TODO: remove?
|
||||||
|
func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) {
|
||||||
|
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the commands are valid
|
||||||
|
for _, n := range ast.Children {
|
||||||
|
if !validCommitCommands[n.Value] {
|
||||||
|
return nil, fmt.Errorf("%s is not a valid change command", n.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := NewBuilder(nil, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.runConfig = config
|
||||||
|
b.Stdout = ioutil.Discard
|
||||||
|
b.Stderr = ioutil.Discard
|
||||||
|
b.disableCommit = true
|
||||||
|
|
||||||
|
for i, n := range ast.Children {
|
||||||
|
if err := b.dispatch(i, n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.runConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit will create a new image from a container's changes
|
||||||
|
// TODO: remove daemon, make Commit a method on *Builder ?
|
||||||
|
func Commit(containerName string, d *daemon.Daemon, c *CommitConfig) (string, error) {
|
||||||
|
container, err := d.Get(containerName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is not possible to commit a running container on Windows
|
||||||
|
if runtime.GOOS == "windows" && container.IsRunning() {
|
||||||
|
return "", fmt.Errorf("Windows does not support commit of a running container")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Config == nil {
|
||||||
|
c.Config = &runconfig.Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
newConfig, err := BuildFromConfig(c.Config, c.Changes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runconfig.Merge(newConfig, container.Config); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitCfg := &daemon.ContainerCommitConfig{
|
||||||
|
Pause: c.Pause,
|
||||||
|
Repo: c.Repo,
|
||||||
|
Tag: c.Tag,
|
||||||
|
Author: c.Author,
|
||||||
|
Comment: c.Comment,
|
||||||
|
Config: newConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := d.Commit(container, commitCfg)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return img.ID, nil
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ const (
|
|||||||
Expose = "expose"
|
Expose = "expose"
|
||||||
Volume = "volume"
|
Volume = "volume"
|
||||||
User = "user"
|
User = "user"
|
||||||
|
StopSignal = "stopsignal"
|
||||||
|
Arg = "arg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commands is list of all Dockerfile commands
|
// Commands is list of all Dockerfile commands
|
||||||
@@ -35,4 +37,6 @@ var Commands = map[string]struct{}{
|
|||||||
Expose: {},
|
Expose: {},
|
||||||
Volume: {},
|
Volume: {},
|
||||||
User: {},
|
User: {},
|
||||||
|
StopSignal: {},
|
||||||
|
Arg: {},
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,647 @@
|
|||||||
|
package dockerfile
|
||||||
|
|
||||||
|
// This file contains the dispatchers for each command. Note that
|
||||||
|
// `nullDispatch` is not actually a command, but support for commands we parse
|
||||||
|
// but do nothing with.
|
||||||
|
//
|
||||||
|
// See evaluator.go for a higher level discussion of the whole evaluator
|
||||||
|
// package.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
derr "github.com/docker/docker/errors"
|
||||||
|
"github.com/docker/docker/image"
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/nat"
|
||||||
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||||
|
// command to specify that no base image is to be used.
|
||||||
|
NoBaseImageSpecifier string = "scratch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dispatch with no layer / parsing. This is effectively not a command.
|
||||||
|
func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENV foo bar
|
||||||
|
//
|
||||||
|
// Sets the environment variable foo to bar, also makes interpolation
|
||||||
|
// in the dockerfile available from the next statement on via ${foo}.
|
||||||
|
//
|
||||||
|
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return derr.ErrorCodeAtLeastOneArg.WithArgs("ENV")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args)%2 != 0 {
|
||||||
|
// should never get here, but just in case
|
||||||
|
return derr.ErrorCodeTooManyArgs.WithArgs("ENV")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO/FIXME/NOT USED
|
||||||
|
// Just here to show how to use the builder flags stuff within the
|
||||||
|
// context of a builder command. Will remove once we actually add
|
||||||
|
// a builder command to something!
|
||||||
|
/*
|
||||||
|
flBool1 := b.flags.AddBool("bool1", false)
|
||||||
|
flStr1 := b.flags.AddString("str1", "HI")
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Bool1:%v\n", flBool1)
|
||||||
|
fmt.Printf("Str1:%v\n", flStr1)
|
||||||
|
*/
|
||||||
|
|
||||||
|
commitStr := "ENV"
|
||||||
|
|
||||||
|
for j := 0; j < len(args); j++ {
|
||||||
|
// name ==> args[j]
|
||||||
|
// value ==> args[j+1]
|
||||||
|
newVar := args[j] + "=" + args[j+1] + ""
|
||||||
|
commitStr += " " + newVar
|
||||||
|
|
||||||
|
gotOne := false
|
||||||
|
for i, envVar := range b.runConfig.Env {
|
||||||
|
envParts := strings.SplitN(envVar, "=", 2)
|
||||||
|
if envParts[0] == args[j] {
|
||||||
|
b.runConfig.Env[i] = newVar
|
||||||
|
gotOne = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !gotOne {
|
||||||
|
b.runConfig.Env = append(b.runConfig.Env, newVar)
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.commit("", b.runConfig.Cmd, commitStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAINTAINER some text <maybe@an.email.address>
|
||||||
|
//
|
||||||
|
// Sets the maintainer metadata.
|
||||||
|
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return derr.ErrorCodeExactlyOneArg.WithArgs("MAINTAINER")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.maintainer = args[0]
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LABEL some json data describing the image
|
||||||
|
//
|
||||||
|
// Sets the Label variable foo to bar,
|
||||||
|
//
|
||||||
|
func label(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return derr.ErrorCodeAtLeastOneArg.WithArgs("LABEL")
|
||||||
|
}
|
||||||
|
if len(args)%2 != 0 {
|
||||||
|
// should never get here, but just in case
|
||||||
|
return derr.ErrorCodeTooManyArgs.WithArgs("LABEL")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitStr := "LABEL"
|
||||||
|
|
||||||
|
if b.runConfig.Labels == nil {
|
||||||
|
b.runConfig.Labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < len(args); j++ {
|
||||||
|
// name ==> args[j]
|
||||||
|
// value ==> args[j+1]
|
||||||
|
newVar := args[j] + "=" + args[j+1] + ""
|
||||||
|
commitStr += " " + newVar
|
||||||
|
|
||||||
|
b.runConfig.Labels[args[j]] = args[j+1]
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return b.commit("", b.runConfig.Cmd, commitStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD foo /path
|
||||||
|
//
|
||||||
|
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
||||||
|
// exist here. If you do not wish to have this automatic handling, use COPY.
|
||||||
|
//
|
||||||
|
func add(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return derr.ErrorCodeAtLeastTwoArgs.WithArgs("ADD")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.runContextCommand(args, true, true, "ADD")
|
||||||
|
}
|
||||||
|
|
||||||
|
// COPY foo /path
|
||||||
|
//
|
||||||
|
// Same as 'ADD' but without the tar and remote url handling.
|
||||||
|
//
|
||||||
|
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return derr.ErrorCodeAtLeastTwoArgs.WithArgs("COPY")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.runContextCommand(args, false, false, "COPY")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FROM imagename
|
||||||
|
//
|
||||||
|
// This sets the image the dockerfile will build on top of.
|
||||||
|
//
|
||||||
|
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return derr.ErrorCodeExactlyOneArg.WithArgs("FROM")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
// Windows cannot support a container with no base image.
|
||||||
|
if name == NoBaseImageSpecifier {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return fmt.Errorf("Windows does not support FROM scratch")
|
||||||
|
}
|
||||||
|
b.image = ""
|
||||||
|
b.noBaseImage = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
image *image.Image
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// TODO: don't use `name`, instead resolve it to a digest
|
||||||
|
if !b.Pull {
|
||||||
|
image, err = b.docker.LookupImage(name)
|
||||||
|
// TODO: shouldn't we error out if error is different from "not found" ?
|
||||||
|
}
|
||||||
|
if image == nil {
|
||||||
|
image, err = b.docker.Pull(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.processImageFrom(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ONBUILD RUN echo yo
|
||||||
|
//
|
||||||
|
// ONBUILD triggers run when the image is used in a FROM statement.
|
||||||
|
//
|
||||||
|
// ONBUILD handling has a lot of special-case functionality, the heading in
|
||||||
|
// evaluator.go and comments around dispatch() in the same file explain the
|
||||||
|
// special cases. search for 'OnBuild' in internals.go for additional special
|
||||||
|
// cases.
|
||||||
|
//
|
||||||
|
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return derr.ErrorCodeAtLeastOneArg.WithArgs("ONBUILD")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
||||||
|
switch triggerInstruction {
|
||||||
|
case "ONBUILD":
|
||||||
|
return derr.ErrorCodeChainOnBuild
|
||||||
|
case "MAINTAINER", "FROM":
|
||||||
|
return derr.ErrorCodeBadOnBuildCmd.WithArgs(triggerInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
|
||||||
|
|
||||||
|
b.runConfig.OnBuild = append(b.runConfig.OnBuild, original)
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WORKDIR /tmp
|
||||||
|
//
|
||||||
|
// Set the working directory for future RUN/CMD/etc statements.
|
||||||
|
//
|
||||||
|
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return derr.ErrorCodeExactlyOneArg.WithArgs("WORKDIR")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is from the Dockerfile and will not necessarily be in platform
|
||||||
|
// specific semantics, hence ensure it is converted.
|
||||||
|
workdir := filepath.FromSlash(args[0])
|
||||||
|
|
||||||
|
if !system.IsAbs(workdir) {
|
||||||
|
current := filepath.FromSlash(b.runConfig.WorkingDir)
|
||||||
|
workdir = filepath.Join(string(os.PathSeparator), current, workdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runConfig.WorkingDir = workdir
|
||||||
|
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RUN some command yo
|
||||||
|
//
|
||||||
|
// run a command and commit the image. Args are automatically prepended with
|
||||||
|
// 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is
|
||||||
|
// only one argument. The difference in processing:
|
||||||
|
//
|
||||||
|
// RUN echo hi # sh -c echo hi (Linux)
|
||||||
|
// RUN echo hi # cmd /S /C echo hi (Windows)
|
||||||
|
// RUN [ "echo", "hi" ] # echo hi
|
||||||
|
//
|
||||||
|
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if b.image == "" && !b.noBaseImage {
|
||||||
|
return derr.ErrorCodeMissingFrom
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args = handleJSONArgs(args, attributes)
|
||||||
|
|
||||||
|
if !attributes["json"] {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
args = append([]string{"/bin/sh", "-c"}, args...)
|
||||||
|
} else {
|
||||||
|
args = append([]string{"cmd", "/S", "/C"}, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||||
|
runCmd.SetOutput(ioutil.Discard)
|
||||||
|
runCmd.Usage = nil
|
||||||
|
|
||||||
|
config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// stash the cmd
|
||||||
|
cmd := b.runConfig.Cmd
|
||||||
|
runconfig.Merge(b.runConfig, config)
|
||||||
|
// stash the config environment
|
||||||
|
env := b.runConfig.Env
|
||||||
|
|
||||||
|
defer func(cmd *stringutils.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||||
|
defer func(env []string) { b.runConfig.Env = env }(env)
|
||||||
|
|
||||||
|
// derive the net build-time environment for this run. We let config
|
||||||
|
// environment override the build time environment.
|
||||||
|
// This means that we take the b.buildArgs list of env vars and remove
|
||||||
|
// any of those variables that are defined as part of the container. In other
|
||||||
|
// words, anything in b.Config.Env. What's left is the list of build-time env
|
||||||
|
// vars that we need to add to each RUN command - note the list could be empty.
|
||||||
|
//
|
||||||
|
// We don't persist the build time environment with container's config
|
||||||
|
// environment, but just sort and prepend it to the command string at time
|
||||||
|
// of commit.
|
||||||
|
// This helps with tracing back the image's actual environment at the time
|
||||||
|
// of RUN, without leaking it to the final image. It also aids cache
|
||||||
|
// lookup for same image built with same build time environment.
|
||||||
|
cmdBuildEnv := []string{}
|
||||||
|
configEnv := runconfig.ConvertKVStringsToMap(b.runConfig.Env)
|
||||||
|
for key, val := range b.BuildArgs {
|
||||||
|
if !b.isBuildArgAllowed(key) {
|
||||||
|
// skip build-args that are not in allowed list, meaning they have
|
||||||
|
// not been defined by an "ARG" Dockerfile command yet.
|
||||||
|
// This is an error condition but only if there is no "ARG" in the entire
|
||||||
|
// Dockerfile, so we'll generate any necessary errors after we parsed
|
||||||
|
// the entire file (see 'leftoverArgs' processing in evaluator.go )
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := configEnv[key]; !ok {
|
||||||
|
cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive the command to use for probeCache() and to commit in this container.
|
||||||
|
// Note that we only do this if there are any build-time env vars. Also, we
|
||||||
|
// use the special argument "|#" at the start of the args array. This will
|
||||||
|
// avoid conflicts with any RUN command since commands can not
|
||||||
|
// start with | (vertical bar). The "#" (number of build envs) is there to
|
||||||
|
// help ensure proper cache matches. We don't want a RUN command
|
||||||
|
// that starts with "foo=abc" to be considered part of a build-time env var.
|
||||||
|
saveCmd := config.Cmd
|
||||||
|
if len(cmdBuildEnv) > 0 {
|
||||||
|
sort.Strings(cmdBuildEnv)
|
||||||
|
tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
|
||||||
|
saveCmd = stringutils.NewStrSlice(append(tmpEnv, saveCmd.Slice()...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runConfig.Cmd = saveCmd
|
||||||
|
hit, err := b.probeCache()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// set Cmd manually, this is special case only for Dockerfiles
|
||||||
|
b.runConfig.Cmd = config.Cmd
|
||||||
|
// set build-time environment for 'run'.
|
||||||
|
b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...)
|
||||||
|
|
||||||
|
logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd)
|
||||||
|
|
||||||
|
c, err := b.create()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we keep the container mounted until the commit
|
||||||
|
// to avoid unmounting and then mounting directly again
|
||||||
|
c.Mount()
|
||||||
|
defer c.Unmount()
|
||||||
|
|
||||||
|
err = b.run(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// revert to original config environment and set the command string to
|
||||||
|
// have the build-time env vars in it (if any) so that future cache look-ups
|
||||||
|
// properly match it.
|
||||||
|
b.runConfig.Env = env
|
||||||
|
b.runConfig.Cmd = saveCmd
|
||||||
|
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMD foo
|
||||||
|
//
|
||||||
|
// Set the default command to run in the container (which may be empty).
|
||||||
|
// Argument handling is the same as RUN.
|
||||||
|
//
|
||||||
|
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdSlice := handleJSONArgs(args, attributes)
|
||||||
|
|
||||||
|
if !attributes["json"] {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
|
||||||
|
} else {
|
||||||
|
cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runConfig.Cmd = stringutils.NewStrSlice(cmdSlice...)
|
||||||
|
|
||||||
|
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) != 0 {
|
||||||
|
b.cmdSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENTRYPOINT /usr/sbin/nginx
|
||||||
|
//
|
||||||
|
// Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to
|
||||||
|
// /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx.
|
||||||
|
//
|
||||||
|
// Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint
|
||||||
|
// is initialized at NewBuilder time instead of through argument parsing.
|
||||||
|
//
|
||||||
|
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed := handleJSONArgs(args, attributes)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case attributes["json"]:
|
||||||
|
// ENTRYPOINT ["echo", "hi"]
|
||||||
|
b.runConfig.Entrypoint = stringutils.NewStrSlice(parsed...)
|
||||||
|
case len(parsed) == 0:
|
||||||
|
// ENTRYPOINT []
|
||||||
|
b.runConfig.Entrypoint = nil
|
||||||
|
default:
|
||||||
|
// ENTRYPOINT echo hi
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
b.runConfig.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0])
|
||||||
|
} else {
|
||||||
|
b.runConfig.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when setting the entrypoint if a CMD was not explicitly set then
|
||||||
|
// set the command to nil
|
||||||
|
if !b.cmdSet {
|
||||||
|
b.runConfig.Cmd = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPOSE 6667/tcp 7000/tcp
|
||||||
|
//
|
||||||
|
// Expose ports for links and port mappings. This all ends up in
|
||||||
|
// b.runConfig.ExposedPorts for runconfig.
|
||||||
|
//
|
||||||
|
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
portsTab := args
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
return derr.ErrorCodeAtLeastOneArg.WithArgs("EXPOSE")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.runConfig.ExposedPorts == nil {
|
||||||
|
b.runConfig.ExposedPorts = make(nat.PortSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
ports, _, err := nat.ParsePortSpecs(portsTab)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// instead of using ports directly, we build a list of ports and sort it so
|
||||||
|
// the order is consistent. This prevents cache burst where map ordering
|
||||||
|
// changes between builds
|
||||||
|
portList := make([]string, len(ports))
|
||||||
|
var i int
|
||||||
|
for port := range ports {
|
||||||
|
if _, exists := b.runConfig.ExposedPorts[port]; !exists {
|
||||||
|
b.runConfig.ExposedPorts[port] = struct{}{}
|
||||||
|
}
|
||||||
|
portList[i] = string(port)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(portList)
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// USER foo
|
||||||
|
//
|
||||||
|
// Set the user to 'foo' for future commands and when running the
|
||||||
|
// ENTRYPOINT/CMD at container run time.
|
||||||
|
//
|
||||||
|
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return derr.ErrorCodeExactlyOneArg.WithArgs("USER")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runConfig.User = args[0]
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("USER %v", args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// VOLUME /foo
|
||||||
|
//
|
||||||
|
// Expose the volume /foo for use. Will also accept the JSON array form.
|
||||||
|
//
|
||||||
|
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return derr.ErrorCodeAtLeastOneArg.WithArgs("VOLUME")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.flags.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.runConfig.Volumes == nil {
|
||||||
|
b.runConfig.Volumes = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
for _, v := range args {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
return derr.ErrorCodeVolumeEmpty
|
||||||
|
}
|
||||||
|
b.runConfig.Volumes[v] = struct{}{}
|
||||||
|
}
|
||||||
|
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// STOPSIGNAL signal
|
||||||
|
//
|
||||||
|
// Set the signal that will be used to kill the container.
|
||||||
|
func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("STOPSIGNAL requires exactly one argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := args[0]
|
||||||
|
_, err := signal.ParseSignal(sig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runConfig.StopSignal = sig
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARG name[=value]
|
||||||
|
//
|
||||||
|
// Adds the variable foo to the trusted list of variables that can be passed
|
||||||
|
// to builder using the --build-arg flag for expansion/subsitution or passing to 'run'.
|
||||||
|
// Dockerfile author may optionally set a default value of this variable.
|
||||||
|
func arg(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("ARG requires exactly one argument definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
hasDefault bool
|
||||||
|
)
|
||||||
|
|
||||||
|
arg := args[0]
|
||||||
|
// 'arg' can just be a name or name-value pair. Note that this is different
|
||||||
|
// from 'env' that handles the split of name and value at the parser level.
|
||||||
|
// The reason for doing it differently for 'arg' is that we support just
|
||||||
|
// defining an arg and not assign it a value (while 'env' always expects a
|
||||||
|
// name-value pair). If possible, it will be good to harmonize the two.
|
||||||
|
if strings.Contains(arg, "=") {
|
||||||
|
parts := strings.SplitN(arg, "=", 2)
|
||||||
|
name = parts[0]
|
||||||
|
value = parts[1]
|
||||||
|
hasDefault = true
|
||||||
|
} else {
|
||||||
|
name = arg
|
||||||
|
hasDefault = false
|
||||||
|
}
|
||||||
|
// add the arg to allowed list of build-time args from this step on.
|
||||||
|
b.allowedBuildArgs[name] = true
|
||||||
|
|
||||||
|
// If there is a default value associated with this arg then add it to the
|
||||||
|
// b.buildArgs if one is not already passed to the builder. The args passed
|
||||||
|
// to builder override the defaut value of 'arg'.
|
||||||
|
if _, ok := b.BuildArgs[name]; !ok && hasDefault {
|
||||||
|
b.BuildArgs[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user