mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 20:26:54 +00:00
Compare commits
1415 Commits
v1.7.0-rc2
..
docs
| Author | SHA1 | Date | |
|---|---|---|---|
| 942363d3ca | |||
| 44f2104028 | |||
| 6117b05e87 | |||
| 7d7ef1188b | |||
| c6d31c934b | |||
| 8df037b7ca | |||
| 72b8e7e3ac | |||
| f9f2dbcb15 | |||
| 3d18cd851b | |||
| 7324d8dd8c | |||
| a3ae93dc2e | |||
| 385676735b | |||
| 3e2e0397f3 | |||
| 512f486e0c | |||
| 745722293d | |||
| 87f58529b2 | |||
| 8f7cdbb060 | |||
| 172c412ae8 | |||
| 67baebe9e7 | |||
| e1eee1290d | |||
| d3fe81f9ba | |||
| 551c4559ad | |||
| 2eaa994b24 | |||
| e9183b35a3 | |||
| 19c08cd7f4 | |||
| e6d84bddc4 | |||
| f5ab4142be | |||
| 7d71532829 | |||
| da5014009c | |||
| 3a2e6b3871 | |||
| e041a26365 | |||
| b7960c1e45 | |||
| 0d03096b65 | |||
| 55e9551aaa | |||
| c65afe6ba8 | |||
| 5745aaed22 | |||
| b6f0f93c94 | |||
| 33b16fef43 | |||
| 9705c349c5 | |||
| 3de34af5d1 | |||
| b3f3abfc94 | |||
| 783baec49c | |||
| c1d9e7c6fb | |||
| 83f6dbe30a | |||
| a97b89b585 | |||
| 29ea36a880 | |||
| ed672d1609 | |||
| 5916664220 | |||
| da4b336233 | |||
| 74df05ccaa | |||
| 2c875215b1 | |||
| be40a48c12 | |||
| 85f7f7cfc7 | |||
| e15f6fca3f | |||
| d3bbaa70cd | |||
| cc6f6cb2e2 | |||
| c967dd289f | |||
| 7895ec25ea | |||
| 5b06c94701 | |||
| 5851e2da60 | |||
| 9eff33735a | |||
| fc7697b050 | |||
| 1bf8954d0d | |||
| dfd9f5989a | |||
| d9581e861d | |||
| c383ceaf37 | |||
| 948912f692 | |||
| d19b1b927b | |||
| 53f5905379 | |||
| 60cbf4da6c | |||
| 183628388c | |||
| fbd2267e7d | |||
| a16ab243e5 | |||
| b3c3c4cddc | |||
| 0fe5aad984 | |||
| 0f5e2fd479 | |||
| 2f7145b1c5 | |||
| 81efe1f32e | |||
| 5ba75ac343 | |||
| 290987fcb4 | |||
| 98855c863d | |||
| b1f394a247 | |||
| a819a60a94 | |||
| 33cdc7f2c4 | |||
| 117860577c | |||
| b0ac5df367 | |||
| c109095a58 | |||
| d394113dfe | |||
| 2af7f63173 | |||
| f156fb7be5 | |||
| 559043b953 | |||
| ba8abcb3dd | |||
| ebf396c6e8 | |||
| 47d52fb872 | |||
| d167338876 | |||
| e6844381f0 | |||
| 589922adf0 | |||
| 689c4e6075 | |||
| 43da1adedb | |||
| 686fe02020 | |||
| 1d02be1c7a | |||
| edb60b950a | |||
| e0e852ee6f | |||
| b537508f8c | |||
| 37e886eb7b | |||
| 50f65742ef | |||
| 56d859d052 | |||
| 546a704c63 | |||
| fa85dc0030 | |||
| 36b6e5884d | |||
| 90991ddb9b | |||
| afb831d35e | |||
| 5bdd4d0ec4 | |||
| 30d4c70d28 | |||
| 4f5b677fd9 | |||
| 542685d856 | |||
| 2ad81da856 | |||
| 78906612a2 | |||
| 935810bd20 | |||
| 94ab0d312f | |||
| 259cadb0b1 | |||
| 3e90b12d42 | |||
| 268fa5af47 | |||
| 871d2b96ed | |||
| 0a5b8c40c0 | |||
| 5ab4b60e11 | |||
| a38b544ef0 | |||
| 0547b5fb2a | |||
| 86c7ea4863 | |||
| d951ef128f | |||
| eeb6d0a71b | |||
| 1406cb35fd | |||
| 356b07c896 | |||
| 578b1521df | |||
| 3021b7a4a0 | |||
| 58a1de9b59 | |||
| ed13c3abfb | |||
| 2084eee02c | |||
| 2b847df8c2 | |||
| 84e917b876 | |||
| a83e4e4a34 | |||
| b405e89d5c | |||
| 4fcb9ac40c | |||
| 44e327b29e | |||
| 5aee8807a6 | |||
| e1dea0c485 | |||
| ff271f5190 | |||
| 3a07e7d115 | |||
| 4eee075f22 | |||
| d68c55bc72 | |||
| f32295eda6 | |||
| 9c0bb22571 | |||
| 1fb29e6c3c | |||
| f5a4a8da15 | |||
| 48250832a3 | |||
| 58bab11ee2 | |||
| 8e66e627d7 | |||
| c9ff01ffc7 | |||
| 7674f21686 | |||
| 24f7d0afc9 | |||
| 163acd07e2 | |||
| d5e085572d | |||
| e246f1e4ee | |||
| e7fc632147 | |||
| 4f62dec548 | |||
| cd9dd55233 | |||
| 96ce3a194a | |||
| 490e78a642 | |||
| ae027c0864 | |||
| 82685367d8 | |||
| bb43bdf6c5 | |||
| a8546df89d | |||
| c6f4c192fe | |||
| f57fc03e3b | |||
| 4af94efcf0 | |||
| f28877257b | |||
| 1e40915e9d | |||
| 4e035031b0 | |||
| 9dcd0cc1ee | |||
| b874ef8f43 | |||
| 8b69552b50 | |||
| 8c28363993 | |||
| ac9fc03c74 | |||
| e5d8fb9658 | |||
| 2cf9fec111 | |||
| 76d6751600 | |||
| 9e438ffb61 | |||
| d44e334a6e | |||
| c9937c3a80 | |||
| 8192a9ac6c | |||
| c2346f6406 | |||
| 18faf6f94e | |||
| a20bf5e61c | |||
| de40f3997a | |||
| 6de8dd1a6e | |||
| 94119c58a1 | |||
| 9086c16f42 | |||
| d593130fda | |||
| c5053d4700 | |||
| 7e4db0a370 | |||
| cef5cb2dcb | |||
| 877dbbbde8 | |||
| ea3dcee913 | |||
| 09b86c46b8 | |||
| 48d057f8a9 | |||
| 810d3b2642 | |||
| 5fdc102396 | |||
| a3857cc379 | |||
| f218192754 | |||
| bc8b8e03b4 | |||
| 3198e76b77 | |||
| 451d3f5950 | |||
| 877d740d48 | |||
| 9a1728a427 | |||
| a628155a72 | |||
| fa34f07ec0 | |||
| f82ebe5932 | |||
| 27b828c9e4 | |||
| 4160d1af7d | |||
| a7d8450312 | |||
| 80fec1dbaf | |||
| 9055eb0ce4 | |||
| 3ee7297b7e | |||
| d106a2ea2a | |||
| d3ba2457d9 | |||
| 933d9f2e0d | |||
| 1fff0a5cc5 | |||
| 53fb4d6fb6 | |||
| 9bc502ecde | |||
| 1639288b0a | |||
| c328e0c827 | |||
| 1612ff9726 | |||
| 1c6fe58efc | |||
| 19ba7f9e23 | |||
| 346ce4f8d2 | |||
| 39dae54a3f | |||
| 40b922418c | |||
| 6bd389b9db | |||
| 65fb04228e | |||
| fe68ac806b | |||
| 542b58d8f7 | |||
| 7d8859a4f5 | |||
| b7184f8574 | |||
| 22347fdb63 | |||
| 5a00326d29 | |||
| ba1f76cbfa | |||
| 5d9f06599c | |||
| 7fb061832a | |||
| 87376c3add | |||
| 875fffdff8 | |||
| a751c0a52f | |||
| fe26669205 | |||
| f0d0a52d80 | |||
| a10cb08bbb | |||
| 5328cc346f | |||
| 6b3c928140 | |||
| 4815fdc334 | |||
| 1a5d6a94c9 | |||
| 0f5c9d301b | |||
| 757c4f0d5c | |||
| 50d2597e49 | |||
| 8c4a282a57 | |||
| 30b951ff08 | |||
| ff011ededb | |||
| 06162fed8b | |||
| 052b23e290 | |||
| b6c5f6d075 | |||
| 012b67c3ea | |||
| 162ae444af | |||
| 5dda570d50 | |||
| 7a06e85270 | |||
| dda1d66ac7 | |||
| af3059855c | |||
| ca3dae523b | |||
| ca552a953e | |||
| d3b3ebc3a4 | |||
| c0391bf554 | |||
| c986f85f73 | |||
| 15d01d6e6c | |||
| 879f440a99 | |||
| 42533e323c | |||
| 36106a20ca | |||
| d52b2b98ba | |||
| 1c3cb2d31e | |||
| e54b1e081a | |||
| 6b36a488e7 | |||
| cb57b25689 | |||
| 418135e7ea | |||
| 93c3e6c91e | |||
| db9cc91a9e | |||
| 7f02cc420a | |||
| 0d17cba922 | |||
| bd30d27636 | |||
| d57ea8da25 | |||
| c88ce893a5 | |||
| 65121e5fce | |||
| 9e76e184e4 | |||
| 962dc622d9 | |||
| c32dde5baa | |||
| a74799b701 | |||
| 3ee15acaad | |||
| d796bb9151 | |||
| 943bf44686 | |||
| 6ae377ffa0 | |||
| e4f1cf787c | |||
| 10bcaca914 | |||
| c4d45b6a29 | |||
| 06b922c63f | |||
| 17ce34116a | |||
| 6cce8d1838 | |||
| 844fc5f77c | |||
| 6c95040e3b | |||
| 380959dd68 | |||
| 9818d8fa22 | |||
| c5667bc569 | |||
| 80d99236c1 | |||
| 7be8336fb5 | |||
| 1ca737875d | |||
| dd7d4d1101 | |||
| a7d61882c0 | |||
| 739f7b1da6 | |||
| 5a29f0ac88 | |||
| 96a4469835 | |||
| 36cc6a985b | |||
| 2e7daffd7b | |||
| 1e9394103c | |||
| dc8b765930 | |||
| 197c9010bc | |||
| f0473eaf01 | |||
| d5aeb3398b | |||
| 2eda00c861 | |||
| d241d2f36c | |||
| 37209190c7 | |||
| de0e883331 | |||
| efd56bbe6f | |||
| 4d0990a753 | |||
| 4c0fb85729 | |||
| 09a3b57f94 | |||
| 9738b9319b | |||
| dea49b7474 | |||
| 6751a49d14 | |||
| 18ca5d7c0e | |||
| 37d737fc2c | |||
| f2aff58483 | |||
| cc7fe0d2d9 | |||
| 8b7923620a | |||
| 9001ea26e7 | |||
| bd8386c127 | |||
| 38acec94c4 | |||
| 6609cfb5e7 | |||
| bd328da555 | |||
| 691e3a3e70 | |||
| faa8b658e7 | |||
| ce3156f1eb | |||
| e09d0febe6 | |||
| 16ea3cf3a3 | |||
| 46cbfcb168 | |||
| fc2f90fc63 | |||
| 27ac154d05 | |||
| ee7af0d2cb | |||
| 732141442a | |||
| 77bbee3d00 | |||
| c6a3517246 | |||
| f95b3a6b6a | |||
| 3a1596f0f5 | |||
| 46d7762f26 | |||
| 6f8ddec1d0 | |||
| 668e2369cc | |||
| eef6eda7d2 | |||
| 008d57bf7f | |||
| 9847c0c8b0 | |||
| e0bceb7064 | |||
| af5fb9b7d0 | |||
| ccc78c9968 | |||
| a51684ff8f | |||
| 38e7069668 | |||
| 08d5424c04 | |||
| 32764fe3b9 | |||
| 120c21c97c | |||
| a89370039a | |||
| c6cde91b7d | |||
| a0a0b8499e | |||
| f06620ece3 | |||
| 7f353a11e4 | |||
| f0f261a899 | |||
| 465192cb28 | |||
| 340bd135dd | |||
| 415f744d0c | |||
| a192105d3b | |||
| 12b6083c8f | |||
| 98ed9a55f4 | |||
| 25d9f38852 | |||
| a763637eae | |||
| e939a30039 | |||
| fb34537a99 | |||
| 97be366961 | |||
| 1b67c38f6f | |||
| ff05f9c285 | |||
| 7b83b0e15c | |||
| 424d5e55a2 | |||
| b900aaac46 | |||
| a40e337882 | |||
| c5c98c31a1 | |||
| ceca4109fa | |||
| ac3f7c71b8 | |||
| 97c5f64001 | |||
| 9ca913d0f1 | |||
| 15cb7dfc9e | |||
| c86189d554 | |||
| 8801906ced | |||
| 39ad38ccf9 | |||
| 5d3043649a | |||
| 6c0795747b | |||
| b84ceb3d0a | |||
| a207ce6ae4 | |||
| 212525f951 | |||
| 83ad0536c1 | |||
| 5365e6b463 | |||
| 1ec25653d8 | |||
| 19515a7ad8 | |||
| 276c640be4 | |||
| 745820fec0 | |||
| 48a01a317c | |||
| b32c4cb459 | |||
| 8c77e39627 | |||
| ac8299ae15 | |||
| 71868228c7 | |||
| 97515a35ca | |||
| 081991c01e | |||
| b70428c4dd | |||
| c4d2b34d13 | |||
| 397de38ae6 | |||
| ed23ecd305 | |||
| ce6410cd4c | |||
| f373bd97c3 | |||
| cd642973fa | |||
| a81dcaef93 | |||
| c850e97c84 | |||
| 57c43960f8 | |||
| 6157937f67 | |||
| 4a5fd6c0f9 | |||
| a5142f6ac3 | |||
| 46aea60fb0 | |||
| e98a881570 | |||
| 386f11a63d | |||
| 308ecafad8 | |||
| e46c7060cc | |||
| e0728035fe | |||
| 39e380b97a | |||
| 5f9d1bc018 | |||
| 1df7a2f940 | |||
| 13c08b898b | |||
| a031b6476c | |||
| 174870e64f | |||
| 8e7f2fc2e7 | |||
| 35746ef97f | |||
| 3a23203255 | |||
| 7f48cd7dce | |||
| 8c7cd78650 | |||
| 918229cbdf | |||
| 82a54001fd | |||
| c7d4a4b2b4 | |||
| 24bb36297c | |||
| b49cbe289f | |||
| 2932069dc7 | |||
| ecdbf86884 | |||
| 4e9280ffdb | |||
| 5c295460da | |||
| d2fe5bd12f | |||
| e5fa2a740d | |||
| 899f6f2a89 | |||
| bc6ad1608c | |||
| 7080f5d1cf | |||
| cf09e435c8 | |||
| aaf1426995 | |||
| 29660f263e | |||
| 91a2d9cc7d | |||
| 12f67141f9 | |||
| a7f5e1c4c3 | |||
| 4290bdefab | |||
| daffcc044b | |||
| 19c7b65ea6 | |||
| 154820aca6 | |||
| 59aa3b27ca | |||
| c3b77bbe8b | |||
| 5ca3e7c54c | |||
| 35b0223921 | |||
| e221291a25 | |||
| 41588a5766 | |||
| 60b34cb957 | |||
| 73bd855dcc | |||
| 23d0676c07 | |||
| d03923732f | |||
| 5e331ad41a | |||
| 4185809659 | |||
| 363cc51e82 | |||
| 5dbb217bcb | |||
| b98edced9e | |||
| 3f0fa1f643 | |||
| 15f9766691 | |||
| c2b9fa8c9a | |||
| 655b1d2367 | |||
| 1da3843149 | |||
| 05a86905a2 | |||
| 9158ddef89 | |||
| e0ec0cc115 | |||
| 33039aa129 | |||
| 7fb456589b | |||
| d77d0268eb | |||
| 0b7938e845 | |||
| e0d96fb3ef | |||
| d3566fa0b1 | |||
| 6c4e14b3e2 | |||
| 4ef67b79bc | |||
| b271593c34 | |||
| 0f466c36b9 | |||
| 41def26c1b | |||
| 0badebe734 | |||
| b39c330a18 | |||
| a20994fd54 | |||
| e13f9edfed | |||
| 7ba54a4628 | |||
| 8aa7ba731a | |||
| 161f5b2790 | |||
| e5da4d62ef | |||
| eb174dbb1e | |||
| 1f963af697 | |||
| 24c09006c9 | |||
| ca4ff1ae36 | |||
| 441ae6c943 | |||
| b28d6eaa94 | |||
| 3eea6f753a | |||
| fa6925aa9b | |||
| 45dbdabdc7 | |||
| d51ae591e0 | |||
| b0da494aa3 | |||
| b58565e9d6 | |||
| 09adf87f23 | |||
| 26ce3f4c90 | |||
| 2ceb114607 | |||
| dfc6c04fa3 | |||
| 921da495d2 | |||
| 62a75fca68 | |||
| ee685dca0d | |||
| c27b93ea26 | |||
| 80e90499aa | |||
| 865d3a1488 | |||
| 9264d38424 | |||
| 4aeb84612f | |||
| ff8cef3326 | |||
| 703248da20 | |||
| 892ac93ec9 | |||
| a828415286 | |||
| 52f4d09ffb | |||
| b7e8169274 | |||
| 5bf98dd997 | |||
| 9ae9d4c87a | |||
| 451789cf88 | |||
| bfd5202c17 | |||
| daced1d303 | |||
| 20ac0e6164 | |||
| 6827782bef | |||
| 5bf335d0a2 | |||
| 471fbc9b99 | |||
| 1dd2fda394 | |||
| c4b85baf3d | |||
| 615681f517 | |||
| 72b75cd4e7 | |||
| 469e234ded | |||
| 899c85b405 | |||
| f18fb5b3ef | |||
| 5a170484d1 | |||
| 6e64e0548f | |||
| 3875be9830 | |||
| 4893ecc112 | |||
| 4348ad68f8 | |||
| 56e8a99b61 | |||
| e64af7a70e | |||
| 7df186dac7 | |||
| 382799a642 | |||
| 7d3bbe9d34 | |||
| a29af0cc42 | |||
| 548caddaf6 | |||
| 34ab8c4326 | |||
| 21a07a63c9 | |||
| c9e2a71509 | |||
| 0f9c458730 | |||
| f18c4f23cc | |||
| dce90c0bdd | |||
| eae99e9da7 | |||
| 0bf0e5a6ef | |||
| a8715ea220 | |||
| c1b524486c | |||
| 9d84d4c2cc | |||
| 37771c122b | |||
| 5369b6cf29 | |||
| 3d029c3bf3 | |||
| d28b0ba115 | |||
| 0afd7bde02 | |||
| f0e90606cd | |||
| ca3e4ccb80 | |||
| 54ac3ad736 | |||
| cab02a5bbc | |||
| d2cde4fa66 | |||
| 47a7f770f4 | |||
| 885e7f2a1e | |||
| 2df65c32d7 | |||
| 96bc377a8d | |||
| 5f017bba48 | |||
| 67058e388b | |||
| 42eb82ae92 | |||
| 7121a2ae46 | |||
| 2f566f747b | |||
| 4cd9301692 | |||
| 04c9f86bdc | |||
| 3833c990e7 | |||
| f089899023 | |||
| c9a4c1401f | |||
| cd454fef5a | |||
| 9a4cbb358d | |||
| 7e4725cb66 | |||
| 0117330ae7 | |||
| e3b5889783 | |||
| f5ebcfe1dc | |||
| 159e830068 | |||
| 289ee90b04 | |||
| 91a496055c | |||
| c92fb4d517 | |||
| 2fe51ecee7 | |||
| 9c96304a86 | |||
| 2cd417df73 | |||
| 25151eb24e | |||
| 045ccc4326 | |||
| 81c30c3303 | |||
| 16f8afb451 | |||
| e13b7862ab | |||
| 806b3fa145 | |||
| a5283d2ff3 | |||
| b2b367a6ad | |||
| f7ea9114d6 | |||
| e27131519f | |||
| e3966c1031 | |||
| 9af7afb9eb | |||
| a14b1f1e9d | |||
| cc955ae73c | |||
| a8f8748f7a | |||
| bfc5966d0b | |||
| bebf534439 | |||
| 2ea4d90da4 | |||
| 12c85f3671 | |||
| cd72c7f3bb | |||
| 10bbf62081 | |||
| 833cdd28ed | |||
| 61418e39d5 | |||
| 13c36ce65e | |||
| 3bee3a2927 | |||
| f244ed2531 | |||
| 270d7e1af8 | |||
| 244141a8aa | |||
| 0db7c2a9a6 | |||
| ea26b38784 | |||
| 81964ca555 | |||
| a25ee91f1c | |||
| 68efb27e99 | |||
| 7e309aed4e | |||
| c6dc6bcbb8 | |||
| 2802831218 | |||
| 58edd21c05 | |||
| f4b08c7f5e | |||
| aea0fd0115 | |||
| 35081ea4b6 | |||
| 2905fe4806 | |||
| 6ffce0131c | |||
| 274c838ced | |||
| 691b2851f7 | |||
| 4f69609ac2 | |||
| e7fb38410d | |||
| 76a853de6e | |||
| 6e4c6da819 | |||
| 4bb3c253e8 | |||
| c7492126ef | |||
| b0f7a1c62c | |||
| a974789537 | |||
| 26a35dd09b | |||
| 7fde07ea16 | |||
| 2c5e044b59 | |||
| 7aa554c444 | |||
| 57286aad25 | |||
| f240bb5e94 | |||
| 762214ccc8 | |||
| 36958ba49b | |||
| 19e6de2654 | |||
| 90bc5513a9 | |||
| a5be803458 | |||
| bca32648f4 | |||
| 1b3bde47ef | |||
| 230179c8dc | |||
| a89f4e36ab | |||
| 515df52f19 | |||
| 0dedadd4d6 | |||
| f3faf59925 | |||
| 33be2c5d2e | |||
| 7b0666f568 | |||
| 0ed7fd46b6 | |||
| 801a7fed19 | |||
| 87cfc2bc58 | |||
| d7326bd787 | |||
| 7dc8e77cc7 | |||
| 7bf26d44b0 | |||
| 8630ad1530 | |||
| a45576ab40 | |||
| 01cfb70b0a | |||
| 5b2d14fbbf | |||
| 9356c76d9f | |||
| 0cc664fae3 | |||
| 88f02c2f33 | |||
| 5400d8873f | |||
| 71f8ca30ce | |||
| f0ed68f852 | |||
| e3d488e280 | |||
| 36f6b7331e | |||
| 0a376291b2 | |||
| d0a299c027 | |||
| f58758ce36 | |||
| 10e114fb95 | |||
| 9300eb6684 | |||
| 9b782d3af3 | |||
| ddc9bc6c56 | |||
| 8b0e40bb38 | |||
| af7f81878f | |||
| f38232b475 | |||
| 942a0f994f | |||
| 98f988f62c | |||
| f51d77f86e | |||
| 75864dcb38 | |||
| 9b9a4b2e59 | |||
| 1751b40b04 | |||
| 9b42de0e57 | |||
| 7b3492df0c | |||
| 21ec389fe1 | |||
| 10a3061c5f | |||
| 8b40e44c39 | |||
| 358c022cac | |||
| 53b897ce9c | |||
| 5fd0563505 | |||
| be5e1498c3 | |||
| 88d32a6109 | |||
| 2cb74e6915 | |||
| 1d7a62d776 | |||
| d02540a44a | |||
| 0dadf11bec | |||
| e848494017 | |||
| 2179dd00dd | |||
| 636ec6383b | |||
| d4aec5f0a6 | |||
| 82b444e3fa | |||
| 126b1b2923 | |||
| bb2cd98b28 | |||
| 27af9a52a1 | |||
| d9af8551f8 | |||
| a2621ac6da | |||
| caa4acc7b1 | |||
| c9328c6ccf | |||
| a453da2114 | |||
| 2de810df3f | |||
| 2d8dd6b93e | |||
| f13b40f6e7 | |||
| 477201a295 | |||
| bb364ff459 | |||
| 96417dd6c3 | |||
| d6da3736d7 | |||
| aa5fb884b4 | |||
| 9c2374d196 | |||
| 90024b952c | |||
| c1477db04f | |||
| 20467faf13 | |||
| 18d5d3ba03 | |||
| 475ae0889e | |||
| b81472a6d8 | |||
| c5e6a4b307 | |||
| e7e48bcfdd | |||
| 0faa4518ed | |||
| 77396bd66a | |||
| 4fd284bde0 | |||
| f05e503aec | |||
| 0aea0e4072 | |||
| d543a01e17 | |||
| 333ac3a3eb | |||
| cb2079e218 | |||
| 985fd1c6cc | |||
| d612782023 | |||
| a386a00a3c | |||
| e1afbbf247 | |||
| 2dfb7f3b7d | |||
| 078b23a37d | |||
| 37846bff58 | |||
| 9bbdaa9280 | |||
| 40a41ffdf0 | |||
| c9175d681b | |||
| 2cca502c05 | |||
| e7a9a0bed8 | |||
| 9030f53165 | |||
| 7571e6d900 | |||
| d175ef6773 | |||
| 389b806945 | |||
| 2d15b35f7a | |||
| 126529c6d0 | |||
| 31ae9d876a | |||
| f46ac8c51c | |||
| 17d6f00ec2 | |||
| b96f109344 | |||
| b49c9850a6 | |||
| d5de3ed09e | |||
| 3c5b17cf7e | |||
| 7eac23cf8d | |||
| c7ece73fa4 | |||
| 78f4a9dad4 | |||
| 361a582ba0 | |||
| dd408891ba | |||
| 2e078b24b8 | |||
| 67473c6d06 | |||
| 857e0ba32e | |||
| 50a1d0f0ef | |||
| be33d13f67 | |||
| de9c5473f7 | |||
| 15d8e7bc6b | |||
| 8c8e8d624f | |||
| 7201127cfb | |||
| 561bfb268d | |||
| c281549610 | |||
| e8b87cfce1 | |||
| 81d6f60935 | |||
| e0cfe36b8e | |||
| e9ac7d24a7 | |||
| b0b2f979c7 | |||
| e594a2fb54 | |||
| 31dd97f4ad | |||
| 62213219dc | |||
| 4ab8b88286 | |||
| 9bb69f9726 | |||
| 221fa4c3d1 | |||
| afd4d544cf | |||
| 85e2957409 | |||
| 0a9c580fad | |||
| 16be453e0f | |||
| 4466d00534 | |||
| 0835b20ace | |||
| 02c7bbefb8 | |||
| 4dd1d1bfa5 | |||
| d5721f0385 | |||
| aa4f495cae | |||
| 5c93c265f6 | |||
| f23698bb14 | |||
| 9b5875eb9d | |||
| 250ce20fee | |||
| 41f69883d2 | |||
| 960cbd2c8d | |||
| e81861ea54 | |||
| 609e7b0a55 | |||
| 1cbf5a54da | |||
| cb6ca19b53 | |||
| 8861d65e97 | |||
| 51264fa63c | |||
| fe1f210c42 | |||
| 1797f03760 | |||
| 74be85500a | |||
| 87eae0d659 | |||
| e02947b236 | |||
| 2e7399a52b | |||
| 182d2668a1 | |||
| a7116c5e2b | |||
| f39b9a0b0f | |||
| a5f6407c5b | |||
| 1a28fb7583 | |||
| 739996c1d7 | |||
| 1e59169509 | |||
| 53b0f686f7 | |||
| 508065a7ad | |||
| 8a9ed09746 | |||
| abb85f822d | |||
| efef353a29 | |||
| c782be0e2a | |||
| 4548d3fb77 | |||
| 7491f9a9c1 | |||
| b2cc6f1b47 | |||
| fef0e84b2d | |||
| 67f9bbcbd4 | |||
| 4736b17315 | |||
| 1ac350a0ec | |||
| 927d13bc3c | |||
| a93a5ee50a | |||
| 7046651b87 | |||
| 52e193bed7 | |||
| 3f64b76d4c | |||
| c3997daeb5 | |||
| e5ded9c378 | |||
| a0a010e2e3 | |||
| 9d0ed1dea0 | |||
| 7313a9ae9c | |||
| d48bface59 | |||
| 7ce20fad99 | |||
| de6d2603f9 | |||
| 3ee67caf3b | |||
| c2315102ff | |||
| 2bc0d2bf51 | |||
| df73d5e0cd | |||
| 742db1737c | |||
| fb85a99f58 | |||
| 0c4c830289 | |||
| 4398693201 | |||
| b3254dab49 | |||
| 4d171281bf | |||
| 9e9d227677 | |||
| 1334a2fc39 | |||
| 0f0f5ecd57 | |||
| 4253a1f87e | |||
| d7544217dd | |||
| b1143f4cb0 | |||
| 1487350b85 | |||
| ebef527b39 | |||
| cca990c67a | |||
| da5a3e6dee | |||
| 8ea1b54065 | |||
| 53f5f5b7f4 | |||
| 2e0e038fb2 | |||
| 3dd134ceca | |||
| fc793bf1a3 | |||
| 910322a893 | |||
| 3cb9f7f6aa | |||
| 423e6dc333 | |||
| 328dbd0aa2 | |||
| baa1664478 | |||
| 9b05aa6ee8 | |||
| 206acbcf89 | |||
| d86345b9f7 | |||
| af845d42a5 | |||
| c107e9d790 | |||
| 3fa2ddc452 | |||
| b8301005ff | |||
| 85d3b75dfd | |||
| 010470dd53 | |||
| 3c177dc877 | |||
| 691ed6ef99 | |||
| 424bb26b6c | |||
| b6a6c56915 | |||
| fe7b3658bd | |||
| 9ad87523c0 | |||
| a27d8f9aa4 | |||
| 57d12a0e0a | |||
| c06b05b11e | |||
| b7f887a9a2 | |||
| ba9db62e68 | |||
| f9ab04ad13 | |||
| b6531d4ec3 | |||
| cb979edec0 | |||
| 6089e679a2 | |||
| 2f6e4fdb29 | |||
| e69df2589c | |||
| 6e2662b3ba | |||
| b1f984a29f | |||
| b674ae0639 | |||
| c80256a31d | |||
| 952d030be2 | |||
| 2420ef8cf1 | |||
| de6e6d4b28 | |||
| 5a6a33f7ac | |||
| d820e00aac | |||
| 49834e8d59 | |||
| 57aa0248af | |||
| b68d0c6612 | |||
| 3f4eeca68f | |||
| b349a74c71 | |||
| 957622d452 | |||
| 9dffd032f4 | |||
| b81f2ee5f2 | |||
| ff4e58ff56 | |||
| f48fadd05c | |||
| 4d23f459ad | |||
| 748814a97a | |||
| 6d022bda3b | |||
| 59cfc08982 | |||
| 835f1c9116 | |||
| 86d6a5b11f | |||
| b3b04fd85a | |||
| d409b05970 | |||
| f93fee5f48 | |||
| dcc4a14ba3 | |||
| 3f11e05db2 | |||
| a98ea87e46 | |||
| 407a626be6 | |||
| d8592eaff8 | |||
| 1157fcc4a4 | |||
| 637023a5f8 | |||
| 6578ad90c3 | |||
| dfec4a48c7 | |||
| ac8cbf4952 | |||
| 0030df868a | |||
| ee40f29712 | |||
| f27c9bc6e7 | |||
| cd44018856 | |||
| cd36b15ccb | |||
| c7975f12d6 | |||
| 261af00282 | |||
| f1ea3e5c74 | |||
| b37832e353 | |||
| 422fa7f0a6 | |||
| 39bf57e085 | |||
| 6a7a7570bb | |||
| 083300168f | |||
| cd71c5ed43 | |||
| 0a529b6e7a | |||
| 421b9b6859 | |||
| d2c5fcc8d5 | |||
| 9465272c28 | |||
| 5bddafe169 | |||
| e7533d7f81 | |||
| 27e2837c6c | |||
| e5b7f61f09 | |||
| 2351557607 | |||
| 855a056af7 | |||
| 09de92b891 | |||
| e89f837bc6 | |||
| 5a02d0727b | |||
| c98eafe564 | |||
| e70d680d72 | |||
| f3d1826350 | |||
| a408790de8 | |||
| d898372568 | |||
| 00b8fec75f | |||
| 85f895ce68 | |||
| 71ead0ef5c | |||
| 0de7065897 | |||
| 98391f0a39 | |||
| 566466e714 | |||
| cada9b54b5 | |||
| 9b43f5a3a1 | |||
| e3821feb13 | |||
| b0d748325f | |||
| 9e4ee3dea5 | |||
| f61d595b55 | |||
| eb97de7dee | |||
| b27f960504 | |||
| 9001c4bc48 | |||
| 4fc6449944 | |||
| 9ad89281ae | |||
| ef171afdbb | |||
| 19c31a703f | |||
| 5d51118c7c | |||
| 2b27fe17a1 | |||
| 212dfb45de | |||
| 386ab25137 | |||
| 883d661b69 | |||
| ad9080a87c | |||
| 0cdc3b7539 | |||
| 4cb0c93f92 | |||
| 0964a664e8 | |||
| f88e620359 | |||
| 466efebf0e | |||
| 84aec1e8e6 | |||
| 141cd2a1f2 | |||
| b6049b5c49 | |||
| 9da65ed84f | |||
| eacae64bd8 | |||
| add64dc297 | |||
| 05644ccea9 | |||
| 17324c91e3 | |||
| 8fb0ca2c35 | |||
| 65d0670d65 | |||
| c9d71317be | |||
| 96d06e106f | |||
| c5f49886fa | |||
| 12149e8293 | |||
| 74c12aa429 | |||
| 88e4dff9a9 | |||
| 5821f13b8c | |||
| 4c81c9dddc | |||
| 46af724e81 | |||
| de90aef411 | |||
| e2c6a8be7c | |||
| e12839fa5a | |||
| 55bdb51659 | |||
| bd9814f0db | |||
| 4750e1f77e | |||
| c3b962d327 | |||
| 13baeb3b70 | |||
| ceee2595b6 | |||
| 416e855e9b | |||
| 38acd31e8a | |||
| 5e86b818ed | |||
| 18ddc2728d | |||
| 06aef2b5c2 | |||
| 30448166de | |||
| 7b9ae696d8 | |||
| 90259fed2e | |||
| 3b01dac44d | |||
| 73bc885b23 | |||
| 2b4facdf2e | |||
| 7f6aa3b783 | |||
| a31be25121 | |||
| 5e0e71bbe0 | |||
| 123a0582b2 | |||
| f4a3e8bef0 | |||
| a2ab067b3c | |||
| 75f63499a0 | |||
| 9a9dc5ba96 | |||
| 4ad05ed985 | |||
| 969cb545ae | |||
| c69d3b749d | |||
| cd7e2a6b2b | |||
| 0e10507a1c | |||
| 71eadd4176 | |||
| b099eb796a | |||
| 875eafb00b | |||
| bf6492e689 | |||
| c622ca6d4b | |||
| 68bfd9e3ae | |||
| dbf5e36fd6 | |||
| a63c7c7be6 | |||
| c68e7f96f9 | |||
| 67552fb22d | |||
| 5d22afc54e | |||
| 54ac3d9c76 | |||
| 07b22fcf50 | |||
| 3c37a307bc | |||
| 173d0918a8 | |||
| ae68dfe31b | |||
| 3d10540bc3 | |||
| 4ce817796e | |||
| 3c132ff62d | |||
| ffd74e74b1 | |||
| c0b4421819 | |||
| bb50a4159b | |||
| 2b58b677a5 | |||
| f20863929b | |||
| 3fcf53db92 | |||
| ff9877ccc4 | |||
| 7c64ed5c8c | |||
| af29aff53c | |||
| 77bd53adfe | |||
| 79621c7728 | |||
| 5c051e2e26 | |||
| 078b8e54e4 | |||
| efe5c64768 | |||
| 69c2f5649a | |||
| 0adfb908a6 | |||
| b180de55ca | |||
| bfed4b7cc3 | |||
| edb7e84ddb | |||
| d32e01be1a | |||
| 159f5e0137 | |||
| 9ed2cb300b | |||
| 5fa60149e2 | |||
| ed39fbeb2a | |||
| cd1a1ee972 | |||
| e33aeac708 | |||
| 4572329d4b | |||
| a06e0bb8b4 | |||
| a2602c617d | |||
| 0c84604f54 | |||
| 543cf79ffb | |||
| b63ec6e4b1 | |||
| fd935ee63d | |||
| b26428257f | |||
| 7c2178b358 | |||
| cbe120db58 | |||
| d8680f7beb | |||
| 8352f2e264 | |||
| 16a5590c5b | |||
| 9069cded57 | |||
| 2c09dbb7dd | |||
| 4efedb3fba | |||
| 9ec5e4f7df | |||
| d31224743b | |||
| 318defe1de | |||
| ea180a73bc | |||
| ef3e956aa2 | |||
| 57bba29f79 | |||
| 83208a531d | |||
| f37e6e180f | |||
| fdd6175bca | |||
| 229b599259 | |||
| babf751e1b | |||
| ca66419522 | |||
| 6945ac2d02 | |||
| ad56d797d3 | |||
| 95dfc4c4a5 | |||
| 7943bce894 | |||
| fa020341e4 | |||
| f18ce101fb | |||
| d0c4c7c83f | |||
| a29633f687 | |||
| b30121984e | |||
| c962f53a3e | |||
| 9e7fc245a7 | |||
| a448b7aff2 | |||
| aa54a93f74 | |||
| 521926f6c4 | |||
| 725f34151c | |||
| 0636bb7b73 | |||
| 641f3e0932 | |||
| f03daf29dc | |||
| 01a9a9284f | |||
| 8bfc8102be | |||
| 85fffe1341 | |||
| bd2575cc4f | |||
| 63823cd6c1 | |||
| e0475d331b | |||
| 4f42097883 | |||
| 8b4c0decfc | |||
| 25376c652d | |||
| 1664d6cd66 | |||
| af09b06dfa | |||
| 6deaa58ba5 | |||
| 6c42b3947a | |||
| 3f26066425 | |||
| 4e3b21f99e | |||
| da255ec962 | |||
| 7e8639a9df | |||
| 3df19749d4 | |||
| cb4af853f6 | |||
| 8979fe4d8a | |||
| a81af2e26a | |||
| e6bd8c1e19 | |||
| 63b5d5fadc | |||
| ad244668c3 | |||
| 278798236b | |||
| 274baf70bf | |||
| 7d33bc0bec | |||
| ad41efd9a2 | |||
| 814a92c5a1 | |||
| bdb6fad34c | |||
| 2de2782fe8 | |||
| 9f098cabfc | |||
| fbadf11d20 | |||
| 39eec4c25b | |||
| d13af493f8 | |||
| 8a11b40fea | |||
| da1a77defd | |||
| 879f0fb338 | |||
| ea5ce27564 | |||
| 25f7d7822d | |||
| b1a1cff368 | |||
| 55cea4952b | |||
| e55649192e | |||
| 0d72772c37 | |||
| 1f903f652c | |||
| cd79751cfa | |||
| 05b1d02423 | |||
| 65c5105fcc | |||
| aa8d8fdc0c | |||
| 1f472c3768 | |||
| f358b39521 | |||
| 6e4ff1bb13 | |||
| c790aa36ea | |||
| 78bc233a01 | |||
| 488c355d94 | |||
| 4fde1cb695 | |||
| b7101d528d | |||
| cd80a61cc1 | |||
| 5606f32eec | |||
| 9d98c28855 | |||
| 64a7431663 | |||
| 84413be3c9 | |||
| 71d960d455 | |||
| 22b195f241 | |||
| 8232312c1e | |||
| 7f7ebeffe8 | |||
| 62afcbc6d4 | |||
| 24fbcffeac | |||
| d156bc9dd4 | |||
| f1b59d64d2 | |||
| a7005c44b5 | |||
| 6fb454b9f0 | |||
| d37aad170f | |||
| 7c190b9556 | |||
| 3ce2797541 | |||
| 68c879406b | |||
| 09bfbfd74e | |||
| b445004364 | |||
| 16e649c589 | |||
| 4caa9392f8 | |||
| 3d87b80b46 | |||
| 093f57a261 | |||
| 0ea14e5c6d | |||
| 73683e807d | |||
| 5cd64cb93f | |||
| 20927ffbe7 | |||
| a121ac858e | |||
| ac3388367b | |||
| f2154a5a6b | |||
| 96123a1fd5 | |||
| dc697b1c9f | |||
| 279c877451 | |||
| 18c11dc264 | |||
| e51b311129 | |||
| 82ea6ed2bc | |||
| c61fa8471a | |||
| c913c9921b | |||
| 2b7569ccc3 | |||
| 04c6f09fbd | |||
| 74528be903 | |||
| 1613897d2d | |||
| a18c0b3b3c | |||
| 3aa4a3a0c5 | |||
| c42ede311b | |||
| 1e653ab645 | |||
| 06612cc0fe | |||
| eaa9c1b97e | |||
| 4389fc8bf9 | |||
| d79654d0f6 | |||
| d96ca04f24 | |||
| ec167972bf | |||
| 4d9191e15d | |||
| e2524d8ace | |||
| a39de41871 | |||
| 185f392691 | |||
| 1680c78e63 | |||
| 8d726709ad | |||
| 67cb748e26 | |||
| e02744404d | |||
| 62d3b1bf2e | |||
| ec97f41465 | |||
| 3b831a2f3a | |||
| 15134a3320 | |||
| c42810fe99 | |||
| 04a451998f | |||
| 4c9fd72a91 | |||
| 98180b8954 | |||
| b21ef2d1d3 | |||
| a914101296 | |||
| 6d8e517bce | |||
| f1fed87fc0 | |||
| b372f9f224 | |||
| 24d9edd8d1 | |||
| f779cfc5d8 | |||
| 87d36079a7 | |||
| 00b8d37084 | |||
| 6c36572e8b | |||
| 34342e9cef | |||
| 112b7e6546 | |||
| 059dc15ab9 | |||
| 22fda380a9 | |||
| 9aa8a590b7 | |||
| b2832dffe5 | |||
| 2b69b326bc | |||
| de225b5d13 | |||
| 6c49576a86 | |||
| 27ffccf540 | |||
| 7f6a7973cd | |||
| fcdea09ea5 | |||
| 7317b84ce0 | |||
| e8c88d2533 | |||
| e76d0f5571 | |||
| 742a8a6cff | |||
| 7b6f9da52c | |||
| 1392f99a97 | |||
| 30901609a8 | |||
| 1f22676fcb | |||
| 9292859fce | |||
| 53d9609de4 | |||
| e58161fedc | |||
| 6f9fa64645 | |||
| d66ae67418 | |||
| 3bda841e3e | |||
| 37cc42ee0e | |||
| 617a0c2fde | |||
| f99a962742 | |||
| 24a0800d87 | |||
| 5bb6262d88 | |||
| 85096cfd4f | |||
| 0256bbdebb | |||
| b90732f801 | |||
| 709fa2ad87 | |||
| 45488f9dc0 | |||
| 70bedc8ee5 | |||
| e5ff643aed | |||
| 27919bab2d | |||
| 33c7c01a62 | |||
| a364dd8fc6 | |||
| 36bf6e4440 | |||
| c1c5d760a6 | |||
| ac7a33b45c | |||
| bcd076b5fe | |||
| 38c77ba876 | |||
| 96903c837f | |||
| 7011d9ee8a | |||
| 34e5b6af19 | |||
| 6473b0f127 | |||
| 15b45113eb | |||
| 9f619282db | |||
| 71a4990229 | |||
| b4a52fc4e9 | |||
| 35a7f0c59e | |||
| 66121d1b45 | |||
| 13f2aa7068 | |||
| bdb77078b5 | |||
| 4509f91b26 | |||
| 9c65862cd6 | |||
| ead2f80073 | |||
| a27395e6df | |||
| 8324d7918b | |||
| fba44a7ccf | |||
| 6cdf8623d5 | |||
| 48f1cb4ebe | |||
| 62f648b061 | |||
| 010e3e046b | |||
| c646e514db | |||
| a2787469ac | |||
| 48231d623f | |||
| 9a09664b51 | |||
| 07a75c48fd | |||
| 03956610e5 | |||
| d0a4f310ef | |||
| 45c45a2c9a | |||
| c92377e300 |
+4
-1
@@ -2,6 +2,7 @@
|
|||||||
# if you want to ignore files created by your editor/tools,
|
# if you want to ignore files created by your editor/tools,
|
||||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||||
*.exe
|
*.exe
|
||||||
|
*.exe~
|
||||||
*.orig
|
*.orig
|
||||||
*.rej
|
*.rej
|
||||||
*.test
|
*.test
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
.git/
|
.git/
|
||||||
.gopath/
|
.gopath/
|
||||||
.hg/
|
.hg/
|
||||||
.idea
|
|
||||||
.vagrant*
|
.vagrant*
|
||||||
Vagrantfile
|
Vagrantfile
|
||||||
a.out
|
a.out
|
||||||
@@ -30,5 +30,8 @@ docs/_build
|
|||||||
docs/_static
|
docs/_static
|
||||||
docs/_templates
|
docs/_templates
|
||||||
docs/changed-files
|
docs/changed-files
|
||||||
|
# generated by man/man/md2man-all.sh
|
||||||
|
man/man1
|
||||||
|
man/man5
|
||||||
pyenv
|
pyenv
|
||||||
vendor/pkg/
|
vendor/pkg/
|
||||||
|
|||||||
@@ -142,3 +142,30 @@ Jessica Frazelle <jess@docker.com> Jessie Frazelle <jfrazelle@users.noreply.gith
|
|||||||
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
||||||
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||||
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||||
|
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
|
||||||
|
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
|
||||||
|
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
|
||||||
|
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
|
||||||
|
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
|
||||||
|
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
|
||||||
|
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
|
||||||
|
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
|
||||||
|
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
|
||||||
|
<jess@docker.com> <princess@docker.com>
|
||||||
|
John Howard (VM) <John.Howard@microsoft.com> John Howard <jhoward@microsoft.com>
|
||||||
|
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
|
||||||
|
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
|
||||||
|
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
|
||||||
|
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
|
||||||
|
mattyw <mattyw@me.com> <gh@mattyw.net>
|
||||||
|
resouer <resouer@163.com> <resouer@gmail.com>
|
||||||
|
AJ Bowen <aj@gandi.net> soulshake <amy@gandi.net>
|
||||||
|
AJ Bowen <aj@gandi.net> soulshake <aj@gandi.net>
|
||||||
|
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
|
||||||
|
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
|
||||||
|
Vincent Bernat <bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
|
||||||
|
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
|
||||||
|
bin liu <liubin0329@users.noreply.github.com> <liubin0329@gmail.com>
|
||||||
|
John Howard (VM) <John.Howard@microsoft.com> jhowardmsft <jhoward@microsoft.com>
|
||||||
|
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
|
||||||
|
Tangi COLIN <tangicolin@gmail.com> tangicolin <tangicolin@gmail.com>
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
# For how it is generated, see `hack/generate-authors.sh`.
|
# For how it is generated, see `hack/generate-authors.sh`.
|
||||||
|
|
||||||
Aanand Prasad <aanand.prasad@gmail.com>
|
Aanand Prasad <aanand.prasad@gmail.com>
|
||||||
|
Aaron Davidson <aaron@databricks.com>
|
||||||
Aaron Feng <aaron.feng@gmail.com>
|
Aaron Feng <aaron.feng@gmail.com>
|
||||||
Aaron Huslage <huslage@gmail.com>
|
Aaron Huslage <huslage@gmail.com>
|
||||||
|
Aaron Welch <welch@packet.net>
|
||||||
Abel Muiño <amuino@gmail.com>
|
Abel Muiño <amuino@gmail.com>
|
||||||
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
||||||
|
Abhishek Chanda <abhishek.becs@gmail.com>
|
||||||
Abin Shahab <ashahab@altiscale.com>
|
Abin Shahab <ashahab@altiscale.com>
|
||||||
Adam Miller <admiller@redhat.com>
|
Adam Miller <admiller@redhat.com>
|
||||||
Adam Singer <financeCoding@gmail.com>
|
Adam Singer <financeCoding@gmail.com>
|
||||||
Aditya <aditya@netroy.in>
|
Aditya <aditya@netroy.in>
|
||||||
|
Adria Casas <adriacasas88@gmail.com>
|
||||||
Adrian Mouat <adrian.mouat@gmail.com>
|
Adrian Mouat <adrian.mouat@gmail.com>
|
||||||
Adrien Folie <folie.adrien@gmail.com>
|
Adrien Folie <folie.adrien@gmail.com>
|
||||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||||
@@ -23,6 +27,9 @@ Albert Callarisa <shark234@gmail.com>
|
|||||||
Albert Zhang <zhgwenming@gmail.com>
|
Albert Zhang <zhgwenming@gmail.com>
|
||||||
Aleksa Sarai <cyphar@cyphar.com>
|
Aleksa Sarai <cyphar@cyphar.com>
|
||||||
Aleksandrs Fadins <aleks@s-ko.net>
|
Aleksandrs Fadins <aleks@s-ko.net>
|
||||||
|
Alena Prokharchyk <alena@rancher.com>
|
||||||
|
Alessandro Boch <aboch@docker.com>
|
||||||
|
Alessio Biancalana <dottorblaster@gmail.com>
|
||||||
Alex Gaynor <alex.gaynor@gmail.com>
|
Alex Gaynor <alex.gaynor@gmail.com>
|
||||||
Alex Warhawk <ax.warhawk@gmail.com>
|
Alex Warhawk <ax.warhawk@gmail.com>
|
||||||
Alexander Boyd <alex@opengroove.org>
|
Alexander Boyd <alex@opengroove.org>
|
||||||
@@ -30,14 +37,20 @@ Alexander Larsson <alexl@redhat.com>
|
|||||||
Alexander Morozov <lk4d4@docker.com>
|
Alexander Morozov <lk4d4@docker.com>
|
||||||
Alexander Shopov <ash@kambanaria.org>
|
Alexander Shopov <ash@kambanaria.org>
|
||||||
Alexandr Morozov <lk4d4@docker.com>
|
Alexandr Morozov <lk4d4@docker.com>
|
||||||
|
Alexey Guskov <lexag@mail.ru>
|
||||||
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
||||||
Alexey Shamrin <shamrin@gmail.com>
|
Alexey Shamrin <shamrin@gmail.com>
|
||||||
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
||||||
|
Allen Madsen <blatyo@gmail.com>
|
||||||
almoehi <almoehi@users.noreply.github.com>
|
almoehi <almoehi@users.noreply.github.com>
|
||||||
|
Alvin Richards <alvin.richards@docker.com>
|
||||||
amangoel <amangoel@gmail.com>
|
amangoel <amangoel@gmail.com>
|
||||||
Amit Bakshi <ambakshi@gmail.com>
|
Amit Bakshi <ambakshi@gmail.com>
|
||||||
|
Amy Lindburg <amy.lindburg@docker.com>
|
||||||
Anand Patil <anand.prabhakar.patil@gmail.com>
|
Anand Patil <anand.prabhakar.patil@gmail.com>
|
||||||
AnandkumarPatel <anandkumarpatel@gmail.com>
|
AnandkumarPatel <anandkumarpatel@gmail.com>
|
||||||
|
Anchal Agrawal <aagrawa4@illinois.edu>
|
||||||
|
Anders Janmyr <anders@janmyr.com>
|
||||||
Andre Dublin <81dublin@gmail.com>
|
Andre Dublin <81dublin@gmail.com>
|
||||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||||
Andrea Turli <andrea.turli@gmail.com>
|
Andrea Turli <andrea.turli@gmail.com>
|
||||||
@@ -45,15 +58,19 @@ Andreas Köhler <andi5.py@gmx.net>
|
|||||||
Andreas Savvides <andreas@editd.com>
|
Andreas Savvides <andreas@editd.com>
|
||||||
Andreas Tiefenthaler <at@an-ti.eu>
|
Andreas Tiefenthaler <at@an-ti.eu>
|
||||||
Andrew C. Bodine <acbodine@us.ibm.com>
|
Andrew C. Bodine <acbodine@us.ibm.com>
|
||||||
|
Andrew Clay Shafer <andrewcshafer@gmail.com>
|
||||||
Andrew Duckworth <grillopress@gmail.com>
|
Andrew Duckworth <grillopress@gmail.com>
|
||||||
Andrew France <andrew@avito.co.uk>
|
Andrew France <andrew@avito.co.uk>
|
||||||
|
Andrew Kuklewicz <kookster@gmail.com>
|
||||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||||
|
Andrew Martin <sublimino@gmail.com>
|
||||||
Andrew Munsell <andrew@wizardapps.net>
|
Andrew Munsell <andrew@wizardapps.net>
|
||||||
Andrew Weiss <andrew.weiss@outlook.com>
|
Andrew Weiss <andrew.weiss@outlook.com>
|
||||||
Andrew Williams <williams.andrew@gmail.com>
|
Andrew Williams <williams.andrew@gmail.com>
|
||||||
Andrews Medina <andrewsmedina@gmail.com>
|
Andrews Medina <andrewsmedina@gmail.com>
|
||||||
Andrey Petrov <andrey.petrov@shazow.net>
|
Andrey Petrov <andrey.petrov@shazow.net>
|
||||||
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
||||||
|
André Martins <martins@noironetworks.com>
|
||||||
Andy Chambers <anchambers@paypal.com>
|
Andy Chambers <anchambers@paypal.com>
|
||||||
andy diller <dillera@gmail.com>
|
andy diller <dillera@gmail.com>
|
||||||
Andy Goldstein <agoldste@redhat.com>
|
Andy Goldstein <agoldste@redhat.com>
|
||||||
@@ -61,18 +78,23 @@ Andy Kipp <andy@rstudio.com>
|
|||||||
Andy Rothfusz <github@developersupport.net>
|
Andy Rothfusz <github@developersupport.net>
|
||||||
Andy Smith <github@anarkystic.com>
|
Andy Smith <github@anarkystic.com>
|
||||||
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
||||||
|
Anes Hasicic <anes.hasicic@gmail.com>
|
||||||
Ankush Agarwal <ankushagarwal11@gmail.com>
|
Ankush Agarwal <ankushagarwal11@gmail.com>
|
||||||
Anthony Baire <Anthony.Baire@irisa.fr>
|
Anthony Baire <Anthony.Baire@irisa.fr>
|
||||||
Anthony Bishopric <git@anthonybishopric.com>
|
Anthony Bishopric <git@anthonybishopric.com>
|
||||||
Anton Löfgren <anton.lofgren@gmail.com>
|
Anton Löfgren <anton.lofgren@gmail.com>
|
||||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||||
|
Anton Tiurin <noxiouz@yandex.ru>
|
||||||
|
Antonio Murdaca <antonio.murdaca@gmail.com>
|
||||||
Antony Messerli <amesserl@rackspace.com>
|
Antony Messerli <amesserl@rackspace.com>
|
||||||
apocas <petermdias@gmail.com>
|
apocas <petermdias@gmail.com>
|
||||||
ArikaChen <eaglesora@gmail.com>
|
ArikaChen <eaglesora@gmail.com>
|
||||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||||
|
Arthur Barr <arthur.barr@uk.ibm.com>
|
||||||
Arthur Gautier <baloo@gandi.net>
|
Arthur Gautier <baloo@gandi.net>
|
||||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||||
averagehuman <averagehuman@users.noreply.github.com>
|
averagehuman <averagehuman@users.noreply.github.com>
|
||||||
|
Avi Das <andas222@gmail.com>
|
||||||
Avi Miller <avi.miller@oracle.com>
|
Avi Miller <avi.miller@oracle.com>
|
||||||
Barnaby Gray <barnaby@pickle.me.uk>
|
Barnaby Gray <barnaby@pickle.me.uk>
|
||||||
Barry Allard <barry.allard@gmail.com>
|
Barry Allard <barry.allard@gmail.com>
|
||||||
@@ -80,22 +102,31 @@ Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
|||||||
bdevloed <boris.de.vloed@gmail.com>
|
bdevloed <boris.de.vloed@gmail.com>
|
||||||
Ben Firshman <ben@firshman.co.uk>
|
Ben Firshman <ben@firshman.co.uk>
|
||||||
Ben Sargent <ben@brokendigits.com>
|
Ben Sargent <ben@brokendigits.com>
|
||||||
|
Ben Severson <BenSeverson@users.noreply.github.com>
|
||||||
Ben Toews <mastahyeti@gmail.com>
|
Ben Toews <mastahyeti@gmail.com>
|
||||||
Ben Wiklund <ben@daisyowl.com>
|
Ben Wiklund <ben@daisyowl.com>
|
||||||
Benjamin Atkin <ben@benatkin.com>
|
Benjamin Atkin <ben@benatkin.com>
|
||||||
Benoit Chesneau <bchesneau@gmail.com>
|
Benoit Chesneau <bchesneau@gmail.com>
|
||||||
Bernerd Schaefer <bj.schaefer@gmail.com>
|
Bernerd Schaefer <bj.schaefer@gmail.com>
|
||||||
Bert Goethals <bert@bertg.be>
|
Bert Goethals <bert@bertg.be>
|
||||||
|
Bharath Thiruveedula <bharath_ves@hotmail.com>
|
||||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||||
bin liu <liubin0329@users.noreply.github.com>
|
bin liu <liubin0329@users.noreply.github.com>
|
||||||
Blake Geno <blakegeno@gmail.com>
|
Blake Geno <blakegeno@gmail.com>
|
||||||
|
bobby abbott <ttobbaybbob@gmail.com>
|
||||||
|
boucher <rboucher@gmail.com>
|
||||||
Bouke Haarsma <bouke@webatoom.nl>
|
Bouke Haarsma <bouke@webatoom.nl>
|
||||||
Boyd Hemphill <boyd@feedmagnet.com>
|
Boyd Hemphill <boyd@feedmagnet.com>
|
||||||
|
Bradley Cicenas <bradley.cicenas@gmail.com>
|
||||||
|
Bradley Wright <brad@intranation.com>
|
||||||
Brandon Liu <bdon@bdon.org>
|
Brandon Liu <bdon@bdon.org>
|
||||||
Brandon Philips <brandon@ifup.org>
|
Brandon Philips <brandon@ifup.org>
|
||||||
Brandon Rhodes <brandon@rhodesmill.org>
|
Brandon Rhodes <brandon@rhodesmill.org>
|
||||||
|
Brendan Dixon <brendand@microsoft.com>
|
||||||
|
Brent Salisbury <brent.salisbury@docker.com>
|
||||||
Brett Kochendorfer <brett.kochendorfer@gmail.com>
|
Brett Kochendorfer <brett.kochendorfer@gmail.com>
|
||||||
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
||||||
|
Brian DeHamer <brian@dehamer.com>
|
||||||
Brian Dorsey <brian@dorseys.org>
|
Brian Dorsey <brian@dorseys.org>
|
||||||
Brian Flad <bflad417@gmail.com>
|
Brian Flad <bflad417@gmail.com>
|
||||||
Brian Goff <cpuguy83@gmail.com>
|
Brian Goff <cpuguy83@gmail.com>
|
||||||
@@ -106,48 +137,67 @@ Brice Jaglin <bjaglin@teads.tv>
|
|||||||
Briehan Lombaard <briehan.lombaard@gmail.com>
|
Briehan Lombaard <briehan.lombaard@gmail.com>
|
||||||
Bruno Bigras <bigras.bruno@gmail.com>
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
Bruno Binet <bruno.binet@gmail.com>
|
Bruno Binet <bruno.binet@gmail.com>
|
||||||
|
Bruno Gazzera <bgazzera@paginar.com>
|
||||||
Bruno Renié <brutasse@gmail.com>
|
Bruno Renié <brutasse@gmail.com>
|
||||||
Bryan Bess <squarejaw@bsbess.com>
|
Bryan Bess <squarejaw@bsbess.com>
|
||||||
|
Bryan Boreham <bjboreham@gmail.com>
|
||||||
Bryan Matsuo <bryan.matsuo@gmail.com>
|
Bryan Matsuo <bryan.matsuo@gmail.com>
|
||||||
Bryan Murphy <bmurphy1976@gmail.com>
|
Bryan Murphy <bmurphy1976@gmail.com>
|
||||||
|
buddhamagnet <buddhamagnet@gmail.com>
|
||||||
Burke Libbey <burke@libbey.me>
|
Burke Libbey <burke@libbey.me>
|
||||||
Byung Kang <byung.kang.ctr@amrdec.army.mil>
|
Byung Kang <byung.kang.ctr@amrdec.army.mil>
|
||||||
Caleb Spare <cespare@gmail.com>
|
Caleb Spare <cespare@gmail.com>
|
||||||
Calen Pennington <cale@edx.org>
|
Calen Pennington <cale@edx.org>
|
||||||
Cameron Boehmer <cameron.boehmer@gmail.com>
|
Cameron Boehmer <cameron.boehmer@gmail.com>
|
||||||
Carl X. Su <bcbcarl@gmail.com>
|
Carl X. Su <bcbcarl@gmail.com>
|
||||||
|
Cary <caryhartline@users.noreply.github.com>
|
||||||
|
Casey Bisson <casey.bisson@joyent.com>
|
||||||
Charles Hooper <charles.hooper@dotcloud.com>
|
Charles Hooper <charles.hooper@dotcloud.com>
|
||||||
Charles Lindsay <chaz@chazomatic.us>
|
Charles Lindsay <chaz@chazomatic.us>
|
||||||
Charles Merriam <charles.merriam@gmail.com>
|
Charles Merriam <charles.merriam@gmail.com>
|
||||||
Charlie Lewis <charliel@lab41.org>
|
Charlie Lewis <charliel@lab41.org>
|
||||||
Chen Chao <cc272309126@gmail.com>
|
Chen Chao <cc272309126@gmail.com>
|
||||||
|
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
||||||
|
cheney90 <cheney-90@hotmail.com>
|
||||||
Chewey <prosto-chewey@users.noreply.github.com>
|
Chewey <prosto-chewey@users.noreply.github.com>
|
||||||
Chia-liang Kao <clkao@clkao.org>
|
Chia-liang Kao <clkao@clkao.org>
|
||||||
|
chli <chli@freewheel.tv>
|
||||||
Chris Alfonso <calfonso@redhat.com>
|
Chris Alfonso <calfonso@redhat.com>
|
||||||
Chris Armstrong <chris@opdemand.com>
|
Chris Armstrong <chris@opdemand.com>
|
||||||
|
Chris Khoo <chris.khoo@gmail.com>
|
||||||
Chris Snow <chsnow123@gmail.com>
|
Chris Snow <chsnow123@gmail.com>
|
||||||
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
||||||
|
Chris Stivers <chris@stivers.us>
|
||||||
|
Chris Wahl <github@wahlnetwork.com>
|
||||||
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||||
Christian Berendt <berendt@b1-systems.de>
|
Christian Berendt <berendt@b1-systems.de>
|
||||||
|
Christian Simon <simon@swine.de>
|
||||||
Christian Stefanescu <st.chris@gmail.com>
|
Christian Stefanescu <st.chris@gmail.com>
|
||||||
ChristoperBiscardi <biscarch@sketcht.com>
|
ChristoperBiscardi <biscarch@sketcht.com>
|
||||||
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
||||||
Christopher Currie <codemonkey+github@gmail.com>
|
Christopher Currie <codemonkey+github@gmail.com>
|
||||||
Christopher Latham <sudosurootdev@gmail.com>
|
Christopher Latham <sudosurootdev@gmail.com>
|
||||||
Christopher Rigor <crigor@gmail.com>
|
Christopher Rigor <crigor@gmail.com>
|
||||||
|
Christy Perez <christy@linux.vnet.ibm.com>
|
||||||
Chun Chen <chenchun.feed@gmail.com>
|
Chun Chen <chenchun.feed@gmail.com>
|
||||||
Ciro S. Costa <ciro.costa@usp.br>
|
Ciro S. Costa <ciro.costa@usp.br>
|
||||||
Clayton Coleman <ccoleman@redhat.com>
|
Clayton Coleman <ccoleman@redhat.com>
|
||||||
|
Coenraad Loubser <coenraad@wish.org.za>
|
||||||
Colin Dunklau <colin.dunklau@gmail.com>
|
Colin Dunklau <colin.dunklau@gmail.com>
|
||||||
Colin Rice <colin@daedrum.net>
|
Colin Rice <colin@daedrum.net>
|
||||||
Colin Walters <walters@verbum.org>
|
Colin Walters <walters@verbum.org>
|
||||||
|
Colm Hally <colmhally@gmail.com>
|
||||||
Cory Forsyth <cory.forsyth@gmail.com>
|
Cory Forsyth <cory.forsyth@gmail.com>
|
||||||
cressie176 <github@stephen-cresswell.net>
|
cressie176 <github@stephen-cresswell.net>
|
||||||
Cristian Staretu <cristian.staretu@gmail.com>
|
Cristian Staretu <cristian.staretu@gmail.com>
|
||||||
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
||||||
|
Cyril F <cyrilf7x@gmail.com>
|
||||||
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
||||||
Daehyeok Mun <daehyeok@gmail.com>
|
Daehyeok Mun <daehyeok@gmail.com>
|
||||||
Dafydd Crosby <dtcrsby@gmail.com>
|
Dafydd Crosby <dtcrsby@gmail.com>
|
||||||
|
dalanlan <dalanlan925@gmail.com>
|
||||||
|
Damjan Georgievski <gdamjan@gmail.com>
|
||||||
|
Dan Anolik <dan@anolik.net>
|
||||||
Dan Buch <d.buch@modcloth.com>
|
Dan Buch <d.buch@modcloth.com>
|
||||||
Dan Cotora <dan@bluevision.ro>
|
Dan Cotora <dan@bluevision.ro>
|
||||||
Dan Griffin <dgriffin@peer1.com>
|
Dan Griffin <dgriffin@peer1.com>
|
||||||
@@ -157,35 +207,46 @@ Dan McPherson <dmcphers@redhat.com>
|
|||||||
Dan Stine <sw@stinemail.com>
|
Dan Stine <sw@stinemail.com>
|
||||||
Dan Walsh <dwalsh@redhat.com>
|
Dan Walsh <dwalsh@redhat.com>
|
||||||
Dan Williams <me@deedubs.com>
|
Dan Williams <me@deedubs.com>
|
||||||
|
Daniel Antlinger <d.antlinger@gmx.at>
|
||||||
Daniel Exner <dex@dragonslave.de>
|
Daniel Exner <dex@dragonslave.de>
|
||||||
Daniel Farrell <dfarrell@redhat.com>
|
Daniel Farrell <dfarrell@redhat.com>
|
||||||
Daniel Garcia <daniel@danielgarcia.info>
|
Daniel Garcia <daniel@danielgarcia.info>
|
||||||
Daniel Gasienica <daniel@gasienica.ch>
|
Daniel Gasienica <daniel@gasienica.ch>
|
||||||
Daniel Menet <membership@sontags.ch>
|
Daniel Menet <membership@sontags.ch>
|
||||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||||
|
Daniel Nephin <dnephin@gmail.com>
|
||||||
Daniel Norberg <dano@spotify.com>
|
Daniel Norberg <dano@spotify.com>
|
||||||
Daniel Nordberg <dnordberg@gmail.com>
|
Daniel Nordberg <dnordberg@gmail.com>
|
||||||
Daniel Robinson <gottagetmac@gmail.com>
|
Daniel Robinson <gottagetmac@gmail.com>
|
||||||
|
Daniel S <dan.streby@gmail.com>
|
||||||
Daniel Von Fange <daniel@leancoder.com>
|
Daniel Von Fange <daniel@leancoder.com>
|
||||||
Daniel YC Lin <dlin.tw@gmail.com>
|
Daniel YC Lin <dlin.tw@gmail.com>
|
||||||
|
Daniel Zhang <jmzwcn@gmail.com>
|
||||||
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
||||||
Danny Berger <dpb587@gmail.com>
|
Danny Berger <dpb587@gmail.com>
|
||||||
Danny Yates <danny@codeaholics.org>
|
Danny Yates <danny@codeaholics.org>
|
||||||
Darren Coxall <darren@darrencoxall.com>
|
Darren Coxall <darren@darrencoxall.com>
|
||||||
Darren Shepherd <darren.s.shepherd@gmail.com>
|
Darren Shepherd <darren.s.shepherd@gmail.com>
|
||||||
|
Dave Henderson <Dave.Henderson@ca.ibm.com>
|
||||||
David Anderson <dave@natulte.net>
|
David Anderson <dave@natulte.net>
|
||||||
David Calavera <david.calavera@gmail.com>
|
David Calavera <david.calavera@gmail.com>
|
||||||
David Corking <dmc-source@dcorking.com>
|
David Corking <dmc-source@dcorking.com>
|
||||||
|
David Davis <daviddavis@redhat.com>
|
||||||
David Gageot <david@gageot.net>
|
David Gageot <david@gageot.net>
|
||||||
David Gebler <davidgebler@gmail.com>
|
David Gebler <davidgebler@gmail.com>
|
||||||
|
David Mackey <tdmackey@booleanhaiku.com>
|
||||||
David Mat <david@davidmat.com>
|
David Mat <david@davidmat.com>
|
||||||
David Mcanulty <github@hellspark.com>
|
David Mcanulty <github@hellspark.com>
|
||||||
David Pelaez <pelaez89@gmail.com>
|
David Pelaez <pelaez89@gmail.com>
|
||||||
|
David R. Jenni <david.r.jenni@gmail.com>
|
||||||
David Röthlisberger <david@rothlis.net>
|
David Röthlisberger <david@rothlis.net>
|
||||||
David Sissitka <me@dsissitka.com>
|
David Sissitka <me@dsissitka.com>
|
||||||
|
David Xia <dxia@spotify.com>
|
||||||
|
David Young <yangboh@cn.ibm.com>
|
||||||
Davide Ceretti <davide.ceretti@hogarthww.com>
|
Davide Ceretti <davide.ceretti@hogarthww.com>
|
||||||
Dawn Chen <dawnchen@google.com>
|
Dawn Chen <dawnchen@google.com>
|
||||||
decadent <decadent@users.noreply.github.com>
|
decadent <decadent@users.noreply.github.com>
|
||||||
|
Deng Guangxing <dengguangxing@huawei.com>
|
||||||
Deni Bertovic <deni@kset.org>
|
Deni Bertovic <deni@kset.org>
|
||||||
Derek <crq@kernel.org>
|
Derek <crq@kernel.org>
|
||||||
Derek <crquan@gmail.com>
|
Derek <crquan@gmail.com>
|
||||||
@@ -193,39 +254,53 @@ Derek McGowan <derek@mcgstyle.net>
|
|||||||
Deric Crago <deric.crago@gmail.com>
|
Deric Crago <deric.crago@gmail.com>
|
||||||
Deshi Xiao <dxiao@redhat.com>
|
Deshi Xiao <dxiao@redhat.com>
|
||||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||||
|
DiuDiugirl <sophia.wang@pku.edu.cn>
|
||||||
Djibril Koné <kone.djibril@gmail.com>
|
Djibril Koné <kone.djibril@gmail.com>
|
||||||
dkumor <daniel@dkumor.com>
|
dkumor <daniel@dkumor.com>
|
||||||
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
||||||
|
Dmitry Gusev <dmitry.gusev@gmail.com>
|
||||||
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
||||||
Dolph Mathews <dolph.mathews@gmail.com>
|
Dolph Mathews <dolph.mathews@gmail.com>
|
||||||
|
Dominik Finkbeiner <finkes93@gmail.com>
|
||||||
Dominik Honnef <dominik@honnef.co>
|
Dominik Honnef <dominik@honnef.co>
|
||||||
|
Don Kirkby <donkirkby@users.noreply.github.com>
|
||||||
Don Kjer <don.kjer@gmail.com>
|
Don Kjer <don.kjer@gmail.com>
|
||||||
Don Spaulding <donspauldingii@gmail.com>
|
Don Spaulding <donspauldingii@gmail.com>
|
||||||
Doug Davis <dug@us.ibm.com>
|
Doug Davis <dug@us.ibm.com>
|
||||||
|
Doug MacEachern <dougm@vmware.com>
|
||||||
doug tangren <d.tangren@gmail.com>
|
doug tangren <d.tangren@gmail.com>
|
||||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||||
dragon788 <dragon788@users.noreply.github.com>
|
dragon788 <dragon788@users.noreply.github.com>
|
||||||
Dražen Lučanin <kermit666@gmail.com>
|
Dražen Lučanin <kermit666@gmail.com>
|
||||||
Dustin Sallings <dustin@spy.net>
|
Dustin Sallings <dustin@spy.net>
|
||||||
|
Ed Costello <epc@epcostello.com>
|
||||||
Edmund Wagner <edmund-wagner@web.de>
|
Edmund Wagner <edmund-wagner@web.de>
|
||||||
Eiichi Tsukata <devel@etsukata.com>
|
Eiichi Tsukata <devel@etsukata.com>
|
||||||
Eike Herzbach <eike@herzbach.net>
|
Eike Herzbach <eike@herzbach.net>
|
||||||
Eivind Uggedal <eivind@uggedal.com>
|
Eivind Uggedal <eivind@uggedal.com>
|
||||||
Elias Probst <mail@eliasprobst.eu>
|
Elias Probst <mail@eliasprobst.eu>
|
||||||
|
Elijah Zupancic <elijah@zupancic.name>
|
||||||
|
eluck <mail@eluck.me>
|
||||||
Emil Hernvall <emil@quench.at>
|
Emil Hernvall <emil@quench.at>
|
||||||
Emily Maier <emily@emilymaier.net>
|
Emily Maier <emily@emilymaier.net>
|
||||||
Emily Rose <emily@contactvibe.com>
|
Emily Rose <emily@contactvibe.com>
|
||||||
|
Emir Ozer <emirozer@yandex.com>
|
||||||
|
Enguerran <engcolson@gmail.com>
|
||||||
|
Eohyung Lee <liquidnuker@gmail.com>
|
||||||
Eric Hanchrow <ehanchrow@ine.com>
|
Eric Hanchrow <ehanchrow@ine.com>
|
||||||
Eric Lee <thenorthsecedes@gmail.com>
|
Eric Lee <thenorthsecedes@gmail.com>
|
||||||
Eric Myhre <hash@exultant.us>
|
Eric Myhre <hash@exultant.us>
|
||||||
Eric Paris <eparis@redhat.com>
|
Eric Paris <eparis@redhat.com>
|
||||||
|
Eric Rafaloff <erafaloff@gmail.com>
|
||||||
Eric Windisch <ewindisch@docker.com>
|
Eric Windisch <ewindisch@docker.com>
|
||||||
|
Eric-Olivier Lamey <eo@lamey.me>
|
||||||
Erik Dubbelboer <erik@dubbelboer.com>
|
Erik Dubbelboer <erik@dubbelboer.com>
|
||||||
Erik Hollensbe <github@hollensbe.org>
|
Erik Hollensbe <github@hollensbe.org>
|
||||||
Erik Inge Bolsø <knan@redpill-linpro.com>
|
Erik Inge Bolsø <knan@redpill-linpro.com>
|
||||||
Erik Kristensen <erik@erikkristensen.com>
|
Erik Kristensen <erik@erikkristensen.com>
|
||||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||||
Erwin van der Koogh <info@erronis.nl>
|
Erwin van der Koogh <info@erronis.nl>
|
||||||
|
Euan <euank@amazon.com>
|
||||||
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||||
eugenkrizo <eugen.krizo@gmail.com>
|
eugenkrizo <eugen.krizo@gmail.com>
|
||||||
Evan Carmi <carmi@users.noreply.github.com>
|
Evan Carmi <carmi@users.noreply.github.com>
|
||||||
@@ -233,58 +308,78 @@ Evan Hazlett <ejhazlett@gmail.com>
|
|||||||
Evan Krall <krall@yelp.com>
|
Evan Krall <krall@yelp.com>
|
||||||
Evan Phoenix <evan@fallingsnow.net>
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
Evan Wies <evan@neomantra.net>
|
Evan Wies <evan@neomantra.net>
|
||||||
|
Evgeny Vereshchagin <evvers@ya.ru>
|
||||||
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
||||||
ezbercih <cem.ezberci@gmail.com>
|
ezbercih <cem.ezberci@gmail.com>
|
||||||
|
Fabiano Rosas <farosas@br.ibm.com>
|
||||||
Fabio Falci <fabiofalci@gmail.com>
|
Fabio Falci <fabiofalci@gmail.com>
|
||||||
Fabio Rehm <fgrehm@gmail.com>
|
Fabio Rehm <fgrehm@gmail.com>
|
||||||
Fabrizio Regini <freegenie@gmail.com>
|
Fabrizio Regini <freegenie@gmail.com>
|
||||||
Faiz Khan <faizkhan00@gmail.com>
|
Faiz Khan <faizkhan00@gmail.com>
|
||||||
|
falmp <chico.lopes@gmail.com>
|
||||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||||
Felix Rabe <felix@rabe.io>
|
Felix Rabe <felix@rabe.io>
|
||||||
|
Felix Schindler <fschindler@weluse.de>
|
||||||
|
Ferenc Szabo <pragmaticfrank@gmail.com>
|
||||||
Fernando <fermayo@gmail.com>
|
Fernando <fermayo@gmail.com>
|
||||||
Filipe Brandenburger <filbranden@google.com>
|
Filipe Brandenburger <filbranden@google.com>
|
||||||
Flavio Castelli <fcastelli@suse.com>
|
Flavio Castelli <fcastelli@suse.com>
|
||||||
FLGMwt <ryan.stelly@live.com>
|
FLGMwt <ryan.stelly@live.com>
|
||||||
|
Florian Weingarten <flo@hackvalue.de>
|
||||||
Francisco Carriedo <fcarriedo@gmail.com>
|
Francisco Carriedo <fcarriedo@gmail.com>
|
||||||
Francisco Souza <f@souza.cc>
|
Francisco Souza <f@souza.cc>
|
||||||
|
Frank Herrmann <fgh@4gh.tv>
|
||||||
Frank Macreery <frank@macreery.com>
|
Frank Macreery <frank@macreery.com>
|
||||||
Frank Rosquin <frank.rosquin+github@gmail.com>
|
Frank Rosquin <frank.rosquin+github@gmail.com>
|
||||||
Fred Lifton <fred.lifton@docker.com>
|
Fred Lifton <fred.lifton@docker.com>
|
||||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||||
Frederik Loeffert <frederik@zitrusmedia.de>
|
Frederik Loeffert <frederik@zitrusmedia.de>
|
||||||
Freek Kalter <freek@kalteronline.org>
|
Freek Kalter <freek@kalteronline.org>
|
||||||
|
Félix Baylac-Jacqué <baylac.felix@gmail.com>
|
||||||
Gabe Rosenhouse <gabe@missionst.com>
|
Gabe Rosenhouse <gabe@missionst.com>
|
||||||
Gabor Nagy <mail@aigeruth.hu>
|
Gabor Nagy <mail@aigeruth.hu>
|
||||||
Gabriel Monroy <gabriel@opdemand.com>
|
Gabriel Monroy <gabriel@opdemand.com>
|
||||||
Galen Sampson <galen.sampson@gmail.com>
|
Galen Sampson <galen.sampson@gmail.com>
|
||||||
Gareth Rushgrove <gareth@morethanseven.net>
|
Gareth Rushgrove <gareth@morethanseven.net>
|
||||||
|
Gaurav <gaurav.gosec@gmail.com>
|
||||||
gautam, prasanna <prasannagautam@gmail.com>
|
gautam, prasanna <prasannagautam@gmail.com>
|
||||||
|
GennadySpb <lipenkov@gmail.com>
|
||||||
Geoffrey Bachelet <grosfrais@gmail.com>
|
Geoffrey Bachelet <grosfrais@gmail.com>
|
||||||
|
George MacRorie <gmacr31@gmail.com>
|
||||||
George Xie <georgexsh@gmail.com>
|
George Xie <georgexsh@gmail.com>
|
||||||
Gereon Frey <gereon.frey@dynport.de>
|
Gereon Frey <gereon.frey@dynport.de>
|
||||||
German DZ <germ@ndz.com.ar>
|
German DZ <germ@ndz.com.ar>
|
||||||
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
||||||
|
Gianluca Borello <g.borello@gmail.com>
|
||||||
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
||||||
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
||||||
Gleb M Borisov <borisov.gleb@gmail.com>
|
Gleb M Borisov <borisov.gleb@gmail.com>
|
||||||
Glyn Normington <gnormington@gopivotal.com>
|
Glyn Normington <gnormington@gopivotal.com>
|
||||||
Goffert van Gool <goffert@phusion.nl>
|
Goffert van Gool <goffert@phusion.nl>
|
||||||
golubbe <ben.golub@dotcloud.com>
|
golubbe <ben.golub@dotcloud.com>
|
||||||
|
Gosuke Miyashita <gosukenator@gmail.com>
|
||||||
Graydon Hoare <graydon@pobox.com>
|
Graydon Hoare <graydon@pobox.com>
|
||||||
|
Greg Fausak <greg@tacodata.com>
|
||||||
Greg Thornton <xdissent@me.com>
|
Greg Thornton <xdissent@me.com>
|
||||||
|
grossws <grossws@gmail.com>
|
||||||
grunny <mwgrunny@gmail.com>
|
grunny <mwgrunny@gmail.com>
|
||||||
Guilherme Salgado <gsalgado@gmail.com>
|
Guilherme Salgado <gsalgado@gmail.com>
|
||||||
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
|
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
|
||||||
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||||
|
guoxiuyan <guoxiuyan@huawei.com>
|
||||||
Gurjeet Singh <gurjeet@singh.im>
|
Gurjeet Singh <gurjeet@singh.im>
|
||||||
Guruprasad <lgp171188@gmail.com>
|
Guruprasad <lgp171188@gmail.com>
|
||||||
|
Günter Zöchbauer <guenter@gzoechbauer.com>
|
||||||
Hans Rødtang <hansrodtang@gmail.com>
|
Hans Rødtang <hansrodtang@gmail.com>
|
||||||
Harald Albers <github@albersweb.de>
|
Harald Albers <github@albersweb.de>
|
||||||
Harley Laue <losinggeneration@gmail.com>
|
Harley Laue <losinggeneration@gmail.com>
|
||||||
|
Harry Zhang <harryzhang@zju.edu.cn>
|
||||||
|
He Simei <hesimei@zju.edu.cn>
|
||||||
Hector Castro <hectcastro@gmail.com>
|
Hector Castro <hectcastro@gmail.com>
|
||||||
Henning Sprang <henning.sprang@gmail.com>
|
Henning Sprang <henning.sprang@gmail.com>
|
||||||
Hobofan <goisser94@gmail.com>
|
Hobofan <goisser94@gmail.com>
|
||||||
Hollie Teal <hollie@docker.com>
|
Hollie Teal <hollie@docker.com>
|
||||||
|
Hong Xu <hong@topbug.net>
|
||||||
Hu Keping <hukeping@huawei.com>
|
Hu Keping <hukeping@huawei.com>
|
||||||
Hu Tao <hutao@cn.fujitsu.com>
|
Hu Tao <hutao@cn.fujitsu.com>
|
||||||
Huayi Zhang <irachex@gmail.com>
|
Huayi Zhang <irachex@gmail.com>
|
||||||
@@ -292,21 +387,28 @@ Hugo Duncan <hugo@hugoduncan.org>
|
|||||||
Hunter Blanks <hunter@twilio.com>
|
Hunter Blanks <hunter@twilio.com>
|
||||||
Huu Nguyen <huu@prismskylabs.com>
|
Huu Nguyen <huu@prismskylabs.com>
|
||||||
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
||||||
|
hyp3rdino <markus.kortlang@lhsystems.com>
|
||||||
Ian Babrou <ibobrik@gmail.com>
|
Ian Babrou <ibobrik@gmail.com>
|
||||||
Ian Bishop <ianbishop@pace7.com>
|
Ian Bishop <ianbishop@pace7.com>
|
||||||
Ian Bull <irbull@gmail.com>
|
Ian Bull <irbull@gmail.com>
|
||||||
|
Ian Calvert <ianjcalvert@gmail.com>
|
||||||
Ian Main <imain@redhat.com>
|
Ian Main <imain@redhat.com>
|
||||||
Ian Truslove <ian.truslove@gmail.com>
|
Ian Truslove <ian.truslove@gmail.com>
|
||||||
|
Iavael <iavaelooeyt@gmail.com>
|
||||||
Igor Dolzhikov <bluesriverz@gmail.com>
|
Igor Dolzhikov <bluesriverz@gmail.com>
|
||||||
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
||||||
|
imre Fitos <imre.fitos+github@gmail.com>
|
||||||
inglesp <peter.inglesby@gmail.com>
|
inglesp <peter.inglesby@gmail.com>
|
||||||
Isaac Dupree <antispam@idupree.com>
|
Isaac Dupree <antispam@idupree.com>
|
||||||
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||||
Isao Jonas <isao.jonas@gmail.com>
|
Isao Jonas <isao.jonas@gmail.com>
|
||||||
Ivan Fraixedes <ifcdev@gmail.com>
|
Ivan Fraixedes <ifcdev@gmail.com>
|
||||||
|
J Bruni <joaohbruni@yahoo.com.br>
|
||||||
|
J. Nunn <jbnunn@gmail.com>
|
||||||
Jack Danger Canty <jackdanger@squareup.com>
|
Jack Danger Canty <jackdanger@squareup.com>
|
||||||
Jacob Atzen <jacob@jacobatzen.dk>
|
Jacob Atzen <jacob@jacobatzen.dk>
|
||||||
Jacob Edelman <edelman.jd@gmail.com>
|
Jacob Edelman <edelman.jd@gmail.com>
|
||||||
|
Jake Champlin <jake.champlin.27@gmail.com>
|
||||||
Jake Moshenko <jake@devtable.com>
|
Jake Moshenko <jake@devtable.com>
|
||||||
jakedt <jake@devtable.com>
|
jakedt <jake@devtable.com>
|
||||||
James Allen <jamesallen0108@gmail.com>
|
James Allen <jamesallen0108@gmail.com>
|
||||||
@@ -314,41 +416,67 @@ James Carr <james.r.carr@gmail.com>
|
|||||||
James DeFelice <james.defelice@ishisystems.com>
|
James DeFelice <james.defelice@ishisystems.com>
|
||||||
James Harrison Fisher <jameshfisher@gmail.com>
|
James Harrison Fisher <jameshfisher@gmail.com>
|
||||||
James Kyle <james@jameskyle.org>
|
James Kyle <james@jameskyle.org>
|
||||||
|
James Lal <james@lightsofapollo.com>
|
||||||
James Mills <prologic@shortcircuit.net.au>
|
James Mills <prologic@shortcircuit.net.au>
|
||||||
James Turnbull <james@lovedthanlost.net>
|
James Turnbull <james@lovedthanlost.net>
|
||||||
|
Jamie Hannaford <jamie.hannaford@rackspace.com>
|
||||||
|
Jamshid Afshar <jafshar@yahoo.com>
|
||||||
Jan Keromnes <janx@linux.com>
|
Jan Keromnes <janx@linux.com>
|
||||||
|
Jan Koprowski <jan.koprowski@gmail.com>
|
||||||
Jan Pazdziora <jpazdziora@redhat.com>
|
Jan Pazdziora <jpazdziora@redhat.com>
|
||||||
Jan Toebes <jan@toebes.info>
|
Jan Toebes <jan@toebes.info>
|
||||||
|
Jan-Jaap Driessen <janjaapdriessen@gmail.com>
|
||||||
|
Jana Radhakrishnan <mrjana@docker.com>
|
||||||
|
Jared Biel <jared.biel@bolderthinking.com>
|
||||||
Jaroslaw Zabiello <hipertracker@gmail.com>
|
Jaroslaw Zabiello <hipertracker@gmail.com>
|
||||||
jaseg <jaseg@jaseg.net>
|
jaseg <jaseg@jaseg.net>
|
||||||
|
Jason Divock <jdivock@gmail.com>
|
||||||
Jason Giedymin <jasong@apache.org>
|
Jason Giedymin <jasong@apache.org>
|
||||||
Jason Hall <imjasonh@gmail.com>
|
Jason Hall <imjasonh@gmail.com>
|
||||||
Jason Livesay <ithkuil@gmail.com>
|
Jason Livesay <ithkuil@gmail.com>
|
||||||
Jason McVetta <jason.mcvetta@gmail.com>
|
Jason McVetta <jason.mcvetta@gmail.com>
|
||||||
Jason Plum <jplum@devonit.com>
|
Jason Plum <jplum@devonit.com>
|
||||||
|
Jason Shepherd <jason@jasonshepherd.net>
|
||||||
|
Jason Smith <jasonrichardsmith@gmail.com>
|
||||||
|
Jason Sommer <jsdirv@gmail.com>
|
||||||
|
Jason Stangroome <jason@codeassassin.com>
|
||||||
|
Jay <teguhwpurwanto@gmail.com>
|
||||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||||
Jean-Paul Calderone <exarkun@twistedmatrix.com>
|
Jean-Paul Calderone <exarkun@twistedmatrix.com>
|
||||||
Jean-Tiare Le Bigot <jt@yadutaf.fr>
|
Jean-Tiare Le Bigot <jt@yadutaf.fr>
|
||||||
Jeff Anderson <jeff@docker.com>
|
Jeff Anderson <jeff@docker.com>
|
||||||
Jeff Lindsay <progrium@gmail.com>
|
Jeff Lindsay <progrium@gmail.com>
|
||||||
|
Jeff Nickoloff <jeff.nickoloff@gmail.com>
|
||||||
Jeff Welch <whatthejeff@gmail.com>
|
Jeff Welch <whatthejeff@gmail.com>
|
||||||
Jeffrey Bolle <jeffreybolle@gmail.com>
|
Jeffrey Bolle <jeffreybolle@gmail.com>
|
||||||
|
Jeffrey Morgan <jmorganca@gmail.com>
|
||||||
|
Jeffrey van Gogh <jvg@google.com>
|
||||||
Jeremy Grosser <jeremy@synack.me>
|
Jeremy Grosser <jeremy@synack.me>
|
||||||
|
Jesse Dearing <jesse.dearing@gmail.com>
|
||||||
Jesse Dubay <jesse@thefortytwo.net>
|
Jesse Dubay <jesse@thefortytwo.net>
|
||||||
Jessica Frazelle <jess@docker.com>
|
Jessica Frazelle <jess@docker.com>
|
||||||
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||||
|
jianbosun <wonderflow.sun@gmail.com>
|
||||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||||
Jim Alateras <jima@comware.com.au>
|
Jim Alateras <jima@comware.com.au>
|
||||||
Jim Perrin <jperrin@centos.org>
|
Jim Perrin <jperrin@centos.org>
|
||||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||||
|
Jimmy Puckett <jimmy.puckett@spinen.com>
|
||||||
|
jimmyxian <jimmyxian2004@yahoo.com.cn>
|
||||||
|
Jinsoo Park <cellpjs@gmail.com>
|
||||||
|
Jiri Popelka <jpopelka@redhat.com>
|
||||||
Jiří Župka <jzupka@redhat.com>
|
Jiří Župka <jzupka@redhat.com>
|
||||||
|
jjy <jiangjinyang@outlook.com>
|
||||||
|
jmzwcn <jmzwcn@gmail.com>
|
||||||
Joe Beda <joe.github@bedafamily.com>
|
Joe Beda <joe.github@bedafamily.com>
|
||||||
Joe Ferguson <joe@infosiftr.com>
|
Joe Ferguson <joe@infosiftr.com>
|
||||||
|
Joe Gordon <joe.gordon0@gmail.com>
|
||||||
Joe Shaw <joe@joeshaw.org>
|
Joe Shaw <joe@joeshaw.org>
|
||||||
Joe Van Dyk <joe@tanga.com>
|
Joe Van Dyk <joe@tanga.com>
|
||||||
Joel Friedly <joelfriedly@gmail.com>
|
Joel Friedly <joelfriedly@gmail.com>
|
||||||
Joel Handwell <joelhandwell@gmail.com>
|
Joel Handwell <joelhandwell@gmail.com>
|
||||||
|
Joey Gibson <joey@joeygibson.com>
|
||||||
Joffrey F <joffrey@docker.com>
|
Joffrey F <joffrey@docker.com>
|
||||||
Johan Euphrosine <proppy@google.com>
|
Johan Euphrosine <proppy@google.com>
|
||||||
Johan Rydberg <johan.rydberg@gmail.com>
|
Johan Rydberg <johan.rydberg@gmail.com>
|
||||||
@@ -357,13 +485,17 @@ John Costa <john.costa@gmail.com>
|
|||||||
John Feminella <jxf@jxf.me>
|
John Feminella <jxf@jxf.me>
|
||||||
John Gardiner Myers <jgmyers@proofpoint.com>
|
John Gardiner Myers <jgmyers@proofpoint.com>
|
||||||
John Gossman <johngos@microsoft.com>
|
John Gossman <johngos@microsoft.com>
|
||||||
|
John Howard (VM) <John.Howard@microsoft.com>
|
||||||
John OBrien III <jobrieniii@yahoo.com>
|
John OBrien III <jobrieniii@yahoo.com>
|
||||||
|
John Tims <john.k.tims@gmail.com>
|
||||||
John Warwick <jwarwick@gmail.com>
|
John Warwick <jwarwick@gmail.com>
|
||||||
|
John Willis <john.willis@docker.com>
|
||||||
Jon Wedaman <jweede@gmail.com>
|
Jon Wedaman <jweede@gmail.com>
|
||||||
Jonas Pfenniger <jonas@pfenniger.name>
|
Jonas Pfenniger <jonas@pfenniger.name>
|
||||||
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
||||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||||
Jonathan Camp <jonathan@irondojo.com>
|
Jonathan Camp <jonathan@irondojo.com>
|
||||||
|
Jonathan Dowland <jon+github@alcopop.org>
|
||||||
Jonathan McCrohan <jmccrohan@gmail.com>
|
Jonathan McCrohan <jmccrohan@gmail.com>
|
||||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||||
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
||||||
@@ -373,15 +505,18 @@ Jordan Arentsen <blissdev@gmail.com>
|
|||||||
Jordan Sissel <jls@semicomplete.com>
|
Jordan Sissel <jls@semicomplete.com>
|
||||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||||
Joseph Hager <ajhager@gmail.com>
|
Joseph Hager <ajhager@gmail.com>
|
||||||
|
Joseph Kern <jkern@semafour.net>
|
||||||
Josh <jokajak@gmail.com>
|
Josh <jokajak@gmail.com>
|
||||||
Josh Hawn <josh.hawn@docker.com>
|
Josh Hawn <josh.hawn@docker.com>
|
||||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
Josiah Kiehl <jkiehl@riotgames.com>
|
Josiah Kiehl <jkiehl@riotgames.com>
|
||||||
|
José Tomás Albornoz <jojo@eljojo.net>
|
||||||
JP <jpellerin@leapfrogonline.com>
|
JP <jpellerin@leapfrogonline.com>
|
||||||
Julian Taylor <jtaylor.debian@googlemail.com>
|
Julian Taylor <jtaylor.debian@googlemail.com>
|
||||||
Julien Barbier <write0@gmail.com>
|
Julien Barbier <write0@gmail.com>
|
||||||
Julien Bordellier <julienbordellier@gmail.com>
|
Julien Bordellier <julienbordellier@gmail.com>
|
||||||
Julien Dubois <julien.dubois@gmail.com>
|
Julien Dubois <julien.dubois@gmail.com>
|
||||||
|
Jun-Ru Chang <jrjang@gmail.com>
|
||||||
Justin Force <justin.force@gmail.com>
|
Justin Force <justin.force@gmail.com>
|
||||||
Justin Plock <jplock@users.noreply.github.com>
|
Justin Plock <jplock@users.noreply.github.com>
|
||||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||||
@@ -390,22 +525,28 @@ Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
|||||||
Jörg Thalheim <joerg@higgsboson.tk>
|
Jörg Thalheim <joerg@higgsboson.tk>
|
||||||
Kamil Domanski <kamil@domanski.co>
|
Kamil Domanski <kamil@domanski.co>
|
||||||
Karan Lyons <karan@karanlyons.com>
|
Karan Lyons <karan@karanlyons.com>
|
||||||
|
kargakis <kargakis@users.noreply.github.com>
|
||||||
Karl Grzeszczak <karlgrz@gmail.com>
|
Karl Grzeszczak <karlgrz@gmail.com>
|
||||||
|
Katie McLaughlin <katie@glasnt.com>
|
||||||
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
||||||
|
Katrina Owen <katrina.owen@gmail.com>
|
||||||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
||||||
Keli Hu <dev@keli.hu>
|
Keli Hu <dev@keli.hu>
|
||||||
Ken Cochrane <kencochrane@gmail.com>
|
Ken Cochrane <kencochrane@gmail.com>
|
||||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||||
|
Kent Johnson <kentoj@gmail.com>
|
||||||
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
||||||
Kevin Clark <kevin.clark@gmail.com>
|
Kevin Clark <kevin.clark@gmail.com>
|
||||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||||
Kevin Menard <kevin@nirvdrum.com>
|
Kevin Menard <kevin@nirvdrum.com>
|
||||||
Kevin Wallace <kevin@pentabarf.net>
|
Kevin Wallace <kevin@pentabarf.net>
|
||||||
|
Kevin Yap <me@kevinyap.ca>
|
||||||
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
||||||
kies <lleelm@gmail.com>
|
kies <lleelm@gmail.com>
|
||||||
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
||||||
Kimbro Staken <kstaken@kstaken.com>
|
Kimbro Staken <kstaken@kstaken.com>
|
||||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||||
|
Kirill SIbirev <l0kix2@gmail.com>
|
||||||
knappe <tyler.knappe@gmail.com>
|
knappe <tyler.knappe@gmail.com>
|
||||||
Kohei Tsuruta <coheyxyz@gmail.com>
|
Kohei Tsuruta <coheyxyz@gmail.com>
|
||||||
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
||||||
@@ -419,6 +560,8 @@ Lajos Papp <lajos.papp@sequenceiq.com>
|
|||||||
Lakshan Perera <lakshan@laktek.com>
|
Lakshan Perera <lakshan@laktek.com>
|
||||||
lalyos <lalyos@yahoo.com>
|
lalyos <lalyos@yahoo.com>
|
||||||
Lance Chen <cyen0312@gmail.com>
|
Lance Chen <cyen0312@gmail.com>
|
||||||
|
Lance Kinley <lkinley@loyaltymethods.com>
|
||||||
|
Lars Kellogg-Stedman <lars@redhat.com>
|
||||||
Lars R. Damerow <lars@pixar.com>
|
Lars R. Damerow <lars@pixar.com>
|
||||||
Laurie Voss <github@seldo.com>
|
Laurie Voss <github@seldo.com>
|
||||||
leeplay <hyeongkyu.lee@navercorp.com>
|
leeplay <hyeongkyu.lee@navercorp.com>
|
||||||
@@ -428,17 +571,26 @@ Leszek Kowalski <github@leszekkowalski.pl>
|
|||||||
Levi Gross <levi@levigross.com>
|
Levi Gross <levi@levigross.com>
|
||||||
Lewis Marshall <lewis@lmars.net>
|
Lewis Marshall <lewis@lmars.net>
|
||||||
Lewis Peckover <lew+github@lew.io>
|
Lewis Peckover <lew+github@lew.io>
|
||||||
|
Liana Lo <liana.lixia@gmail.com>
|
||||||
Liang-Chi Hsieh <viirya@gmail.com>
|
Liang-Chi Hsieh <viirya@gmail.com>
|
||||||
limsy <seongyeol37@gmail.com>
|
limsy <seongyeol37@gmail.com>
|
||||||
|
Liu Hua <sdu.liu@huawei.com>
|
||||||
|
Lloyd Dewolf <foolswisdom@gmail.com>
|
||||||
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
||||||
Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
|
Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
|
||||||
|
Lorenzo Fontana <fontanalorenzo@me.com>
|
||||||
Louis Opter <kalessin@kalessin.fr>
|
Louis Opter <kalessin@kalessin.fr>
|
||||||
|
Luis Martínez de Bartolomé Izquierdo <lmartinez@biicode.com>
|
||||||
lukaspustina <lukas.pustina@centerdevice.com>
|
lukaspustina <lukas.pustina@centerdevice.com>
|
||||||
lukemarsden <luke@digital-crocus.com>
|
lukemarsden <luke@digital-crocus.com>
|
||||||
Lénaïc Huard <lhuard@amadeus.com>
|
Lénaïc Huard <lhuard@amadeus.com>
|
||||||
|
Ma Shimiao <mashimiao.fnst@cn.fujitsu.com>
|
||||||
|
Mabin <bin.ma@huawei.com>
|
||||||
Madhu Venugopal <madhu@socketplane.io>
|
Madhu Venugopal <madhu@socketplane.io>
|
||||||
Mahesh Tiyyagura <tmahesh@gmail.com>
|
Mahesh Tiyyagura <tmahesh@gmail.com>
|
||||||
|
malnick <malnick@gmail..com>
|
||||||
Malte Janduda <mail@janduda.net>
|
Malte Janduda <mail@janduda.net>
|
||||||
|
Manfred Touron <m@42.am>
|
||||||
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
||||||
Manuel Meurer <manuel@krautcomputing.com>
|
Manuel Meurer <manuel@krautcomputing.com>
|
||||||
Manuel Woelker <github@manuel.woelker.org>
|
Manuel Woelker <github@manuel.woelker.org>
|
||||||
@@ -450,38 +602,52 @@ Marcus Farkas <toothlessgear@finitebox.com>
|
|||||||
Marcus Linke <marcus.linke@gmx.de>
|
Marcus Linke <marcus.linke@gmx.de>
|
||||||
Marcus Ramberg <marcus@nordaaker.com>
|
Marcus Ramberg <marcus@nordaaker.com>
|
||||||
Marek Goldmann <marek.goldmann@gmail.com>
|
Marek Goldmann <marek.goldmann@gmail.com>
|
||||||
|
Marian Marinov <mm@yuhu.biz>
|
||||||
Marianna <mtesselh@gmail.com>
|
Marianna <mtesselh@gmail.com>
|
||||||
Marius Voila <marius.voila@gmail.com>
|
Marius Voila <marius.voila@gmail.com>
|
||||||
Mark Allen <mrallen1@yahoo.com>
|
Mark Allen <mrallen1@yahoo.com>
|
||||||
Mark McGranaghan <mmcgrana@gmail.com>
|
Mark McGranaghan <mmcgrana@gmail.com>
|
||||||
|
Mark West <markewest@gmail.com>
|
||||||
Marko Mikulicic <mmikulicic@gmail.com>
|
Marko Mikulicic <mmikulicic@gmail.com>
|
||||||
Marko Tibold <marko@tibold.nl>
|
Marko Tibold <marko@tibold.nl>
|
||||||
Markus Fix <lispmeister@gmail.com>
|
Markus Fix <lispmeister@gmail.com>
|
||||||
|
Martijn Dwars <ikben@martijndwars.nl>
|
||||||
Martijn van Oosterhout <kleptog@svana.org>
|
Martijn van Oosterhout <kleptog@svana.org>
|
||||||
Martin Honermeyer <maze@strahlungsfrei.de>
|
Martin Honermeyer <maze@strahlungsfrei.de>
|
||||||
Martin Redmond <martin@tinychat.com>
|
Martin Redmond <martin@tinychat.com>
|
||||||
Mary Anthony <moxieandmore@gmail.com>
|
Mary Anthony <mary.anthony@docker.com>
|
||||||
|
Masahito Zembutsu <zembutsu@users.noreply.github.com>
|
||||||
Mason Malone <mason.malone@gmail.com>
|
Mason Malone <mason.malone@gmail.com>
|
||||||
Mateusz Sulima <sulima.mateusz@gmail.com>
|
Mateusz Sulima <sulima.mateusz@gmail.com>
|
||||||
Mathias Monnerville <mathias@monnerville.com>
|
Mathias Monnerville <mathias@monnerville.com>
|
||||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||||
Matt Apperson <me@mattapperson.com>
|
Matt Apperson <me@mattapperson.com>
|
||||||
Matt Bachmann <bachmann.matt@gmail.com>
|
Matt Bachmann <bachmann.matt@gmail.com>
|
||||||
|
Matt Bentley <mbentley@mbentley.net>
|
||||||
Matt Haggard <haggardii@gmail.com>
|
Matt Haggard <haggardii@gmail.com>
|
||||||
|
Matt McCormick <matt.mccormick@kitware.com>
|
||||||
Matthew Heon <mheon@redhat.com>
|
Matthew Heon <mheon@redhat.com>
|
||||||
|
Matthew Mayer <matthewkmayer@gmail.com>
|
||||||
Matthew Mueller <mattmuelle@gmail.com>
|
Matthew Mueller <mattmuelle@gmail.com>
|
||||||
Matthew Riley <mattdr@google.com>
|
Matthew Riley <mattdr@google.com>
|
||||||
Matthias Klumpp <matthias@tenstral.net>
|
Matthias Klumpp <matthias@tenstral.net>
|
||||||
Matthias Kühnle <git.nivoc@neverbox.com>
|
Matthias Kühnle <git.nivoc@neverbox.com>
|
||||||
mattymo <raytrac3r@gmail.com>
|
mattymo <raytrac3r@gmail.com>
|
||||||
mattyw <mattyw@me.com>
|
mattyw <mattyw@me.com>
|
||||||
|
mauriyouth <mauriyouth@gmail.com>
|
||||||
Max Shytikov <mshytikov@gmail.com>
|
Max Shytikov <mshytikov@gmail.com>
|
||||||
|
Maxim Kulkin <mkulkin@mirantis.com>
|
||||||
Maxim Treskin <zerthurd@gmail.com>
|
Maxim Treskin <zerthurd@gmail.com>
|
||||||
Maxime Petazzoni <max@signalfuse.com>
|
Maxime Petazzoni <max@signalfuse.com>
|
||||||
|
Meaglith Ma <genedna@gmail.com>
|
||||||
meejah <meejah@meejah.ca>
|
meejah <meejah@meejah.ca>
|
||||||
|
Megan Kostick <mkostick@us.ibm.com>
|
||||||
|
Mehul Kar <mehul.kar@gmail.com>
|
||||||
Mengdi Gao <usrgdd@gmail.com>
|
Mengdi Gao <usrgdd@gmail.com>
|
||||||
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
||||||
|
Michael A. Smith <michael@smith-li.com>
|
||||||
Michael Brown <michael@netdirect.ca>
|
Michael Brown <michael@netdirect.ca>
|
||||||
|
Michael Chiang <mchiang@docker.com>
|
||||||
Michael Crosby <michael@docker.com>
|
Michael Crosby <michael@docker.com>
|
||||||
Michael Gorsuch <gorsuch@github.com>
|
Michael Gorsuch <gorsuch@github.com>
|
||||||
Michael Hudson-Doyle <michael.hudson@linaro.org>
|
Michael Hudson-Doyle <michael.hudson@linaro.org>
|
||||||
@@ -491,26 +657,35 @@ Michael Scharf <github@scharf.gr>
|
|||||||
Michael Stapelberg <michael+gh@stapelberg.de>
|
Michael Stapelberg <michael+gh@stapelberg.de>
|
||||||
Michael Steinert <mike.steinert@gmail.com>
|
Michael Steinert <mike.steinert@gmail.com>
|
||||||
Michael Thies <michaelthies78@gmail.com>
|
Michael Thies <michaelthies78@gmail.com>
|
||||||
|
Michael West <mwest@mdsol.com>
|
||||||
|
Michal Fojtik <mfojtik@redhat.com>
|
||||||
Michal Jemala <michal.jemala@gmail.com>
|
Michal Jemala <michal.jemala@gmail.com>
|
||||||
Michal Minar <miminar@redhat.com>
|
Michal Minar <miminar@redhat.com>
|
||||||
Michaël Pailloncy <mpapo.dev@gmail.com>
|
Michaël Pailloncy <mpapo.dev@gmail.com>
|
||||||
Michiel@unhosted <michiel@unhosted.org>
|
Michiel@unhosted <michiel@unhosted.org>
|
||||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||||
|
Mihai Borobocea <MihaiBorobocea@gmail.com>
|
||||||
Mike Chelen <michael.chelen@gmail.com>
|
Mike Chelen <michael.chelen@gmail.com>
|
||||||
|
Mike Dillon <mike@embody.org>
|
||||||
Mike Gaffney <mike@uberu.com>
|
Mike Gaffney <mike@uberu.com>
|
||||||
|
Mike Leone <mleone896@gmail.com>
|
||||||
Mike MacCana <mike.maccana@gmail.com>
|
Mike MacCana <mike.maccana@gmail.com>
|
||||||
Mike Naberezny <mike@naberezny.com>
|
Mike Naberezny <mike@naberezny.com>
|
||||||
Mike Snitzer <snitzer@redhat.com>
|
Mike Snitzer <snitzer@redhat.com>
|
||||||
Mikhail Sobolev <mss@mawhrin.net>
|
Mikhail Sobolev <mss@mawhrin.net>
|
||||||
|
Mingzhen Feng <fmzhen@zju.edu.cn>
|
||||||
|
Mitch Capper <mitch.capper@gmail.com>
|
||||||
Mohit Soni <mosoni@ebay.com>
|
Mohit Soni <mosoni@ebay.com>
|
||||||
Morgante Pell <morgante.pell@morgante.net>
|
Morgante Pell <morgante.pell@morgante.net>
|
||||||
Morten Siebuhr <sbhr@sbhr.dk>
|
Morten Siebuhr <sbhr@sbhr.dk>
|
||||||
|
Moysés Borges <moyses.furtado@wplex.com.br>
|
||||||
Mrunal Patel <mrunalp@gmail.com>
|
Mrunal Patel <mrunalp@gmail.com>
|
||||||
mschurenko <matt.schurenko@gmail.com>
|
mschurenko <matt.schurenko@gmail.com>
|
||||||
Mustafa Akın <mustafa91@gmail.com>
|
Mustafa Akın <mustafa91@gmail.com>
|
||||||
Médi-Rémi Hashim <medimatrix@users.noreply.github.com>
|
Médi-Rémi Hashim <medimatrix@users.noreply.github.com>
|
||||||
Nan Monnand Deng <monnand@gmail.com>
|
Nan Monnand Deng <monnand@gmail.com>
|
||||||
Naoki Orii <norii@cs.cmu.edu>
|
Naoki Orii <norii@cs.cmu.edu>
|
||||||
|
Natalie Parker <nparker@omnifone.com>
|
||||||
Nate Eagleson <nate@nateeag.com>
|
Nate Eagleson <nate@nateeag.com>
|
||||||
Nate Jones <nate@endot.org>
|
Nate Jones <nate@endot.org>
|
||||||
Nathan Hsieh <hsieh.nathan@gmail.com>
|
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||||
@@ -518,8 +693,11 @@ Nathan Kleyn <nathan@nathankleyn.com>
|
|||||||
Nathan LeClaire <nathan.leclaire@docker.com>
|
Nathan LeClaire <nathan.leclaire@docker.com>
|
||||||
Neal McBurnett <neal@mcburnett.org>
|
Neal McBurnett <neal@mcburnett.org>
|
||||||
Nelson Chen <crazysim@gmail.com>
|
Nelson Chen <crazysim@gmail.com>
|
||||||
|
Nghia Tran <nghia@google.com>
|
||||||
Niall O'Higgins <niallo@unworkable.org>
|
Niall O'Higgins <niallo@unworkable.org>
|
||||||
Nicholas E. Rabenau <nerab@gmx.at>
|
Nicholas E. Rabenau <nerab@gmx.at>
|
||||||
|
Nick Irvine <nfirvine@nfirvine.com>
|
||||||
|
Nick Parker <nikaios@gmail.com>
|
||||||
Nick Payne <nick@kurai.co.uk>
|
Nick Payne <nick@kurai.co.uk>
|
||||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||||
Nick Stinemates <nick@stinemates.org>
|
Nick Stinemates <nick@stinemates.org>
|
||||||
@@ -528,8 +706,11 @@ Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
|||||||
Nicolas Goy <kuon@goyman.com>
|
Nicolas Goy <kuon@goyman.com>
|
||||||
Nicolas Kaiser <nikai@nikai.net>
|
Nicolas Kaiser <nikai@nikai.net>
|
||||||
NikolaMandic <mn080202@gmail.com>
|
NikolaMandic <mn080202@gmail.com>
|
||||||
|
nikolas <nnyby@columbia.edu>
|
||||||
noducks <onemannoducks@gmail.com>
|
noducks <onemannoducks@gmail.com>
|
||||||
Nolan Darilek <nolan@thewordnerd.info>
|
Nolan Darilek <nolan@thewordnerd.info>
|
||||||
|
nponeccop <andy.melnikov@gmail.com>
|
||||||
|
Nuutti Kotivuori <naked@iki.fi>
|
||||||
nzwsch <hi@nzwsch.com>
|
nzwsch <hi@nzwsch.com>
|
||||||
O.S. Tezer <ostezer@gmail.com>
|
O.S. Tezer <ostezer@gmail.com>
|
||||||
OddBloke <daniel@daniel-watkins.co.uk>
|
OddBloke <daniel@daniel-watkins.co.uk>
|
||||||
@@ -542,11 +723,14 @@ pandrew <letters@paulnotcom.se>
|
|||||||
panticz <mail@konczalski.de>
|
panticz <mail@konczalski.de>
|
||||||
Pascal Borreli <pascal@borreli.com>
|
Pascal Borreli <pascal@borreli.com>
|
||||||
Pascal Hartig <phartig@rdrei.net>
|
Pascal Hartig <phartig@rdrei.net>
|
||||||
|
Patrick Devine <patrick.devine@docker.com>
|
||||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||||
Patrick Stapleton <github@gdi2290.com>
|
Patrick Stapleton <github@gdi2290.com>
|
||||||
pattichen <craftsbear@gmail.com>
|
pattichen <craftsbear@gmail.com>
|
||||||
Paul <paul9869@gmail.com>
|
Paul <paul9869@gmail.com>
|
||||||
|
paul <paul@inkling.com>
|
||||||
Paul Annesley <paul@annesley.cc>
|
Paul Annesley <paul@annesley.cc>
|
||||||
|
Paul Bellamy <paul.a.bellamy@gmail.com>
|
||||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||||
Paul Hammond <paul@paulhammond.org>
|
Paul Hammond <paul@paulhammond.org>
|
||||||
Paul Jimenez <pj@place.org>
|
Paul Jimenez <pj@place.org>
|
||||||
@@ -554,11 +738,18 @@ Paul Lietar <paul@lietar.net>
|
|||||||
Paul Morie <pmorie@gmail.com>
|
Paul Morie <pmorie@gmail.com>
|
||||||
Paul Nasrat <pnasrat@gmail.com>
|
Paul Nasrat <pnasrat@gmail.com>
|
||||||
Paul Weaver <pauweave@cisco.com>
|
Paul Weaver <pauweave@cisco.com>
|
||||||
|
Pavel Lobashov <ShockwaveNN@gmail.com>
|
||||||
|
Pavel Tikhomirov <ptikhomirov@parallels.com>
|
||||||
Pavlos Ratis <dastergon@gentoo.org>
|
Pavlos Ratis <dastergon@gentoo.org>
|
||||||
|
Peggy Li <peggyli.224@gmail.com>
|
||||||
Peter Bourgon <peter@bourgon.org>
|
Peter Bourgon <peter@bourgon.org>
|
||||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||||
|
Peter Choi <reikani@Peters-MacBook-Pro.local>
|
||||||
|
Peter Dave Hello <PeterDaveHello@users.noreply.github.com>
|
||||||
Peter Ericson <pdericson@gmail.com>
|
Peter Ericson <pdericson@gmail.com>
|
||||||
|
Peter Esbensen <pkesbensen@gmail.com>
|
||||||
Peter Salvatore <peter@psftw.com>
|
Peter Salvatore <peter@psftw.com>
|
||||||
|
Peter Volpe <petervo@redhat.com>
|
||||||
Peter Waller <p@pwaller.net>
|
Peter Waller <p@pwaller.net>
|
||||||
Phil <underscorephil@gmail.com>
|
Phil <underscorephil@gmail.com>
|
||||||
Phil Estes <estesp@linux.vnet.ibm.com>
|
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||||
@@ -572,6 +763,7 @@ Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
|||||||
Piotr Bogdan <ppbogdan@gmail.com>
|
Piotr Bogdan <ppbogdan@gmail.com>
|
||||||
pixelistik <pixelistik@users.noreply.github.com>
|
pixelistik <pixelistik@users.noreply.github.com>
|
||||||
Porjo <porjo38@yahoo.com.au>
|
Porjo <porjo38@yahoo.com.au>
|
||||||
|
Pradeep Chhetri <pradeep@indix.com>
|
||||||
Prasanna Gautam <prasannagautam@gmail.com>
|
Prasanna Gautam <prasannagautam@gmail.com>
|
||||||
Przemek Hejman <przemyslaw.hejman@gmail.com>
|
Przemek Hejman <przemyslaw.hejman@gmail.com>
|
||||||
pysqz <randomq@126.com>
|
pysqz <randomq@126.com>
|
||||||
@@ -580,6 +772,7 @@ Quentin Brossard <qbrossard@gmail.com>
|
|||||||
r0n22 <cameron.regan@gmail.com>
|
r0n22 <cameron.regan@gmail.com>
|
||||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||||
Rafe Colton <rafael.colton@gmail.com>
|
Rafe Colton <rafael.colton@gmail.com>
|
||||||
|
Raghuram Devarakonda <draghuram@gmail.com>
|
||||||
Rajat Pandit <rp@rajatpandit.com>
|
Rajat Pandit <rp@rajatpandit.com>
|
||||||
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
||||||
Ralph Bean <rbean@redhat.com>
|
Ralph Bean <rbean@redhat.com>
|
||||||
@@ -588,13 +781,19 @@ Ramon van Alteren <ramon@vanalteren.nl>
|
|||||||
Recursive Madman <recursive.madman@gmx.de>
|
Recursive Madman <recursive.madman@gmx.de>
|
||||||
Remi Rampin <remirampin@gmail.com>
|
Remi Rampin <remirampin@gmail.com>
|
||||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||||
|
resouer <resouer@163.com>
|
||||||
rgstephens <greg@udon.org>
|
rgstephens <greg@udon.org>
|
||||||
Rhys Hiltner <rhys@twitch.tv>
|
Rhys Hiltner <rhys@twitch.tv>
|
||||||
|
Rich Seymour <rseymour@gmail.com>
|
||||||
|
Richard <richard.scothern@gmail.com>
|
||||||
|
Richard Burnison <rburnison@ebay.com>
|
||||||
Richard Harvey <richard@squarecows.com>
|
Richard Harvey <richard@squarecows.com>
|
||||||
Richard Metzler <richard@paadee.com>
|
Richard Metzler <richard@paadee.com>
|
||||||
Richo Healey <richo@psych0tik.net>
|
Richo Healey <richo@psych0tik.net>
|
||||||
Rick Bradley <rick@users.noreply.github.com>
|
Rick Bradley <rick@users.noreply.github.com>
|
||||||
Rick van de Loo <rickvandeloo@gmail.com>
|
Rick van de Loo <rickvandeloo@gmail.com>
|
||||||
|
Rick Wieman <git@rickw.nl>
|
||||||
|
Rik Nijessen <rik@keefo.nl>
|
||||||
Robert Bachmann <rb@robertbachmann.at>
|
Robert Bachmann <rb@robertbachmann.at>
|
||||||
Robert Bittle <guywithnose@gmail.com>
|
Robert Bittle <guywithnose@gmail.com>
|
||||||
Robert Obryk <robryk@gmail.com>
|
Robert Obryk <robryk@gmail.com>
|
||||||
@@ -608,6 +807,7 @@ Rohit Jnagal <jnagal@google.com>
|
|||||||
Roland Huß <roland@jolokia.org>
|
Roland Huß <roland@jolokia.org>
|
||||||
Roland Moriz <rmoriz@users.noreply.github.com>
|
Roland Moriz <rmoriz@users.noreply.github.com>
|
||||||
Ron Smits <ron.smits@gmail.com>
|
Ron Smits <ron.smits@gmail.com>
|
||||||
|
root <docker-dummy@example.com>
|
||||||
Rovanion Luckey <rovanion.luckey@gmail.com>
|
Rovanion Luckey <rovanion.luckey@gmail.com>
|
||||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||||
Ryan Anderson <anderson.ryanc@gmail.com>
|
Ryan Anderson <anderson.ryanc@gmail.com>
|
||||||
@@ -618,6 +818,11 @@ Ryan O'Donnell <odonnellryanc@gmail.com>
|
|||||||
Ryan Seto <ryanseto@yak.net>
|
Ryan Seto <ryanseto@yak.net>
|
||||||
Ryan Thomas <rthomas@atlassian.com>
|
Ryan Thomas <rthomas@atlassian.com>
|
||||||
Rémy Greinhofer <remy.greinhofer@livelovely.com>
|
Rémy Greinhofer <remy.greinhofer@livelovely.com>
|
||||||
|
s. rannou <mxs@sbrk.org>
|
||||||
|
s00318865 <sunyuan3@huawei.com>
|
||||||
|
Sabin Basyal <sabin.basyal@gmail.com>
|
||||||
|
Sachin Joshi <sachin_jayant_joshi@hotmail.com>
|
||||||
|
Sam Abed <sam.abed@gmail.com>
|
||||||
Sam Alba <sam.alba@gmail.com>
|
Sam Alba <sam.alba@gmail.com>
|
||||||
Sam Bailey <cyprix@cyprix.com.au>
|
Sam Bailey <cyprix@cyprix.com.au>
|
||||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||||
@@ -626,6 +831,9 @@ Sam Rijs <srijs@airpost.net>
|
|||||||
Sami Wagiaalla <swagiaal@redhat.com>
|
Sami Wagiaalla <swagiaal@redhat.com>
|
||||||
Samuel Andaya <samuel@andaya.net>
|
Samuel Andaya <samuel@andaya.net>
|
||||||
Samuel PHAN <samuel-phan@users.noreply.github.com>
|
Samuel PHAN <samuel-phan@users.noreply.github.com>
|
||||||
|
Sankar சங்கர் <sankar.curiosity@gmail.com>
|
||||||
|
Sanket Saurav <sanketsaurav@gmail.com>
|
||||||
|
sapphiredev <se.imas.kr@gmail.com>
|
||||||
Satnam Singh <satnam@raintown.org>
|
Satnam Singh <satnam@raintown.org>
|
||||||
satoru <satorulogic@gmail.com>
|
satoru <satorulogic@gmail.com>
|
||||||
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
||||||
@@ -634,26 +842,35 @@ Scott Collier <emailscottcollier@gmail.com>
|
|||||||
Scott Johnston <scott@docker.com>
|
Scott Johnston <scott@docker.com>
|
||||||
Scott Stamp <scottstamp851@gmail.com>
|
Scott Stamp <scottstamp851@gmail.com>
|
||||||
Scott Walls <sawalls@umich.edu>
|
Scott Walls <sawalls@umich.edu>
|
||||||
|
sdreyesg <sdreyesg@gmail.com>
|
||||||
Sean Cronin <seancron@gmail.com>
|
Sean Cronin <seancron@gmail.com>
|
||||||
Sean P. Kane <skane@newrelic.com>
|
Sean P. Kane <skane@newrelic.com>
|
||||||
|
Sebastiaan van Steenis <mail@superseb.nl>
|
||||||
Sebastiaan van Stijn <github@gone.nl>
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
Senthil Kumar Selvaraj <senthil.thecoder@gmail.com>
|
Senthil Kumar Selvaraj <senthil.thecoder@gmail.com>
|
||||||
SeongJae Park <sj38.park@gmail.com>
|
SeongJae Park <sj38.park@gmail.com>
|
||||||
|
Seongyeol Lim <seongyeol37@gmail.com>
|
||||||
|
Sergey Alekseev <sergey.alekseev.minsk@gmail.com>
|
||||||
|
Sergey Evstifeev <sergey.evstifeev@gmail.com>
|
||||||
Shane Canon <scanon@lbl.gov>
|
Shane Canon <scanon@lbl.gov>
|
||||||
shaunol <shaunol@gmail.com>
|
shaunol <shaunol@gmail.com>
|
||||||
Shawn Landden <shawn@churchofgit.com>
|
Shawn Landden <shawn@churchofgit.com>
|
||||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||||
|
Shijiang Wei <mountkin@gmail.com>
|
||||||
Shishir Mahajan <shishir.mahajan@redhat.com>
|
Shishir Mahajan <shishir.mahajan@redhat.com>
|
||||||
shuai-z <zs.broccoli@gmail.com>
|
shuai-z <zs.broccoli@gmail.com>
|
||||||
|
sidharthamani <sid@rancher.com>
|
||||||
Silas Sewell <silas@sewell.org>
|
Silas Sewell <silas@sewell.org>
|
||||||
|
Simei He <hesimei@zju.edu.cn>
|
||||||
|
Simon Eskildsen <sirup@sirupsen.com>
|
||||||
|
Simon Leinen <simon.leinen@gmail.com>
|
||||||
Simon Taranto <simon.taranto@gmail.com>
|
Simon Taranto <simon.taranto@gmail.com>
|
||||||
Sindhu S <sindhus@live.in>
|
Sindhu S <sindhus@live.in>
|
||||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||||
Solomon Hykes <solomon@docker.com>
|
Solomon Hykes <solomon@docker.com>
|
||||||
Song Gao <song@gao.io>
|
Song Gao <song@gao.io>
|
||||||
Soulou <leo@unbekandt.eu>
|
Soulou <leo@unbekandt.eu>
|
||||||
soulshake <amy@gandi.net>
|
|
||||||
Sridatta Thatipamala <sthatipamala@gmail.com>
|
Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||||
Srini Brahmaroutu <sbrahma@us.ibm.com>
|
Srini Brahmaroutu <sbrahma@us.ibm.com>
|
||||||
@@ -661,20 +878,30 @@ Srini Brahmaroutu <srbrahma@us.ibm.com>
|
|||||||
Steeve Morin <steeve.morin@gmail.com>
|
Steeve Morin <steeve.morin@gmail.com>
|
||||||
Stefan Praszalowicz <stefan@greplin.com>
|
Stefan Praszalowicz <stefan@greplin.com>
|
||||||
Stephen Crosby <stevecrozz@gmail.com>
|
Stephen Crosby <stevecrozz@gmail.com>
|
||||||
|
Stephen J Day <stephen.day@docker.com>
|
||||||
|
Steve Francia <steve.francia@gmail.com>
|
||||||
|
Steve Koch <stevekochscience@gmail.com>
|
||||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||||
Steven Merrill <steven.merrill@gmail.com>
|
Steven Merrill <steven.merrill@gmail.com>
|
||||||
|
Steven Richards <steven@axiomzen.co>
|
||||||
|
Steven Taylor <steven.taylor@me.com>
|
||||||
Sven Dowideit <SvenDowideit@home.org.au>
|
Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
|
Swapnil Daingade <swapnil.daingade@gmail.com>
|
||||||
|
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||||
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
||||||
Sébastien <sebastien@yoozio.com>
|
Sébastien <sebastien@yoozio.com>
|
||||||
Sébastien Luttringer <seblu@seblu.net>
|
Sébastien Luttringer <seblu@seblu.net>
|
||||||
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
||||||
tang0th <tang0th@gmx.com>
|
tang0th <tang0th@gmx.com>
|
||||||
|
Tangi COLIN <tangicolin@gmail.com>
|
||||||
Tatsuki Sugiura <sugi@nemui.org>
|
Tatsuki Sugiura <sugi@nemui.org>
|
||||||
Tatsushi Inagaki <e29253@jp.ibm.com>
|
Tatsushi Inagaki <e29253@jp.ibm.com>
|
||||||
Ted M. Young <tedyoung@gmail.com>
|
Ted M. Young <tedyoung@gmail.com>
|
||||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||||
Tejesh Mehta <tejesh.mehta@gmail.com>
|
Tejesh Mehta <tejesh.mehta@gmail.com>
|
||||||
Thatcher Peskens <thatcher@docker.com>
|
Thatcher Peskens <thatcher@docker.com>
|
||||||
|
theadactyl <thea.lamkin@gmail.com>
|
||||||
|
Thell 'Bo' Fowler <thell@tbfowler.name>
|
||||||
Thermionix <bond711@gmail.com>
|
Thermionix <bond711@gmail.com>
|
||||||
Thijs Terlouw <thijsterlouw@gmail.com>
|
Thijs Terlouw <thijsterlouw@gmail.com>
|
||||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||||
@@ -683,8 +910,11 @@ Thomas Hansen <thomas.hansen@gmail.com>
|
|||||||
Thomas LEVEIL <thomasleveil@gmail.com>
|
Thomas LEVEIL <thomasleveil@gmail.com>
|
||||||
Thomas Orozco <thomas@orozco.fr>
|
Thomas Orozco <thomas@orozco.fr>
|
||||||
Thomas Schroeter <thomas@cliqz.com>
|
Thomas Schroeter <thomas@cliqz.com>
|
||||||
|
Thomas Sjögren <konstruktoid@users.noreply.github.com>
|
||||||
|
Thomas Texier <sharkone@en-mousse.org>
|
||||||
Tianon Gravi <admwiggin@gmail.com>
|
Tianon Gravi <admwiggin@gmail.com>
|
||||||
Tibor Vass <teabee89@gmail.com>
|
Tibor Vass <teabee89@gmail.com>
|
||||||
|
Tiffany Low <tiffany@box.com>
|
||||||
Tim Bosse <taim@bosboot.org>
|
Tim Bosse <taim@bosboot.org>
|
||||||
Tim Hockin <thockin@google.com>
|
Tim Hockin <thockin@google.com>
|
||||||
Tim Ruffles <oi@truffles.me.uk>
|
Tim Ruffles <oi@truffles.me.uk>
|
||||||
@@ -698,6 +928,7 @@ Tobias Gesellchen <tobias@gesellix.de>
|
|||||||
Tobias Schmidt <ts@soundcloud.com>
|
Tobias Schmidt <ts@soundcloud.com>
|
||||||
Tobias Schwab <tobias.schwab@dynport.de>
|
Tobias Schwab <tobias.schwab@dynport.de>
|
||||||
Todd Lunter <tlunter@gmail.com>
|
Todd Lunter <tlunter@gmail.com>
|
||||||
|
Todd Whiteman <todd.whiteman@joyent.com>
|
||||||
Tom Fotherby <tom+github@peopleperhour.com>
|
Tom Fotherby <tom+github@peopleperhour.com>
|
||||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||||
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
||||||
@@ -705,16 +936,22 @@ Tomas Tomecek <ttomecek@redhat.com>
|
|||||||
Tomasz Lipinski <tlipinski@users.noreply.github.com>
|
Tomasz Lipinski <tlipinski@users.noreply.github.com>
|
||||||
Tomasz Nurkiewicz <nurkiewicz@gmail.com>
|
Tomasz Nurkiewicz <nurkiewicz@gmail.com>
|
||||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||||
|
Tomáš Hrčka <thrcka@redhat.com>
|
||||||
Tonis Tiigi <tonistiigi@gmail.com>
|
Tonis Tiigi <tonistiigi@gmail.com>
|
||||||
|
Tonny Xu <tonny.xu@gmail.com>
|
||||||
Tony Daws <tony@daws.ca>
|
Tony Daws <tony@daws.ca>
|
||||||
|
Tony Miller <mcfiredrill@gmail.com>
|
||||||
Torstein Husebø <torstein@huseboe.net>
|
Torstein Husebø <torstein@huseboe.net>
|
||||||
tpng <benny.tpng@gmail.com>
|
tpng <benny.tpng@gmail.com>
|
||||||
Travis Cline <travis.cline@gmail.com>
|
Travis Cline <travis.cline@gmail.com>
|
||||||
|
Travis Thieman <travis.thieman@gmail.com>
|
||||||
Trent Ogren <tedwardo2@gmail.com>
|
Trent Ogren <tedwardo2@gmail.com>
|
||||||
|
Tristan Carel <tristan.carel@gmail.com>
|
||||||
Tyler Brock <tyler.brock@gmail.com>
|
Tyler Brock <tyler.brock@gmail.com>
|
||||||
Tzu-Jung Lee <roylee17@gmail.com>
|
Tzu-Jung Lee <roylee17@gmail.com>
|
||||||
Ulysse Carion <ulyssecarion@gmail.com>
|
Ulysse Carion <ulyssecarion@gmail.com>
|
||||||
unknown <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
unknown <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
||||||
|
vagrant <vagrant@ubuntu-14.04-amd64-vbox>
|
||||||
Vaidas Jablonskis <jablonskis@gmail.com>
|
Vaidas Jablonskis <jablonskis@gmail.com>
|
||||||
vgeta <gopikannan.venugopalsamy@gmail.com>
|
vgeta <gopikannan.venugopalsamy@gmail.com>
|
||||||
Victor Coisne <victor.coisne@dotcloud.com>
|
Victor Coisne <victor.coisne@dotcloud.com>
|
||||||
@@ -725,6 +962,7 @@ Viktor Vojnovski <viktor.vojnovski@amadeus.com>
|
|||||||
Vincent Batts <vbatts@redhat.com>
|
Vincent Batts <vbatts@redhat.com>
|
||||||
Vincent Bernat <bernat@luffy.cx>
|
Vincent Bernat <bernat@luffy.cx>
|
||||||
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||||
|
Vincent Demeester <vincent@sbr.pm>
|
||||||
Vincent Giersch <vincent.giersch@ovh.net>
|
Vincent Giersch <vincent.giersch@ovh.net>
|
||||||
Vincent Mayers <vincent.mayers@inbloom.org>
|
Vincent Mayers <vincent.mayers@inbloom.org>
|
||||||
Vincent Woo <me@vincentwoo.com>
|
Vincent Woo <me@vincentwoo.com>
|
||||||
@@ -738,6 +976,7 @@ Vivek Goyal <vgoyal@redhat.com>
|
|||||||
Vladimir Bulyga <xx@ccxx.cc>
|
Vladimir Bulyga <xx@ccxx.cc>
|
||||||
Vladimir Kirillov <proger@wilab.org.ua>
|
Vladimir Kirillov <proger@wilab.org.ua>
|
||||||
Vladimir Rutsky <altsysrq@gmail.com>
|
Vladimir Rutsky <altsysrq@gmail.com>
|
||||||
|
VladimirAus <v_roudakov@yahoo.com>
|
||||||
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
|
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
|
||||||
waitingkuo <waitingkuo0527@gmail.com>
|
waitingkuo <waitingkuo0527@gmail.com>
|
||||||
Walter Leibbrandt <github@wrl.co.za>
|
Walter Leibbrandt <github@wrl.co.za>
|
||||||
@@ -745,25 +984,46 @@ Walter Stanish <walter@pratyeka.org>
|
|||||||
Ward Vandewege <ward@jhvc.com>
|
Ward Vandewege <ward@jhvc.com>
|
||||||
WarheadsSE <max@warheads.net>
|
WarheadsSE <max@warheads.net>
|
||||||
Wayne Chang <wayne@neverfear.org>
|
Wayne Chang <wayne@neverfear.org>
|
||||||
|
Wei-Ting Kuo <waitingkuo0527@gmail.com>
|
||||||
Wes Morgan <cap10morgan@gmail.com>
|
Wes Morgan <cap10morgan@gmail.com>
|
||||||
Will Dietz <w@wdtz.org>
|
Will Dietz <w@wdtz.org>
|
||||||
Will Rouesnel <w.rouesnel@gmail.com>
|
Will Rouesnel <w.rouesnel@gmail.com>
|
||||||
Will Weaver <monkey@buildingbananas.com>
|
Will Weaver <monkey@buildingbananas.com>
|
||||||
|
willhf <willhf@gmail.com>
|
||||||
William Delanoue <william.delanoue@gmail.com>
|
William Delanoue <william.delanoue@gmail.com>
|
||||||
William Henry <whenry@redhat.com>
|
William Henry <whenry@redhat.com>
|
||||||
William Riancho <wr.wllm@gmail.com>
|
William Riancho <wr.wllm@gmail.com>
|
||||||
William Thurston <thurstw@amazon.com>
|
William Thurston <thurstw@amazon.com>
|
||||||
|
WiseTrem <shepelyov.g@gmail.com>
|
||||||
|
wlan0 <sidharthamn@gmail.com>
|
||||||
|
Wolfgang Powisch <powo@powo.priv.at>
|
||||||
|
wonderflow <wonderflow.sun@gmail.com>
|
||||||
|
xamyzhao <x.amy.zhao@gmail.com>
|
||||||
|
XiaoBing Jiang <s7v7nislands@gmail.com>
|
||||||
|
Xinzi Zhou <imdreamrunner@gmail.com>
|
||||||
Xiuming Chen <cc@cxm.cc>
|
Xiuming Chen <cc@cxm.cc>
|
||||||
xuzhaokui <cynicholas@gmail.com>
|
xuzhaokui <cynicholas@gmail.com>
|
||||||
|
y00277921 <yuchangchun1@huawei.com>
|
||||||
|
Yahya <ya7yaz@gmail.com>
|
||||||
|
YAMADA Tsuyoshi <tyamada@minimum2scp.org>
|
||||||
|
Yan Feng <yanfeng2@huawei.com>
|
||||||
Yang Bai <hamo.by@gmail.com>
|
Yang Bai <hamo.by@gmail.com>
|
||||||
Yasunori Mahata <nori@mahata.net>
|
Yasunori Mahata <nori@mahata.net>
|
||||||
|
Yestin Sun <sunyi0804@gmail.com>
|
||||||
|
Yihang Ho <hoyihang5@gmail.com>
|
||||||
Yohei Ueda <yohei@jp.ibm.com>
|
Yohei Ueda <yohei@jp.ibm.com>
|
||||||
|
Yongzhi Pan <panyongzhi@gmail.com>
|
||||||
|
Yuan Sun <sunyuan3@huawei.com>
|
||||||
Yurii Rashkovskii <yrashk@gmail.com>
|
Yurii Rashkovskii <yrashk@gmail.com>
|
||||||
Zac Dover <zdover@redhat.com>
|
Zac Dover <zdover@redhat.com>
|
||||||
Zach Borboa <zachborboa@gmail.com>
|
Zach Borboa <zachborboa@gmail.com>
|
||||||
Zain Memon <zain@inzain.net>
|
Zain Memon <zain@inzain.net>
|
||||||
Zaiste! <oh@zaiste.net>
|
Zaiste! <oh@zaiste.net>
|
||||||
Zane DeGraffenried <zane.deg@gmail.com>
|
Zane DeGraffenried <zane.deg@gmail.com>
|
||||||
|
Zefan Li <lizefan@huawei.com>
|
||||||
|
Zen Lin(Zhinan Lin) <linzhinan@huawei.com>
|
||||||
|
Zhang Wei <zhangwei555@huawei.com>
|
||||||
|
Zhang Wentao <zhangwentao234@huawei.com>
|
||||||
Zilin Du <zilin.du@gmail.com>
|
Zilin Du <zilin.du@gmail.com>
|
||||||
zimbatm <zimbatm@zimbatm.com>
|
zimbatm <zimbatm@zimbatm.com>
|
||||||
Zoltan Tombol <zoltan.tombol@gmail.com>
|
Zoltan Tombol <zoltan.tombol@gmail.com>
|
||||||
|
|||||||
+125
-10
@@ -1,11 +1,126 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.8.0 (2015-08-11)
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
|
||||||
|
+ Trusted pull, push and build, disabled by default
|
||||||
|
* Make tar layers deterministic between registries
|
||||||
|
* Don't allow deleting the image of running containers
|
||||||
|
* Check if a tag name to load is a valid digest
|
||||||
|
* Allow one character repository names
|
||||||
|
* Add a more accurate error description for invalid tag name
|
||||||
|
* Make build cache ignore mtime
|
||||||
|
|
||||||
|
### Cli
|
||||||
|
|
||||||
|
+ Add support for DOCKER_CONFIG/--config to specify config file dir
|
||||||
|
+ Add --type flag for docker inspect command
|
||||||
|
+ Add formatting options to `docker ps` with `--format`
|
||||||
|
+ Replace `docker -d` with new subcommand `docker daemon`
|
||||||
|
* Zsh completion updates and improvements
|
||||||
|
* Add some missing events to bash completion
|
||||||
|
* Support daemon urls with base paths in `docker -H`
|
||||||
|
* Validate status= filter to docker ps
|
||||||
|
* Display when a container is in --net=host in docker ps
|
||||||
|
* Extend docker inspect to export image metadata related to graph driver
|
||||||
|
* Restore --default-gateway{,-v6} daemon options
|
||||||
|
* Add missing unpublished ports in docker ps
|
||||||
|
* Allow duration strings in `docker events` as --since/--until
|
||||||
|
* Expose more mounts information in `docker inspect`
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
+ Add new Fluentd logging driver
|
||||||
|
+ Allow `docker import` to load from local files
|
||||||
|
+ Add logging driver for GELF via UDP
|
||||||
|
+ Allow to copy files from host to containers with `docker cp`
|
||||||
|
+ Promote volume drivers from experimental to master
|
||||||
|
+ Add rollover log driver, and --log-driver-opts flag
|
||||||
|
+ Add memory swappiness tuning options
|
||||||
|
* Remove cgroup read-only flag when privileged
|
||||||
|
* Make /proc, /sys, & /dev readonly for readonly containers
|
||||||
|
* Add cgroup bind mount by default
|
||||||
|
* Overlay: Export metadata for container and image in `docker inspect`
|
||||||
|
* Devicemapper: external device activation
|
||||||
|
* Devicemapper: Compare uuid of base device on startup
|
||||||
|
* Remove RC4 from the list of registry cipher suites
|
||||||
|
* Add syslog-facility option
|
||||||
|
* LXC execdriver compatibility with recent LXC versions
|
||||||
|
* Mark LXC execriver as deprecated (to be removed with the migration to runc)
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
* Separate plugin sockets and specs locations
|
||||||
|
* Allow TLS connections to plugins
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Add missing 'Names' field to /containers/json API output
|
||||||
|
- Make `docker rmi --dangling` safe when pulling
|
||||||
|
- Devicemapper: Change default basesize to 100G
|
||||||
|
- Go Scheduler issue with sync.Mutex and gcc
|
||||||
|
- Fix issue where Search API endpoint would panic due to empty AuthConfig
|
||||||
|
- Set image canonical names correctly
|
||||||
|
- Check dockerinit only if lxc driver is used
|
||||||
|
- Fix ulimit usage of nproc
|
||||||
|
- Always attach STDIN if -i,--interactive is specified
|
||||||
|
- Show error messages when saving container state fails
|
||||||
|
- Fixed incorrect assumption on --bridge=none treated as disable network
|
||||||
|
- Check for invalid port specifications in host configuration
|
||||||
|
- Fix endpoint leave failure for --net=host mode
|
||||||
|
- Fix goroutine leak in the stats API if the container is not running
|
||||||
|
- Check for apparmor file before reading it
|
||||||
|
- Fix DOCKER_TLS_VERIFY being ignored
|
||||||
|
- Set umask to the default on startup
|
||||||
|
- Correct the message of pause and unpause a non-running container
|
||||||
|
- Adjust disallowed CpuShares in container creation
|
||||||
|
- ZFS: correctly apply selinux context
|
||||||
|
- Display empty string instead of <nil> when IP opt is nil
|
||||||
|
- `docker kill` returns error when container is not running
|
||||||
|
- Fix COPY/ADD quoted/json form
|
||||||
|
- Fix goroutine leak on logs -f with no output
|
||||||
|
- Remove panic in nat package on invalid hostport
|
||||||
|
- Fix container linking in Fedora 22
|
||||||
|
- Fix error caused using default gateways outside of the allocated range
|
||||||
|
- Format times in inspect command with a template as RFC3339Nano
|
||||||
|
- Make registry client to accept 2xx and 3xx http status responses as successful
|
||||||
|
- Fix race issue that caused the daemon to crash with certain layer downloads failed in a specific order.
|
||||||
|
- Fix error when the docker ps format was not valid.
|
||||||
|
- Remove redundant ip forward check.
|
||||||
|
- Fix issue trying to push images to repository mirrors.
|
||||||
|
- Fix error cleaning up network entrypoints when there is an initialization issue.
|
||||||
|
|
||||||
|
## 1.7.1 (2015-07-14)
|
||||||
|
|
||||||
|
#### Runtime
|
||||||
|
|
||||||
|
- Fix default user spawning exec process with `docker exec`
|
||||||
|
- Make `--bridge=none` not to configure the network bridge
|
||||||
|
- Publish networking stats properly
|
||||||
|
- Fix implicit devicemapper selection with static binaries
|
||||||
|
- Fix socket connections that hung intermittently
|
||||||
|
- Fix bridge interface creation on CentOS/RHEL 6.6
|
||||||
|
- Fix local dns lookups added to resolv.conf
|
||||||
|
- Fix copy command mounting volumes
|
||||||
|
- Fix read/write privileges in volumes mounted with --volumes-from
|
||||||
|
|
||||||
|
#### Remote API
|
||||||
|
|
||||||
|
- Fix unmarshalling of Command and Entrypoint
|
||||||
|
- Set limit for minimum client version supported
|
||||||
|
- Validate port specification
|
||||||
|
- Return proper errors when attach/reattach fail
|
||||||
|
|
||||||
|
#### Distribution
|
||||||
|
|
||||||
|
- Fix pulling private images
|
||||||
|
- Fix fallback between registry V2 and V1
|
||||||
|
|
||||||
## 1.7.0 (2015-06-16)
|
## 1.7.0 (2015-06-16)
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
+ Experimental feature: support for out-of-process volume plugins
|
+ Experimental feature: support for out-of-process volume plugins
|
||||||
+ Experimental feature: support for out-of-process network plugins
|
|
||||||
* Logging: syslog logging driver is available
|
|
||||||
* The userland proxy can be disabled in favor of hairpin NAT using the daemon’s `--userland-proxy=false` flag
|
* The userland proxy can be disabled in favor of hairpin NAT using the daemon’s `--userland-proxy=false` flag
|
||||||
* The `exec` command supports the `-u|--user` flag to specify the new process owner
|
* The `exec` command supports the `-u|--user` flag to specify the new process owner
|
||||||
+ Default gateway for containers can be specified daemon-wide using the `--default-gateway` and `--default-gateway-v6` flags
|
+ Default gateway for containers can be specified daemon-wide using the `--default-gateway` and `--default-gateway-v6` flags
|
||||||
@@ -17,7 +132,7 @@
|
|||||||
|
|
||||||
#### Quality
|
#### Quality
|
||||||
* Networking stack was entirely rewritten as part of the libnetwork effort
|
* Networking stack was entirely rewritten as part of the libnetwork effort
|
||||||
* Engine internals refactoring (shout out to the contributors?)
|
* Engine internals refactoring
|
||||||
* Volumes code was entirely rewritten to support the plugins effort
|
* Volumes code was entirely rewritten to support the plugins effort
|
||||||
+ Sending SIGUSR1 to a daemon will dump all goroutines stacks without exiting
|
+ Sending SIGUSR1 to a daemon will dump all goroutines stacks without exiting
|
||||||
|
|
||||||
@@ -49,7 +164,7 @@
|
|||||||
- Prohibit mount of /sys
|
- Prohibit mount of /sys
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
- Update Apparmor policy to not allow mounts
|
- Update AppArmor policy to not allow mounts
|
||||||
|
|
||||||
## 1.6.0 (2015-04-07)
|
## 1.6.0 (2015-04-07)
|
||||||
|
|
||||||
@@ -343,7 +458,7 @@
|
|||||||
|
|
||||||
#### Hack
|
#### Hack
|
||||||
* Clean up "go test" output from "make test" to be much more readable/scannable.
|
* Clean up "go test" output from "make test" to be much more readable/scannable.
|
||||||
* Excluse more "definitely not unit tested Go source code" directories from hack/make/test.
|
* Exclude more "definitely not unit tested Go source code" directories from hack/make/test.
|
||||||
+ Generate md5 and sha256 hashes when building, and upload them via hack/release.sh.
|
+ Generate md5 and sha256 hashes when building, and upload them via hack/release.sh.
|
||||||
- Include contributed completions in Ubuntu PPA.
|
- Include contributed completions in Ubuntu PPA.
|
||||||
+ Add cli integration tests.
|
+ Add cli integration tests.
|
||||||
@@ -623,7 +738,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
* The ADD instruction now supports caching, which avoids unnecessarily re-uploading the same source content again and again when it hasn’t changed
|
* The ADD instruction now supports caching, which avoids unnecessarily re-uploading the same source content again and again when it hasn’t changed
|
||||||
* The new ONBUILD instruction adds to your image a “trigger” instruction to be executed at a later time, when the image is used as the base for another build
|
* The new ONBUILD instruction adds to your image a “trigger” instruction to be executed at a later time, when the image is used as the base for another build
|
||||||
* Docker now ships with an experimental storage driver which uses the BTRFS filesystem for copy-on-write
|
* Docker now ships with an experimental storage driver which uses the BTRFS filesystem for copy-on-write
|
||||||
* Docker is officially supported on Mac OSX
|
* Docker is officially supported on Mac OS X
|
||||||
* The Docker daemon supports systemd socket activation
|
* The Docker daemon supports systemd socket activation
|
||||||
|
|
||||||
## 0.7.6 (2014-01-14)
|
## 0.7.6 (2014-01-14)
|
||||||
@@ -677,12 +792,12 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
- Fix ADD caching issue with . prefixed path
|
- Fix ADD caching issue with . prefixed path
|
||||||
- Fix docker build on devicemapper by reverting sparse file tar option
|
- Fix docker build on devicemapper by reverting sparse file tar option
|
||||||
- Fix issue with file caching and prevent wrong cache hit
|
- Fix issue with file caching and prevent wrong cache hit
|
||||||
* Use same error handling while unmarshalling CMD and ENTRYPOINT
|
* Use same error handling while unmarshalling CMD and ENTRYPOINT
|
||||||
|
|
||||||
#### Documentation
|
#### Documentation
|
||||||
|
|
||||||
* Simplify and streamline Amazon Quickstart
|
* Simplify and streamline Amazon Quickstart
|
||||||
* Install instructions use unprefixed fedora image
|
* Install instructions use unprefixed Fedora image
|
||||||
* Update instructions for mtu flag for Docker on GCE
|
* Update instructions for mtu flag for Docker on GCE
|
||||||
+ Add Ubuntu Saucy to installation
|
+ Add Ubuntu Saucy to installation
|
||||||
- Fix for wrong version warning on master instead of latest
|
- Fix for wrong version warning on master instead of latest
|
||||||
@@ -857,7 +972,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
* Improve unit tests
|
* Improve unit tests
|
||||||
* The test suite now runs all tests even if one fails
|
* The test suite now runs all tests even if one fails
|
||||||
* Refactor C in Go (Devmapper)
|
* Refactor C in Go (Devmapper)
|
||||||
- Fix OSX compilation
|
- Fix OS X compilation
|
||||||
|
|
||||||
## 0.7.0 (2013-11-25)
|
## 0.7.0 (2013-11-25)
|
||||||
|
|
||||||
@@ -875,7 +990,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
|
|
||||||
* Improve stability, fixes some race conditons
|
* Improve stability, fixes some race conditions
|
||||||
* Skip the volumes mounted when deleting the volumes of container.
|
* Skip the volumes mounted when deleting the volumes of container.
|
||||||
* Fix layer size computation: handle hard links correctly
|
* Fix layer size computation: handle hard links correctly
|
||||||
* Use the work Path for docker cp CONTAINER:PATH
|
* Use the work Path for docker cp CONTAINER:PATH
|
||||||
|
|||||||
+53
-5
@@ -4,7 +4,7 @@ Want to hack on Docker? Awesome! We have a contributor's guide that explains
|
|||||||
[setting up a Docker development environment and the contribution
|
[setting up a Docker development environment and the contribution
|
||||||
process](https://docs.docker.com/project/who-written-for/).
|
process](https://docs.docker.com/project/who-written-for/).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This page contains information about reporting issues as well as some tips and
|
This page contains information about reporting issues as well as some tips and
|
||||||
guidelines useful to experienced open source contributors. Finally, make sure
|
guidelines useful to experienced open source contributors. Finally, make sure
|
||||||
@@ -28,8 +28,8 @@ 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
|
||||||
us know We currently do not offer a paid security bounty program, but are not
|
us know. We currently do not offer a paid security bounty program, but are not
|
||||||
ruling it out in the future.
|
ruling it out in the future.
|
||||||
|
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ set of patches that should be reviewed together: for example, upgrading the
|
|||||||
version of a vendored dependency and taking advantage of its now available new
|
version of a vendored dependency and taking advantage of its now available new
|
||||||
feature constitute two separate units of work. Implementing a new function and
|
feature constitute two separate units of work. Implementing a new function and
|
||||||
calling it in another file constitute a single logical unit of work. The very
|
calling it in another file constitute a single logical unit of work. The very
|
||||||
high majory of submissions should have a single commit, so if in doubt: squash
|
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]
|
||||||
@@ -234,6 +234,8 @@ close an issue. Including references automatically closes the issue on a merge.
|
|||||||
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||||
from the Git history.
|
from the Git history.
|
||||||
|
|
||||||
|
Please see the [Coding Style](#coding-style) for further guidelines.
|
||||||
|
|
||||||
### Merge approval
|
### Merge approval
|
||||||
|
|
||||||
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
||||||
@@ -317,7 +319,7 @@ maintainer to make a difference on the project!
|
|||||||
|
|
||||||
### IRC meetings
|
### IRC meetings
|
||||||
|
|
||||||
There are two monthly meetings taking place on #docker-dev IRC to accomodate all
|
There are two monthly meetings taking place on #docker-dev IRC to accommodate all
|
||||||
timezones. Anybody can propose a topic for discussion prior to the meeting.
|
timezones. Anybody can propose a topic for discussion prior to the meeting.
|
||||||
|
|
||||||
If you feel the conversation is going off-topic, feel free to point it out.
|
If you feel the conversation is going off-topic, feel free to point it out.
|
||||||
@@ -385,3 +387,49 @@ do need a fair way to deal with people who are making our community suck.
|
|||||||
appeals, we know that mistakes happen, and we'll work with you to come up with a
|
appeals, we know that mistakes happen, and we'll work with you to come up with a
|
||||||
fair solution if there has been a misunderstanding.
|
fair solution if there has been a misunderstanding.
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
|
||||||
|
Unless explicitly stated, we follow all coding guidelines from the Go
|
||||||
|
community. While some of these standards may seem arbitrary, they somehow seem
|
||||||
|
to result in a solid, consistent codebase.
|
||||||
|
|
||||||
|
It is possible that the code base does not currently comply with these
|
||||||
|
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||||
|
goes against the spirit of the guidelines. All new contributions should make a
|
||||||
|
best effort to clean up and make the code base better than they left it.
|
||||||
|
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||||
|
code base easier for humans to navigate and understand. Always keep that in
|
||||||
|
mind when nudging others to comply.
|
||||||
|
|
||||||
|
The rules:
|
||||||
|
|
||||||
|
1. All code should be formatted with `gofmt -s`.
|
||||||
|
2. All code should pass the default levels of
|
||||||
|
[`golint`](https://github.com/golang/lint).
|
||||||
|
3. All code should follow the guidelines covered in [Effective
|
||||||
|
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||||
|
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||||
|
4. Comment the code. Tell us the why, the history and the context.
|
||||||
|
5. Document _all_ declarations and methods, even private ones. Declare
|
||||||
|
expectations, caveats and anything else that may be important. If a type
|
||||||
|
gets exported, having the comments already there will ensure it's ready.
|
||||||
|
6. Variable name length should be proportional to it's context and no longer.
|
||||||
|
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
|
||||||
|
In practice, short methods will have short variable names and globals will
|
||||||
|
have longer names.
|
||||||
|
7. No underscores in package names. If you need a compound name, step back,
|
||||||
|
and re-examine why you need a compound name. If you still think you need a
|
||||||
|
compound name, lose the underscore.
|
||||||
|
8. No utils or helpers packages. If a function is not general enough to
|
||||||
|
warrant it's own package, it has not been written generally enough to be a
|
||||||
|
part of a util package. Just leave it unexported and well-documented.
|
||||||
|
9. All tests should run with `go test` and outside tooling should not be
|
||||||
|
required. No, we don't need another unit testing framework. Assertion
|
||||||
|
packages are acceptable if they provide _real_ incremental value.
|
||||||
|
10. Even though we call these "rules" above, they are actually just
|
||||||
|
guidelines. Since you've read all the rules, you now know that.
|
||||||
|
|
||||||
|
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||||
|
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
|
||||||
|
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
|
||||||
|
kool-aid is a lot easier than going thirsty.
|
||||||
|
|||||||
+47
-20
@@ -19,14 +19,14 @@
|
|||||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||||
# docker hack/release.sh
|
# docker hack/release.sh
|
||||||
#
|
#
|
||||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
# Note: AppArmor used to mess with privileged mode, but this is no longer
|
||||||
# the case. Therefore, you don't have to disable it anymore.
|
# the case. Therefore, you don't have to disable it anymore.
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM ubuntu:14.04
|
FROM ubuntu:14.04
|
||||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
|
||||||
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
|
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
|
||||||
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
|
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
|
||||||
|
|
||||||
# Packaged dependencies
|
# Packaged dependencies
|
||||||
@@ -37,6 +37,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
bash-completion \
|
bash-completion \
|
||||||
btrfs-tools \
|
btrfs-tools \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
createrepo \
|
||||||
curl \
|
curl \
|
||||||
dpkg-sig \
|
dpkg-sig \
|
||||||
git \
|
git \
|
||||||
@@ -69,7 +70,7 @@ RUN cd /usr/local/lvm2 \
|
|||||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||||
|
|
||||||
# Install lxc
|
# Install lxc
|
||||||
ENV LXC_VERSION 1.0.7
|
ENV LXC_VERSION 1.1.2
|
||||||
RUN mkdir -p /usr/src/lxc \
|
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
|
&& 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 \
|
RUN cd /usr/src/lxc \
|
||||||
@@ -116,21 +117,37 @@ RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
|
|||||||
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
|
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
|
||||||
&& go install -v golang.org/x/tools/cmd/cover \
|
&& go install -v golang.org/x/tools/cmd/cover \
|
||||||
&& go install -v golang.org/x/tools/cmd/vet
|
&& go install -v golang.org/x/tools/cmd/vet
|
||||||
|
# Grab Go's lint tool
|
||||||
|
ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
|
||||||
|
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
|
||||||
|
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
|
||||||
|
&& go install -v github.com/golang/lint/golint
|
||||||
|
|
||||||
# TODO replace FPM with some very minimal debhelper stuff
|
# TODO replace FPM with some very minimal debhelper stuff
|
||||||
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
||||||
|
|
||||||
# Install registry
|
# Install registry
|
||||||
ENV REGISTRY_COMMIT d957768537c5af40e4f4cd96871f7b2bde9e2923
|
ENV REGISTRY_COMMIT 2317f721a3d8428215a2b65da4ae85212ed473b4
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \
|
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
|
||||||
&& GOPATH=/go/src/github.com/docker/distribution/Godeps/_workspace:/go \
|
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
|
||||||
go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry \
|
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
|
||||||
&& rm -rf /go/src/github.com/docker/distribution/
|
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
|
||||||
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
|
# Install notary server
|
||||||
|
ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7
|
||||||
|
RUN set -x \
|
||||||
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
|
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||||
|
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \
|
||||||
|
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
|
||||||
|
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
|
||||||
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Get the "docker-py" source so we can run their integration tests
|
# Get the "docker-py" source so we can run their integration tests
|
||||||
ENV DOCKER_PY_COMMIT 91985b239764fe54714fa0a93d52aa362357d251
|
ENV DOCKER_PY_COMMIT 8a87001d09852058f08a807ab6e8491d57ca1e88
|
||||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||||
&& cd /docker-py \
|
&& cd /docker-py \
|
||||||
&& git checkout -q $DOCKER_PY_COMMIT
|
&& git checkout -q $DOCKER_PY_COMMIT
|
||||||
@@ -162,26 +179,36 @@ RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
|
|||||||
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
|
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
|
||||||
COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/
|
COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/
|
||||||
RUN ./contrib/download-frozen-image.sh /docker-frozen-images \
|
RUN ./contrib/download-frozen-image.sh /docker-frozen-images \
|
||||||
busybox:latest@4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125 \
|
busybox:latest@8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55 \
|
||||||
hello-world:frozen@e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5 \
|
hello-world:frozen@91c95931e552b11604fea91c2f537284149ec32fff0f700a4769cfd31d7696ae \
|
||||||
jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
|
jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
|
||||||
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
|
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
|
||||||
|
|
||||||
# Download man page generator
|
# Download man page generator
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& git clone -b v1.0.1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday
|
&& git clone -b v1.0.3 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||||
|
&& git clone -b v1.2 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
||||||
|
&& go get -v -d github.com/cpuguy83/go-md2man \
|
||||||
|
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
||||||
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Download toml validator
|
# Download toml validator
|
||||||
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
|
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& git clone https://github.com/BurntSushi/toml.git /go/src/github.com/BurntSushi/toml \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT)
|
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
|
||||||
|
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
|
||||||
|
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
|
||||||
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# copy vendor/ because go-md2man needs golang.org/x/net
|
# Build/install the tool for embedding resources in Windows binaries
|
||||||
COPY vendor /go/src/github.com/docker/docker/vendor
|
ENV RSRC_COMMIT e48dbf1b7fc464a9e85fcec450dddf80816b76e0
|
||||||
RUN go install -v github.com/cpuguy83/go-md2man \
|
RUN set -x \
|
||||||
github.com/BurntSushi/toml/cmd/tomlv
|
&& git clone https://github.com/akavel/rsrc.git /go/src/github.com/akavel/rsrc \
|
||||||
|
&& cd /go/src/github.com/akavel/rsrc \
|
||||||
|
&& git checkout -q $RSRC_COMMIT \
|
||||||
|
&& go install -v
|
||||||
|
|
||||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||||
ENTRYPOINT ["hack/dind"]
|
ENTRYPOINT ["hack/dind"]
|
||||||
|
|||||||
+10
-94
@@ -124,98 +124,8 @@ the relevant operators.
|
|||||||
|
|
||||||
* If the change affects the governance, philosophy, goals or principles of the project,
|
* If the change affects the governance, philosophy, goals or principles of the project,
|
||||||
it must be approved by BDFL.
|
it must be approved by BDFL.
|
||||||
|
|
||||||
* A pull request can be in 1 of 5 distinct states, for each of which there is a corresponding label
|
|
||||||
that needs to be applied. `Rules.review.states` contains the list of states with possible targets
|
|
||||||
for each.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Triage
|
|
||||||
[Rules.review.states.0-needs-triage]
|
|
||||||
|
|
||||||
# Maintainers are expected to triage new incoming pull requests by removing
|
|
||||||
# the `0-triage` label and adding the correct labels (e.g. `1-design-review`)
|
|
||||||
# potentially skipping some steps depending on the kind of pull request.
|
|
||||||
# Use common sense for judging.
|
|
||||||
#
|
|
||||||
# Checking for DCO should be done at this stage.
|
|
||||||
#
|
|
||||||
# If an owner, responsible for closing or merging, can be assigned to the PR,
|
|
||||||
# the better.
|
|
||||||
|
|
||||||
close = "e.g. unresponsive contributor without DCO"
|
|
||||||
3-docs-review = "non-proposal documentation-only change"
|
|
||||||
2-code-review = "e.g. trivial bugfix"
|
|
||||||
1-design-review = "general case"
|
|
||||||
|
|
||||||
# Design review
|
|
||||||
[Rules.review.states.1-needs-design-review]
|
|
||||||
|
|
||||||
# Maintainers are expected to comment on the design of the pull request.
|
|
||||||
# Review of documentation is expected only in the context of design validation,
|
|
||||||
# not for stylistic changes.
|
|
||||||
#
|
|
||||||
# Ideally, documentation should reflect the expected behavior of the code.
|
|
||||||
# No code review should take place in this step.
|
|
||||||
#
|
|
||||||
# Once design is approved, a maintainer should make sure to remove this label
|
|
||||||
# and add the next one.
|
|
||||||
|
|
||||||
close = "design rejected"
|
|
||||||
3-docs-review = "proposals with only documentation changes"
|
|
||||||
2-code-review = "general case"
|
|
||||||
|
|
||||||
# Code review
|
|
||||||
[Rules.review.states.2-needs-code-review]
|
|
||||||
|
|
||||||
# Maintainers are expected to review the code and ensure that it is good
|
|
||||||
# quality and in accordance with the documentation in the PR.
|
|
||||||
#
|
|
||||||
# If documentation is absent but expected, maintainers should ask for documentation.
|
|
||||||
#
|
|
||||||
# All tests should pass.
|
|
||||||
#
|
|
||||||
# Once code is approved according to the rules of the subsystem, a maintainer
|
|
||||||
# should make sure to remove this label and add the next one.
|
|
||||||
|
|
||||||
close = ""
|
|
||||||
1-design-review = "raises design concerns"
|
|
||||||
4-merge = "trivial change not impacting documentation"
|
|
||||||
3-docs-review = "general case"
|
|
||||||
|
|
||||||
# Docs review
|
|
||||||
[Rules.review.states.3-needs-docs-review]
|
|
||||||
|
|
||||||
# Maintainers are expected to review the documentation in its bigger context,
|
|
||||||
# ensuring consistency, completeness, validity, and breadth of coverage across
|
|
||||||
# all extent and new documentation.
|
|
||||||
#
|
|
||||||
# They should ask for any editorial change that makes the documentation more
|
|
||||||
# consistent and easier to understand.
|
|
||||||
#
|
|
||||||
# Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of
|
|
||||||
# two docs sub-project maintainers. If the docs change originates with a docs
|
|
||||||
# maintainer, only one additional LGTM is required (since we assume a docs maintainer
|
|
||||||
# approves of their own PR).
|
|
||||||
#
|
|
||||||
# Once documentation is approved (see below), a maintainer should make sure to remove this
|
|
||||||
# label and add the next one.
|
|
||||||
|
|
||||||
close = ""
|
|
||||||
2-code-review = "requires more code changes"
|
|
||||||
1-design-review = "raises design concerns"
|
|
||||||
4-merge = "general case"
|
|
||||||
|
|
||||||
# Merge
|
|
||||||
[Rules.review.states.4-needs-merge]
|
|
||||||
|
|
||||||
# Maintainers are expected to merge this pull request as soon as possible.
|
|
||||||
# They can ask for a rebase, or carry the pull request themselves.
|
|
||||||
# These should be the easy PRs to merge.
|
|
||||||
|
|
||||||
close = "carry PR"
|
|
||||||
merge = ""
|
|
||||||
|
|
||||||
[Rules.DCO]
|
[Rules.DCO]
|
||||||
|
|
||||||
title = "Helping contributors with the DCO"
|
title = "Helping contributors with the DCO"
|
||||||
@@ -314,7 +224,7 @@ made through a pull request.
|
|||||||
"jfrazelle",
|
"jfrazelle",
|
||||||
"crosbymichael"
|
"crosbymichael"
|
||||||
]
|
]
|
||||||
|
|
||||||
[Org.Operators.community]
|
[Org.Operators.community]
|
||||||
people = [
|
people = [
|
||||||
"theadactyl"
|
"theadactyl"
|
||||||
@@ -326,7 +236,7 @@ made through a pull request.
|
|||||||
# day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll
|
# day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll
|
||||||
# be fine".
|
# be fine".
|
||||||
"Chief Maintainer" = "crosbymichael"
|
"Chief Maintainer" = "crosbymichael"
|
||||||
|
|
||||||
# The community manager is responsible for serving the project community, including users,
|
# The community manager is responsible for serving the project community, including users,
|
||||||
# contributors and partners. This involves:
|
# contributors and partners. This involves:
|
||||||
# - facilitating communication between maintainers, contributors and users
|
# - facilitating communication between maintainers, contributors and users
|
||||||
@@ -335,7 +245,7 @@ made through a pull request.
|
|||||||
# - anything the project community needs to be successful
|
# - anything the project community needs to be successful
|
||||||
#
|
#
|
||||||
# The community manager is a point of contact for any contributor who has questions, concerns
|
# The community manager is a point of contact for any contributor who has questions, concerns
|
||||||
# or feedback about project operations.
|
# or feedback about project operations.
|
||||||
"Community Manager" = "theadactyl"
|
"Community Manager" = "theadactyl"
|
||||||
|
|
||||||
[Org."Core maintainers"]
|
[Org."Core maintainers"]
|
||||||
@@ -357,6 +267,7 @@ made through a pull request.
|
|||||||
|
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
|
"calavera",
|
||||||
"crosbymichael",
|
"crosbymichael",
|
||||||
"erikh",
|
"erikh",
|
||||||
"estesp",
|
"estesp",
|
||||||
@@ -536,6 +447,11 @@ made through a pull request.
|
|||||||
Email = "ben@firshman.co.uk"
|
Email = "ben@firshman.co.uk"
|
||||||
GitHub = "bfirsh"
|
GitHub = "bfirsh"
|
||||||
|
|
||||||
|
[people.calavera]
|
||||||
|
Name = "David Calavera"
|
||||||
|
Email = "david.calavera@gmail.com"
|
||||||
|
GitHub = "calavera"
|
||||||
|
|
||||||
[people.cpuguy83]
|
[people.cpuguy83]
|
||||||
Name = "Brian Goff"
|
Name = "Brian Goff"
|
||||||
Email = "cpuguy83@gmail.com"
|
Email = "cpuguy83@gmail.com"
|
||||||
@@ -650,7 +566,7 @@ made through a pull request.
|
|||||||
Name = "Sebastiaan van Stijn"
|
Name = "Sebastiaan van Stijn"
|
||||||
Email = "github@gone.nl"
|
Email = "github@gone.nl"
|
||||||
GitHub = "thaJeztah"
|
GitHub = "thaJeztah"
|
||||||
|
|
||||||
[people.theadactyl]
|
[people.theadactyl]
|
||||||
Name = "Thea Lamkin"
|
Name = "Thea Lamkin"
|
||||||
Email = "thea@docker.com"
|
Email = "thea@docker.com"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
DOCKER_ENVS := \
|
DOCKER_ENVS := \
|
||||||
-e BUILDFLAGS \
|
-e BUILDFLAGS \
|
||||||
-e DOCKER_CLIENTONLY \
|
-e DOCKER_CLIENTONLY \
|
||||||
|
-e DOCKER_DEBUG \
|
||||||
-e DOCKER_EXECDRIVER \
|
-e DOCKER_EXECDRIVER \
|
||||||
-e DOCKER_EXPERIMENTAL \
|
-e DOCKER_EXPERIMENTAL \
|
||||||
-e DOCKER_GRAPHDRIVER \
|
-e DOCKER_GRAPHDRIVER \
|
||||||
@@ -22,11 +23,6 @@ DOCKER_ENVS := \
|
|||||||
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
|
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
|
||||||
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
|
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
|
||||||
|
|
||||||
# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
|
|
||||||
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
|
|
||||||
|
|
||||||
# to allow `make DOCSPORT=9000 docs`
|
|
||||||
DOCSPORT := 8000
|
|
||||||
|
|
||||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||||
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||||
@@ -50,19 +46,11 @@ binary: build
|
|||||||
cross: build
|
cross: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||||
|
|
||||||
docs: docs-build
|
deb: build
|
||||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" mkdocs serve
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-deb
|
||||||
|
|
||||||
docs-shell: docs-build
|
rpm: build
|
||||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-rpm
|
||||||
|
|
||||||
docs-release: docs-build
|
|
||||||
$(DOCKER_RUN_DOCS) -e OPTIONS -e BUILD_ROOT -e DISTRIBUTION_ID \
|
|
||||||
-v $(CURDIR)/docs/awsconfig:/docs/awsconfig \
|
|
||||||
"$(DOCKER_DOCS_IMAGE)" ./release.sh
|
|
||||||
|
|
||||||
docs-test: docs-build
|
|
||||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
|
|
||||||
|
|
||||||
test: build
|
test: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py
|
||||||
@@ -77,7 +65,7 @@ test-docker-py: build
|
|||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py
|
||||||
|
|
||||||
validate: build
|
validate: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-test validate-toml validate-vet
|
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet
|
||||||
|
|
||||||
shell: build
|
shell: build
|
||||||
$(DOCKER_RUN_DOCKER) bash
|
$(DOCKER_RUN_DOCKER) bash
|
||||||
@@ -85,13 +73,8 @@ shell: build
|
|||||||
build: bundles
|
build: bundles
|
||||||
docker build -t "$(DOCKER_IMAGE)" .
|
docker build -t "$(DOCKER_IMAGE)" .
|
||||||
|
|
||||||
docs-build:
|
|
||||||
cp ./VERSION docs/VERSION
|
|
||||||
echo "$(GIT_BRANCH)" > docs/GIT_BRANCH
|
|
||||||
# echo "$(AWS_S3_BUCKET)" > docs/AWS_S3_BUCKET
|
|
||||||
echo "$(GITCOMMIT)" > docs/GITCOMMIT
|
|
||||||
docker pull docs/base
|
|
||||||
docker build -t "$(DOCKER_DOCS_IMAGE)" docs
|
|
||||||
|
|
||||||
bundles:
|
bundles:
|
||||||
mkdir bundles
|
mkdir bundles
|
||||||
|
|
||||||
|
docs:
|
||||||
|
$(MAKE) -C docs docs
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ databases, and backend services without depending on a particular stack
|
|||||||
or provider.
|
or provider.
|
||||||
|
|
||||||
Docker began as an open-source implementation of the deployment engine which
|
Docker began as an open-source implementation of the deployment engine which
|
||||||
powers [dotCloud](https://dotcloud.com), a popular Platform-as-a-Service.
|
powers [dotCloud](https://www.dotcloud.com), a popular Platform-as-a-Service.
|
||||||
It benefits directly from the experience accumulated over several years
|
It benefits directly from the experience accumulated over several years
|
||||||
of large-scale operation and support of hundreds of thousands of
|
of large-scale operation and support of hundreds of thousands of
|
||||||
applications and databases.
|
applications and databases.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Security Disclosure
|
## Security Disclosure
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ security@docker.com and not by creating a github issue.
|
|||||||
|
|
||||||
A common method for distributing applications and sandboxing their
|
A common method for distributing applications and sandboxing their
|
||||||
execution is to use virtual machines, or VMs. Typical VM formats are
|
execution is to use virtual machines, or VMs. Typical VM formats are
|
||||||
VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
|
VMware's vmdk, Oracle VirtualBox's vdi, and Amazon EC2's ami. In theory
|
||||||
these formats should allow every developer to automatically package
|
these formats should allow every developer to automatically package
|
||||||
their application into a "machine" for easy distribution and deployment.
|
their application into a "machine" for easy distribution and deployment.
|
||||||
In practice, that almost never happens, for a few reasons:
|
In practice, that almost never happens, for a few reasons:
|
||||||
@@ -58,7 +58,7 @@ takes place at the kernel level. Most modern operating system kernels
|
|||||||
now support the primitives necessary for containerization, including
|
now support the primitives necessary for containerization, including
|
||||||
Linux with [openvz](https://openvz.org),
|
Linux with [openvz](https://openvz.org),
|
||||||
[vserver](http://linux-vserver.org) and more recently
|
[vserver](http://linux-vserver.org) and more recently
|
||||||
[lxc](http://lxc.sourceforge.net), Solaris with
|
[lxc](https://linuxcontainers.org/), Solaris with
|
||||||
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
|
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
|
||||||
and FreeBSD with
|
and FreeBSD with
|
||||||
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
|
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
|
||||||
@@ -168,9 +168,9 @@ Under the hood
|
|||||||
Under the hood, Docker is built on the following components:
|
Under the hood, Docker is built on the following components:
|
||||||
|
|
||||||
* The
|
* The
|
||||||
[cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
|
[cgroups](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt)
|
||||||
and
|
and
|
||||||
[namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
|
[namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html)
|
||||||
capabilities of the Linux kernel
|
capabilities of the Linux kernel
|
||||||
* The [Go](https://golang.org) programming language
|
* The [Go](https://golang.org) programming language
|
||||||
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
||||||
@@ -183,7 +183,7 @@ Contributing to Docker
|
|||||||
[](https://jenkins.dockerproject.org/job/Docker%20Master/)
|
[](https://jenkins.dockerproject.org/job/Docker%20Master/)
|
||||||
|
|
||||||
Want to hack on Docker? Awesome! We have [instructions to help you get
|
Want to hack on Docker? Awesome! We have [instructions to help you get
|
||||||
started contributing code or documentation.](https://docs.docker.com/project/who-written-for/).
|
started contributing code or documentation](https://docs.docker.com/project/who-written-for/).
|
||||||
|
|
||||||
These instructions are probably not perfect, please let us know if anything
|
These instructions are probably not perfect, please let us know if anything
|
||||||
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
|
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
|
||||||
@@ -289,7 +289,7 @@ system
|
|||||||
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
|
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
|
||||||
Define and run multi-container apps
|
Define and run multi-container apps
|
||||||
* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use
|
* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use
|
||||||
Docker on a Mac
|
Docker on Mac and Windows
|
||||||
|
|
||||||
If you know of another project underway that should be listed here, please help
|
If you know of another project underway that should be listed here, please help
|
||||||
us keep this list up-to-date by submitting a PR.
|
us keep this list up-to-date by submitting a PR.
|
||||||
|
|||||||
+183
@@ -0,0 +1,183 @@
|
|||||||
|
Docker Engine Roadmap
|
||||||
|
=====================
|
||||||
|
|
||||||
|
### How should I use this document?
|
||||||
|
|
||||||
|
This document provides description of items that the project decided to prioritize. This should
|
||||||
|
serve as a reference point for Docker contributors to understand where the project is going, and
|
||||||
|
help determine if a contribution could be conflicting with some longer terms plans.
|
||||||
|
|
||||||
|
The fact that a feature isn't listed here doesn't mean that a patch for it will automatically be
|
||||||
|
refused (except for those mentioned as "frozen features" below)! We are always happy to receive
|
||||||
|
patches for new cool features we haven't thought about, or didn't judge priority. Please however
|
||||||
|
understand that such patches might take longer for us to review.
|
||||||
|
|
||||||
|
### How can I help?
|
||||||
|
|
||||||
|
Short term objectives are listed in the [wiki](https://github.com/docker/docker/wiki) and described
|
||||||
|
in [Issues](https://github.com/docker/docker/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap). Our
|
||||||
|
goal is to split down the workload in such way that anybody can jump in and help. Please comment on
|
||||||
|
issues if you want to take it to avoid duplicating effort! Similarly, if a maintainer is already
|
||||||
|
assigned on an issue you'd like to participate in, pinging him on IRC or GitHub to offer your help is
|
||||||
|
the best way to go.
|
||||||
|
|
||||||
|
### How can I add something to the roadmap?
|
||||||
|
|
||||||
|
The roadmap process is new to the Docker Engine: we are only beginning to structure and document the
|
||||||
|
project objectives. Our immediate goal is to be more transparent, and work with our community to
|
||||||
|
focus our efforts on fewer prioritized topics.
|
||||||
|
|
||||||
|
We hope to offer in the near future a process allowing anyone to propose a topic to the roadmap, but
|
||||||
|
we are not quite there yet. For the time being, the BDFL remains the keeper of the roadmap, and we
|
||||||
|
won't be accepting pull requests adding or removing items from this file.
|
||||||
|
|
||||||
|
# 1. Features and refactoring
|
||||||
|
|
||||||
|
## 1.1 Security
|
||||||
|
|
||||||
|
Security is a top objective for the Docker Engine. The most notable items we intend to provide in
|
||||||
|
the near future are:
|
||||||
|
|
||||||
|
- Trusted distribution of images: the effort is driven by the [distribution](https://github.com/docker/distribution)
|
||||||
|
group but will have significant impact on the Engine
|
||||||
|
- [User namespaces](https://github.com/docker/docker/pull/12648)
|
||||||
|
- [Seccomp support](https://github.com/docker/libcontainer/pull/613)
|
||||||
|
|
||||||
|
## 1.2 Plumbing project
|
||||||
|
|
||||||
|
We define a plumbing tool as a standalone piece of software usable and meaningful on its own. In
|
||||||
|
the current state of the Docker Engine, most subsystems provide independent functionalities (such
|
||||||
|
the builder, pushing and pulling images, running applications in a containerized environment, etc)
|
||||||
|
but all are coupled in a single binary. We want to offer the users to flexibility to use only the
|
||||||
|
pieces they need, and we will also gain in maintainability by splitting the project among multiple
|
||||||
|
repositories.
|
||||||
|
|
||||||
|
As it currently stands, the rough design outlines is to have:
|
||||||
|
- Low level plumbing tools, each dealing with one responsibility (e.g., [runC](https://runc.io))
|
||||||
|
- Docker subsystems services, each exposing an elementary concept over an API, and relying on one or
|
||||||
|
multiple lower level plumbing tools for their implementation (e.g., network management)
|
||||||
|
- Docker Engine to expose higher level actions (e.g., create a container with volume `V` and network
|
||||||
|
`N`), while still providing pass-through access to the individual subsystems.
|
||||||
|
|
||||||
|
The architectural details are still being worked on, but one thing we know for sure is that we need
|
||||||
|
to technically decouple the pieces.
|
||||||
|
|
||||||
|
### 1.2.1 Runtime
|
||||||
|
|
||||||
|
A Runtime tool already exists today in the form of [runC](https://github.com/opencontainers/runc).
|
||||||
|
We intend to modify the Engine to directly call out to a binary implementing the Open Containers
|
||||||
|
Specification such as runC rather than relying on libcontainer to set the container runtime up.
|
||||||
|
|
||||||
|
This plan will deprecate the existing [`execdriver`](https://github.com/docker/docker/tree/master/daemon/execdriver)
|
||||||
|
as different runtime backends will be implemented as separated binaries instead of being compiled
|
||||||
|
into the Engine.
|
||||||
|
|
||||||
|
### 1.2.2 Builder
|
||||||
|
|
||||||
|
The Builder (i.e., the ability to build an image from a Dockerfile) is already nicely decoupled,
|
||||||
|
but would benefit from being entirely separated from the Engine, and rely on the standard Engine
|
||||||
|
API for its operations.
|
||||||
|
|
||||||
|
### 1.2.3 Distribution
|
||||||
|
|
||||||
|
Distribution already has a [dedicated repository](https://github.com/docker/distribution) which
|
||||||
|
holds the implementation for Registry v2 and client libraries. We could imagine going further by
|
||||||
|
having the Engine call out to a binary providing image distribution related functionalities.
|
||||||
|
|
||||||
|
There are two short term goals related to image distribution. The first is stabilize and simplify
|
||||||
|
the push/pull code. Following that is the conversion to the more secure Registry V2 protocol.
|
||||||
|
|
||||||
|
### 1.2.4 Networking
|
||||||
|
|
||||||
|
Most of networking related code was already decoupled today in [libnetwork](https://github.com/docker/libnetwork).
|
||||||
|
As with other ingredients, we might want to take it a step further and make it a meaningful utility
|
||||||
|
that the Engine would call out to instead of a library.
|
||||||
|
|
||||||
|
## 1.3 Plugins
|
||||||
|
|
||||||
|
An initiative around plugins started with Docker 1.7.0, with the goal of allowing for out of
|
||||||
|
process extensibility of some Docker functionalities, starting with volumes and networking. The
|
||||||
|
approach is to provide specific extension points rather than generic hooking facilities. We also
|
||||||
|
deliberately keep the extensions API the simplest possible, expanding as we discover valid use
|
||||||
|
cases that cannot be implemented.
|
||||||
|
|
||||||
|
At the time of writing:
|
||||||
|
|
||||||
|
- Plugin support is merged as an experimental feature: real world use cases and user feedback will
|
||||||
|
help us refine the UX to make the feature more user friendly.
|
||||||
|
- There are no immediate plans to expand on the number of pluggable subsystems.
|
||||||
|
- Golang 1.5 might add language support for [plugins](https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ)
|
||||||
|
which we consider supporting as an alternative to JSON/HTTP.
|
||||||
|
|
||||||
|
## 1.4 Volume management
|
||||||
|
|
||||||
|
Volumes are not a first class citizen in the Engine today: we would like better volume management,
|
||||||
|
similar to the way network are managed in the new [CNM](https://github.com/docker/docker/issues/9983).
|
||||||
|
|
||||||
|
## 1.5 Better API implementation
|
||||||
|
|
||||||
|
The current Engine API is insufficiently typed, versioned, and ultimately hard to maintain. We
|
||||||
|
also suffer from the lack of a common implementation with [Swarm](https://github.com/docker/swarm).
|
||||||
|
|
||||||
|
## 1.6 Checkpoint/restore
|
||||||
|
|
||||||
|
Support for checkpoint/restore was [merged](https://github.com/docker/libcontainer/pull/479) in
|
||||||
|
[libcontainer](https://github.com/docker/libcontainer) and made available through [runC](https://runc.io):
|
||||||
|
we intend to take advantage of it in the Engine.
|
||||||
|
|
||||||
|
# 2 Frozen features
|
||||||
|
|
||||||
|
## 2.1 Docker exec
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 2.2 Dockerfile syntax
|
||||||
|
|
||||||
|
The Dockerfile syntax as we know it is simple, and has proven succesful in supporting all our
|
||||||
|
[official images](https://github.com/docker-library/official-images). Although this is *not* a
|
||||||
|
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
|
||||||
|
reasons:
|
||||||
|
|
||||||
|
- Long term impact of syntax changes is a sensitive matter that require an amount of attention
|
||||||
|
the volume of Engine codebase and activity today doesn't allow us to provide.
|
||||||
|
- Allowing the Builder to be implemented as a separate utility consuming the Engine's API will
|
||||||
|
open the door for many possibilities, such as offering alternate syntaxes or DSL for existing
|
||||||
|
languages without cluttering the Engine's codebase.
|
||||||
|
- A standalone Builder will also offer the opportunity for a better dedicated group of maintainers
|
||||||
|
to own the Dockerfile syntax and decide collectively on the direction to give it.
|
||||||
|
- Our experience with official images tend to show that no new instruction or syntax expansion is
|
||||||
|
*strictly* necessary for the majority of use cases, and although we are aware many things are still
|
||||||
|
lacking for many, we cannot make it a priority yet for the above reasons.
|
||||||
|
|
||||||
|
Again, this is not about saying that the Dockerfile syntax is done, it's about making choices about
|
||||||
|
what we want to do first!
|
||||||
|
|
||||||
|
## 2.3 Remote Registry Operations
|
||||||
|
|
||||||
|
A large amount of work is ongoing in the area of image distribution and
|
||||||
|
provenance. This includes moving to the V2 Registry API and heavily
|
||||||
|
refactoring the code that powers these features. The desired result is more
|
||||||
|
secure, reliable and easier to use image distribution.
|
||||||
|
|
||||||
|
Part of the problem with this part of the code base is the lack of a stable
|
||||||
|
and flexible interface. If new features are added that access the registry
|
||||||
|
without solidifying these interfaces, achieving feature parity will continue
|
||||||
|
to be elusive. While we get a handle on this situation, we are imposing a
|
||||||
|
moratorium on new code that accesses the Registry API in commands that don't
|
||||||
|
already make remote calls.
|
||||||
|
|
||||||
|
Currently, only the following commands cause interaction with a remote
|
||||||
|
registry:
|
||||||
|
|
||||||
|
- push
|
||||||
|
- pull
|
||||||
|
- run
|
||||||
|
- build
|
||||||
|
- search
|
||||||
|
- login
|
||||||
|
|
||||||
|
In the interest of stabilizing the registry access model during this ongoing
|
||||||
|
work, we are not accepting additions to other commands that will cause remote
|
||||||
|
interaction with the Registry API. This moratorium will lift when the goals of
|
||||||
|
the distribution project have been met.
|
||||||
+10
-9
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
)
|
)
|
||||||
@@ -16,23 +17,23 @@ 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 {
|
||||||
var (
|
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
|
||||||
cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", 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")
|
|
||||||
)
|
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
name := cmd.Arg(0)
|
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return StatusError{StatusCode: status}
|
return Cli.StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+461
-137
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -13,20 +14,25 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
"github.com/docker/docker/pkg/fileutils"
|
||||||
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
"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"
|
||||||
@@ -43,7 +49,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", "PATH | URL | -", "Build a new image from the source code at PATH", true)
|
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", 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")
|
||||||
@@ -60,154 +66,117 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
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")
|
||||||
|
|
||||||
|
ulimits := make(map[string]*ulimit.Ulimit)
|
||||||
|
flUlimits := opts.NewUlimitOpt(&ulimits)
|
||||||
|
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
|
// For trusted pull on "FROM <image>" instruction.
|
||||||
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
context archive.Archive
|
context io.ReadCloser
|
||||||
isRemote bool
|
isRemote bool
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err = exec.LookPath("git")
|
_, err = exec.LookPath("git")
|
||||||
hasGit := err == nil
|
hasGit := err == nil
|
||||||
if cmd.Arg(0) == "-" {
|
|
||||||
// As a special case, 'docker build -' will build from either an empty context with the
|
|
||||||
// contents of stdin as a Dockerfile, or a tar-ed context from stdin.
|
|
||||||
buf := bufio.NewReader(cli.in)
|
|
||||||
magic, err := buf.Peek(tarHeaderSize)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return fmt.Errorf("failed to peek context header from STDIN: %v", err)
|
|
||||||
}
|
|
||||||
if !archive.IsArchive(magic) {
|
|
||||||
dockerfile, err := ioutil.ReadAll(buf)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -f option has no meaning when we're reading it from stdin,
|
specifiedContext := cmd.Arg(0)
|
||||||
// so just use our default Dockerfile name
|
|
||||||
*dockerfileName = api.DefaultDockerfileName
|
|
||||||
context, err = archive.Generate(*dockerfileName, string(dockerfile))
|
|
||||||
} else {
|
|
||||||
context = ioutil.NopCloser(buf)
|
|
||||||
}
|
|
||||||
} else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) {
|
|
||||||
isRemote = true
|
|
||||||
} else {
|
|
||||||
root := cmd.Arg(0)
|
|
||||||
if urlutil.IsGitURL(root) {
|
|
||||||
root, err = utils.GitClone(root)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(root); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
absRoot, err := filepath.Abs(root)
|
var (
|
||||||
if err != nil {
|
contextDir string
|
||||||
return err
|
tempDir string
|
||||||
}
|
relDockerfile string
|
||||||
|
)
|
||||||
|
|
||||||
filename := *dockerfileName // path to Dockerfile
|
switch {
|
||||||
|
case specifiedContext == "-":
|
||||||
if *dockerfileName == "" {
|
tempDir, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
|
||||||
// No -f/--file was specified so use the default
|
case urlutil.IsGitURL(specifiedContext) && hasGit:
|
||||||
*dockerfileName = api.DefaultDockerfileName
|
tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
|
||||||
filename = filepath.Join(absRoot, *dockerfileName)
|
case urlutil.IsURL(specifiedContext):
|
||||||
|
tempDir, relDockerfile, err = getContextFromURL(cli.out, specifiedContext, *dockerfileName)
|
||||||
// Just to be nice ;-) look for 'dockerfile' too but only
|
default:
|
||||||
// use it if we found it, otherwise ignore this check
|
contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
|
||||||
if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
||||||
tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName))
|
|
||||||
if _, err = os.Lstat(tmpFN); err == nil {
|
|
||||||
*dockerfileName = strings.ToLower(*dockerfileName)
|
|
||||||
filename = tmpFN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
origDockerfile := *dockerfileName // used for error msg
|
|
||||||
if filename, err = filepath.Abs(filename); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that 'filename' is within the build context
|
|
||||||
filename, err = symlink.FollowSymlinkInScope(filename, absRoot)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now reset the dockerfileName to be relative to the build context
|
|
||||||
*dockerfileName, err = filepath.Rel(absRoot, filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// And canonicalize dockerfile name to a platform-independent one
|
|
||||||
*dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", *dockerfileName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
|
|
||||||
}
|
|
||||||
var includes = []string{"."}
|
|
||||||
|
|
||||||
excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If .dockerignore mentions .dockerignore or the Dockerfile
|
|
||||||
// then make sure we send both files over to the daemon
|
|
||||||
// because Dockerfile is, obviously, needed no matter what, and
|
|
||||||
// .dockerignore is needed to know if either one needs to be
|
|
||||||
// removed. The deamon will remove them for us, if needed, after it
|
|
||||||
// parses the Dockerfile.
|
|
||||||
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
|
||||||
keepThem2, _ := fileutils.Matches(*dockerfileName, excludes)
|
|
||||||
if keepThem1 || keepThem2 {
|
|
||||||
includes = append(includes, ".dockerignore", *dockerfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := utils.ValidateContextDirectory(root, excludes); err != nil {
|
|
||||||
return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
|
|
||||||
}
|
|
||||||
options := &archive.TarOptions{
|
|
||||||
Compression: archive.Uncompressed,
|
|
||||||
ExcludePatterns: excludes,
|
|
||||||
IncludeFiles: includes,
|
|
||||||
}
|
|
||||||
context, err = archive.TarWithOptions(root, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// windows: show error message about modified file permissions
|
if err != nil {
|
||||||
// FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build.
|
return fmt.Errorf("unable to prepare context: %s", err)
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body io.Reader
|
if tempDir != "" {
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
contextDir = tempDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the FROM lines in the Dockerfile to trusted digest references
|
||||||
|
// using Notary. On a successful build, we must tag the resolved digests
|
||||||
|
// to the original name specified in the Dockerfile.
|
||||||
|
newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to process Dockerfile: %v", err)
|
||||||
|
}
|
||||||
|
defer newDockerfile.Close()
|
||||||
|
|
||||||
|
// And canonicalize dockerfile name to a platform-independent one
|
||||||
|
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var includes = []string{"."}
|
||||||
|
|
||||||
|
excludes, err := utils.ReadDockerIgnore(path.Join(contextDir, ".dockerignore"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||||
|
return fmt.Errorf("Error checking context: '%s'.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If .dockerignore mentions .dockerignore or the Dockerfile
|
||||||
|
// then make sure we send both files over to the daemon
|
||||||
|
// because Dockerfile is, obviously, needed no matter what, and
|
||||||
|
// .dockerignore is needed to know if either one needs to be
|
||||||
|
// removed. The deamon will remove them for us, if needed, after it
|
||||||
|
// parses the Dockerfile. Ignore errors here, as they will have been
|
||||||
|
// caught by ValidateContextDirectory above.
|
||||||
|
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
||||||
|
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
||||||
|
if keepThem1 || keepThem2 {
|
||||||
|
includes = append(includes, ".dockerignore", relDockerfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
context, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
||||||
|
Compression: archive.Uncompressed,
|
||||||
|
ExcludePatterns: excludes,
|
||||||
|
IncludeFiles: includes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
|
||||||
|
// Dockerfile which uses trusted pulls.
|
||||||
|
context = replaceDockerfileTarWrapper(context, newDockerfile, relDockerfile)
|
||||||
|
|
||||||
// Setup an upload progress bar
|
// Setup an upload progress bar
|
||||||
// FIXME: ProgressReader shouldn't be this annoying to use
|
// FIXME: ProgressReader shouldn't be this annoying to use
|
||||||
if context != nil {
|
sf := streamformatter.NewStreamFormatter()
|
||||||
sf := streamformatter.NewStreamFormatter()
|
var body io.Reader = progressreader.New(progressreader.Config{
|
||||||
body = progressreader.New(progressreader.Config{
|
In: context,
|
||||||
In: context,
|
Out: cli.out,
|
||||||
Out: cli.out,
|
Formatter: sf,
|
||||||
Formatter: sf,
|
NewLines: true,
|
||||||
NewLines: true,
|
ID: "",
|
||||||
ID: "",
|
Action: "Sending build context to Docker daemon",
|
||||||
Action: "Sending build context to Docker daemon",
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var memory int64
|
var memory int64
|
||||||
if *flMemoryString != "" {
|
if *flMemoryString != "" {
|
||||||
@@ -280,7 +249,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
||||||
v.Set("cgroupparent", *flCgroupParent)
|
v.Set("cgroupparent", *flCgroupParent)
|
||||||
|
|
||||||
v.Set("dockerfile", *dockerfileName)
|
v.Set("dockerfile", relDockerfile)
|
||||||
|
|
||||||
|
ulimitsVar := flUlimits.GetList()
|
||||||
|
ulimitsJson, err := json.Marshal(ulimitsVar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set("ulimits", string(ulimitsJson))
|
||||||
|
|
||||||
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)
|
||||||
@@ -288,23 +264,371 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
||||||
|
headers.Set("Content-Type", "application/tar")
|
||||||
|
|
||||||
if context != nil {
|
|
||||||
headers.Set("Content-Type", "application/tar")
|
|
||||||
}
|
|
||||||
sopts := &streamOpts{
|
sopts := &streamOpts{
|
||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
in: body,
|
in: body,
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
}
|
}
|
||||||
err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
|
|
||||||
|
serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
|
||||||
|
|
||||||
|
// Windows: show error message about modified file permissions.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
h, err := httputils.ParseServerHeader(serverResp.header.Get("Server"))
|
||||||
|
if err == nil {
|
||||||
|
if h.OS != "windows" {
|
||||||
|
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||||
// If no error code is set, default to 1
|
// If no error code is set, default to 1
|
||||||
if jerr.Code == 0 {
|
if jerr.Code == 0 {
|
||||||
jerr.Code = 1
|
jerr.Code = 1
|
||||||
}
|
}
|
||||||
return StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the build was successful, now we must tag any of the resolved
|
||||||
|
// images from the above Dockerfile rewrite.
|
||||||
|
for _, resolved := range resolvedTags {
|
||||||
|
if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDockerfileRelPath uses the given context directory for a `docker build`
|
||||||
|
// and returns the absolute path to the context directory, the relative path of
|
||||||
|
// the dockerfile in that context directory, and a non-nil error on success.
|
||||||
|
func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
|
||||||
|
if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The context dir might be a symbolic link, so follow it to the actual
|
||||||
|
// target directory.
|
||||||
|
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := os.Lstat(absContextDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
absDockerfile := givenDockerfile
|
||||||
|
if absDockerfile == "" {
|
||||||
|
// No -f/--file was specified so use the default relative to the
|
||||||
|
// context directory.
|
||||||
|
absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
|
||||||
|
|
||||||
|
// Just to be nice ;-) look for 'dockerfile' too but only
|
||||||
|
// use it if we found it, otherwise ignore this check
|
||||||
|
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
|
||||||
|
altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
|
||||||
|
if _, err = os.Lstat(altPath); err == nil {
|
||||||
|
absDockerfile = altPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not already an absolute path, the Dockerfile path should be joined to
|
||||||
|
// the base directory.
|
||||||
|
if !filepath.IsAbs(absDockerfile) {
|
||||||
|
absDockerfile = filepath.Join(absContextDir, absDockerfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that 'filename' is within the build context
|
||||||
|
absDockerfile, err = symlink.FollowSymlinkInScope(absDockerfile, absContextDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Lstat(absDockerfile); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", "", fmt.Errorf("Cannot locate Dockerfile: absDockerfile: %q", absDockerfile)
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return absContextDir, relDockerfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeToFile copies from the given reader and writes it to a file with the
|
||||||
|
// given filename.
|
||||||
|
func writeToFile(r io.Reader, filename string) error {
|
||||||
|
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create file: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(file, r); err != nil {
|
||||||
|
return fmt.Errorf("unable to write file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContextFromReader will read the contents of the given reader as either a
|
||||||
|
// Dockerfile or tar archive to be extracted to a temporary directory used as
|
||||||
|
// the context directory. Returns the absolute path to the temporary context
|
||||||
|
// directory, the relative path of the dockerfile in that context directory,
|
||||||
|
// and a non-nil error on success.
|
||||||
|
func getContextFromReader(r io.Reader, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
||||||
|
buf := bufio.NewReader(r)
|
||||||
|
|
||||||
|
magic, err := buf.Peek(tarHeaderSize)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return "", "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if absContextDir, err = ioutil.TempDir("", "docker-build-context-"); err != nil {
|
||||||
|
return "", "", fmt.Errorf("unbale to create temporary context directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(d string) {
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(d)
|
||||||
|
}
|
||||||
|
}(absContextDir)
|
||||||
|
|
||||||
|
if !archive.IsArchive(magic) { // Input should be read as a Dockerfile.
|
||||||
|
// -f option has no meaning when we're reading it from stdin,
|
||||||
|
// so just use our default Dockerfile name
|
||||||
|
relDockerfile = api.DefaultDockerfileName
|
||||||
|
|
||||||
|
return absContextDir, relDockerfile, writeToFile(buf, filepath.Join(absContextDir, relDockerfile))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := archive.Untar(buf, absContextDir, nil); err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to extract stdin to temporary context direcotry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDockerfileRelPath(absContextDir, dockerfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContextFromGitURL uses a Git URL as context for a `docker build`. The
|
||||||
|
// git repo is cloned into a temporary directory used as the context directory.
|
||||||
|
// Returns the absolute path to the temporary context directory, the relative
|
||||||
|
// path of the dockerfile in that context directory, and a non-nil error on
|
||||||
|
// success.
|
||||||
|
func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
||||||
|
if absContextDir, err = utils.GitClone(gitURL); err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDockerfileRelPath(absContextDir, dockerfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContextFromURL uses a remote URL as context for a `docker build`. The
|
||||||
|
// remote resource is downloaded as either a Dockerfile or a context tar
|
||||||
|
// archive and stored in a temporary directory used as the context directory.
|
||||||
|
// Returns the absolute path to the temporary context directory, the relative
|
||||||
|
// path of the dockerfile in that context directory, and a non-nil error on
|
||||||
|
// success.
|
||||||
|
func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
||||||
|
response, err := httputils.Download(remoteURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Pass the response body through a progress reader.
|
||||||
|
progReader := &progressreader.Config{
|
||||||
|
In: response.Body,
|
||||||
|
Out: out,
|
||||||
|
Formatter: streamformatter.NewStreamFormatter(),
|
||||||
|
Size: int(response.ContentLength),
|
||||||
|
NewLines: true,
|
||||||
|
ID: "",
|
||||||
|
Action: fmt.Sprintf("Downloading build context from remote url: %s", remoteURL),
|
||||||
|
}
|
||||||
|
|
||||||
|
return getContextFromReader(progReader, dockerfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContextFromLocalDir uses the given local directory as context for a
|
||||||
|
// `docker build`. Returns the absolute path to the local context directory,
|
||||||
|
// the relative path of the dockerfile in that context directory, and a non-nil
|
||||||
|
// error on success.
|
||||||
|
func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
||||||
|
// When using a local context directory, when the Dockerfile is specified
|
||||||
|
// with the `-f/--file` option then it is considered relative to the
|
||||||
|
// current directory and not the context directory.
|
||||||
|
if dockerfileName != "" {
|
||||||
|
if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDockerfileRelPath(localDir, dockerfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
|
||||||
|
|
||||||
|
type trustedDockerfile struct {
|
||||||
|
*os.File
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *trustedDockerfile) Close() error {
|
||||||
|
td.File.Close()
|
||||||
|
return os.Remove(td.File.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolvedTag records the repository, tag, and resolved digest reference
|
||||||
|
// from a Dockerfile rewrite.
|
||||||
|
type resolvedTag struct {
|
||||||
|
repoInfo *registry.RepositoryInfo
|
||||||
|
digestRef, tagRef registry.Reference
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
|
||||||
|
// "FROM <image>" instructions to a digest reference. `translator` is a
|
||||||
|
// function that takes a repository name and tag reference and returns a
|
||||||
|
// trusted digest reference.
|
||||||
|
func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
|
||||||
|
dockerfile, err := os.Open(dockerfileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
|
||||||
|
}
|
||||||
|
defer dockerfile.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(dockerfile)
|
||||||
|
|
||||||
|
// Make a tempfile to store the rewritten Dockerfile.
|
||||||
|
tempFile, err := ioutil.TempFile("", "trusted-dockerfile-")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustedFile := &trustedDockerfile{
|
||||||
|
File: tempFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
// Close the tempfile if there was an error during Notary lookups.
|
||||||
|
// Otherwise the caller should close it.
|
||||||
|
trustedFile.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Scan the lines of the Dockerfile, looking for a "FROM" line.
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
|
||||||
|
if matches != nil && matches[1] != "scratch" {
|
||||||
|
// Replace the line with a resolved "FROM repo@digest"
|
||||||
|
repo, tag := parsers.ParseRepositoryTag(matches[1])
|
||||||
|
if tag == "" {
|
||||||
|
tag = tags.DEFAULTTAG
|
||||||
|
}
|
||||||
|
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to parse repository info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := registry.ParseReference(tag)
|
||||||
|
|
||||||
|
if !ref.HasDigest() && isTrusted() {
|
||||||
|
trustedRef, err := translator(repo, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo)))
|
||||||
|
resolvedTags = append(resolvedTags, &resolvedTag{
|
||||||
|
repoInfo: repoInfo,
|
||||||
|
digestRef: trustedRef,
|
||||||
|
tagRef: ref,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := fmt.Fprintln(tempFile, line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trustedFile.size += int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile.Seek(0, os.SEEK_SET)
|
||||||
|
|
||||||
|
return trustedFile, resolvedTags, scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
|
||||||
|
// replaces the entry with the given Dockerfile name with the contents of the
|
||||||
|
// new Dockerfile. Returns a new tar archive stream with the replaced
|
||||||
|
// Dockerfile.
|
||||||
|
func replaceDockerfileTarWrapper(inputTarStream io.ReadCloser, newDockerfile *trustedDockerfile, dockerfileName string) io.ReadCloser {
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
tarReader := tar.NewReader(inputTarStream)
|
||||||
|
tarWriter := tar.NewWriter(pipeWriter)
|
||||||
|
|
||||||
|
defer inputTarStream.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
hdr, err := tarReader.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
// Signals end of archive.
|
||||||
|
tarWriter.Close()
|
||||||
|
pipeWriter.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var content io.Reader = tarReader
|
||||||
|
|
||||||
|
if hdr.Name == dockerfileName {
|
||||||
|
// This entry is the Dockerfile. Since the tar archive was
|
||||||
|
// generated from a directory on the local filesystem, the
|
||||||
|
// Dockerfile will only appear once in the archive.
|
||||||
|
hdr.Size = newDockerfile.size
|
||||||
|
content = newDockerfile
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tarWriter.WriteHeader(hdr); err != nil {
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(tarWriter, content); err != nil {
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return pipeReader
|
||||||
}
|
}
|
||||||
|
|||||||
+89
-130
@@ -2,30 +2,34 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"net/url"
|
||||||
"reflect"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/pkg/homedir"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
"github.com/docker/docker/pkg/sockets"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/pkg/tlsconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerCli represents the docker command line client.
|
// DockerCli represents the docker command line client.
|
||||||
// Instances of the client can be returned from NewDockerCli.
|
// Instances of the client can be returned from NewDockerCli.
|
||||||
type DockerCli struct {
|
type DockerCli struct {
|
||||||
|
// initializing closure
|
||||||
|
init func() error
|
||||||
|
|
||||||
// proto holds the client protocol i.e. unix.
|
// proto holds the client protocol i.e. unix.
|
||||||
proto string
|
proto string
|
||||||
// addr holds the client address.
|
// addr holds the client address.
|
||||||
addr string
|
addr string
|
||||||
|
// basePath holds the path to prepend to the requests
|
||||||
|
basePath string
|
||||||
|
|
||||||
// configFile has the client configuration file
|
// configFile has the client configuration file
|
||||||
configFile *cliconfig.ConfigFile
|
configFile *cliconfig.ConfigFile
|
||||||
@@ -54,83 +58,11 @@ type DockerCli struct {
|
|||||||
transport *http.Transport
|
transport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
func (cli *DockerCli) Initialize() error {
|
||||||
"json": func(v interface{}) string {
|
if cli.init == nil {
|
||||||
a, _ := json.Marshal(v)
|
return nil
|
||||||
return string(a)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) Out() io.Writer {
|
|
||||||
return cli.out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) Err() io.Writer {
|
|
||||||
return cli.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
|
|
||||||
camelArgs := make([]string, len(args))
|
|
||||||
for i, s := range args {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
|
||||||
}
|
}
|
||||||
methodName := "Cmd" + strings.Join(camelArgs, "")
|
return cli.init()
|
||||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
|
||||||
if !method.IsValid() {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return method.Interface().(func(...string) error), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd executes the specified command.
|
|
||||||
func (cli *DockerCli) Cmd(args ...string) error {
|
|
||||||
if len(args) > 1 {
|
|
||||||
method, exists := cli.getMethod(args[:2]...)
|
|
||||||
if exists {
|
|
||||||
return method(args[2:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(args) > 0 {
|
|
||||||
method, exists := cli.getMethod(args[0])
|
|
||||||
if !exists {
|
|
||||||
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0])
|
|
||||||
}
|
|
||||||
return method(args[1:]...)
|
|
||||||
}
|
|
||||||
return cli.CmdHelp()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subcmd is a subcommand of the main "docker" command.
|
|
||||||
// A subcommand represents an action that can be performed
|
|
||||||
// from the Docker command line client.
|
|
||||||
//
|
|
||||||
// To see all available subcommands, run "docker --help".
|
|
||||||
func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet {
|
|
||||||
var errorHandling flag.ErrorHandling
|
|
||||||
if exitOnError {
|
|
||||||
errorHandling = flag.ExitOnError
|
|
||||||
} else {
|
|
||||||
errorHandling = flag.ContinueOnError
|
|
||||||
}
|
|
||||||
flags := flag.NewFlagSet(name, errorHandling)
|
|
||||||
if signature != "" {
|
|
||||||
signature = " " + signature
|
|
||||||
}
|
|
||||||
flags.Usage = func() {
|
|
||||||
flags.ShortUsage()
|
|
||||||
flags.PrintDefaults()
|
|
||||||
}
|
|
||||||
flags.ShortUsage = func() {
|
|
||||||
options := ""
|
|
||||||
if flags.FlagCountUndeprecated() > 0 {
|
|
||||||
options = " [OPTIONS]"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(flags.Out(), "\nUsage: docker %s%s%s\n\n%s\n", name, options, signature, description)
|
|
||||||
}
|
|
||||||
return flags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckTtyInput checks if we are trying to attach to a container tty
|
// CheckTtyInput checks if we are trying to attach to a container tty
|
||||||
@@ -145,59 +77,86 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) PsFormat() string {
|
||||||
|
return cli.configFile.PsFormat
|
||||||
|
}
|
||||||
|
|
||||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||||
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
|
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
|
||||||
// is set the client scheme will be set to https.
|
// is set the client scheme will be set to https.
|
||||||
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
|
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
|
||||||
var (
|
cli := &DockerCli{
|
||||||
inFd uintptr
|
in: in,
|
||||||
outFd uintptr
|
out: out,
|
||||||
isTerminalIn = false
|
err: err,
|
||||||
isTerminalOut = false
|
keyFile: clientFlags.Common.TrustKey,
|
||||||
scheme = "http"
|
|
||||||
)
|
|
||||||
|
|
||||||
if tlsConfig != nil {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
if in != nil {
|
|
||||||
inFd, isTerminalIn = term.GetFdInfo(in)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if out != nil {
|
cli.init = func() error {
|
||||||
outFd, isTerminalOut = term.GetFdInfo(out)
|
clientFlags.PostParse()
|
||||||
|
|
||||||
|
hosts := clientFlags.Common.Hosts
|
||||||
|
|
||||||
|
switch len(hosts) {
|
||||||
|
case 0:
|
||||||
|
defaultHost := os.Getenv("DOCKER_HOST")
|
||||||
|
if defaultHost == "" {
|
||||||
|
defaultHost = opts.DefaultHost
|
||||||
|
}
|
||||||
|
defaultHost, err := opts.ValidateHost(defaultHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hosts = []string{defaultHost}
|
||||||
|
case 1:
|
||||||
|
// only accept one host to talk to
|
||||||
|
default:
|
||||||
|
return errors.New("Please specify only one -H")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
||||||
|
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
||||||
|
|
||||||
|
if cli.proto == "tcp" {
|
||||||
|
// error is checked in pkg/parsers already
|
||||||
|
parsed, _ := url.Parse("tcp://" + cli.addr)
|
||||||
|
cli.addr = parsed.Host
|
||||||
|
cli.basePath = parsed.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientFlags.Common.TLSOptions != nil {
|
||||||
|
cli.scheme = "https"
|
||||||
|
var e error
|
||||||
|
cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cli.scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.in != nil {
|
||||||
|
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
||||||
|
}
|
||||||
|
if cli.out != nil {
|
||||||
|
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The transport is created here for reuse during the client session.
|
||||||
|
cli.transport = &http.Transport{
|
||||||
|
TLSClientConfig: cli.tlsConfig,
|
||||||
|
}
|
||||||
|
sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)
|
||||||
|
|
||||||
|
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
|
||||||
|
if e != nil {
|
||||||
|
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
|
||||||
|
}
|
||||||
|
cli.configFile = configFile
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
return cli
|
||||||
err = out
|
|
||||||
}
|
|
||||||
|
|
||||||
// The transport is created here for reuse during the client session.
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
}
|
|
||||||
utils.ConfigureTCPTransport(tr, proto, addr)
|
|
||||||
|
|
||||||
configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
|
|
||||||
if e != nil {
|
|
||||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DockerCli{
|
|
||||||
proto: proto,
|
|
||||||
addr: addr,
|
|
||||||
configFile: configFile,
|
|
||||||
in: in,
|
|
||||||
out: out,
|
|
||||||
err: err,
|
|
||||||
keyFile: keyFile,
|
|
||||||
inFd: inFd,
|
|
||||||
outFd: outFd,
|
|
||||||
isTerminalIn: isTerminalIn,
|
|
||||||
isTerminalOut: isTerminalOut,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
scheme: scheme,
|
|
||||||
transport: tr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,3 @@
|
|||||||
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
|
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
|
||||||
// See https://docs.docker.com/installation/ for instructions on installing Docker.
|
// See https://docs.docker.com/installation/ for instructions on installing Docker.
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// An StatusError reports an unsuccessful exit by a command.
|
|
||||||
type StatusError struct {
|
|
||||||
Status string
|
|
||||||
StatusCode int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e StatusError) Error() string {
|
|
||||||
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
@@ -17,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", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true)
|
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", 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>\")")
|
||||||
@@ -27,6 +28,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|||||||
flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
||||||
cmd.Require(flag.Max, 2)
|
cmd.Require(flag.Max, 2)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -66,12 +68,14 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
serverResp, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
|
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+295
-28
@@ -1,57 +1,324 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdCp copies files/folders from a path on the container to a directory on the host running the command.
|
type copyDirection int
|
||||||
//
|
|
||||||
// If HOSTDIR is '-', the data is written as a tar file to STDOUT.
|
|
||||||
//
|
|
||||||
// Usage: docker cp CONTAINER:PATH HOSTDIR
|
|
||||||
func (cli *DockerCli) CmdCp(args ...string) error {
|
|
||||||
cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data as a tar file to STDOUT.", true)
|
|
||||||
cmd.Require(flag.Exact, 2)
|
|
||||||
|
|
||||||
|
const (
|
||||||
|
fromContainer copyDirection = (1 << iota)
|
||||||
|
toContainer
|
||||||
|
acrossContainers = fromContainer | toContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdCp copies files/folders to or from a path in a container.
|
||||||
|
//
|
||||||
|
// When copying from a container, if LOCALPATH is '-' the data is written as a
|
||||||
|
// tar archive file to STDOUT.
|
||||||
|
//
|
||||||
|
// When copying to a container, if LOCALPATH is '-' the data is read as a tar
|
||||||
|
// archive file from STDIN, and the destination CONTAINER:PATH, must specify
|
||||||
|
// a directory.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// docker cp CONTAINER:PATH LOCALPATH|-
|
||||||
|
// docker cp LOCALPATH|- CONTAINER:PATH
|
||||||
|
func (cli *DockerCli) CmdCp(args ...string) error {
|
||||||
|
cmd := Cli.Subcmd(
|
||||||
|
"cp",
|
||||||
|
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
||||||
|
strings.Join([]string{
|
||||||
|
"Copy files/folders between a container and your host.\n",
|
||||||
|
"Use '-' as the source to read a tar archive from stdin\n",
|
||||||
|
"and extract it to a directory destination in a container.\n",
|
||||||
|
"Use '-' as the destination to stream a tar archive of a\n",
|
||||||
|
"container source to stdout.",
|
||||||
|
}, ""),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd.Require(flag.Exact, 2)
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
// deal with path name with `:`
|
if cmd.Arg(0) == "" {
|
||||||
info := strings.SplitN(cmd.Arg(0), ":", 2)
|
return fmt.Errorf("source can not be empty")
|
||||||
|
}
|
||||||
if len(info) != 2 {
|
if cmd.Arg(1) == "" {
|
||||||
return fmt.Errorf("Error: Path not specified")
|
return fmt.Errorf("destination can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := &types.CopyConfig{
|
srcContainer, srcPath := splitCpArg(cmd.Arg(0))
|
||||||
Resource: info[1],
|
dstContainer, dstPath := splitCpArg(cmd.Arg(1))
|
||||||
|
|
||||||
|
var direction copyDirection
|
||||||
|
if srcContainer != "" {
|
||||||
|
direction |= fromContainer
|
||||||
}
|
}
|
||||||
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", cfg, nil)
|
if dstContainer != "" {
|
||||||
if stream != nil {
|
direction |= toContainer
|
||||||
defer stream.Close()
|
|
||||||
}
|
}
|
||||||
if statusCode == 404 {
|
|
||||||
return fmt.Errorf("No such container: %v", info[0])
|
switch direction {
|
||||||
|
case fromContainer:
|
||||||
|
return cli.copyFromContainer(srcContainer, srcPath, dstPath)
|
||||||
|
case toContainer:
|
||||||
|
return cli.copyToContainer(srcPath, dstContainer, dstPath)
|
||||||
|
case acrossContainers:
|
||||||
|
// Copying between containers isn't supported.
|
||||||
|
return fmt.Errorf("copying between containers is not supported")
|
||||||
|
default:
|
||||||
|
// User didn't specify any container.
|
||||||
|
return fmt.Errorf("must specify at least one container source")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
|
||||||
|
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
|
||||||
|
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
|
||||||
|
// absolute path:
|
||||||
|
// `/path/to/file:name.txt` or `./file:name.txt`
|
||||||
|
//
|
||||||
|
// This is apparently how `scp` handles this as well:
|
||||||
|
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
|
||||||
|
//
|
||||||
|
// We can't simply check for a filepath separator because container names may
|
||||||
|
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
|
||||||
|
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
|
||||||
|
// client, a `:` could be part of an absolute Windows path, in which case it
|
||||||
|
// is immediately proceeded by a backslash.
|
||||||
|
func splitCpArg(arg string) (container, path string) {
|
||||||
|
if filepath.IsAbs(arg) {
|
||||||
|
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
|
||||||
|
return "", arg
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(arg, ":", 2)
|
||||||
|
|
||||||
|
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
|
||||||
|
// Either there's no `:` in the arg
|
||||||
|
// OR it's an explicit local relative path like `./file:name.txt`.
|
||||||
|
return "", arg
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) statContainerPath(containerName, path string) (types.ContainerPathStat, error) {
|
||||||
|
var stat types.ContainerPathStat
|
||||||
|
|
||||||
|
query := make(url.Values, 1)
|
||||||
|
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/containers/%s/archive?%s", containerName, query.Encode())
|
||||||
|
|
||||||
|
response, err := cli.call("HEAD", urlStr, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return stat, err
|
||||||
|
}
|
||||||
|
defer response.body.Close()
|
||||||
|
|
||||||
|
if response.statusCode != http.StatusOK {
|
||||||
|
return stat, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPath := cmd.Arg(1)
|
return getContainerPathStatFromHeader(response.header)
|
||||||
if statusCode == 200 {
|
}
|
||||||
if hostPath == "-" {
|
|
||||||
_, err = io.Copy(cli.out, stream)
|
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
|
||||||
} else {
|
var stat types.ContainerPathStat
|
||||||
err = archive.Untar(stream, hostPath, &archive.TarOptions{NoLchown: true})
|
|
||||||
}
|
encodedStat := header.Get("X-Docker-Container-Path-Stat")
|
||||||
|
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
|
||||||
|
|
||||||
|
err := json.NewDecoder(statDecoder).Decode(&stat)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("unable to decode container path stat header: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveLocalPath(localPath string) (absPath string, err error) {
|
||||||
|
if absPath, err = filepath.Abs(localPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string) (err error) {
|
||||||
|
if dstPath != "-" {
|
||||||
|
// Get an absolute destination path.
|
||||||
|
dstPath, err = resolveLocalPath(dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query := make(url.Values, 1)
|
||||||
|
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/containers/%s/archive?%s", srcContainer, query.Encode())
|
||||||
|
|
||||||
|
response, err := cli.call("GET", urlStr, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.body.Close()
|
||||||
|
|
||||||
|
if response.statusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstPath == "-" {
|
||||||
|
// Send the response to STDOUT.
|
||||||
|
_, err = io.Copy(os.Stdout, response.body)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// In order to get the copy behavior right, we need to know information
|
||||||
|
// about both the source and the destination. The response headers include
|
||||||
|
// stat info about the source that we can use in deciding exactly how to
|
||||||
|
// copy it locally. Along with the stat info about the local destination,
|
||||||
|
// we have everything we need to handle the multiple possibilities there
|
||||||
|
// can be when copying a file/dir from one location to another file/dir.
|
||||||
|
stat, err := getContainerPathStatFromHeader(response.header)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get resource stat from response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare source copy info.
|
||||||
|
srcInfo := archive.CopyInfo{
|
||||||
|
Path: srcPath,
|
||||||
|
Exists: true,
|
||||||
|
IsDir: stat.Mode.IsDir(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// See comments in the implementation of `archive.CopyTo` for exactly what
|
||||||
|
// goes into deciding how and whether the source archive needs to be
|
||||||
|
// altered for the correct copy behavior.
|
||||||
|
return archive.CopyTo(response.body, srcInfo, dstPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (err error) {
|
||||||
|
if srcPath != "-" {
|
||||||
|
// Get an absolute source path.
|
||||||
|
srcPath, err = resolveLocalPath(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In order to get the copy behavior right, we need to know information
|
||||||
|
// about both the source and destination. The API is a simple tar
|
||||||
|
// archive/extract API but we can use the stat info header about the
|
||||||
|
// destination to be more informed about exactly what the destination is.
|
||||||
|
|
||||||
|
// Prepare destination copy info by stat-ing the container path.
|
||||||
|
dstInfo := archive.CopyInfo{Path: dstPath}
|
||||||
|
dstStat, err := cli.statContainerPath(dstContainer, dstPath)
|
||||||
|
|
||||||
|
// If the destination is a symbolic link, we should evaluate it.
|
||||||
|
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||||
|
linkTarget := dstStat.LinkTarget
|
||||||
|
if !filepath.IsAbs(linkTarget) {
|
||||||
|
// Join with the parent directory.
|
||||||
|
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
||||||
|
linkTarget = filepath.Join(dstParent, linkTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstInfo.Path = linkTarget
|
||||||
|
dstStat, err = cli.statContainerPath(dstContainer, linkTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore any error and assume that the parent directory of the destination
|
||||||
|
// path exists, in which case the copy may still succeed. If there is any
|
||||||
|
// type of conflict (e.g., non-directory overwriting an existing directory
|
||||||
|
// or vice versia) the extraction will fail. If the destination simply did
|
||||||
|
// not exist, but the parent directory does, the extraction will still
|
||||||
|
// succeed.
|
||||||
|
if err == nil {
|
||||||
|
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
content io.Reader
|
||||||
|
resolvedDstPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
if srcPath == "-" {
|
||||||
|
// Use STDIN.
|
||||||
|
content = os.Stdin
|
||||||
|
resolvedDstPath = dstInfo.Path
|
||||||
|
if !dstInfo.IsDir {
|
||||||
|
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Prepare source copy info.
|
||||||
|
srcInfo, err := archive.CopyInfoSourcePath(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srcArchive, err := archive.TarResource(srcInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcArchive.Close()
|
||||||
|
|
||||||
|
// With the stat info about the local source as well as the
|
||||||
|
// destination, we have enough information to know whether we need to
|
||||||
|
// alter the archive that we upload so that when the server extracts
|
||||||
|
// it to the specified directory in the container we get the disired
|
||||||
|
// copy behavior.
|
||||||
|
|
||||||
|
// See comments in the implementation of `archive.PrepareArchiveCopy`
|
||||||
|
// for exactly what goes into deciding how and whether the source
|
||||||
|
// archive needs to be altered for the correct copy behavior when it is
|
||||||
|
// extracted. This function also infers from the source and destination
|
||||||
|
// info which directory to extract to, which may be the parent of the
|
||||||
|
// destination that the user specified.
|
||||||
|
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer preparedArchive.Close()
|
||||||
|
|
||||||
|
resolvedDstPath = dstDir
|
||||||
|
content = preparedArchive
|
||||||
|
}
|
||||||
|
|
||||||
|
query := make(url.Values, 2)
|
||||||
|
query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API.
|
||||||
|
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
||||||
|
query.Set("noOverwriteDirNonDir", "true")
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/containers/%s/archive?%s", dstContainer, query.Encode())
|
||||||
|
|
||||||
|
response, err := cli.stream("PUT", urlStr, &streamOpts{in: content})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.body.Close()
|
||||||
|
|
||||||
|
if response.statusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-13
@@ -10,11 +10,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) pullImage(image string) error {
|
func (cli *DockerCli) pullImage(image string) error {
|
||||||
@@ -52,7 +52,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|||||||
out: out,
|
out: out,
|
||||||
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
||||||
}
|
}
|
||||||
if err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -94,30 +94,54 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
defer containerIDFile.Close()
|
defer containerIDFile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
//create the container
|
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
if tag == "" {
|
||||||
//if image not found try to pull it
|
tag = tags.DEFAULTTAG
|
||||||
if statusCode == 404 && strings.Contains(err.Error(), config.Image) {
|
}
|
||||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
|
||||||
if tag == "" {
|
ref := registry.ParseReference(tag)
|
||||||
tag = tags.DEFAULTTAG
|
var trustedRef registry.Reference
|
||||||
|
|
||||||
|
if isTrusted() && !ref.HasDigest() {
|
||||||
|
var err error
|
||||||
|
trustedRef, err = cli.trustedReference(repo, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag))
|
config.Image = trustedRef.ImageName(repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
//create the container
|
||||||
|
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
||||||
|
//if image not found try to pull it
|
||||||
|
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
|
||||||
|
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo))
|
||||||
|
|
||||||
// we don't want to write to stdout anything apart from container.ID
|
// we don't want to write to stdout anything apart from container.ID
|
||||||
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if trustedRef != nil && !ref.HasDigest() {
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
// Retry
|
// Retry
|
||||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var response types.ContainerCreateResponse
|
var response types.ContainerCreateResponse
|
||||||
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, warning := range response.Warnings {
|
for _, warning := range response.Warnings {
|
||||||
@@ -135,7 +159,8 @@ 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", "IMAGE [COMMAND] [ARG...]", "Create a new container", true)
|
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true)
|
||||||
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
var (
|
var (
|
||||||
|
|||||||
+7
-3
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
@@ -17,21 +18,24 @@ 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", "CONTAINER", "Inspect changes on a container's filesystem", true)
|
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
if cmd.Arg(0) == "" {
|
if cmd.Arg(0) == "" {
|
||||||
return fmt.Errorf("Container name cannot be empty")
|
return fmt.Errorf("Container name cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
rdr, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
changes := []types.ContainerChange{}
|
changes := []types.ContainerChange{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&changes); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&changes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
@@ -13,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", "", "Get real time events from the server", true)
|
cmd := Cli.Subcmd("events", nil, "Get real time events from the server", 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)
|
||||||
@@ -36,11 +38,12 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ref := time.Now()
|
||||||
if *since != "" {
|
if *since != "" {
|
||||||
v.Set("since", timeutils.GetTimestamp(*since))
|
v.Set("since", timeutils.GetTimestamp(*since, ref))
|
||||||
}
|
}
|
||||||
if *until != "" {
|
if *until != "" {
|
||||||
v.Set("until", timeutils.GetTimestamp(*until))
|
v.Set("until", timeutils.GetTimestamp(*until, ref))
|
||||||
}
|
}
|
||||||
if len(eventFilterArgs) > 0 {
|
if len(eventFilterArgs) > 0 {
|
||||||
filterJSON, err := filters.ToParam(eventFilterArgs)
|
filterJSON, err := filters.ToParam(eventFilterArgs)
|
||||||
@@ -53,7 +56,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
if err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+8
-5
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
@@ -15,21 +16,23 @@ 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", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true)
|
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", 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
|
||||||
if execConfig.Container == "" || err != nil {
|
if execConfig.Container == "" || err != nil {
|
||||||
return StatusError{StatusCode: 1}
|
return Cli.StatusError{StatusCode: 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var response types.ContainerExecCreateResponse
|
var response types.ContainerExecCreateResponse
|
||||||
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +127,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return StatusError{StatusCode: status}
|
return Cli.StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +15,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", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true)
|
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", 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)
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
|
|||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
out: output,
|
out: output,
|
||||||
}
|
}
|
||||||
if err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdHelp displays information on a Docker command.
|
|
||||||
//
|
|
||||||
// If more than one command is specified, information is only shown for the first command.
|
|
||||||
//
|
|
||||||
// Usage: docker help COMMAND or docker COMMAND --help
|
|
||||||
func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
||||||
if len(args) > 1 {
|
|
||||||
method, exists := cli.getMethod(args[:2]...)
|
|
||||||
if exists {
|
|
||||||
method("--help")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(args) > 0 {
|
|
||||||
method, exists := cli.getMethod(args[0])
|
|
||||||
if !exists {
|
|
||||||
return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0])
|
|
||||||
}
|
|
||||||
method("--help")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.Usage()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -138,18 +138,18 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||||
// then the user can't change OUR headers
|
// then the user can't change OUR headers
|
||||||
for k, v := range cli.configFile.HttpHeaders {
|
for k, v := range cli.configFile.HTTPHeaders {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
req.Header.Set("Content-Type", "text/plain")
|
||||||
req.Header.Set("Connection", "Upgrade")
|
req.Header.Set("Connection", "Upgrade")
|
||||||
req.Header.Set("Upgrade", "tcp")
|
req.Header.Set("Upgrade", "tcp")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
@@ -17,20 +18,23 @@ 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", "IMAGE", "Show the history of an image", true)
|
cmd := Cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", 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")
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
rdr, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
|
serverResp, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
history := []types.ImageHistory{}
|
history := []types.ImageHistory{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&history); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&history); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
@@ -21,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", "[REPOSITORY]", "List images", true)
|
cmd := Cli.Subcmd("images", []string{"[REPOSITORY]"}, "List images", 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")
|
||||||
@@ -30,6 +31,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||||||
flFilter := opts.NewListOpts(nil)
|
flFilter := opts.NewListOpts(nil)
|
||||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
// Consolidate all filter flags, and sanity check them early.
|
// Consolidate all filter flags, and sanity check them early.
|
||||||
@@ -61,13 +63,15 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||||||
v.Set("all", "1")
|
v.Set("all", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
|
serverResp, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
images := []types.Image{}
|
images := []types.Image{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&images); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&images); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-5
@@ -4,20 +4,23 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
|
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
|
||||||
//
|
//
|
||||||
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file. If the URL is '-', then the tar file is read from STDIN.
|
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file or a path to local file relative to docker client. If the URL is '-', then the tar file is read from STDIN.
|
||||||
//
|
//
|
||||||
// Usage: docker import [OPTIONS] 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", "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]]"}, "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)
|
||||||
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")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
@@ -36,7 +39,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
v.Add("changes", change)
|
v.Add("changes", change)
|
||||||
}
|
}
|
||||||
if cmd.NArg() == 3 {
|
if cmd.NArg() == 3 {
|
||||||
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n")
|
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'file|URL|- [REPOSITORY [TAG]]' has been deprecated. Please use file|URL|- [REPOSITORY[:TAG]]\n")
|
||||||
v.Set("tag", cmd.Arg(2))
|
v.Set("tag", cmd.Arg(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +55,15 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
|
|
||||||
if src == "-" {
|
if src == "-" {
|
||||||
in = cli.in
|
in = cli.in
|
||||||
|
} else if !urlutil.IsURL(src) {
|
||||||
|
v.Set("fromSrc", "-")
|
||||||
|
file, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
in = file
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sopts := &streamOpts{
|
sopts := &streamOpts{
|
||||||
@@ -60,5 +72,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-11
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
@@ -14,17 +16,20 @@ 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", "", "Display system-wide information", true)
|
cmd := Cli.Subcmd("info", nil, "Display system-wide information", true)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
rdr, _, err := cli.call("GET", "/info", nil, nil)
|
serverResp, err := cli.call("GET", "/info", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
info := &types.Info{}
|
info := &types.Info{}
|
||||||
if err := json.NewDecoder(rdr).Decode(info); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(info); err != nil {
|
||||||
return fmt.Errorf("Error reading remote info: %v", err)
|
return fmt.Errorf("Error reading remote info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,15 +72,27 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !info.MemoryLimit {
|
// Only output these warnings if the server supports these features
|
||||||
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
||||||
}
|
if h.OS != "windows" {
|
||||||
if !info.SwapLimit {
|
if !info.MemoryLimit {
|
||||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
||||||
}
|
}
|
||||||
if !info.IPv4Forwarding {
|
if !info.SwapLimit {
|
||||||
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
||||||
|
}
|
||||||
|
if !info.IPv4Forwarding {
|
||||||
|
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
||||||
|
}
|
||||||
|
if !info.BridgeNfIptables {
|
||||||
|
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
|
||||||
|
}
|
||||||
|
if !info.BridgeNfIp6tables {
|
||||||
|
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.Labels != nil {
|
if info.Labels != nil {
|
||||||
fmt.Fprintln(cli.out, "Labels:")
|
fmt.Fprintln(cli.out, "Labels:")
|
||||||
for _, attribute := range info.Labels {
|
for _, attribute := range info.Labels {
|
||||||
|
|||||||
+47
-11
@@ -9,41 +9,55 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var funcMap = template.FuncMap{
|
||||||
|
"json": func(v interface{}) string {
|
||||||
|
a, _ := json.Marshal(v)
|
||||||
|
return string(a)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// CmdInspect displays low-level information on one or more containers or images.
|
// CmdInspect displays low-level information on one or more containers or images.
|
||||||
//
|
//
|
||||||
// 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", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true)
|
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", 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)")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
|
var err error
|
||||||
|
var obj []byte
|
||||||
|
|
||||||
if *tmplStr != "" {
|
if *tmplStr != "" {
|
||||||
var err error
|
|
||||||
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
||||||
return StatusError{StatusCode: 64,
|
return Cli.StatusError{StatusCode: 64,
|
||||||
Status: "Template parsing error: " + err.Error()}
|
Status: "Template parsing error: " + err.Error()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
|
||||||
|
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
|
||||||
|
}
|
||||||
|
|
||||||
indented := new(bytes.Buffer)
|
indented := new(bytes.Buffer)
|
||||||
indented.WriteString("[\n")
|
indented.WriteString("[\n")
|
||||||
status := 0
|
status := 0
|
||||||
isImage := false
|
isImage := false
|
||||||
|
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
|
||||||
if err != nil {
|
if *inspectType == "" || *inspectType == "container" {
|
||||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
||||||
isImage = true
|
if err != nil && *inspectType == "container" {
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "No such") {
|
if strings.Contains(err.Error(), "No such") {
|
||||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(cli.err, "%s", err)
|
fmt.Fprintf(cli.err, "%s", err)
|
||||||
}
|
}
|
||||||
@@ -52,8 +66,27 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if obj == nil && (*inspectType == "" || *inspectType == "image") {
|
||||||
|
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
||||||
|
isImage = true
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "No such") {
|
||||||
|
if *inspectType == "" {
|
||||||
|
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(cli.err, "Error: No such image: %s\n", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(cli.err, "%s", err)
|
||||||
|
}
|
||||||
|
status = 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
if err = json.Indent(indented, obj, "", " "); err != nil {
|
if err := json.Indent(indented, obj, "", " "); err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
status = 1
|
status = 1
|
||||||
continue
|
continue
|
||||||
@@ -109,13 +142,16 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||||||
indented.WriteString("]\n")
|
indented.WriteString("]\n")
|
||||||
|
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
|
// Note that we will always write "[]" when "-f" isn't specified,
|
||||||
|
// to make sure the output would always be array, see
|
||||||
|
// https://github.com/docker/docker/pull/9500#issuecomment-65846734
|
||||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return StatusError{StatusCode: status}
|
return Cli.StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,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", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true)
|
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", 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)
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -4,6 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,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", "", "Load an image from a tar archive on STDIN", true)
|
cmd := Cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", 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)
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
|||||||
in: input,
|
in: input,
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
if err := cli.stream("POST", "/images/load", sopts); err != nil {
|
if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+8
-5
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
@@ -21,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", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
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.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
var username, password, email string
|
var username, password, email string
|
||||||
@@ -32,7 +33,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
serverAddress := registry.IndexServerAddress()
|
serverAddress := registry.IndexServer
|
||||||
if len(cmd.Args()) > 0 {
|
if len(cmd.Args()) > 0 {
|
||||||
serverAddress = cmd.Arg(0)
|
serverAddress = cmd.Arg(0)
|
||||||
}
|
}
|
||||||
@@ -113,8 +114,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
authconfig.ServerAddress = serverAddress
|
authconfig.ServerAddress = serverAddress
|
||||||
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
||||||
|
|
||||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
|
serverResp, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
|
||||||
if statusCode == 401 {
|
if serverResp.statusCode == 401 {
|
||||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||||
if err2 := cli.configFile.Save(); err2 != nil {
|
if err2 := cli.configFile.Save(); err2 != nil {
|
||||||
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
||||||
@@ -125,8 +126,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var response types.AuthResponse
|
var response types.AuthResponse
|
||||||
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||||
// Upon error, remove entry
|
// Upon error, remove entry
|
||||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
)
|
)
|
||||||
@@ -13,11 +14,12 @@ 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", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
serverAddress := registry.IndexServerAddress()
|
|
||||||
|
serverAddress := registry.IndexServer
|
||||||
if len(cmd.Args()) > 0 {
|
if len(cmd.Args()) > 0 {
|
||||||
serverAddress = cmd.Arg(0)
|
serverAddress = cmd.Arg(0)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-11
@@ -4,8 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/timeutils"
|
"github.com/docker/docker/pkg/timeutils"
|
||||||
)
|
)
|
||||||
@@ -14,26 +16,24 @@ import (
|
|||||||
//
|
//
|
||||||
// docker logs [OPTIONS] CONTAINER
|
// docker logs [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
var (
|
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true)
|
||||||
cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", 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")
|
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
||||||
tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
|
||||||
)
|
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
name := cmd.Arg(0)
|
name := cmd.Arg(0)
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|
||||||
if *since != "" {
|
if *since != "" {
|
||||||
v.Set("since", timeutils.GetTimestamp(*since))
|
v.Set("since", timeutils.GetTimestamp(*since, time.Now()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *times {
|
if *times {
|
||||||
@@ -64,5 +64,6 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
err: cli.err,
|
err: cli.err,
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
|
_, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// +build experimental
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
nwclient "github.com/docker/libnetwork/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
||||||
|
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
||||||
|
args = append([]string{"network"}, args...)
|
||||||
|
return nCli.Cmd(os.Args[0], args...)
|
||||||
|
}
|
||||||
+3
-1
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,8 +11,9 @@ 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", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
|
|||||||
+15
-7
@@ -5,8 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/nat"
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/nat"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPort lists port mappings for a container.
|
// CmdPort lists port mappings for a container.
|
||||||
@@ -14,22 +15,25 @@ 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", "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]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var c struct {
|
var c struct {
|
||||||
NetworkSettings struct {
|
NetworkSettings struct {
|
||||||
Ports nat.PortMap
|
Ports nat.PortMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +49,13 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||||||
proto = parts[1]
|
proto = parts[1]
|
||||||
}
|
}
|
||||||
natPort := port + "/" + proto
|
natPort := port + "/" + proto
|
||||||
if frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
newP, err := nat.NewPort(proto, port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
|
||||||
for _, frontend := range frontends {
|
for _, frontend := range frontends {
|
||||||
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
|
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIP, frontend.HostPort)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -56,7 +64,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||||||
|
|
||||||
for from, frontends := range c.NetworkSettings.Ports {
|
for from, frontends := range c.NetworkSettings.Ports {
|
||||||
for _, frontend := range frontends {
|
for _, frontend := range frontends {
|
||||||
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort)
|
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-79
@@ -2,21 +2,15 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api/client/ps"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/docker/pkg/units"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPs outputs a list of Docker containers.
|
// CmdPs outputs a list of Docker containers.
|
||||||
@@ -29,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", "", "List containers", true)
|
cmd = Cli.Subcmd("ps", nil, "List containers", 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)")
|
||||||
@@ -38,6 +32,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
||||||
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
||||||
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
||||||
|
format = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
|
||||||
flFilter = opts.NewListOpts(nil)
|
flFilter = opts.NewListOpts(nil)
|
||||||
)
|
)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
@@ -86,90 +81,36 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
v.Set("filters", filterJSON)
|
v.Set("filters", filterJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
rdr, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
|
serverResp, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
containers := []types.Container{}
|
containers := []types.Container{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&containers); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&containers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
f := *format
|
||||||
if !*quiet {
|
if len(f) == 0 {
|
||||||
fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
if len(cli.PsFormat()) > 0 {
|
||||||
|
f = cli.PsFormat()
|
||||||
if *size {
|
|
||||||
fmt.Fprintln(w, "\tSIZE")
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(w, "\n")
|
f = "table"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stripNamePrefix := func(ss []string) []string {
|
psCtx := ps.Context{
|
||||||
for i, s := range ss {
|
Output: cli.out,
|
||||||
ss[i] = s[1:]
|
Format: f,
|
||||||
}
|
Quiet: *quiet,
|
||||||
|
Size: *size,
|
||||||
return ss
|
Trunc: !*noTrunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range containers {
|
ps.Format(psCtx, containers)
|
||||||
ID := container.ID
|
|
||||||
|
|
||||||
if !*noTrunc {
|
|
||||||
ID = stringid.TruncateID(ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *quiet {
|
|
||||||
fmt.Fprintln(w, ID)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
names = stripNamePrefix(container.Names)
|
|
||||||
command = strconv.Quote(container.Command)
|
|
||||||
)
|
|
||||||
|
|
||||||
if !*noTrunc {
|
|
||||||
command = stringutils.Truncate(command, 20)
|
|
||||||
|
|
||||||
// only display the default name for the container with notrunc is passed
|
|
||||||
for _, name := range names {
|
|
||||||
if len(strings.Split(name, "/")) == 1 {
|
|
||||||
names = []string{name}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image := container.Image
|
|
||||||
if image == "" {
|
|
||||||
image = "<no image>"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", ID, image, command,
|
|
||||||
units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))),
|
|
||||||
container.Status, api.DisplayablePorts(container.Ports), strings.Join(names, ","))
|
|
||||||
|
|
||||||
if *size {
|
|
||||||
if container.SizeRootFs > 0 {
|
|
||||||
fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(container.SizeRw)), units.HumanSize(float64(container.SizeRootFs)))
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "%s\n", units.HumanSize(float64(container.SizeRw)))
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprint(w, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*quiet {
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
|
"github.com/docker/docker/pkg/units"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tableKey = "table"
|
||||||
|
|
||||||
|
idHeader = "CONTAINER ID"
|
||||||
|
imageHeader = "IMAGE"
|
||||||
|
namesHeader = "NAMES"
|
||||||
|
commandHeader = "COMMAND"
|
||||||
|
createdAtHeader = "CREATED AT"
|
||||||
|
runningForHeader = "CREATED"
|
||||||
|
statusHeader = "STATUS"
|
||||||
|
portsHeader = "PORTS"
|
||||||
|
sizeHeader = "SIZE"
|
||||||
|
labelsHeader = "LABELS"
|
||||||
|
)
|
||||||
|
|
||||||
|
type containerContext struct {
|
||||||
|
trunc bool
|
||||||
|
header []string
|
||||||
|
c types.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) ID() string {
|
||||||
|
c.addHeader(idHeader)
|
||||||
|
if c.trunc {
|
||||||
|
return stringid.TruncateID(c.c.ID)
|
||||||
|
}
|
||||||
|
return c.c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Names() string {
|
||||||
|
c.addHeader(namesHeader)
|
||||||
|
names := stripNamePrefix(c.c.Names)
|
||||||
|
if c.trunc {
|
||||||
|
for _, name := range names {
|
||||||
|
if len(strings.Split(name, "/")) == 1 {
|
||||||
|
names = []string{name}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(names, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Image() string {
|
||||||
|
c.addHeader(imageHeader)
|
||||||
|
if c.c.Image == "" {
|
||||||
|
return "<no image>"
|
||||||
|
}
|
||||||
|
return c.c.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Command() string {
|
||||||
|
c.addHeader(commandHeader)
|
||||||
|
command := c.c.Command
|
||||||
|
if c.trunc {
|
||||||
|
command = stringutils.Truncate(command, 20)
|
||||||
|
}
|
||||||
|
return strconv.Quote(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) CreatedAt() string {
|
||||||
|
c.addHeader(createdAtHeader)
|
||||||
|
return time.Unix(int64(c.c.Created), 0).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) RunningFor() string {
|
||||||
|
c.addHeader(runningForHeader)
|
||||||
|
createdAt := time.Unix(int64(c.c.Created), 0)
|
||||||
|
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Ports() string {
|
||||||
|
c.addHeader(portsHeader)
|
||||||
|
return api.DisplayablePorts(c.c.Ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Status() string {
|
||||||
|
c.addHeader(statusHeader)
|
||||||
|
return c.c.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Size() string {
|
||||||
|
c.addHeader(sizeHeader)
|
||||||
|
srw := units.HumanSize(float64(c.c.SizeRw))
|
||||||
|
sv := units.HumanSize(float64(c.c.SizeRootFs))
|
||||||
|
|
||||||
|
sf := srw
|
||||||
|
if c.c.SizeRootFs > 0 {
|
||||||
|
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
|
||||||
|
}
|
||||||
|
return sf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Labels() string {
|
||||||
|
c.addHeader(labelsHeader)
|
||||||
|
if c.c.Labels == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var joinLabels []string
|
||||||
|
for k, v := range c.c.Labels {
|
||||||
|
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
return strings.Join(joinLabels, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) Label(name string) string {
|
||||||
|
n := strings.Split(name, ".")
|
||||||
|
r := strings.NewReplacer("-", " ", "_", " ")
|
||||||
|
h := r.Replace(n[len(n)-1])
|
||||||
|
|
||||||
|
c.addHeader(h)
|
||||||
|
|
||||||
|
if c.c.Labels == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.c.Labels[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) fullHeader() string {
|
||||||
|
if c.header == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join(c.header, "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerContext) addHeader(header string) {
|
||||||
|
if c.header == nil {
|
||||||
|
c.header = []string{}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
for i, s := range ss {
|
||||||
|
ss[i] = s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContainerPsContext(t *testing.T) {
|
||||||
|
containerId := stringid.GenerateRandomID()
|
||||||
|
unix := time.Now().Unix()
|
||||||
|
|
||||||
|
var ctx containerContext
|
||||||
|
cases := []struct {
|
||||||
|
container types.Container
|
||||||
|
trunc bool
|
||||||
|
expValue string
|
||||||
|
expHeader string
|
||||||
|
call func() string
|
||||||
|
}{
|
||||||
|
{types.Container{ID: containerId}, true, stringid.TruncateID(containerId), idHeader, ctx.ID},
|
||||||
|
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
||||||
|
{types.Container{Image: "ubuntu"}, true, "ubuntu", 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{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
||||||
|
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
|
||||||
|
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
||||||
|
{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{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
ctx = containerContext{c: c.container, trunc: c.trunc}
|
||||||
|
v := c.call()
|
||||||
|
if strings.Contains(v, ",") {
|
||||||
|
// comma-separated values means probably a map input, which won't
|
||||||
|
// be guaranteed to have the same order as our expected value
|
||||||
|
// We'll create maps and use reflect.DeepEquals to check instead:
|
||||||
|
entriesMap := make(map[string]string)
|
||||||
|
expMap := make(map[string]string)
|
||||||
|
entries := strings.Split(v, ",")
|
||||||
|
expectedEntries := strings.Split(c.expValue, ",")
|
||||||
|
for _, entry := range entries {
|
||||||
|
keyval := strings.Split(entry, "=")
|
||||||
|
entriesMap[keyval[0]] = keyval[1]
|
||||||
|
}
|
||||||
|
for _, expected := range expectedEntries {
|
||||||
|
keyval := strings.Split(expected, "=")
|
||||||
|
expMap[keyval[0]] = keyval[1]
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expMap, entriesMap) {
|
||||||
|
t.Fatalf("Expected entries: %v, got: %v", c.expValue, v)
|
||||||
|
}
|
||||||
|
} else if v != c.expValue {
|
||||||
|
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := ctx.fullHeader()
|
||||||
|
if h != c.expHeader {
|
||||||
|
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
||||||
|
ctx = containerContext{c: c, trunc: true}
|
||||||
|
|
||||||
|
sid := ctx.Label("com.docker.swarm.swarm-id")
|
||||||
|
node := ctx.Label("com.docker.swarm.node_name")
|
||||||
|
if sid != "33" {
|
||||||
|
t.Fatalf("Expected 33, was %s\n", sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if node != "ubuntu" {
|
||||||
|
t.Fatalf("Expected ubuntu, was %s\n", node)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := ctx.fullHeader()
|
||||||
|
if h != "SWARM ID\tNODE NAME" {
|
||||||
|
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerPsFormatError(t *testing.T) {
|
||||||
|
out := bytes.NewBufferString("")
|
||||||
|
ctx := Context{
|
||||||
|
Format: "{{InvalidFunction}}",
|
||||||
|
Output: out,
|
||||||
|
}
|
||||||
|
|
||||||
|
customFormat(ctx, make([]types.Container, 0))
|
||||||
|
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
|
||||||
|
t.Fatalf("Expected format error, got `%v`\n", out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tableFormatKey = "table"
|
||||||
|
rawFormatKey = "raw"
|
||||||
|
|
||||||
|
defaultTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
|
||||||
|
defaultQuietFormat = "{{.ID}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
Output io.Writer
|
||||||
|
Format string
|
||||||
|
Size bool
|
||||||
|
Quiet bool
|
||||||
|
Trunc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Format(ctx Context, containers []types.Container) {
|
||||||
|
switch ctx.Format {
|
||||||
|
case tableFormatKey:
|
||||||
|
tableFormat(ctx, containers)
|
||||||
|
case rawFormatKey:
|
||||||
|
rawFormat(ctx, containers)
|
||||||
|
default:
|
||||||
|
customFormat(ctx, containers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawFormat(ctx Context, containers []types.Container) {
|
||||||
|
if ctx.Quiet {
|
||||||
|
ctx.Format = `container_id: {{.ID}}`
|
||||||
|
} else {
|
||||||
|
ctx.Format = `container_id: {{.ID}}
|
||||||
|
image: {{.Image}}
|
||||||
|
command: {{.Command}}
|
||||||
|
created_at: {{.CreatedAt}}
|
||||||
|
status: {{.Status}}
|
||||||
|
names: {{.Names}}
|
||||||
|
labels: {{.Labels}}
|
||||||
|
ports: {{.Ports}}
|
||||||
|
`
|
||||||
|
if ctx.Size {
|
||||||
|
ctx.Format += `size: {{.Size}}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customFormat(ctx, containers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableFormat(ctx Context, containers []types.Container) {
|
||||||
|
ctx.Format = defaultTableFormat
|
||||||
|
if ctx.Quiet {
|
||||||
|
ctx.Format = defaultQuietFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
customFormat(ctx, containers)
|
||||||
|
}
|
||||||
+17
-11
@@ -4,37 +4,34 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPull pulls an image or a repository from the registry.
|
// CmdPull pulls an image or a repository from the registry.
|
||||||
//
|
//
|
||||||
// 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", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true)
|
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", 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)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
remote := cmd.Arg(0)
|
||||||
|
|
||||||
var (
|
|
||||||
v = url.Values{}
|
|
||||||
remote = cmd.Arg(0)
|
|
||||||
newRemote = remote
|
|
||||||
)
|
|
||||||
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
||||||
if tag == "" && !*allTags {
|
if tag == "" && !*allTags {
|
||||||
newRemote = utils.ImageReference(taglessRemote, tags.DEFAULTTAG)
|
tag = tags.DEFAULTTAG
|
||||||
}
|
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
|
||||||
if tag != "" && *allTags {
|
} else if tag != "" && *allTags {
|
||||||
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Set("fromImage", newRemote)
|
ref := registry.ParseReference(tag)
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
// Resolve the Repository name from fqn to RepositoryInfo
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||||
@@ -42,6 +39,15 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isTrusted() && !ref.HasDigest() {
|
||||||
|
// Check if tag is digest
|
||||||
|
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
||||||
|
return cli.trustedPull(repoInfo, ref, authConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("fromImage", ref.ImageName(taglessRemote))
|
||||||
|
|
||||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
@@ -13,14 +14,13 @@ 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", "NAME[:TAG]", "Push an image or a repository to the registry", true)
|
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true)
|
||||||
|
addTrustedFlags(cmd, false)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
name := cmd.Arg(0)
|
remote, tag := parsers.ParseRepositoryTag(cmd.Arg(0))
|
||||||
|
|
||||||
remote, tag := parsers.ParseRepositoryTag(name)
|
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
// Resolve the Repository name from fqn to RepositoryInfo
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(remote)
|
repoInfo, err := registry.ParseRepositoryInfo(remote)
|
||||||
@@ -41,6 +41,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
|||||||
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isTrusted() {
|
||||||
|
return cli.trustedPush(repoInfo, tag, authConfig)
|
||||||
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("tag", tag)
|
v.Set("tag", tag)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,8 +11,9 @@ 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", "OLD_NAME NEW_NAME", "Rename a container", true)
|
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true)
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
oldName := cmd.Arg(0)
|
oldName := cmd.Arg(0)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||||
cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true)
|
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", 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)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,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", "CONTAINER [CONTAINER...]", "Remove one or more containers", true)
|
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", 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)")
|
||||||
|
|||||||
+9
-7
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,12 +14,11 @@ 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 {
|
||||||
var (
|
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true)
|
||||||
cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", 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)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
@@ -31,13 +31,15 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
|||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
rdr, _, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
|
serverResp, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
errNames = append(errNames, name)
|
errNames = append(errNames, name)
|
||||||
} else {
|
} else {
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
dels := []types.ImageDelete{}
|
dels := []types.ImageDelete{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&dels); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&dels); err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
errNames = append(errNames, name)
|
errNames = append(errNames, name)
|
||||||
continue
|
continue
|
||||||
|
|||||||
+12
-2
@@ -5,8 +5,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
@@ -38,7 +40,8 @@ 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", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true)
|
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true)
|
||||||
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
var (
|
var (
|
||||||
@@ -103,6 +106,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
sigProxy = false
|
sigProxy = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Telling the Windows daemon the initial size of the tty during start makes
|
||||||
|
// a far better user experience rather than relying on subsequent resizes
|
||||||
|
// to cause things to catch up.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
|
||||||
|
}
|
||||||
|
|
||||||
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -241,7 +251,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return StatusError{StatusCode: status}
|
return Cli.StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +16,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", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
|
|
||||||
if len(cmd.Args()) == 1 {
|
if len(cmd.Args()) == 1 {
|
||||||
image := cmd.Arg(0)
|
image := cmd.Arg(0)
|
||||||
if err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -49,7 +50,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
for _, arg := range cmd.Args() {
|
for _, arg := range cmd.Args() {
|
||||||
v.Add("names", arg)
|
v.Add("names", arg)
|
||||||
}
|
}
|
||||||
if err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
@@ -25,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", "TERM", "Search the Docker Hub for images", true)
|
cmd := Cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", 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")
|
||||||
@@ -50,6 +51,8 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rdr.Close()
|
||||||
|
|
||||||
results := ByStars{}
|
results := ByStars{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// +build experimental
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
nwclient "github.com/docker/libnetwork/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
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...)
|
||||||
|
}
|
||||||
+13
-10
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
@@ -44,30 +45,32 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|||||||
//
|
//
|
||||||
// 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)
|
||||||
|
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
||||||
|
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
||||||
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cErr chan error
|
cErr chan error
|
||||||
tty bool
|
tty bool
|
||||||
|
|
||||||
cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true)
|
|
||||||
attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
|
||||||
openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
if *attach || *openStdin {
|
if *attach || *openStdin {
|
||||||
if cmd.NArg() > 1 {
|
if cmd.NArg() > 1 {
|
||||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +163,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return StatusError{StatusCode: status}
|
return Cli.StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+8
-4
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
)
|
)
|
||||||
@@ -35,18 +36,20 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
} else {
|
} else {
|
||||||
v.Set("stream", "0")
|
v.Set("stream", "0")
|
||||||
}
|
}
|
||||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.err = err
|
s.err = err
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer stream.Close()
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
previousCPU uint64
|
previousCPU uint64
|
||||||
previousSystem uint64
|
previousSystem uint64
|
||||||
dec = json.NewDecoder(stream)
|
dec = json.NewDecoder(serverResp.body)
|
||||||
u = make(chan error, 1)
|
u = make(chan error, 1)
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -122,9 +125,10 @@ 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", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true)
|
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", 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)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
names := cmd.Args()
|
names := cmd.Args()
|
||||||
|
|||||||
+2
-1
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// 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", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace 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)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
@@ -12,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", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true)
|
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", 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)
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -8,6 +8,7 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,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", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true)
|
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -25,13 +26,15 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
|||||||
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
procList := types.ContainerProcessList{}
|
procList := types.ContainerProcessList{}
|
||||||
if err := json.NewDecoder(stream).Decode(&procList); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&procList); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,454 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
|
"github.com/docker/docker/pkg/ansiescape"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/tlsconfig"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/docker/notary/client"
|
||||||
|
"github.com/docker/notary/pkg/passphrase"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
var untrusted bool
|
||||||
|
|
||||||
|
func addTrustedFlags(fs *flag.FlagSet, verify bool) {
|
||||||
|
var trusted bool
|
||||||
|
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
||||||
|
if t, err := strconv.ParseBool(e); t || err != nil {
|
||||||
|
// treat any other value as true
|
||||||
|
trusted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message := "Skip image signing"
|
||||||
|
if verify {
|
||||||
|
message = "Skip image verification"
|
||||||
|
}
|
||||||
|
fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTrusted() bool {
|
||||||
|
return !untrusted
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetRegexp = regexp.MustCompile(`([\S]+): digest: ([\S]+) size: ([\d]+)`)
|
||||||
|
|
||||||
|
type target struct {
|
||||||
|
reference registry.Reference
|
||||||
|
digest digest.Digest
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) trustDirectory() string {
|
||||||
|
return filepath.Join(cliconfig.ConfigDir(), "trust")
|
||||||
|
}
|
||||||
|
|
||||||
|
// certificateDirectory returns the directory containing
|
||||||
|
// TLS certificates for the given server. An error is
|
||||||
|
// returned if there was an error parsing the server string.
|
||||||
|
func (cli *DockerCli) certificateDirectory(server string) (string, error) {
|
||||||
|
u, err := url.Parse(server)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustServer(index *registry.IndexInfo) string {
|
||||||
|
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||||
|
if !strings.HasPrefix(s, "https://") {
|
||||||
|
return "https://" + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if index.Official {
|
||||||
|
return registry.NotaryServer
|
||||||
|
}
|
||||||
|
return "https://" + index.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleCredentialStore struct {
|
||||||
|
auth cliconfig.AuthConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
|
||||||
|
return scs.auth.Username, scs.auth.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
|
||||||
|
server := trustServer(repoInfo.Index)
|
||||||
|
if !strings.HasPrefix(server, "https://") {
|
||||||
|
return nil, errors.New("unsupported scheme: https required for trust server")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg = tlsconfig.ClientDefault
|
||||||
|
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
|
||||||
|
|
||||||
|
// Get certificate base directory
|
||||||
|
certDir, err := cli.certificateDirectory(server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("reading certificate directory: %s", certDir)
|
||||||
|
|
||||||
|
if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
base := &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}).Dial,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
TLSClientConfig: &cfg,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip configuration headers since request is not going to Docker daemon
|
||||||
|
modifiers := registry.DockerHeaders(http.Header{})
|
||||||
|
authTransport := transport.NewTransport(base, modifiers...)
|
||||||
|
pingClient := &http.Client{
|
||||||
|
Transport: authTransport,
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
endpointStr := server + "/v2/"
|
||||||
|
req, err := http.NewRequest("GET", endpointStr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := pingClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
challengeManager := auth.NewSimpleChallengeManager()
|
||||||
|
if err := challengeManager.AddResponse(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := simpleCredentialStore{auth: authConfig}
|
||||||
|
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, "push", "pull")
|
||||||
|
basicHandler := auth.NewBasicHandler(creds)
|
||||||
|
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
|
||||||
|
tr := transport.NewTransport(base, modifiers...)
|
||||||
|
|
||||||
|
return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName, server, tr, cli.getPassphraseRetriever())
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertTarget(t client.Target) (target, error) {
|
||||||
|
h, ok := t.Hashes["sha256"]
|
||||||
|
if !ok {
|
||||||
|
return target{}, errors.New("no valid hash, expecting sha256")
|
||||||
|
}
|
||||||
|
return target{
|
||||||
|
reference: registry.ParseReference(t.Name),
|
||||||
|
digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
|
||||||
|
size: t.Length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
||||||
|
aliasMap := map[string]string{
|
||||||
|
"root": "offline",
|
||||||
|
"snapshot": "tagging",
|
||||||
|
"targets": "tagging",
|
||||||
|
}
|
||||||
|
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
||||||
|
env := map[string]string{
|
||||||
|
"root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
|
||||||
|
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
||||||
|
"targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
||||||
|
}
|
||||||
|
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
||||||
|
if v := env[alias]; v != "" {
|
||||||
|
return v, numAttempts > 1, nil
|
||||||
|
}
|
||||||
|
return baseRetriever(keyName, alias, createNew, numAttempts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (registry.Reference, error) {
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the Auth config relevant for this server
|
||||||
|
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
||||||
|
|
||||||
|
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := notaryRepo.GetTargetByName(ref.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r, err := convertTarget(*t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return registry.DigestReference(r.digest), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) tagTrusted(repoInfo *registry.RepositoryInfo, trustedRef, ref registry.Reference) error {
|
||||||
|
fullName := trustedRef.ImageName(repoInfo.LocalName)
|
||||||
|
fmt.Fprintf(cli.out, "Tagging %s as %s\n", fullName, ref.ImageName(repoInfo.LocalName))
|
||||||
|
tv := url.Values{}
|
||||||
|
tv.Set("repo", repoInfo.LocalName)
|
||||||
|
tv.Set("tag", ref.String())
|
||||||
|
tv.Set("force", "1")
|
||||||
|
|
||||||
|
if _, _, err := readBody(cli.call("POST", "/images/"+fullName+"/tag?"+tv.Encode(), nil, nil)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryError(err error) error {
|
||||||
|
switch err.(type) {
|
||||||
|
case *json.SyntaxError:
|
||||||
|
logrus.Debugf("Notary syntax error: %s", err)
|
||||||
|
return errors.New("no trust data available for remote repository")
|
||||||
|
case client.ErrExpired:
|
||||||
|
return fmt.Errorf("remote repository out-of-date: %v", err)
|
||||||
|
case trustmanager.ErrKeyNotFound:
|
||||||
|
return fmt.Errorf("signing keys not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {
|
||||||
|
var (
|
||||||
|
v = url.Values{}
|
||||||
|
refs = []target{}
|
||||||
|
)
|
||||||
|
|
||||||
|
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref.String() == "" {
|
||||||
|
// List all targets
|
||||||
|
targets, err := notaryRepo.ListTargets()
|
||||||
|
if err != nil {
|
||||||
|
return notaryError(err)
|
||||||
|
}
|
||||||
|
for _, tgt := range targets {
|
||||||
|
t, err := convertTarget(*tgt)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.LocalName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
refs = append(refs, t)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t, err := notaryRepo.GetTargetByName(ref.String())
|
||||||
|
if err != nil {
|
||||||
|
return notaryError(err)
|
||||||
|
}
|
||||||
|
r, err := convertTarget(*t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
refs = append(refs, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set("fromImage", repoInfo.LocalName)
|
||||||
|
for i, r := range refs {
|
||||||
|
displayTag := r.reference.String()
|
||||||
|
if displayTag != "" {
|
||||||
|
displayTag = ":" + displayTag
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)
|
||||||
|
v.Set("tag", r.digest.String())
|
||||||
|
|
||||||
|
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If reference is not trusted, tag by trusted reference
|
||||||
|
if !r.reference.HasDigest() {
|
||||||
|
if err := cli.tagTrusted(repoInfo, registry.DigestReference(r.digest), r.reference); err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectKey(keys map[string]string) string {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
keyIDs := []string{}
|
||||||
|
for k := range keys {
|
||||||
|
keyIDs = append(keyIDs, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dmcgowan): let user choose if multiple keys, now pick consistently
|
||||||
|
sort.Strings(keyIDs)
|
||||||
|
|
||||||
|
return keyIDs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
out := io.MultiWriter(in, w)
|
||||||
|
targetChan := make(chan []target)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
targets := []target{}
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
scanner.Split(ansiescape.ScanANSILines)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
if matches := targetRegexp.FindSubmatch(line); len(matches) == 4 {
|
||||||
|
dgst, err := digest.ParseDigest(string(matches[2]))
|
||||||
|
if err != nil {
|
||||||
|
// Line does match what is expected, continue looking for valid lines
|
||||||
|
logrus.Debugf("Bad digest value %q in matched line, ignoring\n", string(matches[2]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, err := strconv.ParseInt(string(matches[3]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// Line does match what is expected, continue looking for valid lines
|
||||||
|
logrus.Debugf("Bad size value %q in matched line, ignoring\n", string(matches[3]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
targets = append(targets, target{
|
||||||
|
reference: registry.ParseReference(string(matches[1])),
|
||||||
|
digest: dgst,
|
||||||
|
size: s,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetChan <- targets
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error {
|
||||||
|
streamOut, targetChan := targetStream(cli.out)
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("tag", tag)
|
||||||
|
|
||||||
|
_, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
|
||||||
|
// Close stream channel to finish target parsing
|
||||||
|
if err := streamOut.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check error from request
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target results
|
||||||
|
targets := <-targetChan
|
||||||
|
|
||||||
|
if tag == "" {
|
||||||
|
fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(targets) == 0 {
|
||||||
|
fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
|
||||||
|
|
||||||
|
repo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
h, err := hex.DecodeString(target.digest.Hex())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t := &client.Target{
|
||||||
|
Name: target.reference.String(),
|
||||||
|
Hashes: data.Hashes{
|
||||||
|
string(target.digest.Algorithm()): h,
|
||||||
|
},
|
||||||
|
Length: int64(target.size),
|
||||||
|
}
|
||||||
|
if err := repo.AddTarget(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Publish()
|
||||||
|
if _, ok := err.(*client.ErrRepoNotInitialized); !ok {
|
||||||
|
return notaryError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ks := repo.KeyStoreManager
|
||||||
|
keys := ks.RootKeyStore().ListKeys()
|
||||||
|
|
||||||
|
rootKey := selectKey(keys)
|
||||||
|
if rootKey == "" {
|
||||||
|
rootKey, err = ks.GenRootKey("ecdsa")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoService, err := ks.GetRootCryptoService(rootKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.Initialize(cryptoService); err != nil {
|
||||||
|
return notaryError(err)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.CanonicalName)
|
||||||
|
|
||||||
|
return notaryError(repo.Publish())
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,8 +11,9 @@ 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", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
|
|||||||
+74
-40
@@ -33,7 +33,13 @@ var (
|
|||||||
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPClient creates a new HTP client with the cli's client transport instance.
|
type serverResponse struct {
|
||||||
|
body io.ReadCloser
|
||||||
|
header http.Header
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPClient creates a new HTTP client with the cli's client transport instance.
|
||||||
func (cli *DockerCli) HTTPClient() *http.Client {
|
func (cli *DockerCli) HTTPClient() *http.Client {
|
||||||
return &http.Client{Transport: cli.transport}
|
return &http.Client{Transport: cli.transport}
|
||||||
}
|
}
|
||||||
@@ -48,23 +54,29 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
|||||||
return params, nil
|
return params, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) {
|
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
|
||||||
|
serverResp := &serverResponse{
|
||||||
|
body: nil,
|
||||||
|
statusCode: -1,
|
||||||
|
}
|
||||||
|
|
||||||
expectedPayload := (method == "POST" || method == "PUT")
|
expectedPayload := (method == "POST" || method == "PUT")
|
||||||
if expectedPayload && in == nil {
|
if expectedPayload && in == nil {
|
||||||
in = bytes.NewReader([]byte{})
|
in = bytes.NewReader([]byte{})
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", -1, err
|
return serverResp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||||
// then the user can't change OUR headers
|
// then the user can't change OUR headers
|
||||||
for k, v := range cli.configFile.HttpHeaders {
|
for k, v := range cli.configFile.HTTPHeaders {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||||
req.URL.Host = cli.addr
|
req.URL.Host = cli.addr
|
||||||
req.URL.Scheme = cli.scheme
|
req.URL.Scheme = cli.scheme
|
||||||
|
|
||||||
@@ -79,33 +91,38 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, err := cli.HTTPClient().Do(req)
|
resp, err := cli.HTTPClient().Do(req)
|
||||||
statusCode := -1
|
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
statusCode = resp.StatusCode
|
serverResp.statusCode = resp.StatusCode
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return nil, "", statusCode, errConnectionRefused
|
return serverResp, errConnectionRefused
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.tlsConfig == nil {
|
if cli.tlsConfig == nil {
|
||||||
return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err)
|
||||||
}
|
}
|
||||||
return nil, "", statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
|
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
|
||||||
|
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusCode < 200 || statusCode >= 400 {
|
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", statusCode, err
|
return serverResp, err
|
||||||
}
|
}
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return nil, "", statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL)
|
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
|
||||||
}
|
}
|
||||||
return nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Body, resp.Header.Get("Content-Type"), statusCode, nil
|
serverResp.body = resp.Body
|
||||||
|
serverResp.header = resp.Header
|
||||||
|
return serverResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
||||||
@@ -119,24 +136,25 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade
|
|||||||
}
|
}
|
||||||
|
|
||||||
// begin the request
|
// begin the request
|
||||||
body, contentType, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{
|
serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
|
||||||
"X-Registry-Auth": registryAuthHeader,
|
"X-Registry-Auth": registryAuthHeader,
|
||||||
})
|
})
|
||||||
if err == nil && out != nil {
|
if err == nil && out != nil {
|
||||||
// If we are streaming output, complete the stream since
|
// If we are streaming output, complete the stream since
|
||||||
// errors may not appear until later.
|
// errors may not appear until later.
|
||||||
err = cli.streamBody(body, contentType, true, out, nil)
|
err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Since errors in a stream appear after status 200 has been written,
|
// Since errors in a stream appear after status 200 has been written,
|
||||||
// we may need to change the status code.
|
// we may need to change the status code.
|
||||||
if strings.Contains(err.Error(), "Authentication is required") ||
|
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||||
strings.Contains(err.Error(), "Status 401") ||
|
strings.Contains(err.Error(), "Status 401") ||
|
||||||
|
strings.Contains(err.Error(), "401 Unauthorized") ||
|
||||||
strings.Contains(err.Error(), "status code 401") {
|
strings.Contains(err.Error(), "status code 401") {
|
||||||
statusCode = http.StatusUnauthorized
|
serverResp.statusCode = http.StatusUnauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return body, statusCode, err
|
return serverResp.body, serverResp.statusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
// Resolve the Auth config relevant for this server
|
||||||
@@ -153,10 +171,20 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade
|
|||||||
return body, statusCode, err
|
return body, statusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
|
func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
||||||
|
sr, err := cli.call(method, path, data, headers)
|
||||||
|
return sr.body, sr.header, sr.statusCode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
|
||||||
params, err := cli.encodeData(data)
|
params, err := cli.encodeData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
sr := &serverResponse{
|
||||||
|
body: nil,
|
||||||
|
header: nil,
|
||||||
|
statusCode: -1,
|
||||||
|
}
|
||||||
|
return sr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if data != nil {
|
if data != nil {
|
||||||
@@ -166,8 +194,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, headers map[st
|
|||||||
headers["Content-Type"] = []string{"application/json"}
|
headers["Content-Type"] = []string{"application/json"}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _, statusCode, err := cli.clientRequest(method, path, params, headers)
|
serverResp, err := cli.clientRequest(method, path, params, headers)
|
||||||
return body, statusCode, err
|
return serverResp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamOpts struct {
|
type streamOpts struct {
|
||||||
@@ -178,12 +206,12 @@ type streamOpts struct {
|
|||||||
headers map[string][]string
|
headers map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) stream(method, path string, opts *streamOpts) error {
|
func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
|
||||||
body, contentType, _, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return serverResp, err
|
||||||
}
|
}
|
||||||
return cli.streamBody(body, contentType, opts.rawTerminal, opts.out, opts.err)
|
return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
|
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
|
||||||
@@ -228,13 +256,15 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
||||||
stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var res types.ContainerWaitResponse
|
var res types.ContainerWaitResponse
|
||||||
if err := json.NewDecoder(stream).Decode(&res); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +274,7 @@ func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
|||||||
// getExitCode perform an inspect on the container. It returns
|
// getExitCode perform an inspect on the container. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
||||||
stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionRefused {
|
if err != errConnectionRefused {
|
||||||
@@ -253,8 +283,10 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
|||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +296,7 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
|||||||
// getExecExitCode perform an inspect on the exec command. It returns
|
// getExecExitCode perform an inspect on the exec command. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||||
stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionRefused {
|
if err != errConnectionRefused {
|
||||||
@@ -273,6 +305,8 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
|||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer serverResp.body.Close()
|
||||||
|
|
||||||
//TODO: Should we reconsider having a type in api/types?
|
//TODO: Should we reconsider having a type in api/types?
|
||||||
//this is a response to exex/id/json not container
|
//this is a response to exex/id/json not container
|
||||||
var c struct {
|
var c struct {
|
||||||
@@ -280,7 +314,7 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
|||||||
ExitCode int
|
ExitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,16 +364,16 @@ func (cli *DockerCli) getTtySize() (int, int) {
|
|||||||
return int(ws.Height), int(ws.Width)
|
return int(ws.Height), int(ws.Width)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
func readBody(serverResp *serverResponse, err error) ([]byte, int, error) {
|
||||||
if stream != nil {
|
if serverResp.body != nil {
|
||||||
defer stream.Close()
|
defer serverResp.body.Close()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, statusCode, err
|
return nil, serverResp.statusCode, err
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(stream)
|
body, err := ioutil.ReadAll(serverResp.body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
return body, statusCode, nil
|
return body, serverResp.statusCode, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-31
@@ -2,60 +2,95 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var VersionTemplate = `Client:
|
||||||
|
Version: {{.Client.Version}}
|
||||||
|
API version: {{.Client.ApiVersion}}
|
||||||
|
Go version: {{.Client.GoVersion}}
|
||||||
|
Git commit: {{.Client.GitCommit}}
|
||||||
|
Built: {{.Client.BuildTime}}
|
||||||
|
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
|
||||||
|
Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
|
||||||
|
|
||||||
|
Server:
|
||||||
|
Version: {{.Server.Version}}
|
||||||
|
API version: {{.Server.ApiVersion}}
|
||||||
|
Go version: {{.Server.GoVersion}}
|
||||||
|
Git commit: {{.Server.GitCommit}}
|
||||||
|
Built: {{.Server.BuildTime}}
|
||||||
|
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
|
||||||
|
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
|
||||||
|
|
||||||
|
type VersionData struct {
|
||||||
|
Client types.Version
|
||||||
|
ServerOK bool
|
||||||
|
Server types.Version
|
||||||
|
}
|
||||||
|
|
||||||
// CmdVersion shows Docker version information.
|
// CmdVersion shows Docker version information.
|
||||||
//
|
//
|
||||||
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
|
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
|
||||||
//
|
//
|
||||||
// Usage: docker version
|
// Usage: docker version
|
||||||
func (cli *DockerCli) CmdVersion(args ...string) error {
|
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
||||||
cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
cmd := Cli.Subcmd("version", nil, "Show the Docker version information.", true)
|
||||||
|
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)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
if *tmplStr == "" {
|
||||||
if dockerversion.VERSION != "" {
|
*tmplStr = VersionTemplate
|
||||||
fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION)
|
|
||||||
fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version())
|
|
||||||
if dockerversion.GITCOMMIT != "" {
|
|
||||||
fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
|
||||||
if utils.ExperimentalBuild() {
|
|
||||||
fmt.Fprintf(cli.out, "Experimental (client): true\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/version", nil, nil)
|
var tmpl *template.Template
|
||||||
|
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
||||||
|
return Cli.StatusError{StatusCode: 64,
|
||||||
|
Status: "Template parsing error: " + err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
vd := VersionData{
|
||||||
|
Client: types.Version{
|
||||||
|
Version: dockerversion.VERSION,
|
||||||
|
ApiVersion: api.Version,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
GitCommit: dockerversion.GITCOMMIT,
|
||||||
|
BuildTime: dockerversion.BUILDTIME,
|
||||||
|
Os: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
Experimental: utils.ExperimentalBuild(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
cli.out.Write([]byte{'\n'})
|
||||||
|
}()
|
||||||
|
|
||||||
|
serverResp, err := cli.call("GET", "/version", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var v types.Version
|
defer serverResp.body.Close()
|
||||||
if err := json.NewDecoder(stream).Decode(&v); err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "Error reading remote version: %s\n", err)
|
if err = json.NewDecoder(serverResp.body).Decode(&vd.Server); err != nil {
|
||||||
return err
|
return Cli.StatusError{StatusCode: 1,
|
||||||
|
Status: "Error reading remote version: " + err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(cli.out, "Server version: %s\n", v.Version)
|
vd.ServerOK = true
|
||||||
if v.ApiVersion != "" {
|
|
||||||
fmt.Fprintf(cli.out, "Server API version: %s\n", v.ApiVersion)
|
return
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "Go version (server): %s\n", v.GoVersion)
|
|
||||||
fmt.Fprintf(cli.out, "Git commit (server): %s\n", v.GitCommit)
|
|
||||||
fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", v.Os, v.Arch)
|
|
||||||
if v.Experimental {
|
|
||||||
fmt.Fprintf(cli.out, "Experimental (server): true\n")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,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", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true)
|
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+8
-2
@@ -16,8 +16,14 @@ import (
|
|||||||
|
|
||||||
// Common constants for daemon and client.
|
// Common constants for daemon and client.
|
||||||
const (
|
const (
|
||||||
APIVERSION version.Version = "1.19" // Current REST API version
|
// Current REST API version
|
||||||
DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build
|
Version version.Version = "1.20"
|
||||||
|
|
||||||
|
// Minimun REST API version supported
|
||||||
|
MinVersion version.Version = "1.12"
|
||||||
|
|
||||||
|
// Default filename with Docker commands, read by docker build
|
||||||
|
DefaultDockerfileName string = "Dockerfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ByPrivatePort []types.Port
|
type ByPrivatePort []types.Port
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,3 +28,29 @@ func int64ValueOrZero(r *http.Request, k string) int64 {
|
|||||||
}
|
}
|
||||||
return val
|
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 := 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
|
||||||
|
}
|
||||||
|
|||||||
+197
-83
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -13,8 +14,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go.net/websocket"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
@@ -33,10 +34,10 @@ import (
|
|||||||
"github.com/docker/docker/pkg/sockets"
|
"github.com/docker/docker/pkg/sockets"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/pkg/version"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
"github.com/docker/libnetwork/portallocator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
@@ -45,11 +46,7 @@ type ServerConfig struct {
|
|||||||
CorsHeaders string
|
CorsHeaders string
|
||||||
Version string
|
Version string
|
||||||
SocketGroup string
|
SocketGroup string
|
||||||
Tls bool
|
TLSConfig *tls.Config
|
||||||
TlsVerify bool
|
|
||||||
TlsCa string
|
|
||||||
TlsCert string
|
|
||||||
TlsKey string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -248,11 +245,12 @@ func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *htt
|
|||||||
func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
v := &types.Version{
|
v := &types.Version{
|
||||||
Version: dockerversion.VERSION,
|
Version: dockerversion.VERSION,
|
||||||
ApiVersion: api.APIVERSION,
|
ApiVersion: api.Version,
|
||||||
GitCommit: dockerversion.GITCOMMIT,
|
GitCommit: dockerversion.GITCOMMIT,
|
||||||
GoVersion: runtime.Version(),
|
GoVersion: runtime.Version(),
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
|
BuildTime: dockerversion.BUILDTIME,
|
||||||
}
|
}
|
||||||
|
|
||||||
if version.GreaterThanOrEqualTo("1.19") {
|
if version.GreaterThanOrEqualTo("1.19") {
|
||||||
@@ -300,7 +298,13 @@ func (s *Server) postContainersKill(version version.Version, w http.ResponseWrit
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.daemon.ContainerKill(name, sig); err != nil {
|
if err := s.daemon.ContainerKill(name, sig); err != nil {
|
||||||
return err
|
_, 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)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
@@ -399,6 +403,7 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
until = u
|
until = u
|
||||||
}
|
}
|
||||||
|
|
||||||
timer := time.NewTimer(0)
|
timer := time.NewTimer(0)
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
if until > 0 {
|
if until > 0 {
|
||||||
@@ -412,6 +417,9 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
isFiltered := func(field string, filter []string) bool {
|
isFiltered := func(field string, filter []string) bool {
|
||||||
|
if len(field) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if len(filter) == 0 {
|
if len(filter) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -432,7 +440,9 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
d := s.daemon
|
d := s.daemon
|
||||||
es := d.EventsService
|
es := d.EventsService
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
enc := json.NewEncoder(ioutils.NewWriteFlusher(w))
|
outStream := ioutils.NewWriteFlusher(w)
|
||||||
|
outStream.Write(nil) // make sure response is sent immediately
|
||||||
|
enc := json.NewEncoder(outStream)
|
||||||
|
|
||||||
getContainerId := func(cn string) string {
|
getContainerId := func(cn string) string {
|
||||||
c, err := d.Get(cn)
|
c, err := d.Get(cn)
|
||||||
@@ -448,8 +458,8 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
ef["container"][i] = getContainerId(cn)
|
ef["container"][i] = getContainerId(cn)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFiltered(ev.Status, ef["event"]) || isFiltered(ev.From, ef["image"]) ||
|
if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) &&
|
||||||
isFiltered(ev.ID, ef["container"]) {
|
isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +467,9 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
current, l := es.Subscribe()
|
current, l := es.Subscribe()
|
||||||
|
if since == -1 {
|
||||||
|
current = nil
|
||||||
|
}
|
||||||
defer es.Evict(l)
|
defer es.Evict(l)
|
||||||
for _, ev := range current {
|
for _, ev := range current {
|
||||||
if ev.Time < since {
|
if ev.Time < since {
|
||||||
@@ -572,7 +585,27 @@ func (s *Server) getContainersStats(version version.Version, w http.ResponseWrit
|
|||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.daemon.ContainerStats(vars["name"], boolValueOrDefault(r, "stream", true), ioutils.NewWriteFlusher(w))
|
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 {
|
func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
@@ -598,6 +631,22 @@ func (s *Server) getContainersLogs(version version.Version, w http.ResponseWrite
|
|||||||
since = time.Unix(s, 0)
|
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 immediatly, even if the container has
|
||||||
|
// not yet produced any data)
|
||||||
|
outStream.Write(nil)
|
||||||
|
|
||||||
logsConfig := &daemon.ContainerLogsConfig{
|
logsConfig := &daemon.ContainerLogsConfig{
|
||||||
Follow: boolValue(r, "follow"),
|
Follow: boolValue(r, "follow"),
|
||||||
Timestamps: boolValue(r, "timestamps"),
|
Timestamps: boolValue(r, "timestamps"),
|
||||||
@@ -605,10 +654,11 @@ func (s *Server) getContainersLogs(version version.Version, w http.ResponseWrite
|
|||||||
Tail: r.Form.Get("tail"),
|
Tail: r.Form.Get("tail"),
|
||||||
UseStdout: stdout,
|
UseStdout: stdout,
|
||||||
UseStderr: stderr,
|
UseStderr: stderr,
|
||||||
OutStream: ioutils.NewWriteFlusher(w),
|
OutStream: outStream,
|
||||||
|
Stop: closeNotifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.daemon.ContainerLogs(vars["name"], logsConfig); err != nil {
|
if err := s.daemon.ContainerLogs(c, logsConfig); err != nil {
|
||||||
fmt.Fprintf(w, "Error running logs job: %s\n", err)
|
fmt.Fprintf(w, "Error running logs job: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,7 +694,7 @@ func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *h
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cont := r.Form.Get("container")
|
cname := r.Form.Get("container")
|
||||||
|
|
||||||
pause := boolValue(r, "pause")
|
pause := boolValue(r, "pause")
|
||||||
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
||||||
@@ -656,11 +706,7 @@ func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *h
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == nil {
|
commitCfg := &builder.CommitConfig{
|
||||||
c = &runconfig.Config{}
|
|
||||||
}
|
|
||||||
|
|
||||||
containerCommitConfig := &daemon.ContainerCommitConfig{
|
|
||||||
Pause: pause,
|
Pause: pause,
|
||||||
Repo: r.Form.Get("repo"),
|
Repo: r.Form.Get("repo"),
|
||||||
Tag: r.Form.Get("tag"),
|
Tag: r.Form.Get("tag"),
|
||||||
@@ -670,7 +716,7 @@ func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *h
|
|||||||
Config: c,
|
Config: c,
|
||||||
}
|
}
|
||||||
|
|
||||||
imgID, err := builder.Commit(s.daemon, cont, containerCommitConfig)
|
imgID, err := builder.Commit(cname, s.daemon, commitCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -790,7 +836,7 @@ func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return json.NewEncoder(w).Encode(query.Results)
|
return writeJSON(w, http.StatusOK, query.Results)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
@@ -895,6 +941,7 @@ func (s *Server) postContainersCreate(version version.Version, w http.ResponseWr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
adjustCpuShares(version, hostConfig)
|
||||||
|
|
||||||
containerId, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig)
|
containerId, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1092,6 +1139,11 @@ func (s *Server) postContainersAttach(version version.Version, w http.ResponseWr
|
|||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cont, err := s.daemon.Get(vars["name"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
inStream, outStream, err := hijackServer(w)
|
inStream, outStream, err := hijackServer(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1112,10 +1164,9 @@ func (s *Server) postContainersAttach(version version.Version, w http.ResponseWr
|
|||||||
UseStderr: boolValue(r, "stderr"),
|
UseStderr: boolValue(r, "stderr"),
|
||||||
Logs: boolValue(r, "logs"),
|
Logs: boolValue(r, "logs"),
|
||||||
Stream: boolValue(r, "stream"),
|
Stream: boolValue(r, "stream"),
|
||||||
Multiplex: version.GreaterThanOrEqualTo("1.6"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.daemon.ContainerAttachWithLogs(vars["name"], attachWithLogsConfig); err != nil {
|
if err := s.daemon.ContainerAttachWithLogs(cont, attachWithLogsConfig); err != nil {
|
||||||
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,6 +1181,11 @@ func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWrit
|
|||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cont, err := s.daemon.Get(vars["name"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
h := websocket.Handler(func(ws *websocket.Conn) {
|
h := websocket.Handler(func(ws *websocket.Conn) {
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
@@ -1141,7 +1197,7 @@ func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWrit
|
|||||||
Stream: boolValue(r, "stream"),
|
Stream: boolValue(r, "stream"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.daemon.ContainerWsAttachWithLogs(vars["name"], wsAttachWithLogsConfig); err != nil {
|
if err := s.daemon.ContainerWsAttachWithLogs(cont, wsAttachWithLogsConfig); err != nil {
|
||||||
logrus.Errorf("Error attaching websocket: %s", err)
|
logrus.Errorf("Error attaching websocket: %s", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1155,8 +1211,8 @@ func (s *Server) getContainersByName(version version.Version, w http.ResponseWri
|
|||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
if version.LessThan("1.19") {
|
if version.LessThan("1.20") {
|
||||||
containerJSONRaw, err := s.daemon.ContainerInspectRaw(vars["name"])
|
containerJSONRaw, err := s.daemon.ContainerInspectPre120(vars["name"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1198,18 +1254,17 @@ func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter,
|
|||||||
|
|
||||||
func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
var (
|
var (
|
||||||
authConfig = &cliconfig.AuthConfig{}
|
authConfigs = map[string]cliconfig.AuthConfig{}
|
||||||
configFileEncoded = r.Header.Get("X-Registry-Config")
|
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
||||||
configFile = &cliconfig.ConfigFile{}
|
buildConfig = builder.NewBuildConfig()
|
||||||
buildConfig = builder.NewBuildConfig()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if configFileEncoded != "" {
|
if authConfigsEncoded != "" {
|
||||||
configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded))
|
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
|
||||||
if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
|
if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
|
||||||
// for a pull it is not an error if no auth was given
|
// for a pull it is not an error if no auth was given
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
// to increase compatibility with the existing api it is defaulting
|
||||||
configFile = &cliconfig.ConfigFile{}
|
// to be empty.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1236,17 +1291,25 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
buildConfig.SuppressOutput = boolValue(r, "q")
|
buildConfig.SuppressOutput = boolValue(r, "q")
|
||||||
buildConfig.NoCache = boolValue(r, "nocache")
|
buildConfig.NoCache = boolValue(r, "nocache")
|
||||||
buildConfig.ForceRemove = boolValue(r, "forcerm")
|
buildConfig.ForceRemove = boolValue(r, "forcerm")
|
||||||
buildConfig.AuthConfig = authConfig
|
buildConfig.AuthConfigs = authConfigs
|
||||||
buildConfig.ConfigFile = configFile
|
|
||||||
buildConfig.MemorySwap = int64ValueOrZero(r, "memswap")
|
buildConfig.MemorySwap = int64ValueOrZero(r, "memswap")
|
||||||
buildConfig.Memory = int64ValueOrZero(r, "memory")
|
buildConfig.Memory = int64ValueOrZero(r, "memory")
|
||||||
buildConfig.CpuShares = int64ValueOrZero(r, "cpushares")
|
buildConfig.CPUShares = int64ValueOrZero(r, "cpushares")
|
||||||
buildConfig.CpuPeriod = int64ValueOrZero(r, "cpuperiod")
|
buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod")
|
||||||
buildConfig.CpuQuota = int64ValueOrZero(r, "cpuquota")
|
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")
|
||||||
|
|
||||||
|
var buildUlimits = []*ulimit.Ulimit{}
|
||||||
|
ulimitsJson := r.FormValue("ulimits")
|
||||||
|
if ulimitsJson != "" {
|
||||||
|
if err := json.NewDecoder(strings.NewReader(ulimitsJson)).Decode(&buildUlimits); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildConfig.Ulimits = buildUlimits
|
||||||
|
}
|
||||||
|
|
||||||
// Job cancellation. Note: not all job types support this.
|
// Job cancellation. Note: not all job types support this.
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||||
finished := make(chan struct{})
|
finished := make(chan struct{})
|
||||||
@@ -1273,6 +1336,7 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postContainersCopy is deprecated in favor of getContainersArchivePath.
|
||||||
func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
if vars == nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
@@ -1312,10 +1376,74 @@ func (s *Server) postContainersCopy(version version.Version, w http.ResponseWrit
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // Encode the stat to JSON, base64 encode, and place in a header.
|
||||||
|
func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
|
||||||
|
statJSON, err := json.Marshal(stat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header.Set(
|
||||||
|
"X-Docker-Container-Path-Stat",
|
||||||
|
base64.StdEncoding.EncodeToString(statJSON),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
v, err := archiveFormValues(r, vars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := s.daemon.ContainerStatPath(v.name, v.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return setContainerPathStatHeader(stat, w.Header())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
v, err := archiveFormValues(r, vars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tarArchive.Close()
|
||||||
|
|
||||||
|
if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/x-tar")
|
||||||
|
_, err = io.Copy(w, tarArchive)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
v, err := archiveFormValues(r, vars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir")
|
||||||
|
return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := parseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := checkForJson(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
|
||||||
execConfig := &runconfig.ExecConfig{}
|
execConfig := &runconfig.ExecConfig{}
|
||||||
@@ -1430,22 +1558,15 @@ func (s *Server) ping(version version.Version, w http.ResponseWriter, r *http.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.TlsVerify {
|
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 /!\\")
|
||||||
}
|
}
|
||||||
|
if l, err = sockets.NewTcpSocket(addr, s.cfg.TLSConfig, s.start); err != nil {
|
||||||
var c *sockets.TlsConfig
|
|
||||||
if s.cfg.Tls || s.cfg.TlsVerify {
|
|
||||||
c = sockets.NewTlsConfig(s.cfg.TlsCert, s.cfg.TlsKey, s.cfg.TlsCa, s.cfg.TlsVerify)
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, err = sockets.NewTcpSocket(addr, c, s.start); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := allocateDaemonPort(addr); err != nil {
|
if err := allocateDaemonPort(addr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1460,22 +1581,35 @@ func makeHttpHandler(logging bool, localMethod string, localRoute string, handle
|
|||||||
|
|
||||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
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])) {
|
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)
|
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"])
|
version := version.Version(mux.Vars(r)["version"])
|
||||||
if version == "" {
|
if version == "" {
|
||||||
version = api.APIVERSION
|
version = api.Version
|
||||||
}
|
}
|
||||||
if corsHeaders != "" {
|
if corsHeaders != "" {
|
||||||
writeCorsHeaders(w, r, corsHeaders)
|
writeCorsHeaders(w, r, corsHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version.GreaterThan(api.APIVERSION) {
|
if version.GreaterThan(api.Version) {
|
||||||
http.Error(w, fmt.Errorf("client and server don't have same version (client API version: %s, server API version: %s)", version, api.APIVERSION).Error(), http.StatusBadRequest)
|
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
|
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 {
|
if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil {
|
||||||
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
|
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
|
||||||
@@ -1491,6 +1625,9 @@ func createRouter(s *Server) *mux.Router {
|
|||||||
ProfilerSetup(r, "/debug/")
|
ProfilerSetup(r, "/debug/")
|
||||||
}
|
}
|
||||||
m := map[string]map[string]HttpApiFunc{
|
m := map[string]map[string]HttpApiFunc{
|
||||||
|
"HEAD": {
|
||||||
|
"/containers/{name:.*}/archive": s.headContainersArchive,
|
||||||
|
},
|
||||||
"GET": {
|
"GET": {
|
||||||
"/_ping": s.ping,
|
"/_ping": s.ping,
|
||||||
"/events": s.getEvents,
|
"/events": s.getEvents,
|
||||||
@@ -1512,6 +1649,7 @@ func createRouter(s *Server) *mux.Router {
|
|||||||
"/containers/{name:.*}/stats": s.getContainersStats,
|
"/containers/{name:.*}/stats": s.getContainersStats,
|
||||||
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
||||||
"/exec/{id:.*}/json": s.getExecByID,
|
"/exec/{id:.*}/json": s.getExecByID,
|
||||||
|
"/containers/{name:.*}/archive": s.getContainersArchive,
|
||||||
},
|
},
|
||||||
"POST": {
|
"POST": {
|
||||||
"/auth": s.postAuth,
|
"/auth": s.postAuth,
|
||||||
@@ -1537,6 +1675,9 @@ func createRouter(s *Server) *mux.Router {
|
|||||||
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
||||||
"/containers/{name:.*}/rename": s.postContainerRename,
|
"/containers/{name:.*}/rename": s.postContainerRename,
|
||||||
},
|
},
|
||||||
|
"PUT": {
|
||||||
|
"/containers/{name:.*}/archive": s.putContainersArchive,
|
||||||
|
},
|
||||||
"DELETE": {
|
"DELETE": {
|
||||||
"/containers/{name:.*}": s.deleteContainers,
|
"/containers/{name:.*}": s.deleteContainers,
|
||||||
"/images/{name:.*}": s.deleteImages,
|
"/images/{name:.*}": s.deleteImages,
|
||||||
@@ -1576,30 +1717,3 @@ func createRouter(s *Server) *mux.Router {
|
|||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func allocateDaemonPort(addr string) error {
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
intPort, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostIPs []net.IP
|
|
||||||
if parsedIP := net.ParseIP(host); parsedIP != nil {
|
|
||||||
hostIPs = append(hostIPs, parsedIP)
|
|
||||||
} else if hostIPs, err = net.LookupIP(host); err != nil {
|
|
||||||
return fmt.Errorf("failed to lookup %s address in host specification", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
pa := portallocator.Get()
|
|
||||||
for _, hostIP := range hostIPs {
|
|
||||||
if _, err := pa.RequestPort(hostIP, "tcp", intPort); err != nil {
|
|
||||||
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// +build experimental
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -6,10 +6,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"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/systemd"
|
"github.com/docker/docker/pkg/systemd"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/libnetwork/portallocator"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// See http://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
|
||||||
|
linuxMinCpuShares = 2
|
||||||
|
linuxMaxCpuShares = 262144
|
||||||
)
|
)
|
||||||
|
|
||||||
// newServer sets up the required serverClosers and does protocol specific checking.
|
// newServer sets up the required serverClosers and does protocol specific checking.
|
||||||
@@ -59,6 +70,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
// Tell the init daemon we are accepting requests
|
// Tell the init daemon we are accepting requests
|
||||||
s.daemon = d
|
s.daemon = d
|
||||||
|
s.registerSubRouter()
|
||||||
go systemd.SdNotify("READY=1")
|
go systemd.SdNotify("READY=1")
|
||||||
// close the lock so the listeners start accepting connections
|
// close the lock so the listeners start accepting connections
|
||||||
select {
|
select {
|
||||||
@@ -67,3 +79,45 @@ func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
|||||||
close(s.start)
|
close(s.start)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allocateDaemonPort(addr string) error {
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
intPort, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostIPs []net.IP
|
||||||
|
if parsedIP := net.ParseIP(host); parsedIP != nil {
|
||||||
|
hostIPs = append(hostIPs, parsedIP)
|
||||||
|
} else if hostIPs, err = net.LookupIP(host); err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup %s address in host specification", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
pa := portallocator.Get()
|
||||||
|
for _, hostIP := range hostIPs {
|
||||||
|
if _, err := pa.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||||
|
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
|
||||||
|
if version.LessThan("1.19") {
|
||||||
|
if hostConfig != nil && hostConfig.CpuShares > 0 {
|
||||||
|
// Handle unsupported CpuShares
|
||||||
|
if hostConfig.CpuShares < linuxMinCpuShares {
|
||||||
|
logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares)
|
||||||
|
hostConfig.CpuShares = linuxMinCpuShares
|
||||||
|
} else if hostConfig.CpuShares > linuxMaxCpuShares {
|
||||||
|
logrus.Warnf("Changing requested CpuShares of %d to maximum allowed of %d", hostConfig.CpuShares, linuxMaxCpuShares)
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdjustCpuSharesOldApi(t *testing.T) {
|
||||||
|
apiVersion := version.Version("1.18")
|
||||||
|
hostConfig := &runconfig.HostConfig{
|
||||||
|
CpuShares: linuxMinCpuShares - 1,
|
||||||
|
}
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMinCpuShares {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares + 1
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMaxCpuShares {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 0
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 0 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 1024
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 1024 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustCpuSharesNoAdjustment(t *testing.T) {
|
||||||
|
apiVersion := version.Version("1.19")
|
||||||
|
hostConfig := &runconfig.HostConfig{
|
||||||
|
CpuShares: linuxMinCpuShares - 1,
|
||||||
|
}
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMinCpuShares-1 {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares + 1
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMaxCpuShares+1 {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 0
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 0 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 1024
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 1024 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// +build !experimental
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
func (s *Server) registerSubRouter() {
|
||||||
|
}
|
||||||
@@ -8,34 +8,44 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) (Server, error) {
|
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
ls []net.Listener
|
||||||
l net.Listener
|
|
||||||
)
|
)
|
||||||
switch proto {
|
switch proto {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
l, err = s.initTcpSocket(addr)
|
l, err := s.initTcpSocket(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
||||||
}
|
}
|
||||||
return &HttpServer{
|
|
||||||
&http.Server{
|
var res []serverCloser
|
||||||
Addr: addr,
|
for _, l := range ls {
|
||||||
Handler: s.router,
|
res = append(res, &HttpServer{
|
||||||
},
|
&http.Server{
|
||||||
l,
|
Addr: addr,
|
||||||
}, nil
|
Handler: s.router,
|
||||||
|
},
|
||||||
|
l,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
s.daemon = d
|
s.daemon = d
|
||||||
|
s.registerSubRouter()
|
||||||
// close the lock so the listeners start accepting connections
|
// close the lock so the listeners start accepting connections
|
||||||
select {
|
select {
|
||||||
case <-s.start:
|
case <-s.start:
|
||||||
@@ -43,3 +53,10 @@ func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
|||||||
close(s.start)
|
close(s.start)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allocateDaemonPort(addr string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type MemoryStats struct {
|
|||||||
Limit uint64 `json:"limit"`
|
Limit uint64 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Windows: This can be factored out
|
||||||
type BlkioStatEntry struct {
|
type BlkioStatEntry struct {
|
||||||
Major uint64 `json:"major"`
|
Major uint64 `json:"major"`
|
||||||
Minor uint64 `json:"minor"`
|
Minor uint64 `json:"minor"`
|
||||||
@@ -55,6 +56,7 @@ type BlkioStatEntry struct {
|
|||||||
Value uint64 `json:"value"`
|
Value uint64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Windows: This can be factored out
|
||||||
type BlkioStats struct {
|
type BlkioStats struct {
|
||||||
// number of bytes tranferred to and from the block device
|
// number of bytes tranferred to and from the block device
|
||||||
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
||||||
@@ -67,6 +69,7 @@ type BlkioStats struct {
|
|||||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Windows: This will require refactoring
|
||||||
type Network struct {
|
type Network struct {
|
||||||
RxBytes uint64 `json:"rx_bytes"`
|
RxBytes uint64 `json:"rx_bytes"`
|
||||||
RxPackets uint64 `json:"rx_packets"`
|
RxPackets uint64 `json:"rx_packets"`
|
||||||
|
|||||||
+46
-10
@@ -1,6 +1,7 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
@@ -75,12 +76,17 @@ type Image struct {
|
|||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GraphDriverData struct {
|
||||||
|
Name string
|
||||||
|
Data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
// GET "/images/{name:.*}/json"
|
// GET "/images/{name:.*}/json"
|
||||||
type ImageInspect struct {
|
type ImageInspect struct {
|
||||||
Id string
|
Id string
|
||||||
Parent string
|
Parent string
|
||||||
Comment string
|
Comment string
|
||||||
Created time.Time
|
Created string
|
||||||
Container string
|
Container string
|
||||||
ContainerConfig *runconfig.Config
|
ContainerConfig *runconfig.Config
|
||||||
DockerVersion string
|
DockerVersion string
|
||||||
@@ -90,13 +96,14 @@ type ImageInspect struct {
|
|||||||
Os string
|
Os string
|
||||||
Size int64
|
Size int64
|
||||||
VirtualSize int64
|
VirtualSize int64
|
||||||
|
GraphDriver GraphDriverData
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET "/containers/json"
|
// GET "/containers/json"
|
||||||
type Port struct {
|
type Port struct {
|
||||||
IP string
|
IP string `json:",omitempty"`
|
||||||
PrivatePort int
|
PrivatePort int
|
||||||
PublicPort int
|
PublicPort int `json:",omitempty"`
|
||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +118,9 @@ type Container struct {
|
|||||||
SizeRootFs int `json:",omitempty"`
|
SizeRootFs int `json:",omitempty"`
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
Status string
|
Status string
|
||||||
|
HostConfig struct {
|
||||||
|
NetworkMode string `json:",omitempty"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST "/containers/"+containerID+"/copy"
|
// POST "/containers/"+containerID+"/copy"
|
||||||
@@ -118,6 +128,17 @@ type CopyConfig struct {
|
|||||||
Resource string
|
Resource string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerPathStat is used to encode the header from
|
||||||
|
// GET /containers/{name:.*}/archive
|
||||||
|
// "name" is basename of the resource.
|
||||||
|
type ContainerPathStat struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Mode os.FileMode `json:"mode"`
|
||||||
|
Mtime time.Time `json:"mtime"`
|
||||||
|
LinkTarget string `json:"linkTarget"`
|
||||||
|
}
|
||||||
|
|
||||||
// GET "/containers/{name:.*}/top"
|
// GET "/containers/{name:.*}/top"
|
||||||
type ContainerProcessList struct {
|
type ContainerProcessList struct {
|
||||||
Processes [][]string
|
Processes [][]string
|
||||||
@@ -133,6 +154,7 @@ type Version struct {
|
|||||||
Arch string
|
Arch string
|
||||||
KernelVersion string `json:",omitempty"`
|
KernelVersion string `json:",omitempty"`
|
||||||
Experimental bool `json:",omitempty"`
|
Experimental bool `json:",omitempty"`
|
||||||
|
BuildTime string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET "/info"
|
// GET "/info"
|
||||||
@@ -147,6 +169,8 @@ type Info struct {
|
|||||||
CpuCfsPeriod bool
|
CpuCfsPeriod bool
|
||||||
CpuCfsQuota bool
|
CpuCfsQuota bool
|
||||||
IPv4Forwarding bool
|
IPv4Forwarding bool
|
||||||
|
BridgeNfIptables bool
|
||||||
|
BridgeNfIp6tables bool
|
||||||
Debug bool
|
Debug bool
|
||||||
NFd int
|
NFd int
|
||||||
OomKillDisable bool
|
OomKillDisable bool
|
||||||
@@ -190,14 +214,14 @@ type ContainerState struct {
|
|||||||
Pid int
|
Pid int
|
||||||
ExitCode int
|
ExitCode int
|
||||||
Error string
|
Error string
|
||||||
StartedAt time.Time
|
StartedAt string
|
||||||
FinishedAt time.Time
|
FinishedAt string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET "/containers/{name:.*}/json"
|
// GET "/containers/{name:.*}/json"
|
||||||
type ContainerJSONBase struct {
|
type ContainerJSONBase struct {
|
||||||
Id string
|
Id string
|
||||||
Created time.Time
|
Created string
|
||||||
Path string
|
Path string
|
||||||
Args []string
|
Args []string
|
||||||
State *ContainerState
|
State *ContainerState
|
||||||
@@ -213,22 +237,24 @@ type ContainerJSONBase struct {
|
|||||||
ExecDriver string
|
ExecDriver string
|
||||||
MountLabel string
|
MountLabel string
|
||||||
ProcessLabel string
|
ProcessLabel string
|
||||||
Volumes map[string]string
|
|
||||||
VolumesRW map[string]bool
|
|
||||||
AppArmorProfile string
|
AppArmorProfile string
|
||||||
ExecIDs []string
|
ExecIDs []string
|
||||||
HostConfig *runconfig.HostConfig
|
HostConfig *runconfig.HostConfig
|
||||||
|
GraphDriver GraphDriverData
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerJSON struct {
|
type ContainerJSON struct {
|
||||||
*ContainerJSONBase
|
*ContainerJSONBase
|
||||||
|
Mounts []MountPoint
|
||||||
Config *runconfig.Config
|
Config *runconfig.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// backcompatibility struct along with ContainerConfig
|
// backcompatibility struct along with ContainerConfig
|
||||||
type ContainerJSONRaw struct {
|
type ContainerJSONPre120 struct {
|
||||||
*ContainerJSONBase
|
*ContainerJSONBase
|
||||||
Config *ContainerConfig
|
Volumes map[string]string
|
||||||
|
VolumesRW map[string]bool
|
||||||
|
Config *ContainerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerConfig struct {
|
type ContainerConfig struct {
|
||||||
@@ -240,3 +266,13 @@ type ContainerConfig struct {
|
|||||||
CpuShares int64
|
CpuShares int64
|
||||||
Cpuset string
|
Cpuset string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MountPoint represents a mount point configuration inside the container.
|
||||||
|
type MountPoint struct {
|
||||||
|
Name string `json:",omitempty"`
|
||||||
|
Source string
|
||||||
|
Destination string
|
||||||
|
Driver string `json:",omitempty"`
|
||||||
|
Mode string // this is internally named `Relabel`
|
||||||
|
RW bool
|
||||||
|
}
|
||||||
|
|||||||
+17
-8
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FlagType is the type of the build flag
|
||||||
type FlagType int
|
type FlagType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -12,28 +13,32 @@ const (
|
|||||||
stringType
|
stringType
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuilderFlags struct {
|
// BFlags contains all flags information for the builder
|
||||||
|
type BFlags struct {
|
||||||
Args []string // actual flags/args from cmd line
|
Args []string // actual flags/args from cmd line
|
||||||
flags map[string]*Flag
|
flags map[string]*Flag
|
||||||
used map[string]*Flag
|
used map[string]*Flag
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag contains all information for a flag
|
||||||
type Flag struct {
|
type Flag struct {
|
||||||
bf *BuilderFlags
|
bf *BFlags
|
||||||
name string
|
name string
|
||||||
flagType FlagType
|
flagType FlagType
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBuilderFlags() *BuilderFlags {
|
// NewBFlags return the new BFlags struct
|
||||||
return &BuilderFlags{
|
func NewBFlags() *BFlags {
|
||||||
|
return &BFlags{
|
||||||
flags: make(map[string]*Flag),
|
flags: make(map[string]*Flag),
|
||||||
used: make(map[string]*Flag),
|
used: make(map[string]*Flag),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bf *BuilderFlags) AddBool(name string, def bool) *Flag {
|
// AddBool adds a bool flag to BFlags
|
||||||
|
func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
||||||
flag := bf.addFlag(name, boolType)
|
flag := bf.addFlag(name, boolType)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -46,7 +51,8 @@ func (bf *BuilderFlags) AddBool(name string, def bool) *Flag {
|
|||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bf *BuilderFlags) AddString(name string, def string) *Flag {
|
// AddString adds a string flag to BFlags
|
||||||
|
func (bf *BFlags) AddString(name string, def string) *Flag {
|
||||||
flag := bf.addFlag(name, stringType)
|
flag := bf.addFlag(name, stringType)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -55,7 +61,7 @@ func (bf *BuilderFlags) AddString(name string, def string) *Flag {
|
|||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bf *BuilderFlags) addFlag(name string, flagType FlagType) *Flag {
|
func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
|
||||||
if _, ok := bf.flags[name]; ok {
|
if _, ok := bf.flags[name]; ok {
|
||||||
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
||||||
return nil
|
return nil
|
||||||
@@ -71,6 +77,7 @@ func (bf *BuilderFlags) addFlag(name string, flagType FlagType) *Flag {
|
|||||||
return newFlag
|
return newFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsUsed checks if the flag is used
|
||||||
func (fl *Flag) IsUsed() bool {
|
func (fl *Flag) IsUsed() bool {
|
||||||
if _, ok := fl.bf.used[fl.name]; ok {
|
if _, ok := fl.bf.used[fl.name]; ok {
|
||||||
return true
|
return true
|
||||||
@@ -78,6 +85,7 @@ func (fl *Flag) IsUsed() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsTrue checks if a bool flag is true
|
||||||
func (fl *Flag) IsTrue() bool {
|
func (fl *Flag) IsTrue() bool {
|
||||||
if fl.flagType != boolType {
|
if fl.flagType != boolType {
|
||||||
// Should never get here
|
// Should never get here
|
||||||
@@ -86,7 +94,8 @@ func (fl *Flag) IsTrue() bool {
|
|||||||
return fl.Value == "true"
|
return fl.Value == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bf *BuilderFlags) Parse() error {
|
// Parse parses and checks if the BFlags is valid
|
||||||
|
func (bf *BFlags) Parse() error {
|
||||||
// If there was an error while defining the possible flags
|
// If there was an error while defining the possible flags
|
||||||
// go ahead and bubble it back up here since we didn't do it
|
// go ahead and bubble it back up here since we didn't do it
|
||||||
// earlier in the processing
|
// earlier in the processing
|
||||||
|
|||||||
+13
-13
@@ -10,7 +10,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf := NewBuilderFlags()
|
bf := NewBFlags()
|
||||||
bf.Args = []string{}
|
bf.Args = []string{}
|
||||||
if err := bf.Parse(); err != nil {
|
if err := bf.Parse(); err != nil {
|
||||||
t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err)
|
t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err)
|
||||||
@@ -18,7 +18,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
bf.Args = []string{"--"}
|
bf.Args = []string{"--"}
|
||||||
if err := bf.Parse(); err != nil {
|
if err := bf.Parse(); err != nil {
|
||||||
t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err)
|
t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err)
|
||||||
@@ -26,7 +26,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flStr1 := bf.AddString("str1", "")
|
flStr1 := bf.AddString("str1", "")
|
||||||
flBool1 := bf.AddBool("bool1", false)
|
flBool1 := bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{}
|
bf.Args = []string{}
|
||||||
@@ -43,7 +43,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{}
|
bf.Args = []string{}
|
||||||
@@ -67,7 +67,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
bf.Args = []string{"--str1"}
|
bf.Args = []string{"--str1"}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
bf.Args = []string{"--str1="}
|
bf.Args = []string{"--str1="}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
bf.Args = []string{"--str1=BYE"}
|
bf.Args = []string{"--str1=BYE"}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1"}
|
bf.Args = []string{"--bool1"}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1=true"}
|
bf.Args = []string{"--bool1=true"}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1=false"}
|
bf.Args = []string{"--bool1=false"}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1=false1"}
|
bf.Args = []string{"--bool1=false1"}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool2"}
|
bf.Args = []string{"--bool2"}
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBuilderFlags()
|
bf = NewBFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1", "--str1=BYE"}
|
bf.Args = []string{"--bool1", "--str1=BYE"}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// This package contains the set of Dockerfile commands.
|
// Package command contains the set of Dockerfile commands.
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
// Define constants for the command strings
|
||||||
const (
|
const (
|
||||||
Env = "env"
|
Env = "env"
|
||||||
Label = "label"
|
Label = "label"
|
||||||
|
|||||||
+52
-36
@@ -10,6 +10,7 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -17,8 +18,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/nat"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/nat"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// dispatch with no layer / parsing. This is effectively not a command.
|
// dispatch with no layer / parsing. This is effectively not a command.
|
||||||
func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func nullDispatch(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,10 +39,7 @@ func nullDispatch(b *Builder, args []string, attributes map[string]bool, origina
|
|||||||
// Sets the environment variable foo to bar, also makes interpolation
|
// Sets the environment variable foo to bar, also makes interpolation
|
||||||
// in the dockerfile available from the next statement on via ${foo}.
|
// in the dockerfile available from the next statement on via ${foo}.
|
||||||
//
|
//
|
||||||
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func env(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return fmt.Errorf("ENV is not supported on Windows.")
|
|
||||||
}
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("ENV requires at least one argument")
|
return fmt.Errorf("ENV requires at least one argument")
|
||||||
}
|
}
|
||||||
@@ -100,7 +98,7 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
|
|||||||
// MAINTAINER some text <maybe@an.email.address>
|
// MAINTAINER some text <maybe@an.email.address>
|
||||||
//
|
//
|
||||||
// Sets the maintainer metadata.
|
// Sets the maintainer metadata.
|
||||||
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func maintainer(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("MAINTAINER requires exactly one argument")
|
return fmt.Errorf("MAINTAINER requires exactly one argument")
|
||||||
}
|
}
|
||||||
@@ -117,7 +115,7 @@ func maintainer(b *Builder, args []string, attributes map[string]bool, original
|
|||||||
//
|
//
|
||||||
// Sets the Label variable foo to bar,
|
// Sets the Label variable foo to bar,
|
||||||
//
|
//
|
||||||
func label(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func label(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("LABEL requires at least one argument")
|
return fmt.Errorf("LABEL requires at least one argument")
|
||||||
}
|
}
|
||||||
@@ -153,7 +151,7 @@ func label(b *Builder, args []string, attributes map[string]bool, original strin
|
|||||||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
// 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.
|
// 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 {
|
func add(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("ADD requires at least two arguments")
|
return fmt.Errorf("ADD requires at least two arguments")
|
||||||
}
|
}
|
||||||
@@ -169,7 +167,7 @@ func add(b *Builder, args []string, attributes map[string]bool, original string)
|
|||||||
//
|
//
|
||||||
// Same as 'ADD' but without the tar and remote url handling.
|
// Same as 'ADD' but without the tar and remote url handling.
|
||||||
//
|
//
|
||||||
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func dispatchCopy(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("COPY requires at least two arguments")
|
return fmt.Errorf("COPY requires at least two arguments")
|
||||||
}
|
}
|
||||||
@@ -185,7 +183,7 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina
|
|||||||
//
|
//
|
||||||
// This sets the image the dockerfile will build on top of.
|
// This sets the image the dockerfile will build on top of.
|
||||||
//
|
//
|
||||||
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func from(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("FROM requires one argument")
|
return fmt.Errorf("FROM requires one argument")
|
||||||
}
|
}
|
||||||
@@ -233,7 +231,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
|||||||
// special cases. search for 'OnBuild' in internals.go for additional special
|
// special cases. search for 'OnBuild' in internals.go for additional special
|
||||||
// cases.
|
// cases.
|
||||||
//
|
//
|
||||||
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func onbuild(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("ONBUILD requires at least one argument")
|
return fmt.Errorf("ONBUILD requires at least one argument")
|
||||||
}
|
}
|
||||||
@@ -260,7 +258,7 @@ func onbuild(b *Builder, args []string, attributes map[string]bool, original str
|
|||||||
//
|
//
|
||||||
// Set the working directory for future RUN/CMD/etc statements.
|
// Set the working directory for future RUN/CMD/etc statements.
|
||||||
//
|
//
|
||||||
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func workdir(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("WORKDIR requires exactly one argument")
|
return fmt.Errorf("WORKDIR requires exactly one argument")
|
||||||
}
|
}
|
||||||
@@ -269,12 +267,39 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that workdir passed comes from the Dockerfile. Hence it is in
|
||||||
|
// Linux format using forward-slashes, even on Windows. However,
|
||||||
|
// b.Config.WorkingDir is in platform-specific notation (in other words
|
||||||
|
// on Windows will use `\`
|
||||||
workdir := args[0]
|
workdir := args[0]
|
||||||
|
|
||||||
if !filepath.IsAbs(workdir) {
|
isAbs := false
|
||||||
workdir = filepath.Join("/", b.Config.WorkingDir, workdir)
|
if runtime.GOOS == "windows" {
|
||||||
|
// Alternate processing for Windows here is necessary as we can't call
|
||||||
|
// filepath.IsAbs(workDir) as that would verify Windows style paths,
|
||||||
|
// along with drive-letters (eg c:\pathto\file.txt). We (arguably
|
||||||
|
// correctly or not) check for both forward and back slashes as this
|
||||||
|
// is what the 1.4.2 GoLang implementation of IsAbs() does in the
|
||||||
|
// isSlash() function.
|
||||||
|
isAbs = workdir[0] == '\\' || workdir[0] == '/'
|
||||||
|
} else {
|
||||||
|
isAbs = filepath.IsAbs(workdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isAbs {
|
||||||
|
current := b.Config.WorkingDir
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Convert to Linux format before join
|
||||||
|
current = strings.Replace(current, "\\", "/", -1)
|
||||||
|
}
|
||||||
|
// Must use path.Join so works correctly on Windows, not filepath
|
||||||
|
workdir = path.Join("/", current, workdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to platform specific format
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
workdir = strings.Replace(workdir, "/", "\\", -1)
|
||||||
|
}
|
||||||
b.Config.WorkingDir = workdir
|
b.Config.WorkingDir = workdir
|
||||||
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||||
@@ -290,7 +315,7 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
|
|||||||
// RUN echo hi # cmd /S /C echo hi (Windows)
|
// RUN echo hi # cmd /S /C echo hi (Windows)
|
||||||
// RUN [ "echo", "hi" ] # echo hi
|
// RUN [ "echo", "hi" ] # echo hi
|
||||||
//
|
//
|
||||||
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func run(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if b.image == "" && !b.noBaseImage {
|
if b.image == "" && !b.noBaseImage {
|
||||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||||
}
|
}
|
||||||
@@ -299,7 +324,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
args = handleJsonArgs(args, attributes)
|
args = handleJSONArgs(args, attributes)
|
||||||
|
|
||||||
if !attributes["json"] {
|
if !attributes["json"] {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
@@ -361,12 +386,12 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||||||
// Set the default command to run in the container (which may be empty).
|
// Set the default command to run in the container (which may be empty).
|
||||||
// Argument handling is the same as RUN.
|
// Argument handling is the same as RUN.
|
||||||
//
|
//
|
||||||
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func cmd(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
if err := b.BuilderFlags.Parse(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdSlice := handleJsonArgs(args, attributes)
|
cmdSlice := handleJSONArgs(args, attributes)
|
||||||
|
|
||||||
if !attributes["json"] {
|
if !attributes["json"] {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
@@ -397,12 +422,12 @@ func cmd(b *Builder, args []string, attributes map[string]bool, original string)
|
|||||||
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
||||||
// is initialized at NewBuilder time instead of through argument parsing.
|
// is initialized at NewBuilder time instead of through argument parsing.
|
||||||
//
|
//
|
||||||
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func entrypoint(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
if err := b.BuilderFlags.Parse(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed := handleJsonArgs(args, attributes)
|
parsed := handleJSONArgs(args, attributes)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case attributes["json"]:
|
case attributes["json"]:
|
||||||
@@ -438,7 +463,7 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
|
|||||||
// Expose ports for links and port mappings. This all ends up in
|
// Expose ports for links and port mappings. This all ends up in
|
||||||
// b.Config.ExposedPorts for runconfig.
|
// b.Config.ExposedPorts for runconfig.
|
||||||
//
|
//
|
||||||
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func expose(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
portsTab := args
|
portsTab := args
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
@@ -453,19 +478,11 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
|
|||||||
b.Config.ExposedPorts = make(nat.PortSet)
|
b.Config.ExposedPorts = make(nat.PortSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
ports, bindingMap, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
|
ports, _, err := nat.ParsePortSpecs(portsTab)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bindings := range bindingMap {
|
|
||||||
if bindings[0].HostIp != "" || bindings[0].HostPort != "" {
|
|
||||||
fmt.Fprintf(b.ErrStream, " ---> Using Dockerfile's EXPOSE instruction"+
|
|
||||||
" to map host ports to container ports (ip:hostPort:containerPort) is deprecated.\n"+
|
|
||||||
" Please use -p to publish the ports.\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// instead of using ports directly, we build a list of ports and sort it so
|
// 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
|
// the order is consistent. This prevents cache burst where map ordering
|
||||||
// changes between builds
|
// changes between builds
|
||||||
@@ -479,7 +496,6 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
sort.Strings(portList)
|
sort.Strings(portList)
|
||||||
b.Config.PortSpecs = nil
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,9 +504,9 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
|
|||||||
// Set the user to 'foo' for future commands and when running the
|
// Set the user to 'foo' for future commands and when running the
|
||||||
// ENTRYPOINT/CMD at container run time.
|
// ENTRYPOINT/CMD at container run time.
|
||||||
//
|
//
|
||||||
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func user(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return fmt.Errorf("USER is not supported on Windows.")
|
return fmt.Errorf("USER is not supported on Windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
@@ -509,9 +525,9 @@ func user(b *Builder, args []string, attributes map[string]bool, original string
|
|||||||
//
|
//
|
||||||
// Expose the volume /foo for use. Will also accept the JSON array form.
|
// 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 {
|
func volume(b *builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return fmt.Errorf("VOLUME is not supported on Windows.")
|
return fmt.Errorf("VOLUME is not supported on Windows")
|
||||||
}
|
}
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("VOLUME requires at least one argument")
|
return fmt.Errorf("VOLUME requires at least one argument")
|
||||||
|
|||||||
+16
-11
@@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
@@ -53,10 +54,10 @@ var replaceEnvAllowed = map[string]struct{}{
|
|||||||
command.User: {},
|
command.User: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
|
var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
evaluateTable = map[string]func(*builder, []string, map[string]bool, string) error{
|
||||||
command.Env: env,
|
command.Env: env,
|
||||||
command.Label: label,
|
command.Label: label,
|
||||||
command.Maintainer: maintainer,
|
command.Maintainer: maintainer,
|
||||||
@@ -74,9 +75,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal struct, used to maintain configuration of the Dockerfile's
|
// builder is an internal struct, used to maintain configuration of the Dockerfile's
|
||||||
// processing as it evaluates the parsing result.
|
// processing as it evaluates the parsing result.
|
||||||
type Builder struct {
|
type builder struct {
|
||||||
Daemon *daemon.Daemon
|
Daemon *daemon.Daemon
|
||||||
|
|
||||||
// effectively stdio for the run. Because it is not stdio, I said
|
// effectively stdio for the run. Because it is not stdio, I said
|
||||||
@@ -98,8 +99,8 @@ type Builder struct {
|
|||||||
// the final configs of the Dockerfile but dont want the layers
|
// the final configs of the Dockerfile but dont want the layers
|
||||||
disableCommit bool
|
disableCommit bool
|
||||||
|
|
||||||
AuthConfig *cliconfig.AuthConfig
|
// Registry server auth configs used to pull images when handling `FROM`.
|
||||||
ConfigFile *cliconfig.ConfigFile
|
AuthConfigs map[string]cliconfig.AuthConfig
|
||||||
|
|
||||||
// Deprecated, original writer used for ImagePull. To be removed.
|
// Deprecated, original writer used for ImagePull. To be removed.
|
||||||
OutOld io.Writer
|
OutOld io.Writer
|
||||||
@@ -115,7 +116,7 @@ type Builder struct {
|
|||||||
image string // image name for commit processing
|
image string // image name for commit processing
|
||||||
maintainer string // maintainer name. could probably be removed.
|
maintainer string // maintainer name. could probably be removed.
|
||||||
cmdSet bool // indicates is CMD was set in current Dockerfile
|
cmdSet bool // indicates is CMD was set in current Dockerfile
|
||||||
BuilderFlags *BuilderFlags // current cmd's BuilderFlags - temporary
|
BuilderFlags *BFlags // current cmd's BuilderFlags - temporary
|
||||||
context tarsum.TarSum // the context is a tarball that is uploaded by the client
|
context tarsum.TarSum // the context is a tarball that is uploaded by the client
|
||||||
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
|
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
|
||||||
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
|
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
|
||||||
@@ -129,8 +130,12 @@ type Builder struct {
|
|||||||
cgroupParent string
|
cgroupParent string
|
||||||
memory int64
|
memory int64
|
||||||
memorySwap int64
|
memorySwap int64
|
||||||
|
ulimits []*ulimit.Ulimit
|
||||||
|
|
||||||
cancelled <-chan struct{} // When closed, job was cancelled.
|
cancelled <-chan struct{} // When closed, job was cancelled.
|
||||||
|
|
||||||
|
activeImages []string
|
||||||
|
id string // Used to hold reference images
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the builder with the context. This is the lynchpin of this package. This
|
// Run the builder with the context. This is the lynchpin of this package. This
|
||||||
@@ -145,7 +150,7 @@ type Builder struct {
|
|||||||
// processing.
|
// processing.
|
||||||
// * Print a happy message and return the image ID.
|
// * Print a happy message and return the image ID.
|
||||||
//
|
//
|
||||||
func (b *Builder) Run(context io.Reader) (string, error) {
|
func (b *builder) Run(context io.Reader) (string, error) {
|
||||||
if err := b.readContext(context); err != nil {
|
if err := b.readContext(context); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -196,7 +201,7 @@ func (b *Builder) Run(context io.Reader) (string, error) {
|
|||||||
|
|
||||||
// Reads a Dockerfile from the current context. It assumes that the
|
// Reads a Dockerfile from the current context. It assumes that the
|
||||||
// 'filename' is a relative path from the root of the context
|
// 'filename' is a relative path from the root of the context
|
||||||
func (b *Builder) readDockerfile() error {
|
func (b *builder) readDockerfile() error {
|
||||||
// If no -f was specified then look for 'Dockerfile'. If we can't find
|
// If no -f was specified then look for 'Dockerfile'. If we can't find
|
||||||
// that then look for 'dockerfile'. If neither are found then default
|
// that then look for 'dockerfile'. If neither are found then default
|
||||||
// back to 'Dockerfile' and use that in the error message.
|
// back to 'Dockerfile' and use that in the error message.
|
||||||
@@ -274,7 +279,7 @@ func (b *Builder) readDockerfile() error {
|
|||||||
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
||||||
// deal with that, at least until it becomes more of a general concern with new
|
// deal with that, at least until it becomes more of a general concern with new
|
||||||
// features.
|
// features.
|
||||||
func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
func (b *builder) dispatch(stepN int, ast *parser.Node) error {
|
||||||
cmd := ast.Value
|
cmd := ast.Value
|
||||||
attrs := ast.Attributes
|
attrs := ast.Attributes
|
||||||
original := ast.Original
|
original := ast.Original
|
||||||
@@ -337,7 +342,7 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
|||||||
// XXX yes, we skip any cmds that are not valid; the parser should have
|
// XXX yes, we skip any cmds that are not valid; the parser should have
|
||||||
// picked these out already.
|
// picked these out already.
|
||||||
if f, ok := evaluateTable[cmd]; ok {
|
if f, ok := evaluateTable[cmd]; ok {
|
||||||
b.BuilderFlags = NewBuilderFlags()
|
b.BuilderFlags = NewBFlags()
|
||||||
b.BuilderFlags.Args = flags
|
b.BuilderFlags.Args = flags
|
||||||
return f(b, strList, attrs, original)
|
return f(b, strList, attrs, original)
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-96
@@ -12,8 +12,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -21,9 +21,10 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/builder/parser"
|
"github.com/docker/docker/builder/parser"
|
||||||
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
imagepkg "github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
"github.com/docker/docker/pkg/httputils"
|
||||||
@@ -39,30 +40,40 @@ import (
|
|||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Builder) readContext(context io.Reader) error {
|
func (b *builder) readContext(context io.Reader) (err error) {
|
||||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure we clean-up upon error. In the happy case the caller
|
||||||
|
// is expected to manage the clean-up
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if e := os.RemoveAll(tmpdirPath); e != nil {
|
||||||
|
logrus.Debugf("[BUILDER] failed to remove temporary context: %s", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
decompressedStream, err := archive.DecompressStream(context)
|
decompressedStream, err := archive.DecompressStream(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version0); err != nil {
|
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version1); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
|
if err = chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b.contextPath = tmpdirPath
|
b.contextPath = tmpdirPath
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
|
func (b *builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
|
||||||
if b.disableCommit {
|
if b.disableCommit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -72,7 +83,11 @@ func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string)
|
|||||||
b.Config.Image = b.image
|
b.Config.Image = b.image
|
||||||
if id == "" {
|
if id == "" {
|
||||||
cmd := b.Config.Cmd
|
cmd := b.Config.Cmd
|
||||||
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
|
if runtime.GOOS != "windows" {
|
||||||
|
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
|
||||||
|
} else {
|
||||||
|
b.Config.Cmd = runconfig.NewCommand("cmd", "/S /C", "REM (nop) "+comment)
|
||||||
|
}
|
||||||
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
||||||
|
|
||||||
hit, err := b.probeCache()
|
hit, err := b.probeCache()
|
||||||
@@ -103,11 +118,19 @@ func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string)
|
|||||||
autoConfig := *b.Config
|
autoConfig := *b.Config
|
||||||
autoConfig.Cmd = autoCmd
|
autoConfig.Cmd = autoCmd
|
||||||
|
|
||||||
|
commitCfg := &daemon.ContainerCommitConfig{
|
||||||
|
Author: b.maintainer,
|
||||||
|
Pause: true,
|
||||||
|
Config: &autoConfig,
|
||||||
|
}
|
||||||
|
|
||||||
// Commit the container
|
// Commit the container
|
||||||
image, err := b.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
|
image, err := b.Daemon.Commit(container, commitCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
b.Daemon.Graph().Retain(b.id, image.ID)
|
||||||
|
b.activeImages = append(b.activeImages, image.ID)
|
||||||
b.image = image.ID
|
b.image = image.ID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -120,7 +143,7 @@ type copyInfo struct {
|
|||||||
tmpDir string
|
tmpDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
||||||
if b.context == nil {
|
if b.context == nil {
|
||||||
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
||||||
}
|
}
|
||||||
@@ -129,7 +152,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
dest := args[len(args)-1] // last one is always the dest
|
// Work in daemon-specific filepath semantics
|
||||||
|
dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest
|
||||||
|
|
||||||
copyInfos := []*copyInfo{}
|
copyInfos := []*copyInfo{}
|
||||||
|
|
||||||
@@ -164,8 +188,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
if len(copyInfos) == 0 {
|
if len(copyInfos) == 0 {
|
||||||
return fmt.Errorf("No source files were specified")
|
return fmt.Errorf("No source files were specified")
|
||||||
}
|
}
|
||||||
|
if len(copyInfos) > 1 && !strings.HasSuffix(dest, string(os.PathSeparator)) {
|
||||||
if len(copyInfos) > 1 && !strings.HasSuffix(dest, "/") {
|
|
||||||
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
|
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +214,11 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := b.Config.Cmd
|
cmd := b.Config.Cmd
|
||||||
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
|
if runtime.GOOS != "windows" {
|
||||||
|
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
|
||||||
|
} else {
|
||||||
|
b.Config.Cmd = runconfig.NewCommand("cmd", "/S /C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
|
||||||
|
}
|
||||||
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
||||||
|
|
||||||
hit, err := b.probeCache()
|
hit, err := b.probeCache()
|
||||||
@@ -214,39 +241,59 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
}
|
}
|
||||||
defer container.Unmount()
|
defer container.Unmount()
|
||||||
|
|
||||||
|
if err := container.PrepareStorage(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, ci := range copyInfos {
|
for _, ci := range copyInfos {
|
||||||
if err := b.addContext(container, ci.origPath, ci.destPath, ci.decompress); err != nil {
|
if err := b.addContext(container, ci.origPath, ci.destPath, ci.decompress); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := container.CleanupStorage(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)); err != nil {
|
if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool, allowWildcards bool) error {
|
func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool, allowWildcards bool) error {
|
||||||
|
|
||||||
if origPath != "" && origPath[0] == '/' && len(origPath) > 1 {
|
// Work in daemon-specific OS filepath semantics. However, we save
|
||||||
|
// the the origPath passed in here, as it might also be a URL which
|
||||||
|
// we need to check for in this function.
|
||||||
|
passedInOrigPath := origPath
|
||||||
|
origPath = filepath.FromSlash(origPath)
|
||||||
|
destPath = filepath.FromSlash(destPath)
|
||||||
|
|
||||||
|
if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 {
|
||||||
origPath = origPath[1:]
|
origPath = origPath[1:]
|
||||||
}
|
}
|
||||||
origPath = strings.TrimPrefix(origPath, "./")
|
origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator))
|
||||||
|
|
||||||
// Twiddle the destPath when its a relative path - meaning, make it
|
// Twiddle the destPath when its a relative path - meaning, make it
|
||||||
// relative to the WORKINGDIR
|
// relative to the WORKINGDIR
|
||||||
if !filepath.IsAbs(destPath) {
|
if !filepath.IsAbs(destPath) {
|
||||||
hasSlash := strings.HasSuffix(destPath, "/")
|
hasSlash := strings.HasSuffix(destPath, string(os.PathSeparator))
|
||||||
destPath = filepath.Join("/", b.Config.WorkingDir, destPath)
|
destPath = filepath.Join(string(os.PathSeparator), filepath.FromSlash(b.Config.WorkingDir), destPath)
|
||||||
|
|
||||||
// Make sure we preserve any trailing slash
|
// Make sure we preserve any trailing slash
|
||||||
if hasSlash {
|
if hasSlash {
|
||||||
destPath += "/"
|
destPath += string(os.PathSeparator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the remote/URL case, download it and gen its hashcode
|
// In the remote/URL case, download it and gen its hashcode
|
||||||
if urlutil.IsURL(origPath) {
|
if urlutil.IsURL(passedInOrigPath) {
|
||||||
|
|
||||||
|
// As it's a URL, we go back to processing on what was passed in
|
||||||
|
// to this function
|
||||||
|
origPath = passedInOrigPath
|
||||||
|
|
||||||
if !allowRemote {
|
if !allowRemote {
|
||||||
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
||||||
}
|
}
|
||||||
@@ -272,7 +319,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
ci.tmpDir = tmpDirName
|
ci.tmpDir = tmpDirName
|
||||||
|
|
||||||
// Create a tmp file within our tmp dir
|
// Create a tmp file within our tmp dir
|
||||||
tmpFileName := path.Join(tmpDirName, "tmp")
|
tmpFileName := filepath.Join(tmpDirName, "tmp")
|
||||||
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -312,19 +359,19 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ci.origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
ci.origPath = filepath.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||||
|
|
||||||
// If the destination is a directory, figure out the filename.
|
// If the destination is a directory, figure out the filename.
|
||||||
if strings.HasSuffix(ci.destPath, "/") {
|
if strings.HasSuffix(ci.destPath, string(os.PathSeparator)) {
|
||||||
u, err := url.Parse(origPath)
|
u, err := url.Parse(origPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path := u.Path
|
path := u.Path
|
||||||
if strings.HasSuffix(path, "/") {
|
if strings.HasSuffix(path, string(os.PathSeparator)) {
|
||||||
path = path[:len(path)-1]
|
path = path[:len(path)-1]
|
||||||
}
|
}
|
||||||
parts := strings.Split(path, "/")
|
parts := strings.Split(path, string(os.PathSeparator))
|
||||||
filename := parts[len(parts)-1]
|
filename := parts[len(parts)-1]
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||||
@@ -337,7 +384,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version0)
|
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -351,12 +398,12 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deal with wildcards
|
// Deal with wildcards
|
||||||
if allowWildcards && ContainsWildcards(origPath) {
|
if allowWildcards && containsWildcards(origPath) {
|
||||||
for _, fileInfo := range b.context.GetSums() {
|
for _, fileInfo := range b.context.GetSums() {
|
||||||
if fileInfo.Name() == "" {
|
if fileInfo.Name() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
match, _ := path.Match(origPath, fileInfo.Name())
|
match, _ := filepath.Match(origPath, fileInfo.Name())
|
||||||
if !match {
|
if !match {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -373,7 +420,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
if err := b.checkPathForAddition(origPath); err != nil {
|
if err := b.checkPathForAddition(origPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fi, _ := os.Stat(path.Join(b.contextPath, origPath))
|
fi, _ := os.Stat(filepath.Join(b.contextPath, origPath))
|
||||||
|
|
||||||
ci := copyInfo{}
|
ci := copyInfo{}
|
||||||
ci.origPath = origPath
|
ci.origPath = origPath
|
||||||
@@ -394,20 +441,20 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
|
|
||||||
// Must be a dir
|
// Must be a dir
|
||||||
var subfiles []string
|
var subfiles []string
|
||||||
absOrigPath := path.Join(b.contextPath, ci.origPath)
|
absOrigPath := filepath.Join(b.contextPath, ci.origPath)
|
||||||
|
|
||||||
// Add a trailing / to make sure we only pick up nested files under
|
// Add a trailing / to make sure we only pick up nested files under
|
||||||
// the dir and not sibling files of the dir that just happen to
|
// the dir and not sibling files of the dir that just happen to
|
||||||
// start with the same chars
|
// start with the same chars
|
||||||
if !strings.HasSuffix(absOrigPath, "/") {
|
if !strings.HasSuffix(absOrigPath, string(os.PathSeparator)) {
|
||||||
absOrigPath += "/"
|
absOrigPath += string(os.PathSeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need path w/o / too to find matching dir w/o trailing /
|
// Need path w/o slash too to find matching dir w/o trailing slash
|
||||||
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
|
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
|
||||||
|
|
||||||
for _, fileInfo := range b.context.GetSums() {
|
for _, fileInfo := range b.context.GetSums() {
|
||||||
absFile := path.Join(b.contextPath, fileInfo.Name())
|
absFile := filepath.Join(b.contextPath, fileInfo.Name())
|
||||||
// Any file in the context that starts with the given path will be
|
// Any file in the context that starts with the given path will be
|
||||||
// picked up and its hashcode used. However, we'll exclude the
|
// picked up and its hashcode used. However, we'll exclude the
|
||||||
// root dir itself. We do this for a coupel of reasons:
|
// root dir itself. We do this for a coupel of reasons:
|
||||||
@@ -428,7 +475,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContainsWildcards(name string) bool {
|
func containsWildcards(name string) bool {
|
||||||
for i := 0; i < len(name); i++ {
|
for i := 0; i < len(name); i++ {
|
||||||
ch := name[i]
|
ch := name[i]
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
@@ -440,21 +487,25 @@ func ContainsWildcards(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
func (b *builder) pullImage(name string) (*image.Image, error) {
|
||||||
remote, tag := parsers.ParseRepositoryTag(name)
|
remote, tag := parsers.ParseRepositoryTag(name)
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = "latest"
|
tag = "latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
pullRegistryAuth := b.AuthConfig
|
pullRegistryAuth := &cliconfig.AuthConfig{}
|
||||||
if len(b.ConfigFile.AuthConfigs) > 0 {
|
if len(b.AuthConfigs) > 0 {
|
||||||
// The request came with a full auth config file, we prefer to use that
|
// The request came with a full auth config file, we prefer to use that
|
||||||
repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote)
|
repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resolvedAuth := registry.ResolveAuthConfig(b.ConfigFile, repoInfo.Index)
|
|
||||||
pullRegistryAuth = &resolvedAuth
|
resolvedConfig := registry.ResolveAuthConfig(
|
||||||
|
&cliconfig.ConfigFile{AuthConfigs: b.AuthConfigs},
|
||||||
|
repoInfo.Index,
|
||||||
|
)
|
||||||
|
pullRegistryAuth = &resolvedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
imagePullConfig := &graph.ImagePullConfig{
|
imagePullConfig := &graph.ImagePullConfig{
|
||||||
@@ -474,14 +525,15 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
|||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) processImageFrom(img *imagepkg.Image) error {
|
func (b *builder) processImageFrom(img *image.Image) error {
|
||||||
b.image = img.ID
|
b.image = img.ID
|
||||||
|
|
||||||
if img.Config != nil {
|
if img.Config != nil {
|
||||||
b.Config = img.Config
|
b.Config = img.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b.Config.Env) == 0 {
|
// The default path will be blank on Windows (set by HCS)
|
||||||
|
if len(b.Config.Env) == 0 && daemon.DefaultPathEnv != "" {
|
||||||
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
|
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +577,7 @@ func (b *Builder) processImageFrom(img *imagepkg.Image) error {
|
|||||||
// in the current server `b.Daemon`. If an image is found, probeCache returns
|
// in the current server `b.Daemon`. If an image is found, probeCache returns
|
||||||
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
||||||
// is any error, it returns `(false, err)`.
|
// is any error, it returns `(false, err)`.
|
||||||
func (b *Builder) probeCache() (bool, error) {
|
func (b *builder) probeCache() (bool, error) {
|
||||||
if !b.UtilizeCache || b.cacheBusted {
|
if !b.UtilizeCache || b.cacheBusted {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -543,10 +595,12 @@ func (b *Builder) probeCache() (bool, error) {
|
|||||||
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
|
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
|
||||||
logrus.Debugf("[BUILDER] Use cached version")
|
logrus.Debugf("[BUILDER] Use cached version")
|
||||||
b.image = cache.ID
|
b.image = cache.ID
|
||||||
|
b.Daemon.Graph().Retain(b.id, cache.ID)
|
||||||
|
b.activeImages = append(b.activeImages, cache.ID)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) create() (*daemon.Container, error) {
|
func (b *builder) create() (*daemon.Container, error) {
|
||||||
if b.image == "" && !b.noBaseImage {
|
if b.image == "" && !b.noBaseImage {
|
||||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||||
}
|
}
|
||||||
@@ -561,7 +615,7 @@ func (b *Builder) create() (*daemon.Container, error) {
|
|||||||
CgroupParent: b.cgroupParent,
|
CgroupParent: b.cgroupParent,
|
||||||
Memory: b.memory,
|
Memory: b.memory,
|
||||||
MemorySwap: b.memorySwap,
|
MemorySwap: b.memorySwap,
|
||||||
NetworkMode: "bridge",
|
Ulimits: b.ulimits,
|
||||||
}
|
}
|
||||||
|
|
||||||
config := *b.Config
|
config := *b.Config
|
||||||
@@ -590,7 +644,7 @@ func (b *Builder) create() (*daemon.Container, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) run(c *daemon.Container) error {
|
func (b *builder) run(c *daemon.Container) error {
|
||||||
var errCh chan error
|
var errCh chan error
|
||||||
if b.Verbose {
|
if b.Verbose {
|
||||||
errCh = c.Attach(nil, b.OutStream, b.ErrStream)
|
errCh = c.Attach(nil, b.OutStream, b.ErrStream)
|
||||||
@@ -630,8 +684,8 @@ func (b *Builder) run(c *daemon.Container) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) checkPathForAddition(orig string) error {
|
func (b *builder) checkPathForAddition(orig string) error {
|
||||||
origPath := path.Join(b.contextPath, orig)
|
origPath := filepath.Join(b.contextPath, orig)
|
||||||
origPath, err := filepath.EvalSymlinks(origPath)
|
origPath, err := filepath.EvalSymlinks(origPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -639,7 +693,11 @@ func (b *Builder) checkPathForAddition(orig string) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(origPath, b.contextPath) {
|
contextPath, err := filepath.EvalSymlinks(b.contextPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(origPath, contextPath) {
|
||||||
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(origPath); err != nil {
|
if _, err := os.Stat(origPath); err != nil {
|
||||||
@@ -651,27 +709,31 @@ func (b *Builder) checkPathForAddition(orig string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
func (b *builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
destExists = true
|
destExists = true
|
||||||
origPath = path.Join(b.contextPath, orig)
|
origPath = filepath.Join(b.contextPath, orig)
|
||||||
destPath string
|
destPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Work in daemon-local OS specific file paths
|
||||||
|
dest = filepath.FromSlash(dest)
|
||||||
|
|
||||||
destPath, err = container.GetResourcePath(dest)
|
destPath, err = container.GetResourcePath(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve the trailing '/'
|
// Preserve the trailing slash
|
||||||
if strings.HasSuffix(dest, "/") || dest == "." {
|
if strings.HasSuffix(dest, string(os.PathSeparator)) || dest == "." {
|
||||||
destPath = destPath + "/"
|
destPath = destPath + string(os.PathSeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
destStat, err := os.Stat(destPath)
|
destStat, err := os.Stat(destPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
|
logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
destExists = false
|
destExists = false
|
||||||
@@ -694,9 +756,9 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec
|
|||||||
// First try to unpack the source as an archive
|
// First try to unpack the source as an archive
|
||||||
// to support the untar feature we need to clean up the path a little bit
|
// to support the untar feature we need to clean up the path a little bit
|
||||||
// because tar is very forgiving. First we need to strip off the archive's
|
// because tar is very forgiving. First we need to strip off the archive's
|
||||||
// filename from the path but this is only added if it does not end in / .
|
// filename from the path but this is only added if it does not end in slash
|
||||||
tarDest := destPath
|
tarDest := destPath
|
||||||
if strings.HasSuffix(tarDest, "/") {
|
if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
|
||||||
tarDest = filepath.Dir(destPath)
|
tarDest = filepath.Dir(destPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +770,7 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
if err := system.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := chrootarchive.CopyWithTar(origPath, destPath); err != nil {
|
if err := chrootarchive.CopyWithTar(origPath, destPath); err != nil {
|
||||||
@@ -717,7 +779,7 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec
|
|||||||
|
|
||||||
resPath := destPath
|
resPath := destPath
|
||||||
if destExists && destStat.IsDir() {
|
if destExists && destStat.IsDir() {
|
||||||
resPath = path.Join(destPath, path.Base(origPath))
|
resPath = filepath.Join(destPath, filepath.Base(origPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixPermissions(origPath, resPath, 0, 0, destExists)
|
return fixPermissions(origPath, resPath, 0, 0, destExists)
|
||||||
@@ -730,39 +792,7 @@ func copyAsDirectory(source, destination string, destExisted bool) error {
|
|||||||
return fixPermissions(source, destination, 0, 0, destExisted)
|
return fixPermissions(source, destination, 0, 0, destExisted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
func (b *builder) clearTmp() {
|
||||||
// If the destination didn't already exist, or the destination isn't a
|
|
||||||
// directory, then we should Lchown the destination. Otherwise, we shouldn't
|
|
||||||
// Lchown the destination.
|
|
||||||
destStat, err := os.Stat(destination)
|
|
||||||
if err != nil {
|
|
||||||
// This should *never* be reached, because the destination must've already
|
|
||||||
// been created while untar-ing the context.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
doChownDestination := !destExisted || !destStat.IsDir()
|
|
||||||
|
|
||||||
// We Walk on the source rather than on the destination because we don't
|
|
||||||
// want to change permissions on things we haven't created or modified.
|
|
||||||
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
|
|
||||||
// Do not alter the walk root iff. it existed before, as it doesn't fall under
|
|
||||||
// the domain of "things we should chown".
|
|
||||||
if !doChownDestination && (source == fullpath) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path is prefixed by source: substitute with destination instead.
|
|
||||||
cleaned, err := filepath.Rel(source, fullpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fullpath = path.Join(destination, cleaned)
|
|
||||||
return os.Lchown(fullpath, uid, gid)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) clearTmp() {
|
|
||||||
for c := range b.TmpContainers {
|
for c := range b.TmpContainers {
|
||||||
rmConfig := &daemon.ContainerRmConfig{
|
rmConfig := &daemon.ContainerRmConfig{
|
||||||
ForceRemove: true,
|
ForceRemove: true,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||||
|
// If the destination didn't already exist, or the destination isn't a
|
||||||
|
// directory, then we should Lchown the destination. Otherwise, we shouldn't
|
||||||
|
// Lchown the destination.
|
||||||
|
destStat, err := os.Stat(destination)
|
||||||
|
if err != nil {
|
||||||
|
// This should *never* be reached, because the destination must've already
|
||||||
|
// been created while untar-ing the context.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
doChownDestination := !destExisted || !destStat.IsDir()
|
||||||
|
|
||||||
|
// We Walk on the source rather than on the destination because we don't
|
||||||
|
// want to change permissions on things we haven't created or modified.
|
||||||
|
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
|
||||||
|
// Do not alter the walk root iff. it existed before, as it doesn't fall under
|
||||||
|
// the domain of "things we should chown".
|
||||||
|
if !doChownDestination && (source == fullpath) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path is prefixed by source: substitute with destination instead.
|
||||||
|
cleaned, err := filepath.Rel(source, fullpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullpath = filepath.Join(destination, cleaned)
|
||||||
|
return os.Lchown(fullpath, uid, gid)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package builder
|
||||||
|
|
||||||
|
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||||
|
// chown is not supported on Windows
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+157
-41
@@ -2,6 +2,7 @@ package builder
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -17,25 +18,34 @@ import (
|
|||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"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/stringid"
|
||||||
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
"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/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// When downloading remote contexts, limit the amount (in bytes)
|
||||||
|
// to be read from the response body in order to detect its Content-Type
|
||||||
|
const maxPreambleLength = 100
|
||||||
|
|
||||||
// whitelist of commands allowed for a commit/import
|
// whitelist of commands allowed for a commit/import
|
||||||
var validCommitCommands = map[string]bool{
|
var validCommitCommands = map[string]bool{
|
||||||
"entrypoint": true,
|
|
||||||
"cmd": true,
|
"cmd": true,
|
||||||
"user": true,
|
"entrypoint": true,
|
||||||
"workdir": true,
|
|
||||||
"env": true,
|
"env": true,
|
||||||
"volume": true,
|
|
||||||
"expose": true,
|
"expose": true,
|
||||||
|
"label": true,
|
||||||
"onbuild": true,
|
"onbuild": true,
|
||||||
|
"user": true,
|
||||||
|
"volume": true,
|
||||||
|
"workdir": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config contains all configs for a build job
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DockerfileName string
|
DockerfileName string
|
||||||
RemoteURL string
|
RemoteURL string
|
||||||
@@ -47,14 +57,14 @@ type Config struct {
|
|||||||
Pull bool
|
Pull bool
|
||||||
Memory int64
|
Memory int64
|
||||||
MemorySwap int64
|
MemorySwap int64
|
||||||
CpuShares int64
|
CPUShares int64
|
||||||
CpuPeriod int64
|
CPUPeriod int64
|
||||||
CpuQuota int64
|
CPUQuota int64
|
||||||
CpuSetCpus string
|
CPUSetCpus string
|
||||||
CpuSetMems string
|
CPUSetMems string
|
||||||
CgroupParent string
|
CgroupParent string
|
||||||
AuthConfig *cliconfig.AuthConfig
|
Ulimits []*ulimit.Ulimit
|
||||||
ConfigFile *cliconfig.ConfigFile
|
AuthConfigs map[string]cliconfig.AuthConfig
|
||||||
|
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Context io.ReadCloser
|
Context io.ReadCloser
|
||||||
@@ -65,32 +75,36 @@ type Config struct {
|
|||||||
cancelOnce sync.Once
|
cancelOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// When called, causes the Job.WaitCancelled channel to unblock.
|
// Cancel signals the build job to cancel
|
||||||
func (b *Config) Cancel() {
|
func (b *Config) Cancel() {
|
||||||
b.cancelOnce.Do(func() {
|
b.cancelOnce.Do(func() {
|
||||||
close(b.cancelled)
|
close(b.cancelled)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a channel which is closed ("never blocks") when the job is cancelled.
|
// WaitCancelled returns a channel which is closed ("never blocks") when
|
||||||
|
// the job is cancelled.
|
||||||
func (b *Config) WaitCancelled() <-chan struct{} {
|
func (b *Config) WaitCancelled() <-chan struct{} {
|
||||||
return b.cancelled
|
return b.cancelled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewBuildConfig returns a new Config struct
|
||||||
func NewBuildConfig() *Config {
|
func NewBuildConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
AuthConfig: &cliconfig.AuthConfig{},
|
AuthConfigs: map[string]cliconfig.AuthConfig{},
|
||||||
ConfigFile: &cliconfig.ConfigFile{},
|
cancelled: make(chan struct{}),
|
||||||
cancelled: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build is the main interface of the package, it gathers the Builder
|
||||||
|
// struct and calls builder.Run() to do all the real build job.
|
||||||
func Build(d *daemon.Daemon, buildConfig *Config) error {
|
func Build(d *daemon.Daemon, buildConfig *Config) error {
|
||||||
var (
|
var (
|
||||||
repoName string
|
repoName string
|
||||||
tag string
|
tag string
|
||||||
context io.ReadCloser
|
context io.ReadCloser
|
||||||
)
|
)
|
||||||
|
sf := streamformatter.NewJSONStreamFormatter()
|
||||||
|
|
||||||
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
|
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
|
||||||
if repoName != "" {
|
if repoName != "" {
|
||||||
@@ -121,29 +135,52 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
|||||||
} else if urlutil.IsURL(buildConfig.RemoteURL) {
|
} else if urlutil.IsURL(buildConfig.RemoteURL) {
|
||||||
f, err := httputils.Download(buildConfig.RemoteURL)
|
f, err := httputils.Download(buildConfig.RemoteURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Error downloading remote context %s: %v", buildConfig.RemoteURL, err)
|
||||||
}
|
}
|
||||||
defer f.Body.Close()
|
defer f.Body.Close()
|
||||||
dockerFile, err := ioutil.ReadAll(f.Body)
|
ct := f.Header.Get("Content-Type")
|
||||||
if err != nil {
|
clen := int(f.ContentLength)
|
||||||
return err
|
contentType, bodyReader, err := inspectResponse(ct, f.Body, clen)
|
||||||
}
|
|
||||||
|
|
||||||
// When we're downloading just a Dockerfile put it in
|
defer bodyReader.Close()
|
||||||
// the default name - don't allow the client to move/specify it
|
|
||||||
buildConfig.DockerfileName = api.DefaultDockerfileName
|
|
||||||
|
|
||||||
c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Error detecting content type for remote %s: %v", buildConfig.RemoteURL, err)
|
||||||
|
}
|
||||||
|
if contentType == httputils.MimeTypes.TextPlain {
|
||||||
|
dockerFile, err := ioutil.ReadAll(bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we're downloading just a Dockerfile put it in
|
||||||
|
// the default name - don't allow the client to move/specify it
|
||||||
|
buildConfig.DockerfileName = api.DefaultDockerfileName
|
||||||
|
|
||||||
|
c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
context = c
|
||||||
|
} else {
|
||||||
|
// Pass through - this is a pre-packaged context, presumably
|
||||||
|
// with a Dockerfile with the right name inside it.
|
||||||
|
prCfg := progressreader.Config{
|
||||||
|
In: bodyReader,
|
||||||
|
Out: buildConfig.Stdout,
|
||||||
|
Formatter: sf,
|
||||||
|
Size: clen,
|
||||||
|
NewLines: true,
|
||||||
|
ID: "Downloading context",
|
||||||
|
Action: buildConfig.RemoteURL,
|
||||||
|
}
|
||||||
|
context = progressreader.New(prCfg)
|
||||||
}
|
}
|
||||||
context = c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer context.Close()
|
defer context.Close()
|
||||||
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
builder := &builder{
|
||||||
|
|
||||||
builder := &Builder{
|
|
||||||
Daemon: d,
|
Daemon: d,
|
||||||
OutStream: &streamformatter.StdoutFormater{
|
OutStream: &streamformatter.StdoutFormater{
|
||||||
Writer: buildConfig.Stdout,
|
Writer: buildConfig.Stdout,
|
||||||
@@ -160,31 +197,40 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
|||||||
Pull: buildConfig.Pull,
|
Pull: buildConfig.Pull,
|
||||||
OutOld: buildConfig.Stdout,
|
OutOld: buildConfig.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
AuthConfig: buildConfig.AuthConfig,
|
AuthConfigs: buildConfig.AuthConfigs,
|
||||||
ConfigFile: buildConfig.ConfigFile,
|
|
||||||
dockerfileName: buildConfig.DockerfileName,
|
dockerfileName: buildConfig.DockerfileName,
|
||||||
cpuShares: buildConfig.CpuShares,
|
cpuShares: buildConfig.CPUShares,
|
||||||
cpuPeriod: buildConfig.CpuPeriod,
|
cpuPeriod: buildConfig.CPUPeriod,
|
||||||
cpuQuota: buildConfig.CpuQuota,
|
cpuQuota: buildConfig.CPUQuota,
|
||||||
cpuSetCpus: buildConfig.CpuSetCpus,
|
cpuSetCpus: buildConfig.CPUSetCpus,
|
||||||
cpuSetMems: buildConfig.CpuSetMems,
|
cpuSetMems: buildConfig.CPUSetMems,
|
||||||
cgroupParent: buildConfig.CgroupParent,
|
cgroupParent: buildConfig.CgroupParent,
|
||||||
memory: buildConfig.Memory,
|
memory: buildConfig.Memory,
|
||||||
memorySwap: buildConfig.MemorySwap,
|
memorySwap: buildConfig.MemorySwap,
|
||||||
|
ulimits: buildConfig.Ulimits,
|
||||||
cancelled: buildConfig.WaitCancelled(),
|
cancelled: buildConfig.WaitCancelled(),
|
||||||
|
id: stringid.GenerateRandomID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
builder.Daemon.Graph().Release(builder.id, builder.activeImages...)
|
||||||
|
}()
|
||||||
|
|
||||||
id, err := builder.Run(context)
|
id, err := builder.Run(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if repoName != "" {
|
if repoName != "" {
|
||||||
return d.Repositories().Tag(repoName, tag, id, true)
|
return d.Repositories().Tag(repoName, tag, id, true)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
|
func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
|
||||||
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -198,7 +244,7 @@ func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder := &Builder{
|
builder := &builder{
|
||||||
Daemon: d,
|
Daemon: d,
|
||||||
Config: c,
|
Config: c,
|
||||||
OutStream: ioutil.Discard,
|
OutStream: ioutil.Discard,
|
||||||
@@ -215,12 +261,28 @@ func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*
|
|||||||
return builder.Config, nil
|
return builder.Config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (string, error) {
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit will create a new image from a container's changes
|
||||||
|
func Commit(name string, d *daemon.Daemon, c *CommitConfig) (string, error) {
|
||||||
container, err := d.Get(name)
|
container, err := d.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Config == nil {
|
||||||
|
c.Config = &runconfig.Config{}
|
||||||
|
}
|
||||||
|
|
||||||
newConfig, err := BuildFromConfig(d, c.Config, c.Changes)
|
newConfig, err := BuildFromConfig(d, c.Config, c.Changes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -230,10 +292,64 @@ func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (str
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := d.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, newConfig)
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return img.ID, nil
|
return img.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inspectResponse looks into the http response data at r to determine whether its
|
||||||
|
// content-type is on the list of acceptable content types for remote build contexts.
|
||||||
|
// This function returns:
|
||||||
|
// - a string representation of the detected content-type
|
||||||
|
// - an io.Reader for the response body
|
||||||
|
// - an error value which will be non-nil either when something goes wrong while
|
||||||
|
// reading bytes from r or when the detected content-type is not acceptable.
|
||||||
|
func inspectResponse(ct string, r io.ReadCloser, clen int) (string, io.ReadCloser, error) {
|
||||||
|
plen := clen
|
||||||
|
if plen <= 0 || plen > maxPreambleLength {
|
||||||
|
plen = maxPreambleLength
|
||||||
|
}
|
||||||
|
|
||||||
|
preamble := make([]byte, plen, plen)
|
||||||
|
rlen, err := r.Read(preamble)
|
||||||
|
if rlen == 0 {
|
||||||
|
return ct, r, errors.New("Empty response")
|
||||||
|
}
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return ct, r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
preambleR := bytes.NewReader(preamble)
|
||||||
|
bodyReader := ioutil.NopCloser(io.MultiReader(preambleR, r))
|
||||||
|
// Some web servers will use application/octet-stream as the default
|
||||||
|
// content type for files without an extension (e.g. 'Dockerfile')
|
||||||
|
// so if we receive this value we better check for text content
|
||||||
|
contentType := ct
|
||||||
|
if len(ct) == 0 || ct == httputils.MimeTypes.OctetStream {
|
||||||
|
contentType, _, err = httputils.DetectContentType(preamble)
|
||||||
|
if err != nil {
|
||||||
|
return contentType, bodyReader, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType = selectAcceptableMIME(contentType)
|
||||||
|
var cterr error
|
||||||
|
if len(contentType) == 0 {
|
||||||
|
cterr = fmt.Errorf("unsupported Content-Type %q", ct)
|
||||||
|
contentType = ct
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentType, bodyReader, cterr
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var textPlainDockerfile = "FROM busybox"
|
||||||
|
var binaryContext = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} //xz magic
|
||||||
|
|
||||||
|
func TestInspectEmptyResponse(t *testing.T) {
|
||||||
|
ct := "application/octet-stream"
|
||||||
|
br := ioutil.NopCloser(bytes.NewReader([]byte("")))
|
||||||
|
contentType, bReader, err := inspectResponse(ct, br, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Should have generated an error for an empty response")
|
||||||
|
}
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
t.Fatalf("Content type should be 'application/octet-stream' but is %q", contentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(bReader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(body) != 0 {
|
||||||
|
t.Fatal("response body should remain empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInspectResponseBinary(t *testing.T) {
|
||||||
|
ct := "application/octet-stream"
|
||||||
|
br := ioutil.NopCloser(bytes.NewReader(binaryContext))
|
||||||
|
contentType, bReader, err := inspectResponse(ct, br, len(binaryContext))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
t.Fatalf("Content type should be 'application/octet-stream' but is %q", contentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(bReader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(body) != len(binaryContext) {
|
||||||
|
t.Fatalf("Wrong response size %d, should be == len(binaryContext)", len(body))
|
||||||
|
}
|
||||||
|
for i := range body {
|
||||||
|
if body[i] != binaryContext[i] {
|
||||||
|
t.Fatalf("Corrupted response body at byte index %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseUnsupportedContentType(t *testing.T) {
|
||||||
|
content := []byte(textPlainDockerfile)
|
||||||
|
ct := "application/json"
|
||||||
|
br := ioutil.NopCloser(bytes.NewReader(content))
|
||||||
|
contentType, bReader, err := inspectResponse(ct, br, len(textPlainDockerfile))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should have returned an error on content-type 'application/json'")
|
||||||
|
}
|
||||||
|
if contentType != ct {
|
||||||
|
t.Fatalf("Should not have altered content-type: orig: %s, altered: %s", ct, contentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(bReader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(body) != textPlainDockerfile {
|
||||||
|
t.Fatalf("Corrupted response body %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInspectResponseTextSimple(t *testing.T) {
|
||||||
|
content := []byte(textPlainDockerfile)
|
||||||
|
ct := "text/plain"
|
||||||
|
br := ioutil.NopCloser(bytes.NewReader(content))
|
||||||
|
contentType, bReader, err := inspectResponse(ct, br, len(content))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if contentType != "text/plain" {
|
||||||
|
t.Fatalf("Content type should be 'text/plain' but is %q", contentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(bReader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(body) != textPlainDockerfile {
|
||||||
|
t.Fatalf("Corrupted response body %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInspectResponseEmptyContentType(t *testing.T) {
|
||||||
|
content := []byte(textPlainDockerfile)
|
||||||
|
br := ioutil.NopCloser(bytes.NewReader(content))
|
||||||
|
contentType, bodyReader, err := inspectResponse("", br, len(content))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if contentType != "text/plain" {
|
||||||
|
t.Fatalf("Content type should be 'text/plain' but is %q", contentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(body) != textPlainDockerfile {
|
||||||
|
t.Fatalf("Corrupted response body %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -151,7 +151,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
if !strings.Contains(words[0], "=") {
|
if !strings.Contains(words[0], "=") {
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
rootnode = node
|
rootnode = node
|
||||||
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
strs := tokenWhitespace.Split(rest, 2)
|
||||||
|
|
||||||
if len(strs) < 2 {
|
if len(strs) < 2 {
|
||||||
return nil, nil, fmt.Errorf(key + " must have two arguments")
|
return nil, nil, fmt.Errorf(key + " must have two arguments")
|
||||||
@@ -205,7 +205,7 @@ func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error
|
|||||||
node := &Node{}
|
node := &Node{}
|
||||||
rootnode := node
|
rootnode := node
|
||||||
prevnode := node
|
prevnode := node
|
||||||
for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
|
for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp
|
||||||
prevnode = node
|
prevnode = node
|
||||||
node.Value = str
|
node.Value = str
|
||||||
node.Next = &Node{}
|
node.Next = &Node{}
|
||||||
@@ -232,13 +232,13 @@ func parseString(rest string) (*Node, map[string]bool, error) {
|
|||||||
|
|
||||||
// parseJSON converts JSON arrays to an AST.
|
// parseJSON converts JSON arrays to an AST.
|
||||||
func parseJSON(rest string) (*Node, map[string]bool, error) {
|
func parseJSON(rest string) (*Node, map[string]bool, error) {
|
||||||
var myJson []interface{}
|
var myJSON []interface{}
|
||||||
if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJson); err != nil {
|
if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var top, prev *Node
|
var top, prev *Node
|
||||||
for _, str := range myJson {
|
for _, str := range myJSON {
|
||||||
s, ok := str.(string)
|
s, ok := str.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, errDockerfileNotStringArray
|
return nil, nil, errDockerfileNotStringArray
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// This package implements a parser and parse tree dumper for Dockerfiles.
|
// Package parser implements a parser and parse tree dumper for Dockerfiles.
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -33,10 +33,10 @@ type Node struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
||||||
TOKEN_WHITESPACE = regexp.MustCompile(`[\t\v\f\r ]+`)
|
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
|
||||||
TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
|
tokenLineContinuation = regexp.MustCompile(`\\[ \t]*$`)
|
||||||
TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
|
tokenComment = regexp.MustCompile(`^#.*$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -70,8 +70,8 @@ func parseLine(line string) (string, *Node, error) {
|
|||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if TOKEN_LINE_CONTINUATION.MatchString(line) {
|
if tokenLineContinuation.MatchString(line) {
|
||||||
line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
|
line = tokenLineContinuation.ReplaceAllString(line, "")
|
||||||
return line, nil, nil
|
return line, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +96,8 @@ func parseLine(line string) (string, *Node, error) {
|
|||||||
return "", node, nil
|
return "", node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The main parse routine. Handles an io.ReadWriteCloser and returns the root
|
// Parse is the main parse routine.
|
||||||
// of the AST.
|
// It handles an io.ReadWriteCloser and returns the root of the AST.
|
||||||
func Parse(rwc io.Reader) (*Node, error) {
|
func Parse(rwc io.Reader) (*Node, error) {
|
||||||
root := &Node{}
|
root := &Node{}
|
||||||
scanner := bufio.NewScanner(rwc)
|
scanner := bufio.NewScanner(rwc)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||||
# docker hack/release.sh
|
# docker hack/release.sh
|
||||||
#
|
#
|
||||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
# Note: AppArmor used to mess with privileged mode, but this is no longer
|
||||||
# the case. Therefore, you don't have to disable it anymore.
|
# the case. Therefore, you don't have to disable it anymore.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dumps the AST defined by `node` as a list of sexps. Returns a string
|
// Dump dumps the AST defined by `node` as a list of sexps.
|
||||||
// suitable for printing.
|
// Returns a string suitable for printing.
|
||||||
func (node *Node) Dump() string {
|
func (node *Node) Dump() string {
|
||||||
str := ""
|
str := ""
|
||||||
str += node.Value
|
str += node.Value
|
||||||
@@ -59,7 +59,7 @@ func splitCommand(line string) (string, []string, string, error) {
|
|||||||
var flags []string
|
var flags []string
|
||||||
|
|
||||||
// Make sure we get the same results irrespective of leading/trailing spaces
|
// Make sure we get the same results irrespective of leading/trailing spaces
|
||||||
cmdline := TOKEN_WHITESPACE.Split(strings.TrimSpace(line), 2)
|
cmdline := tokenWhitespace.Split(strings.TrimSpace(line), 2)
|
||||||
cmd := strings.ToLower(cmdline[0])
|
cmd := strings.ToLower(cmdline[0])
|
||||||
|
|
||||||
if len(cmdline) == 2 {
|
if len(cmdline) == 2 {
|
||||||
@@ -77,8 +77,8 @@ func splitCommand(line string) (string, []string, string, error) {
|
|||||||
// this function.
|
// this function.
|
||||||
func stripComments(line string) string {
|
func stripComments(line string) string {
|
||||||
// string is already trimmed at this point
|
// string is already trimmed at this point
|
||||||
if TOKEN_COMMENT.MatchString(line) {
|
if tokenComment.MatchString(line) {
|
||||||
return TOKEN_COMMENT.ReplaceAllString(line, "")
|
return tokenComment.ReplaceAllString(line, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return line
|
return line
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ type shellWord struct {
|
|||||||
pos int
|
pos int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessWord will use the 'env' list of environment variables,
|
||||||
|
// and replace any env var references in 'word'.
|
||||||
func ProcessWord(word string, env []string) (string, error) {
|
func ProcessWord(word string, env []string) (string, error) {
|
||||||
sw := &shellWord{
|
sw := &shellWord{
|
||||||
word: word,
|
word: word,
|
||||||
|
|||||||
+10
-1
@@ -1,10 +1,19 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
const acceptableRemoteMIME = `(?:application/(?:(?:x\-)?tar|octet\-stream|((?:x\-)?(?:gzip|bzip2?|xz)))|(?:text/plain))`
|
||||||
|
|
||||||
|
var mimeRe = regexp.MustCompile(acceptableRemoteMIME)
|
||||||
|
|
||||||
|
func selectAcceptableMIME(ct string) string {
|
||||||
|
return mimeRe.FindString(ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleJSONArgs(args []string, attributes map[string]bool) []string {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSelectAcceptableMIME(t *testing.T) {
|
||||||
|
validMimeStrings := []string{
|
||||||
|
"application/x-bzip2",
|
||||||
|
"application/bzip2",
|
||||||
|
"application/gzip",
|
||||||
|
"application/x-gzip",
|
||||||
|
"application/x-xz",
|
||||||
|
"application/xz",
|
||||||
|
"application/tar",
|
||||||
|
"application/x-tar",
|
||||||
|
"application/octet-stream",
|
||||||
|
"text/plain",
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidMimeStrings := []string{
|
||||||
|
"",
|
||||||
|
"application/octet",
|
||||||
|
"application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range invalidMimeStrings {
|
||||||
|
if len(selectAcceptableMIME(m)) > 0 {
|
||||||
|
err := fmt.Errorf("Should not have accepted %q", m)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range validMimeStrings {
|
||||||
|
if str := selectAcceptableMIME(m); str == "" {
|
||||||
|
err := fmt.Errorf("Should have accepted %q", m)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+200
@@ -0,0 +1,200 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cli represents a command line interface.
|
||||||
|
type Cli struct {
|
||||||
|
Stderr io.Writer
|
||||||
|
handlers []Handler
|
||||||
|
Usage func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler holds the different commands Cli will call
|
||||||
|
// It should have methods with names starting with `Cmd` like:
|
||||||
|
// func (h myHandler) CmdFoo(args ...string) error
|
||||||
|
type Handler interface{}
|
||||||
|
|
||||||
|
// Initializer can be optionally implemented by a Handler to
|
||||||
|
// initialize before each call to one of its commands.
|
||||||
|
type Initializer interface {
|
||||||
|
Initialize() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New instantiates a ready-to-use Cli.
|
||||||
|
func New(handlers ...Handler) *Cli {
|
||||||
|
// make the generic Cli object the first cli handler
|
||||||
|
// in order to handle `docker help` appropriately
|
||||||
|
cli := new(Cli)
|
||||||
|
cli.handlers = append([]Handler{cli}, handlers...)
|
||||||
|
return cli
|
||||||
|
}
|
||||||
|
|
||||||
|
// initErr is an error returned upon initialization of a handler implementing Initializer.
|
||||||
|
type initErr struct{ error }
|
||||||
|
|
||||||
|
func (err initErr) Error() string {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Cli) command(args ...string) (func(...string) error, error) {
|
||||||
|
for _, c := range cli.handlers {
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
camelArgs := make([]string, len(args))
|
||||||
|
for i, s := range args {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil, errors.New("empty command")
|
||||||
|
}
|
||||||
|
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||||
|
}
|
||||||
|
methodName := "Cmd" + strings.Join(camelArgs, "")
|
||||||
|
method := reflect.ValueOf(c).MethodByName(methodName)
|
||||||
|
if method.IsValid() {
|
||||||
|
if c, ok := c.(Initializer); ok {
|
||||||
|
if err := c.Initialize(); err != nil {
|
||||||
|
return nil, initErr{err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method.Interface().(func(...string) error), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("command not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the specified command.
|
||||||
|
func (cli *Cli) Run(args ...string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
command, err := cli.command(args[:2]...)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
return command(args[2:]...)
|
||||||
|
case initErr:
|
||||||
|
return err.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
command, err := cli.command(args[0])
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
return command(args[1:]...)
|
||||||
|
case initErr:
|
||||||
|
return err.error
|
||||||
|
}
|
||||||
|
cli.noSuchCommand(args[0])
|
||||||
|
}
|
||||||
|
return cli.CmdHelp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Cli) noSuchCommand(command string) {
|
||||||
|
if cli.Stderr == nil {
|
||||||
|
cli.Stderr = os.Stderr
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.Stderr, "docker: '%s' is not a docker command.\nSee 'docker --help'.\n", command)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdHelp displays information on a Docker command.
|
||||||
|
//
|
||||||
|
// If more than one command is specified, information is only shown for the first command.
|
||||||
|
//
|
||||||
|
// Usage: docker help COMMAND or docker COMMAND --help
|
||||||
|
func (cli *Cli) CmdHelp(args ...string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
command, err := cli.command(args[:2]...)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
command("--help")
|
||||||
|
return nil
|
||||||
|
case initErr:
|
||||||
|
return err.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
command, err := cli.command(args[0])
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
command("--help")
|
||||||
|
return nil
|
||||||
|
case initErr:
|
||||||
|
return err.error
|
||||||
|
}
|
||||||
|
cli.noSuchCommand(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.Usage == nil {
|
||||||
|
flag.Usage()
|
||||||
|
} else {
|
||||||
|
cli.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subcmd is a subcommand of the main "docker" command.
|
||||||
|
// A subcommand represents an action that can be performed
|
||||||
|
// from the Docker command line client.
|
||||||
|
//
|
||||||
|
// To see all available subcommands, run "docker --help".
|
||||||
|
func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet {
|
||||||
|
var errorHandling flag.ErrorHandling
|
||||||
|
if exitOnError {
|
||||||
|
errorHandling = flag.ExitOnError
|
||||||
|
} else {
|
||||||
|
errorHandling = flag.ContinueOnError
|
||||||
|
}
|
||||||
|
flags := flag.NewFlagSet(name, errorHandling)
|
||||||
|
flags.Usage = func() {
|
||||||
|
flags.ShortUsage()
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.ShortUsage = func() {
|
||||||
|
options := ""
|
||||||
|
if flags.FlagCountUndeprecated() > 0 {
|
||||||
|
options = " [OPTIONS]"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(synopses) == 0 {
|
||||||
|
synopses = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for multiple command usage synopses.
|
||||||
|
for i, synopsis := range synopses {
|
||||||
|
lead := "\t"
|
||||||
|
if i == 0 {
|
||||||
|
// First line needs the word 'Usage'.
|
||||||
|
lead = "Usage:\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
if synopsis != "" {
|
||||||
|
synopsis = " " + synopsis
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(flags.Out(), "\n%sdocker %s%s%s", lead, name, options, synopsis)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(flags.Out(), "\n\n%s\n", description)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// An StatusError reports an unsuccessful exit by a command.
|
||||||
|
type StatusError struct {
|
||||||
|
Status string
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e StatusError) Error() string {
|
||||||
|
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
|
||||||
|
// ClientFlags represents flags for the docker client.
|
||||||
|
type ClientFlags struct {
|
||||||
|
FlagSet *flag.FlagSet
|
||||||
|
Common *CommonFlags
|
||||||
|
PostParse func()
|
||||||
|
|
||||||
|
ConfigDir string
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/tlsconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommonFlags represents flags that are common to both the client and the daemon.
|
||||||
|
type CommonFlags struct {
|
||||||
|
FlagSet *flag.FlagSet
|
||||||
|
PostParse func()
|
||||||
|
|
||||||
|
Debug bool
|
||||||
|
Hosts []string
|
||||||
|
LogLevel string
|
||||||
|
TLS bool
|
||||||
|
TLSVerify bool
|
||||||
|
TLSOptions *tlsconfig.Options
|
||||||
|
TrustKey string
|
||||||
|
}
|
||||||
+43
-23
@@ -3,7 +3,6 @@ package cliconfig
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,24 +10,41 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/homedir"
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Where we store the config file
|
// ConfigFile is the name of config file
|
||||||
CONFIGFILE = "config.json"
|
ConfigFileName = "config.json"
|
||||||
OLD_CONFIGFILE = ".dockercfg"
|
oldConfigfile = ".dockercfg"
|
||||||
|
|
||||||
// This constant is only used for really old config files when the
|
// This constant is only used for really old config files when the
|
||||||
// URL wasn't saved as part of the config file and it was just
|
// URL wasn't saved as part of the config file and it was just
|
||||||
// assumed to be this value.
|
// assumed to be this value.
|
||||||
DEFAULT_INDEXSERVER = "https://index.docker.io/v1/"
|
defaultIndexserver = "https://index.docker.io/v1/"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
configDir = os.Getenv("DOCKER_CONFIG")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Registry Auth Info
|
func init() {
|
||||||
|
if configDir == "" {
|
||||||
|
configDir = filepath.Join(homedir.Get(), ".docker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigDir returns the directory the configuration file is stored in
|
||||||
|
func ConfigDir() string {
|
||||||
|
return configDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigDir sets the directory the configuration file is stored in
|
||||||
|
func SetConfigDir(dir string) {
|
||||||
|
configDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthConfig contains authorization information for connecting to a Registry
|
||||||
type AuthConfig struct {
|
type AuthConfig struct {
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
@@ -37,31 +53,34 @@ type AuthConfig struct {
|
|||||||
ServerAddress string `json:"serveraddress,omitempty"`
|
ServerAddress string `json:"serveraddress,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ~/.docker/config.json file info
|
// ConfigFile ~/.docker/config.json file info
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
AuthConfigs map[string]AuthConfig `json:"auths"`
|
AuthConfigs map[string]AuthConfig `json:"auths"`
|
||||||
HttpHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||||
|
PsFormat string `json:"psFormat,omitempty"`
|
||||||
filename string // Note: not serialized - for internal use only
|
filename string // Note: not serialized - for internal use only
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConfigFile initilizes an empty configuration file for the given filename 'fn'
|
||||||
func NewConfigFile(fn string) *ConfigFile {
|
func NewConfigFile(fn string) *ConfigFile {
|
||||||
return &ConfigFile{
|
return &ConfigFile{
|
||||||
AuthConfigs: make(map[string]AuthConfig),
|
AuthConfigs: make(map[string]AuthConfig),
|
||||||
HttpHeaders: make(map[string]string),
|
HTTPHeaders: make(map[string]string),
|
||||||
filename: fn,
|
filename: fn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load up the auth config information and return values
|
// Load reads the configuration files in the given directory, and sets up
|
||||||
|
// the auth config information and return values.
|
||||||
// FIXME: use the internal golang config parser
|
// FIXME: use the internal golang config parser
|
||||||
func Load(configDir string) (*ConfigFile, error) {
|
func Load(configDir string) (*ConfigFile, error) {
|
||||||
if configDir == "" {
|
if configDir == "" {
|
||||||
configDir = filepath.Join(homedir.Get(), ".docker")
|
configDir = ConfigDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile := ConfigFile{
|
configFile := ConfigFile{
|
||||||
AuthConfigs: make(map[string]AuthConfig),
|
AuthConfigs: make(map[string]AuthConfig),
|
||||||
filename: filepath.Join(configDir, CONFIGFILE),
|
filename: filepath.Join(configDir, ConfigFileName),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try happy path first - latest config file
|
// Try happy path first - latest config file
|
||||||
@@ -94,8 +113,7 @@ func Load(configDir string) (*ConfigFile, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Can't find latest config file so check for the old one
|
// Can't find latest config file so check for the old one
|
||||||
confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE)
|
confFile := filepath.Join(homedir.Get(), oldConfigfile)
|
||||||
|
|
||||||
if _, err := os.Stat(confFile); err != nil {
|
if _, err := os.Stat(confFile); err != nil {
|
||||||
return &configFile, nil //missing file is not an error
|
return &configFile, nil //missing file is not an error
|
||||||
}
|
}
|
||||||
@@ -124,8 +142,8 @@ func Load(configDir string) (*ConfigFile, error) {
|
|||||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||||
}
|
}
|
||||||
authConfig.Email = origEmail[1]
|
authConfig.Email = origEmail[1]
|
||||||
authConfig.ServerAddress = DEFAULT_INDEXSERVER
|
authConfig.ServerAddress = defaultIndexserver
|
||||||
configFile.AuthConfigs[DEFAULT_INDEXSERVER] = authConfig
|
configFile.AuthConfigs[defaultIndexserver] = authConfig
|
||||||
} else {
|
} else {
|
||||||
for k, authConfig := range configFile.AuthConfigs {
|
for k, authConfig := range configFile.AuthConfigs {
|
||||||
authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth)
|
authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth)
|
||||||
@@ -140,12 +158,13 @@ func Load(configDir string) (*ConfigFile, error) {
|
|||||||
return &configFile, nil
|
return &configFile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save encodes and writes out all the authorization information
|
||||||
func (configFile *ConfigFile) Save() error {
|
func (configFile *ConfigFile) Save() error {
|
||||||
// Encode sensitive data into a new/temp struct
|
// Encode sensitive data into a new/temp struct
|
||||||
tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs))
|
tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs))
|
||||||
for k, authConfig := range configFile.AuthConfigs {
|
for k, authConfig := range configFile.AuthConfigs {
|
||||||
authCopy := authConfig
|
authCopy := authConfig
|
||||||
|
// encode and save the authstring, while blanking out the original fields
|
||||||
authCopy.Auth = EncodeAuth(&authCopy)
|
authCopy.Auth = EncodeAuth(&authCopy)
|
||||||
authCopy.Username = ""
|
authCopy.Username = ""
|
||||||
authCopy.Password = ""
|
authCopy.Password = ""
|
||||||
@@ -162,7 +181,7 @@ func (configFile *ConfigFile) Save() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
|
if err := system.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,11 +192,12 @@ func (configFile *ConfigFile) Save() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *ConfigFile) Filename() string {
|
// Filename returns the name of the configuration file
|
||||||
return config.filename
|
func (configFile *ConfigFile) Filename() string {
|
||||||
|
return configFile.filename
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a base64 encoded auth string to store in config
|
// EncodeAuth creates a base64 encoded string to containing authorization information
|
||||||
func EncodeAuth(authConfig *AuthConfig) string {
|
func EncodeAuth(authConfig *AuthConfig) string {
|
||||||
authStr := authConfig.Username + ":" + authConfig.Password
|
authStr := authConfig.Username + ":" + authConfig.Password
|
||||||
msg := []byte(authStr)
|
msg := []byte(authStr)
|
||||||
@@ -186,7 +206,7 @@ func EncodeAuth(authConfig *AuthConfig) string {
|
|||||||
return string(encoded)
|
return string(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode the auth string
|
// DecodeAuth decodes a base64 encoded string and returns username and password
|
||||||
func DecodeAuth(authStr string) (string, string, error) {
|
func DecodeAuth(authStr string) (string, string, error) {
|
||||||
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||||
decoded := make([]byte, decLen)
|
decoded := make([]byte, decLen)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func TestMissingFile(t *testing.T) {
|
|||||||
t.Fatalf("Failed to save: %q", err)
|
t.Fatalf("Failed to save: %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
if !strings.Contains(string(buf), `"auths":`) {
|
if !strings.Contains(string(buf), `"auths":`) {
|
||||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ func TestSaveFileToDirs(t *testing.T) {
|
|||||||
t.Fatalf("Failed to save: %q", err)
|
t.Fatalf("Failed to save: %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
if !strings.Contains(string(buf), `"auths":`) {
|
if !strings.Contains(string(buf), `"auths":`) {
|
||||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ func TestSaveFileToDirs(t *testing.T) {
|
|||||||
|
|
||||||
func TestEmptyFile(t *testing.T) {
|
func TestEmptyFile(t *testing.T) {
|
||||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
ioutil.WriteFile(fn, []byte(""), 0600)
|
ioutil.WriteFile(fn, []byte(""), 0600)
|
||||||
|
|
||||||
_, err := Load(tmpHome)
|
_, err := Load(tmpHome)
|
||||||
@@ -66,7 +66,7 @@ func TestEmptyFile(t *testing.T) {
|
|||||||
|
|
||||||
func TestEmptyJson(t *testing.T) {
|
func TestEmptyJson(t *testing.T) {
|
||||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
ioutil.WriteFile(fn, []byte("{}"), 0600)
|
ioutil.WriteFile(fn, []byte("{}"), 0600)
|
||||||
|
|
||||||
config, err := Load(tmpHome)
|
config, err := Load(tmpHome)
|
||||||
@@ -80,7 +80,7 @@ func TestEmptyJson(t *testing.T) {
|
|||||||
t.Fatalf("Failed to save: %q", err)
|
t.Fatalf("Failed to save: %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
if !strings.Contains(string(buf), `"auths":`) {
|
if !strings.Contains(string(buf), `"auths":`) {
|
||||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func TestOldJson(t *testing.T) {
|
|||||||
defer func() { os.Setenv(homeKey, homeVal) }()
|
defer func() { os.Setenv(homeKey, homeVal) }()
|
||||||
os.Setenv(homeKey, tmpHome)
|
os.Setenv(homeKey, tmpHome)
|
||||||
|
|
||||||
fn := filepath.Join(tmpHome, OLD_CONFIGFILE)
|
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
ioutil.WriteFile(fn, []byte(js), 0600)
|
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ func TestOldJson(t *testing.T) {
|
|||||||
t.Fatalf("Failed to save: %q", err)
|
t.Fatalf("Failed to save: %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
if !strings.Contains(string(buf), `"auths":`) ||
|
if !strings.Contains(string(buf), `"auths":`) ||
|
||||||
!strings.Contains(string(buf), "user@example.com") {
|
!strings.Contains(string(buf), "user@example.com") {
|
||||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
@@ -129,7 +129,7 @@ func TestOldJson(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewJson(t *testing.T) {
|
func TestNewJson(t *testing.T) {
|
||||||
tmpHome, _ := ioutil.TempDir("", "config-test")
|
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||||
fn := filepath.Join(tmpHome, CONFIGFILE)
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
||||||
ioutil.WriteFile(fn, []byte(js), 0600)
|
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||||
|
|
||||||
@@ -149,9 +149,40 @@ func TestNewJson(t *testing.T) {
|
|||||||
t.Fatalf("Failed to save: %q", err)
|
t.Fatalf("Failed to save: %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
if !strings.Contains(string(buf), `"auths":`) ||
|
if !strings.Contains(string(buf), `"auths":`) ||
|
||||||
!strings.Contains(string(buf), "user@example.com") {
|
!strings.Contains(string(buf), "user@example.com") {
|
||||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJsonWithPsFormat(t *testing.T) {
|
||||||
|
tmpHome, _ := ioutil.TempDir("", "config-test")
|
||||||
|
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||||
|
js := `{
|
||||||
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
|
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
||||||
|
}`
|
||||||
|
ioutil.WriteFile(fn, []byte(js), 0600)
|
||||||
|
|
||||||
|
config, err := Load(tmpHome)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
||||||
|
t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now save it and make sure it shows up in new form
|
||||||
|
err = config.Save()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||||
|
if !strings.Contains(string(buf), `"psFormat":`) ||
|
||||||
|
!strings.Contains(string(buf), "{{.ID}}") {
|
||||||
|
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
@{DOCKER_GRAPH_PATH}=/var/lib/docker
|
||||||
|
|
||||||
|
profile /usr/bin/docker (attach_disconnected, complain) {
|
||||||
|
# Prevent following links to these files during container setup.
|
||||||
|
deny /etc/** mkl,
|
||||||
|
deny /dev/** kl,
|
||||||
|
deny /sys/** mkl,
|
||||||
|
deny /proc/** mkl,
|
||||||
|
|
||||||
|
mount -> @{DOCKER_GRAPH_PATH}/**,
|
||||||
|
mount -> /,
|
||||||
|
mount -> /proc/**,
|
||||||
|
mount -> /sys/**,
|
||||||
|
mount -> /run/docker/netns/**,
|
||||||
|
|
||||||
|
umount,
|
||||||
|
pivot_root,
|
||||||
|
signal (receive) peer=@{profile_name},
|
||||||
|
signal (receive) peer=unconfined,
|
||||||
|
signal (send),
|
||||||
|
ipc rw,
|
||||||
|
network,
|
||||||
|
capability,
|
||||||
|
owner /** rw,
|
||||||
|
/var/lib/docker/** rwl,
|
||||||
|
|
||||||
|
# For non-root client use:
|
||||||
|
/dev/urandom r,
|
||||||
|
/run/docker.sock rw,
|
||||||
|
/proc/** r,
|
||||||
|
/sys/kernel/mm/hugepages/ r,
|
||||||
|
/etc/localtime r,
|
||||||
|
|
||||||
|
ptrace peer=@{profile_name},
|
||||||
|
ptrace (read) peer=docker-default,
|
||||||
|
deny ptrace (trace) peer=docker-default,
|
||||||
|
deny ptrace peer=/usr/bin/docker///bin/ps,
|
||||||
|
|
||||||
|
/usr/bin/docker pix,
|
||||||
|
/sbin/xtables-multi rCx,
|
||||||
|
/sbin/iptables rCx,
|
||||||
|
/sbin/modprobe rCx,
|
||||||
|
/sbin/auplink rCx,
|
||||||
|
/bin/kmod rCx,
|
||||||
|
/usr/bin/xz rCx,
|
||||||
|
/bin/ps rCx,
|
||||||
|
/bin/cat rCx,
|
||||||
|
/sbin/zfs rCx,
|
||||||
|
|
||||||
|
# Transitions
|
||||||
|
change_profile -> docker-*,
|
||||||
|
change_profile -> unconfined,
|
||||||
|
|
||||||
|
profile /bin/cat (complain) {
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/lib/** r,
|
||||||
|
/dev/null rw,
|
||||||
|
/proc r,
|
||||||
|
/bin/cat mr,
|
||||||
|
|
||||||
|
# For reading in 'docker stats':
|
||||||
|
/proc/[0-9]*/net/dev r,
|
||||||
|
}
|
||||||
|
profile /bin/ps (complain) {
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/etc/localtime r,
|
||||||
|
/etc/passwd r,
|
||||||
|
/etc/nsswitch.conf r,
|
||||||
|
/lib/** r,
|
||||||
|
/proc/[0-9]*/** r,
|
||||||
|
/dev/null rw,
|
||||||
|
/bin/ps mr,
|
||||||
|
|
||||||
|
# We don't need ptrace so we'll deny and ignore the error.
|
||||||
|
deny ptrace (read, trace),
|
||||||
|
|
||||||
|
# Quiet dac_override denials
|
||||||
|
deny capability dac_override,
|
||||||
|
deny capability dac_read_search,
|
||||||
|
deny capability sys_ptrace,
|
||||||
|
|
||||||
|
/dev/tty r,
|
||||||
|
/proc/stat r,
|
||||||
|
/proc/cpuinfo r,
|
||||||
|
/proc/meminfo r,
|
||||||
|
/proc/uptime r,
|
||||||
|
/sys/devices/system/cpu/online r,
|
||||||
|
/proc/sys/kernel/pid_max r,
|
||||||
|
/proc/ r,
|
||||||
|
/proc/tty/drivers r,
|
||||||
|
}
|
||||||
|
profile /sbin/iptables (complain) {
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
capability net_admin,
|
||||||
|
}
|
||||||
|
profile /sbin/auplink flags=(attach_disconnected, complain) {
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
capability sys_admin,
|
||||||
|
capability dac_override,
|
||||||
|
|
||||||
|
@{DOCKER_GRAPH_PATH}/aufs/** rw,
|
||||||
|
@{DOCKER_GRAPH_PATH}/tmp/** rw,
|
||||||
|
# For user namespaces:
|
||||||
|
@{DOCKER_GRAPH_PATH}/[0-9]*.[0-9]*/** rw,
|
||||||
|
|
||||||
|
/sys/fs/aufs/** r,
|
||||||
|
/lib/** r,
|
||||||
|
/apparmor/.null r,
|
||||||
|
/dev/null rw,
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/sbin/auplink rm,
|
||||||
|
/proc/fs/aufs/** rw,
|
||||||
|
/proc/[0-9]*/mounts rw,
|
||||||
|
}
|
||||||
|
profile /sbin/modprobe /bin/kmod (complain) {
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
capability sys_module,
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/lib/** r,
|
||||||
|
/dev/null rw,
|
||||||
|
/apparmor/.null rw,
|
||||||
|
/sbin/modprobe rm,
|
||||||
|
/bin/kmod rm,
|
||||||
|
/proc/cmdline r,
|
||||||
|
/sys/module/** r,
|
||||||
|
/etc/modprobe.d{/,/**} r,
|
||||||
|
}
|
||||||
|
# xz works via pipes, so we do not need access to the filesystem.
|
||||||
|
profile /usr/bin/xz (complain) {
|
||||||
|
signal (receive) peer=/usr/bin/docker,
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/lib/** r,
|
||||||
|
/usr/bin/xz rm,
|
||||||
|
deny /proc/** rw,
|
||||||
|
deny /sys/** rw,
|
||||||
|
}
|
||||||
|
profile /sbin/xtables-multi (attach_disconnected, complain) {
|
||||||
|
/etc/ld.so.cache r,
|
||||||
|
/lib/** r,
|
||||||
|
/sbin/xtables-multi rm,
|
||||||
|
/apparmor/.null w,
|
||||||
|
/dev/null rw,
|
||||||
|
capability net_raw,
|
||||||
|
capability net_admin,
|
||||||
|
network raw,
|
||||||
|
}
|
||||||
|
profile /sbin/zfs (attach_disconnected, complain) {
|
||||||
|
file,
|
||||||
|
capability,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@ for version in "${versions[@]}"; do
|
|||||||
|
|
||||||
echo >> "$version/Dockerfile"
|
echo >> "$version/Dockerfile"
|
||||||
|
|
||||||
|
extraBuildTags=
|
||||||
|
|
||||||
# this list is sorted alphabetically; please keep it that way
|
# this list is sorted alphabetically; please keep it that way
|
||||||
packages=(
|
packages=(
|
||||||
bash-completion # for bash-completion debhelper integration
|
bash-completion # for bash-completion debhelper integration
|
||||||
@@ -54,6 +56,23 @@ for version in "${versions[@]}"; do
|
|||||||
libdevmapper-dev # for "libdevmapper.h"
|
libdevmapper-dev # for "libdevmapper.h"
|
||||||
libsqlite3-dev # for "sqlite3.h"
|
libsqlite3-dev # for "sqlite3.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if [ "$suite" = 'precise' ]; then
|
||||||
|
# precise has a few package issues
|
||||||
|
|
||||||
|
# - dh-systemd doesn't exist at all
|
||||||
|
packages=( "${packages[@]/dh-systemd}" )
|
||||||
|
|
||||||
|
# - libdevmapper-dev is missing critical structs (too old)
|
||||||
|
packages=( "${packages[@]/libdevmapper-dev}" )
|
||||||
|
extraBuildTags+=' exclude_graphdriver_devicemapper'
|
||||||
|
|
||||||
|
# - btrfs-tools is missing "ioctl.h" (too old), so it's useless
|
||||||
|
# (since kernels on precise are old too, just skip btrfs entirely)
|
||||||
|
packages=( "${packages[@]/btrfs-tools}" )
|
||||||
|
extraBuildTags+=' exclude_graphdriver_btrfs'
|
||||||
|
fi
|
||||||
|
|
||||||
echo "RUN apt-get update && apt-get install -y ${packages[*]} --no-install-recommends && rm -rf /var/lib/apt/lists/*" >> "$version/Dockerfile"
|
echo "RUN apt-get update && apt-get install -y ${packages[*]} --no-install-recommends && rm -rf /var/lib/apt/lists/*" >> "$version/Dockerfile"
|
||||||
|
|
||||||
echo >> "$version/Dockerfile"
|
echo >> "$version/Dockerfile"
|
||||||
@@ -65,5 +84,5 @@ for version in "${versions[@]}"; do
|
|||||||
echo >> "$version/Dockerfile"
|
echo >> "$version/Dockerfile"
|
||||||
|
|
||||||
echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile"
|
echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile"
|
||||||
awk '$1 == "ENV" && $2 == "DOCKER_BUILDTAGS" { print; exit }' ../../../Dockerfile >> "$version/Dockerfile"
|
awk '$1 == "ENV" && $2 == "DOCKER_BUILDTAGS" { print $0 "'"$extraBuildTags"'"; exit }' ../../../Dockerfile >> "$version/Dockerfile"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#
|
||||||
|
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM ubuntu-debootstrap:precise
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV GO_VERSION 1.4.2
|
||||||
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
ENV PATH $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
|
ENV AUTO_GOPATH 1
|
||||||
|
ENV DOCKER_BUILDTAGS apparmor selinux exclude_graphdriver_devicemapper exclude_graphdriver_btrfs
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
|
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM ubuntu-debootstrap:utopic
|
FROM ubuntu-debootstrap:wily
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
FROM centos:7
|
FROM centos:7
|
||||||
|
|
||||||
RUN yum groupinstall -y "Development Tools"
|
RUN yum groupinstall -y "Development Tools"
|
||||||
|
RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs
|
||||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar
|
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ for version in "${versions[@]}"; do
|
|||||||
centos:*)
|
centos:*)
|
||||||
# get "Development Tools" packages dependencies
|
# get "Development Tools" packages dependencies
|
||||||
echo 'RUN yum groupinstall -y "Development Tools"' >> "$version/Dockerfile"
|
echo 'RUN yum groupinstall -y "Development Tools"' >> "$version/Dockerfile"
|
||||||
|
|
||||||
|
if [[ "$version" == "centos-7" ]]; then
|
||||||
|
echo 'RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs' >> "$version/Dockerfile"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
oraclelinux:*)
|
||||||
|
# get "Development Tools" packages and dependencies
|
||||||
|
echo 'RUN yum groupinstall -y "Development Tools"' >> "$version/Dockerfile"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo 'RUN yum install -y @development-tools fedora-packager' >> "$version/Dockerfile"
|
echo 'RUN yum install -y @development-tools fedora-packager' >> "$version/Dockerfile"
|
||||||
@@ -51,8 +59,15 @@ for version in "${versions[@]}"; do
|
|||||||
glibc-static
|
glibc-static
|
||||||
libselinux-devel # for "libselinux.so"
|
libselinux-devel # for "libselinux.so"
|
||||||
sqlite-devel # for "sqlite3.h"
|
sqlite-devel # for "sqlite3.h"
|
||||||
tar # older versions of dev-tools don't have tar
|
tar # older versions of dev-tools do not have tar
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case "$from" in
|
||||||
|
oraclelinux:7)
|
||||||
|
# Enable the optional repository
|
||||||
|
packages=( --enablerepo=ol7_optional_latest "${packages[*]}" )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
echo "RUN yum install -y ${packages[*]}" >> "$version/Dockerfile"
|
echo "RUN yum install -y ${packages[*]}" >> "$version/Dockerfile"
|
||||||
|
|
||||||
echo >> "$version/Dockerfile"
|
echo >> "$version/Dockerfile"
|
||||||
@@ -65,9 +80,5 @@ for version in "${versions[@]}"; do
|
|||||||
|
|
||||||
echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile"
|
echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile"
|
||||||
|
|
||||||
if [ "$from" == "centos:6" ]; then
|
echo 'ENV DOCKER_BUILDTAGS selinux' >> "$version/Dockerfile"
|
||||||
echo 'ENV DOCKER_BUILDTAGS selinux exclude_graphdriver_btrfs' >> "$version/Dockerfile"
|
|
||||||
else
|
|
||||||
echo 'ENV DOCKER_BUILDTAGS selinux' >> "$version/Dockerfile"
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"!
|
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"!
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM centos:6
|
FROM oraclelinux:6
|
||||||
|
|
||||||
RUN yum groupinstall -y "Development Tools"
|
RUN yum groupinstall -y "Development Tools"
|
||||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar
|
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar
|
||||||
@@ -12,4 +12,4 @@ RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64
|
|||||||
ENV PATH $PATH:/usr/local/go/bin
|
ENV PATH $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
ENV AUTO_GOPATH 1
|
ENV AUTO_GOPATH 1
|
||||||
ENV DOCKER_BUILDTAGS selinux exclude_graphdriver_btrfs
|
ENV DOCKER_BUILDTAGS selinux
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user