mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 18:53:17 +03:00
Compare commits
1060 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4a44f6224 | |||
| 0272973175 | |||
| adca28801d | |||
| d2a3f92452 | |||
| ede86845e5 | |||
| e57c742674 | |||
| bb5976d727 | |||
| 670724184c | |||
| f7c1a59de1 | |||
| 01a2ccc52f | |||
| 51ba1dac49 | |||
| a4463dd40f | |||
| 83a82d818e | |||
| 1d1c4430b2 | |||
| 4e00b47b52 | |||
| 43e6d1ce2d | |||
| 30da442a85 | |||
| 038d91feaa | |||
| e7ba78beee | |||
| ab43804efd | |||
| 1c865dd119 | |||
| b32d0a5b60 | |||
| 79e21601b0 | |||
| 34253aa595 | |||
| 79ed7ce451 | |||
| 900eebb9a4 | |||
| 6914b2c99d | |||
| 0dd3a08169 | |||
| f8f290e848 | |||
| 9179cdfc9d | |||
| 76b6dc0112 | |||
| ce303f5c7e | |||
| b4b7a18497 | |||
| 1e2ebc9945 | |||
| a49e3647b6 | |||
| 954e17c3d0 | |||
| 2a9819aff8 | |||
| 8049184dcc | |||
| 6c6137b2e7 | |||
| 19c4f3082b | |||
| 433c2831ae | |||
| 9138b2b503 | |||
| 6d64aab420 | |||
| 77509ce391 | |||
| adcaa6f9de | |||
| ce129efa09 | |||
| 121164db56 | |||
| 195f95196e | |||
| a20d4568a2 | |||
| e656beb915 | |||
| cd04600862 | |||
| 3acc0ebb81 | |||
| 88daaef76b | |||
| 1cdaced8b6 | |||
| 0b8255529a | |||
| 019fe69a57 | |||
| d90ab904e7 | |||
| 6ae30b21c9 | |||
| b16781846e | |||
| 5ce82b45da | |||
| e99bc177c0 | |||
| b68bc75dad | |||
| d61eac080b | |||
| d8be9f12a2 | |||
| 0cf4539fe8 | |||
| db6bba709a | |||
| 2174a22835 | |||
| a8dd7b3eda | |||
| 25a55bae6f | |||
| fe157166ca | |||
| f7259adf83 | |||
| 6669560cb9 | |||
| b46ab7e921 | |||
| 27266f8a54 | |||
| 1b6ba0d062 | |||
| 0d0f09ee66 | |||
| f200a7fb6a | |||
| 98691f75bc | |||
| 47e304d03c | |||
| 9108abf204 | |||
| 6529dcb3b5 | |||
| 4b22b288a6 | |||
| abbf6ce6cc | |||
| 416ec316bd | |||
| 5ffc733eec | |||
| 8a23988711 | |||
| 35212b673e | |||
| 57ff9abca2 | |||
| 4752323e1c | |||
| 11593c6cc4 | |||
| 10025bda45 | |||
| b800541fbe | |||
| 3a73f1ead5 | |||
| 1340281cb8 | |||
| b9be841fd2 | |||
| 73890f31af | |||
| 456b2ef6eb | |||
| ad8b53e6d4 | |||
| 0b5d1bc91d | |||
| c43da3005a | |||
| ca4c15bc63 | |||
| a880283593 | |||
| e464a5bfbc | |||
| eb6bf454f1 | |||
| ec06089eda | |||
| 0c4be55936 | |||
| 11d21d5d1b | |||
| f9648d3976 | |||
| 2955aae8e4 | |||
| 9fd836e51f | |||
| 518f44908c | |||
| 38f60b3c1d | |||
| e2c71717f8 | |||
| 7764c542f2 | |||
| aa6468932b | |||
| 30104cb12b | |||
| d53e560ce0 | |||
| 44c8af66ad | |||
| 68aaa5bbc3 | |||
| 17747db93f | |||
| 3fe27c8411 | |||
| 14b1c9f8e4 | |||
| d84fc58cac | |||
| 187c3f62df | |||
| 4a447a439a | |||
| 4bfc50411c | |||
| 5e8392c8ef | |||
| d3c81a6e93 | |||
| 7342b5355f | |||
| 1341bf5a9e | |||
| 48aebf2d9d | |||
| 7b14e9b660 | |||
| 07eb24b775 | |||
| cd849bc2ff | |||
| 9c66812b99 | |||
| ec91fa55db | |||
| 00d3cc4b69 | |||
| 14ff7f5fcf | |||
| a97ce3c96e | |||
| cfc5681b36 | |||
| 369a876ebe | |||
| 778e9c864f | |||
| a2616b8227 | |||
| 8d8f28eae4 | |||
| e7d7d5232c | |||
| 1d65ef3201 | |||
| 8d425e3372 | |||
| 3939c6f6e7 | |||
| 60d91234b9 | |||
| 37c14207d6 | |||
| 3b9fbf80ad | |||
| c2fdf73491 | |||
| 143f9371c6 | |||
| 3f1902face | |||
| c0adb52213 | |||
| 3520e946a2 | |||
| f38adc1865 | |||
| d5ff1c8e3b | |||
| ad7417bc50 | |||
| 2d17c16d93 | |||
| aded0bc81a | |||
| 36d36fab0b | |||
| ba756cf366 | |||
| c00d410e61 | |||
| 968342c732 | |||
| 5c15656c55 | |||
| 2e8fc6ebfe | |||
| efe9fe6124 | |||
| 30c251efd3 | |||
| c850905e43 | |||
| a317a2531c | |||
| ee20ebe07a | |||
| 2743d4ca87 | |||
| 6136a963c8 | |||
| 60417950c7 | |||
| fa211f6a10 | |||
| 72e0745e2f | |||
| aa4d1021eb | |||
| 93e7e4a0e5 | |||
| c2f7cd1104 | |||
| 38eef5ce4c | |||
| 4cf80f96ad | |||
| d4af132fc4 | |||
| c087a05b43 | |||
| cdb0e6ffed | |||
| 03b84091fc | |||
| 2be20588bf | |||
| e59ee14f40 | |||
| 21a37e3393 | |||
| 810a4f0723 | |||
| 4c266df863 | |||
| abd999f64a | |||
| 04de19c870 | |||
| 648cb13e02 | |||
| 174f428571 | |||
| 5388ae4acb | |||
| 2e338e84cb | |||
| 5089a7167d | |||
| c0ac25bfff | |||
| 27a1f3ed2b | |||
| 90f36c1389 | |||
| 91817d0d1a | |||
| 55a3b071ea | |||
| a38ce29137 | |||
| 7cea3f7da4 | |||
| dcffd87e08 | |||
| 3b813148b3 | |||
| 4bba2cd034 | |||
| 28a1a17187 | |||
| a5e879fbe4 | |||
| f7f12b8604 | |||
| cf5d051afc | |||
| 2f681bed57 | |||
| 2d0f65a5e3 | |||
| b1705599e1 | |||
| 969b2d2110 | |||
| f4b2ed2a92 | |||
| dee3cf2d7f | |||
| b460b5967f | |||
| 21058c34d0 | |||
| 5b1e6c7dbc | |||
| 691dc04fac | |||
| e92434c2e7 | |||
| cae09d8b84 | |||
| f706a5b4c8 | |||
| c54e3b4ea3 | |||
| 0fff9f9fa6 | |||
| 0becf7f03f | |||
| 972d876ca9 | |||
| b8cb21c954 | |||
| 67062840c1 | |||
| 53f0cc1340 | |||
| 9626a981bc | |||
| b912c8f035 | |||
| fa13fe2184 | |||
| 85a1956e5c | |||
| 72743d1590 | |||
| 7ed1077879 | |||
| 16d7b90adf | |||
| 94424e14d7 | |||
| e79874f58e | |||
| 8aae8b1d27 | |||
| 1813ff9dfa | |||
| 4ac31ea82b | |||
| 67ca157329 | |||
| 7c061fa3b6 | |||
| f5e1b3d09e | |||
| 3ba4804d6c | |||
| 216de230e2 | |||
| a91cfa03e7 | |||
| 087aaaf894 | |||
| cbb7a09376 | |||
| 1a956424e0 | |||
| f9aa239973 | |||
| 2073b79633 | |||
| 6a6a30c33c | |||
| 1cd5d7942f | |||
| 63e9005f01 | |||
| d55f4336ae | |||
| 535efd34a0 | |||
| e11e4bcbc7 | |||
| 4915433bd2 | |||
| 43d6e3ae06 | |||
| 225b812b5e | |||
| a53cb236bb | |||
| 761992ca89 | |||
| 96ed0991b5 | |||
| a42df3d364 | |||
| ff94b1b0a9 | |||
| b1845c6c83 | |||
| 62b1da3e2c | |||
| d26b24f670 | |||
| 8ba40e492c | |||
| 2c372a9894 | |||
| 3d3b75fb8d | |||
| 142b057be8 | |||
| 4790868878 | |||
| 342ade03f6 | |||
| 1a0b7f58f9 | |||
| 9407dbf387 | |||
| 423aeb0d81 | |||
| 790323ac37 | |||
| 920b863955 | |||
| c1382c09d9 | |||
| febe9cc26a | |||
| 2ce2e88adf | |||
| c7599d323b | |||
| e906b511e9 | |||
| 4dd07e5763 | |||
| d93bdea433 | |||
| 26cfd52e7e | |||
| 2d7a96342c | |||
| cdd6c9f52e | |||
| 5e529a1c96 | |||
| 7fee96e9de | |||
| 5686a7e273 | |||
| b91040f7fb | |||
| 566e0e2048 | |||
| 3aad09be28 | |||
| f0358acb32 | |||
| fd0de4ab32 | |||
| 73a308502f | |||
| bd59f150b8 | |||
| 4b68d69188 | |||
| f90422a890 | |||
| 8befedef14 | |||
| 2af3004409 | |||
| 38ee40d59c | |||
| d583f1ac0e | |||
| 2bcb02f628 | |||
| 4301a33371 | |||
| f1a03a4ee8 | |||
| 167ddf9c9c | |||
| f833e41e69 | |||
| 3780ec699f | |||
| 41688a936b | |||
| b2db8123ec | |||
| b330c2c57e | |||
| 231c5cf6de | |||
| 7214a0160a | |||
| 375b79f11b | |||
| 661068d1a2 | |||
| 3da1869d5e | |||
| 000a7aa094 | |||
| 7cedc5369d | |||
| e5ecd20d44 | |||
| 53aaa5d2a5 | |||
| cccf2de129 | |||
| 9d39fb3604 | |||
| 4a007e3767 | |||
| 95814359bd | |||
| de6c286258 | |||
| d0ae69087c | |||
| 5e15b0b844 | |||
| 7ea026ff1d | |||
| 6e0575a53d | |||
| fb9be81fab | |||
| 60791d6dd1 | |||
| eba423bb9d | |||
| 301de169e9 | |||
| 1868c7016d | |||
| 0c71ce3398 | |||
| bc285cf0dd | |||
| 209a3ee7af | |||
| 7d19ab9f62 | |||
| 3f6d624c7b | |||
| c138272d63 | |||
| 7dbfea1353 | |||
| c121d27f31 | |||
| 43c19a6b82 | |||
| 6542bc4a03 | |||
| bede525dc9 | |||
| e45c90060f | |||
| 7d79c723e5 | |||
| 5f6d6c3b70 | |||
| d15042470e | |||
| f1f414ca59 | |||
| cdf4815a6b | |||
| fade056244 | |||
| a546047c95 | |||
| ea210319ce | |||
| 2896e780ae | |||
| eaafb23535 | |||
| baa30f4289 | |||
| 2164984d2b | |||
| 189c861835 | |||
| 6656fa3066 | |||
| 0cc2ed04f5 | |||
| b11adfa5cd | |||
| 9baeda781a | |||
| bd032d13ff | |||
| 730775bb4e | |||
| d31eaddba3 | |||
| 3202f78f0f | |||
| 6de410a0aa | |||
| a3f41c7049 | |||
| 1847f17f50 | |||
| 1bc32215b9 | |||
| 96009975d6 | |||
| de9b391db3 | |||
| a62572fb86 | |||
| 011a2c0b78 | |||
| daf4418cbb | |||
| 0f8df3d340 | |||
| 9cac385aec | |||
| 814ddc0923 | |||
| 247795dd36 | |||
| dfadf70a7f | |||
| d348ec0f6c | |||
| b730bd1396 | |||
| 56e0c6adf8 | |||
| f44a960dcd | |||
| 6c1bbf918d | |||
| 48e614b167 | |||
| fe8d33452b | |||
| c19ece6921 | |||
| 216fa57b88 | |||
| a9558ae248 | |||
| ee9077db7d | |||
| 9c85928740 | |||
| af0309371e | |||
| ead3c186a6 | |||
| 6ac48a65cb | |||
| 2ecf5ba1de | |||
| 94f1a1dea3 | |||
| c045ae15e7 | |||
| 1756b7c6ff | |||
| e25ace2151 | |||
| a8e5a86fa0 | |||
| f8edc233ab | |||
| 337c2a7cb4 | |||
| 2d735144b9 | |||
| 0113035237 | |||
| 52a1d248b2 | |||
| b5ed42c845 | |||
| d9e7cadacf | |||
| 36e88cbd50 | |||
| 6d76efb9bb | |||
| a1de9cec58 | |||
| 6885c72f32 | |||
| 0f1389e992 | |||
| 5bf3eeaa77 | |||
| 9dda1fd624 | |||
| 2dc46cb153 | |||
| 0674c0075e | |||
| 518ef670da | |||
| 0dd626ec67 | |||
| 53f4c0fdc0 | |||
| 7e3ea77fdf | |||
| 7290d23b26 | |||
| 24f20eb1bd | |||
| 4c9de098b0 | |||
| a2ccba69e5 | |||
| 8eb99d3a87 | |||
| 3773874cd3 | |||
| 6c62b1a2ea | |||
| b768645fde | |||
| 7b58dcb28c | |||
| fea4a1e68e | |||
| a9e83dd42c | |||
| 145f501a21 | |||
| 9b3b04ecec | |||
| 3e063cca5c | |||
| 27d716c663 | |||
| fbd15cb7b7 | |||
| f7c91eff54 | |||
| a6bdc086a2 | |||
| 1242dd951a | |||
| d1c8e9f31b | |||
| 83ccae6c8b | |||
| 086be07bf5 | |||
| 28f9c477a8 | |||
| 09571d03a5 | |||
| 71ce63f79c | |||
| 5205c9591f | |||
| 9a547dcbfb | |||
| 27632ca6ec | |||
| c7470e6e6e | |||
| d090a17ed0 | |||
| c2529260e7 | |||
| da87188ff8 | |||
| d099039f5d | |||
| 5dd9cf4398 | |||
| ab77b216d1 | |||
| b37a02cddf | |||
| c3c3e9087b | |||
| 7ad6bc955f | |||
| 7a5271ad96 | |||
| 1b122526aa | |||
| a3b266761e | |||
| 498389123e | |||
| bc61417284 | |||
| 97d952e61c | |||
| 073aac3d92 | |||
| eff4127efd | |||
| b1c0c32ba6 | |||
| f14bf25cb9 | |||
| f216670814 | |||
| 6ecc98fddb | |||
| 4843affd0e | |||
| 558785a4bb | |||
| 60d415bb8a | |||
| 45e22cf8aa | |||
| e4900b99d7 | |||
| 20766069a8 | |||
| e8160c9fae | |||
| 957ecb1b64 | |||
| ac5061df2c | |||
| cddb2714ef | |||
| d7d9cac20b | |||
| 6817c5ea58 | |||
| 4cd6ca02c7 | |||
| a5efcbab51 | |||
| 85be7b39ac | |||
| ebf3dda449 | |||
| 582953260b | |||
| 2d1ea86fc6 | |||
| 322385f1b6 | |||
| 1b38aed05f | |||
| a69c98e394 | |||
| 282c9f790a | |||
| 2eeb0e6a0b | |||
| 3ff5bf2369 | |||
| 69ee28a082 | |||
| d02deff3d7 | |||
| 3e78ea8acc | |||
| b54c0f0ef3 | |||
| c79358c67e | |||
| 75107d7698 | |||
| c4464e36c8 | |||
| d92db198d1 | |||
| 8bae956df6 | |||
| 7758524703 | |||
| c82fa2c829 | |||
| a51280fd20 | |||
| bd437c1c17 | |||
| 69fb68ef0b | |||
| 787dbaff36 | |||
| c50ae1fdbe | |||
| bde0f444db | |||
| 6a8298b137 | |||
| f19cbfad5c | |||
| 78f2183e70 | |||
| 5c11a46412 | |||
| 8a94aebdb8 | |||
| ec11e99667 | |||
| 37d066b563 | |||
| bfec5fe200 | |||
| 525287f4b6 | |||
| 9054ce73b2 | |||
| d079adc167 | |||
| a9d401ac10 | |||
| 1fa65c7f2f | |||
| cc9b63eb51 | |||
| 7e12eab3ad | |||
| fa685d7d9c | |||
| 4314ee1670 | |||
| 7d636a7c13 | |||
| bf9d51cf14 | |||
| 29e0727b58 | |||
| c434dff0a4 | |||
| b2a8cb4aba | |||
| b412a222ae | |||
| 79bcb705bf | |||
| 9f81d014f1 | |||
| 3184205519 | |||
| 7c919329e8 | |||
| db41953618 | |||
| cea078a593 | |||
| f44cfb2863 | |||
| 1b45be0d60 | |||
| 6bb693488c | |||
| e20e08d700 | |||
| ac07df2985 | |||
| 2054ca5c9a | |||
| e51e465543 | |||
| 2bbc6a83e8 | |||
| a78731a3ba | |||
| f4e779c964 | |||
| a973402821 | |||
| 44decbeae0 | |||
| 3ea1be3c52 | |||
| 2642e12d14 | |||
| e7276b7b9b | |||
| e375341c33 | |||
| 2c20716f37 | |||
| 928f5b0564 | |||
| 91f21ddc47 | |||
| 43a3778b45 | |||
| b9b1bfefe7 | |||
| 05cda35b14 | |||
| 2155e74951 | |||
| 4714958e99 | |||
| c6e62b9175 | |||
| ab66b23194 | |||
| 73f9d8a636 | |||
| 541a778d7b | |||
| d49f2ec19c | |||
| 8dd63a462f | |||
| 9902c9baaa | |||
| 7de29e6e6b | |||
| 336460f67e | |||
| 95e89f1712 | |||
| 886ae15464 | |||
| d8af244708 | |||
| c8243706b4 | |||
| 30707659b5 | |||
| 90c365a174 | |||
| 7b732b566f | |||
| ba52a925f9 | |||
| fdda5f98c6 | |||
| fa4d627b57 | |||
| 2c3e34f001 | |||
| 7f8f1ad4e3 | |||
| 6f992134a2 | |||
| 0c80bf45d0 | |||
| 2777956581 | |||
| b207520d98 | |||
| 2196fd9cd5 | |||
| ef6304c5c2 | |||
| 6b984410d5 | |||
| 813e0fc1a8 | |||
| 38cf263409 | |||
| 6f6a2214fc | |||
| 791821d590 | |||
| 9a951da881 | |||
| e7a0be5bd3 | |||
| ff932ca2a0 | |||
| 818d3bcaf5 | |||
| 45b1c66195 | |||
| da04cb91ce | |||
| cfc9cfd84a | |||
| ea18e51f4d | |||
| 3d3beb6a9d | |||
| 27b8f18cce | |||
| bf545dc320 | |||
| f001e99fcd | |||
| b4bfdc92cc | |||
| ae654831aa | |||
| 1ffa983a9d | |||
| ecf1566266 | |||
| c5b87f93dd | |||
| b1a2169dcc | |||
| d45a1808f2 | |||
| db2155551a | |||
| 09d35d3b4c | |||
| 5b9342d35c | |||
| 8d98662633 | |||
| 7fdeb44372 | |||
| 59dced8237 | |||
| 496f4a7dc7 | |||
| 8b880a246a | |||
| eeb5942b6b | |||
| 7ec904d67b | |||
| c9212819af | |||
| 10fd53d6bb | |||
| 3fea1d5e35 | |||
| 35ecc04223 | |||
| 3ca9f5ffa3 | |||
| 2e9fed1a14 | |||
| 6b92f3fd99 | |||
| 06e30b5aa1 | |||
| 603cf2a8bb | |||
| a54cdb9587 | |||
| ed4bd20a7c | |||
| cfd12914e1 | |||
| c55aeaf814 | |||
| fdf65aa9b9 | |||
| 69b2aacf5a | |||
| 0af62d35a0 | |||
| 7c32f3f554 | |||
| cec8cdb35e | |||
| 5ab9cc029d | |||
| 667f42515a | |||
| 3614cb7a8b | |||
| b809c84338 | |||
| 33edb072a3 | |||
| 6a00eb10bf | |||
| 792ee48d2c | |||
| 52873ac3a3 | |||
| 88ae0f1196 | |||
| 23a0415eb7 | |||
| 75a0661213 | |||
| a1c7c9ea73 | |||
| 7f19a9a617 | |||
| 2f2c7d91a8 | |||
| 9ad1c2d07d | |||
| 07a7f329e7 | |||
| c7ca791c58 | |||
| d9be8bc693 | |||
| 9fc7537f2a | |||
| f1b2462193 | |||
| 8fbf2b0b2a | |||
| c93157019f | |||
| e3b44c3829 | |||
| 978bd4e2c4 | |||
| bb942c7311 | |||
| 5d25b10f72 | |||
| eac02c04f7 | |||
| 1330e59307 | |||
| 2dd14c0b89 | |||
| 5b8975bf4b | |||
| 6f66f1a910 | |||
| deb3911f5e | |||
| 23a8411732 | |||
| ece0d4ac53 | |||
| 4c92bec619 | |||
| dcd63b4146 | |||
| 224b4f13b8 | |||
| 51a9d1bdb7 | |||
| b2db1e96e2 | |||
| ab7d3cd508 | |||
| 852fb320f7 | |||
| 8fb37a8417 | |||
| d4dcf1d722 | |||
| 02a59a04d1 | |||
| 16a6e68d7b | |||
| 1b427ddb69 | |||
| 02acff7fac | |||
| 712e82344c | |||
| 9f298d2311 | |||
| 716a52f261 | |||
| e4020fb41f | |||
| d1144c2c7e | |||
| 78125ee853 | |||
| 9ecd66007f | |||
| 64ec17b463 | |||
| 6b9805e891 | |||
| 013773065c | |||
| 7d6766adc6 | |||
| c56c2f5fd3 | |||
| 7e819d00ea | |||
| 33767266e7 | |||
| 63be4709b7 | |||
| 6b1f2fc133 | |||
| f749a9bf0e | |||
| 9b4d46a6ed | |||
| 502e652b7a | |||
| de924605a1 | |||
| 15e2ea2c96 | |||
| 07edb7c7f8 | |||
| d0cea7adea | |||
| 2165d45d3f | |||
| 6d5d77f62c | |||
| 49df290270 | |||
| b1bfd75fcf | |||
| e5951e30d0 | |||
| 45d725c0a3 | |||
| c2c5b09bb1 | |||
| 086fbb745e | |||
| 4f37c8ccf2 | |||
| 026265f8f7 | |||
| f91c072f61 | |||
| 52bdbcd046 | |||
| 301c50b721 | |||
| e9c111c8d0 | |||
| 278a165674 | |||
| a05af9bb98 | |||
| e934c3e2a2 | |||
| 2d295a31de | |||
| 9bbf5cb74f | |||
| 680e493065 | |||
| 1ea2449269 | |||
| 7ce63b3078 | |||
| 7432b5c9b2 | |||
| d76160c245 | |||
| 5d838edcef | |||
| c9116e6bd7 | |||
| d7dc9aaf52 | |||
| bfe8a9bccc | |||
| 9990464cd5 | |||
| 881e983ed9 | |||
| f6a7d4d29b | |||
| f98616dce7 | |||
| 5bd0e95eef | |||
| 0414f01b6e | |||
| 0cbebf0f57 | |||
| 4cb6ebcfa2 | |||
| 2232e095d5 | |||
| aae337f5b5 | |||
| a78e5d4763 | |||
| cf37c7997e | |||
| 1ffbb5c24c | |||
| b9c48e0ab0 | |||
| fe5d599802 | |||
| 55063906b5 | |||
| c7178d2066 | |||
| f14f60a487 | |||
| e2b3c083aa | |||
| 3011692d93 | |||
| 86252ec7e1 | |||
| ef1aa870c5 | |||
| c260182412 | |||
| 61c17c8933 | |||
| dd93eee1e3 | |||
| 7a400542ae | |||
| 88286cf8d0 | |||
| 8cb6184f1d | |||
| b849fd7a75 | |||
| 09ee145e9c | |||
| 04dfcbfdd7 | |||
| 23e46f9dba | |||
| fc5213258e | |||
| 005ebbb9b2 | |||
| 017067e11f | |||
| 2bb69033e5 | |||
| fca4ee84c9 | |||
| 60e60f68dd | |||
| ba758361b3 | |||
| c6b218e5df | |||
| c18fbdb29a | |||
| b1ad99edbf | |||
| 080e0c2323 | |||
| 935546d5ca | |||
| 64fde1ab95 | |||
| 169e8742fc | |||
| 069876e262 | |||
| 442e1698cb | |||
| d76518eeb9 | |||
| 0879a4f743 | |||
| 9be7066715 | |||
| d8660b30cc | |||
| 30922148fb | |||
| 37b32199e3 | |||
| d74818b227 | |||
| 627fdfeab7 | |||
| 3320878dfb | |||
| 3f20011862 | |||
| 5f34b5e6a5 | |||
| 9199033db7 | |||
| 2bf6cf0e15 | |||
| 686d4656de | |||
| 5aa5dcdc6d | |||
| 0a70bc24ac | |||
| 656146b699 | |||
| 5e40b9a563 | |||
| 89d1221217 | |||
| 4cd1bbb50a | |||
| c2cde6beb5 | |||
| abc1c1070a | |||
| fd56aa42a6 | |||
| 3d318bae76 | |||
| aa2e89bfe3 | |||
| 60813bef29 | |||
| b123be5612 | |||
| 933c60bc3a | |||
| 796cca4166 | |||
| fe379f9428 | |||
| ae0b165431 | |||
| 7a06e158f1 | |||
| 5eab3db344 | |||
| 9f44fcd540 | |||
| b9b2b37366 | |||
| 7f31d933a8 | |||
| 6695fd6a61 | |||
| 84e55e2e6f | |||
| b00cda8ad4 | |||
| d861edfc00 | |||
| dd311623df | |||
| cb935980a5 | |||
| 157721f694 | |||
| 97d799b9f0 | |||
| 0b7bd024fb | |||
| 3af70b36fd | |||
| 8eba97da74 | |||
| 669c9da85d | |||
| 5d09233115 | |||
| b2044dd22f | |||
| c31e67dcce | |||
| cd59a945d8 | |||
| d4a390028a | |||
| c476b27a65 | |||
| eaa838a63f | |||
| 4364102363 | |||
| 99ad445260 | |||
| 54431b3953 | |||
| f68a7005c0 | |||
| 725172e13b | |||
| 0f092c2be8 | |||
| a3c8ef79a4 | |||
| 01468d5a75 | |||
| 8f1243986e | |||
| dc1eb57f4e | |||
| 586614c73f | |||
| d140074773 | |||
| 39face27cf | |||
| e047ac52b8 | |||
| 04de3ea4bd | |||
| e6ce9da087 | |||
| c9c0d5eec2 | |||
| 63c3114657 | |||
| 63ac1d6d18 | |||
| 9bb0869b73 | |||
| 5f2318567e | |||
| 16ac4a3c64 | |||
| c8d82588c2 | |||
| 842d0241ed | |||
| 1dc5f2d0af | |||
| c10ecacf91 | |||
| 1cf3e3b7b5 | |||
| c3d4c1f584 | |||
| 471a3a650a | |||
| cc02bf0442 | |||
| 39e8e4f4aa | |||
| 80558e839d | |||
| ca62ac65d4 | |||
| f5abe4e1f1 | |||
| 3211cb5df6 | |||
| abc266caa1 | |||
| c364f0af6c | |||
| 555969ee42 | |||
| e2c5d29017 | |||
| fa00a84709 | |||
| d266b3a066 | |||
| db3f41fcb4 | |||
| 24fb1bf258 | |||
| 8b803491af | |||
| 10b2f15f6f | |||
| 3e9ab5f4a9 | |||
| 46b4dd8e20 | |||
| bf3a97d3aa | |||
| 3b67f629a4 | |||
| 3c30e4503d | |||
| 8390bc26db | |||
| 24ad59316d | |||
| be0c8b1ec0 | |||
| 476111968a | |||
| f1e2e1cc9e | |||
| 97deba2a7c | |||
| 3df7285c3c | |||
| d2dc964cb5 | |||
| d8e3de0cae | |||
| c9940d8c3f | |||
| 794eb54da8 | |||
| 2ab8d5e47f | |||
| 947bc8c7d3 | |||
| 5d3d57c12a | |||
| 0bfd20a8e3 | |||
| db3dbcce3a | |||
| b21835f195 | |||
| c7844fb1fb | |||
| 2ff8132e2d | |||
| 30e80d0a86 | |||
| 87443af49e | |||
| 5d65428b29 | |||
| 78eb3b78bb | |||
| 720442b1a2 | |||
| e542084c37 | |||
| f931fc7bfb | |||
| 0a56e33ce1 | |||
| c3771df641 | |||
| 890b493a2e | |||
| fb708b6b64 | |||
| f96e902f63 | |||
| 06bd1e582a | |||
| fb43d64dc3 | |||
| fd0fa4e5c5 | |||
| 4e9de58675 | |||
| 9565641b9b | |||
| 4da68cfcfc | |||
| a9b87c0a16 | |||
| 5ac4b517c9 | |||
| ca96560d56 | |||
| cc1a84b62e | |||
| 8392d2f510 | |||
| 347b29d059 | |||
| 3a34d98db8 | |||
| 7cdb67680e | |||
| 929951fd49 | |||
| 13a3d17321 | |||
| a8e156d6a5 | |||
| c9be601988 | |||
| 13e2b97ad9 | |||
| 95e5d7a9c3 | |||
| 32c200fe12 | |||
| 1dd38750f7 | |||
| e3273bc5bf | |||
| 26a866a202 | |||
| 60690a7e1d | |||
| e9b2bf00ad | |||
| 069b8ee8ff | |||
| 64759189f5 | |||
| fb48ca5020 | |||
| 2dad14974e | |||
| d97d53bddc | |||
| 1027afa853 | |||
| aa04f97f95 | |||
| 4082764d48 | |||
| 822eb5ddc7 | |||
| 1c90a6bd49 | |||
| 26863009c0 | |||
| 26e760ee62 | |||
| 1e457dda7e | |||
| 26ed9e81b1 | |||
| 4e63e0e372 | |||
| 07a556a10b | |||
| d28bcb4f84 | |||
| fa325665b1 | |||
| eac518b178 | |||
| 9e7a3e6adc | |||
| 599aae5ba6 | |||
| 8cc5ecec23 | |||
| 38e6d911ea | |||
| 47b13cdb80 | |||
| 51456e6adc | |||
| 1f481c0967 | |||
| e31b7cdcd5 | |||
| 4cec0501ca | |||
| 1b6de05a51 | |||
| 4e6bf136a8 | |||
| a2825702f8 | |||
| a49d4a9cb2 | |||
| 10b526ed86 | |||
| 980bf78b4d | |||
| 8aaaa46be9 | |||
| 53862d4a28 | |||
| ddc908207d | |||
| ee4a6a823d | |||
| 8836d57e3c | |||
| f01d53b20f | |||
| 40fcd3dc48 | |||
| 1b74ce3924 | |||
| 7bf093c06a | |||
| 140a7eadb4 | |||
| fce2d6ddd1 | |||
| 18cb15559d | |||
| 3adc311c1c | |||
| ee05280721 | |||
| c4e2af8ca3 | |||
| 5afb1b6747 | |||
| f2cc97a44c | |||
| d48fd6fde9 | |||
| b7ee0bbbc9 | |||
| 68a519a468 | |||
| c33bae057f | |||
| d7060c4c32 | |||
| 8964ef821f | |||
| bd10640846 | |||
| 175b07d6e4 | |||
| f16df2a4e7 | |||
| 1c90485b56 | |||
| 2320a877bc | |||
| ae752ed1fa | |||
| c566cc6b61 | |||
| 36e12a6038 | |||
| 42531db37e | |||
| 6a4ef2e48e | |||
| d2a8be6fc2 | |||
| 290ad0996f | |||
| bffc378a4f | |||
| 3b8adf7528 | |||
| 002ac82631 | |||
| e85df07518 | |||
| 589e32a4ed | |||
| 74008446fe | |||
| e48005ddc7 | |||
| 83066f953c | |||
| 90bfa6260a | |||
| 8b80eca184 | |||
| fb1374f2f7 | |||
| ff5bf51952 | |||
| 61927d228c | |||
| 82b9f2c931 | |||
| 6a19d7b25a | |||
| ac2e0596bd | |||
| 42c821e164 | |||
| 20b907d8fb | |||
| f45977d371 | |||
| 4ec9b349d0 | |||
| 65ac7c5671 | |||
| 5c2af3f792 | |||
| 8771e83545 | |||
| fa5a1cebd9 | |||
| 4f981a0b42 | |||
| 127641731a | |||
| c1a17c2561 | |||
| 1c5b05c130 | |||
| 4155f4e49b | |||
| f3022e891d | |||
| c28405a5c2 |
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: community, triage
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!--- Provide a general summary of the issue in the Title above -->
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
|
|
||||||
## Expected Behavior
|
## Expected Behavior
|
||||||
@@ -15,6 +24,8 @@
|
|||||||
## Steps to Reproduce (for bugs)
|
## Steps to Reproduce (for bugs)
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
<!--- and make sure you have followed https://github.com/minio/minio/tree/release/docs/debugging to capture relevant logs -->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
@@ -30,8 +41,6 @@
|
|||||||
|
|
||||||
## Your Environment
|
## Your Environment
|
||||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||||
* Version used (`minio version`):
|
* Version used (`minio --version`):
|
||||||
* Environment name and version (e.g. nginx 1.9.1):
|
* Server setup and configuration:
|
||||||
* Server type and version:
|
|
||||||
* Operating System and version (`uname -a`):
|
* Operating System and version (`uname -a`):
|
||||||
* Link to your project:
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ assignees: ''
|
|||||||
## Steps to Reproduce (for bugs)
|
## Steps to Reproduce (for bugs)
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
<!--- and make sure you have followed https://github.com/minio/minio/tree/release/docs/debugging to capture relevant logs -->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
@@ -39,8 +41,6 @@ assignees: ''
|
|||||||
|
|
||||||
## Your Environment
|
## Your Environment
|
||||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||||
* Version used (`minio version`):
|
* Version used (`minio --version`):
|
||||||
* Environment name and version (e.g. nginx 1.9.1):
|
* Server setup and configuration:
|
||||||
* Server type and version:
|
|
||||||
* Operating System and version (`uname -a`):
|
* Operating System and version (`uname -a`):
|
||||||
* Link to your project:
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: MinIO Community Support
|
||||||
|
url: https://slack.min.io
|
||||||
|
about: Join here for Community Support
|
||||||
|
- name: MinIO SUBNET Support
|
||||||
|
url: https://min.io/pricing
|
||||||
|
about: Join here for Enterprise Support
|
||||||
@@ -16,4 +16,3 @@
|
|||||||
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
||||||
- [ ] Documentation needed
|
- [ ] Documentation needed
|
||||||
- [ ] Unit tests needed
|
- [ ] Unit tests needed
|
||||||
- [ ] Functional tests needed (If yes, add [mint](https://github.com/minio/mint) PR # here: )
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app
|
||||||
|
|
||||||
|
# Number of days of inactivity before a closed issue or pull request is locked
|
||||||
|
daysUntilLock: 365
|
||||||
|
|
||||||
|
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||||
|
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||||
|
skipCreatedBefore: false
|
||||||
|
|
||||||
|
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||||
|
exemptLabels: []
|
||||||
|
|
||||||
|
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||||
|
lockLabel: true
|
||||||
|
|
||||||
|
# Comment to post before locking. Set to `false` to disable
|
||||||
|
lockComment: >-
|
||||||
|
|
||||||
|
This thread has been automatically locked since there has not been
|
||||||
|
any recent activity after it was closed. Please open a new issue for
|
||||||
|
related bugs.
|
||||||
|
|
||||||
|
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||||
|
setLockReason: true
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
only: issues
|
||||||
|
|
||||||
|
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||||
|
# issues:
|
||||||
|
# exemptLabels:
|
||||||
|
# - help-wanted
|
||||||
|
# lockLabel: outdated
|
||||||
|
|
||||||
|
# pulls:
|
||||||
|
# daysUntilLock: 30
|
||||||
|
|
||||||
|
# Repository to extend settings from
|
||||||
|
# _extends: repo
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.612 24.465"><path d="M52.751.414h9.108v23.63h-9.108zM41.711.74l-18.488 9.92a.919.919 0 0 1-.856 0L3.879.74A2.808 2.808 0 0 0 2.558.414h-.023A2.4 2.4 0 0 0 0 2.641v21.376h9.1V13.842a.918.918 0 0 1 1.385-.682l10.361 5.568a3.634 3.634 0 0 0 3.336.028l10.933-5.634a.917.917 0 0 1 1.371.69v10.205h9.1V2.641A2.4 2.4 0 0 0 43.055.414h-.023a2.808 2.808 0 0 0-1.321.326zm65.564-.326h-9.237v10.755a.913.913 0 0 1-1.338.706L72.762.675a2.824 2.824 0 0 0-1.191-.261h-.016a2.4 2.4 0 0 0-2.535 2.227v21.377h9.163V13.275a.914.914 0 0 1 1.337-.707l24.032 11.2a2.813 2.813 0 0 0 1.188.26 2.4 2.4 0 0 0 2.535-2.227zm7.161 23.63V.414h4.191v23.63zm28.856.421c-11.274 0-19.272-4.7-19.272-12.232C124.02 4.741 132.066 0 143.292 0s19.32 4.7 19.32 12.233-7.902 12.232-19.32 12.232zm0-21.333c-8.383 0-14.84 3.217-14.84 9.1 0 5.926 6.457 9.1 14.84 9.1s14.887-3.174 14.887-9.1c0-5.883-6.504-9.1-14.887-9.1z" fill="#c72c48"/></svg>
|
||||||
|
After Width: | Height: | Size: 978 B |
@@ -0,0 +1,59 @@
|
|||||||
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
|
daysUntilStale: 30
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||||
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
|
daysUntilClose: 15
|
||||||
|
|
||||||
|
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||||
|
onlyLabels: []
|
||||||
|
|
||||||
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- "security"
|
||||||
|
- "pending discussion"
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
|
exemptProjects: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues with an assignee (defaults to false)
|
||||||
|
exemptAssignees: false
|
||||||
|
|
||||||
|
# Label to use when marking as stale
|
||||||
|
staleLabel: stale
|
||||||
|
|
||||||
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
|
markComment: >-
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed after 15 days if no further activity
|
||||||
|
occurs. Thank you for your contributions.
|
||||||
|
# Comment to post when removing the stale label.
|
||||||
|
# unmarkComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale Issue or Pull Request.
|
||||||
|
# closeComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 1
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
# only: issues
|
||||||
|
|
||||||
|
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||||
|
# pulls:
|
||||||
|
# daysUntilStale: 30
|
||||||
|
# markComment: >
|
||||||
|
# This pull request has been automatically marked as stale because it has not had
|
||||||
|
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
# for your contributions.
|
||||||
|
|
||||||
|
# issues:
|
||||||
|
# exemptLabels:
|
||||||
|
# - confirmed
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: "Code scanning - action"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 19 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CodeQL-Build:
|
||||||
|
|
||||||
|
# CodeQL runs on ubuntu-latest and windows-latest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
# We must fetch at least the immediate parents so that if this is
|
||||||
|
# a pull request then we can checkout the head.
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
# If this run was triggered by a pull request event, then checkout
|
||||||
|
# the head of the pull request instead of the merge commit.
|
||||||
|
- run: git checkout HEAD^2
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: go, javascript
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Test on Go ${{ matrix.go-version }} and ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.14.x]
|
||||||
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: '12'
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
- name: Build on ${{ matrix.os }}
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GO111MODULE: on
|
||||||
|
MINIO_CI_CD: 1
|
||||||
|
run: |
|
||||||
|
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
||||||
|
go test -v --timeout 50m ./...
|
||||||
|
- name: Build on ${{ matrix.os }}
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GO111MODULE: on
|
||||||
|
MINIO_CI_CD: 1
|
||||||
|
run: |
|
||||||
|
sudo apt-get install devscripts shellcheck
|
||||||
|
make
|
||||||
|
diff -au <(gofmt -s -d cmd) <(printf "")
|
||||||
|
diff -au <(gofmt -s -d pkg) <(printf "")
|
||||||
|
make test-race
|
||||||
|
make crosscompile
|
||||||
|
make verify
|
||||||
|
make verify-healing
|
||||||
|
cd browser && npm install && npm run test && cd ..
|
||||||
|
bash -c 'shopt -s globstar; shellcheck mint/**/*.sh'
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
linters-settings:
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- typecheck
|
||||||
|
- goimports
|
||||||
|
- misspell
|
||||||
|
- govet
|
||||||
|
- golint
|
||||||
|
- ineffassign
|
||||||
|
- gosimple
|
||||||
|
- deadcode
|
||||||
|
- structcheck
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
|
exclude:
|
||||||
|
- should have a package comment
|
||||||
|
- error strings should not be capitalized or end with punctuation or a newline
|
||||||
|
service:
|
||||||
|
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
project_name: minio
|
||||||
|
|
||||||
|
release:
|
||||||
|
name_template: "Version {{.MinIO.Version}}"
|
||||||
|
disable: true
|
||||||
|
github:
|
||||||
|
owner: minio
|
||||||
|
name: minio
|
||||||
|
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
- GO111MODULE=on
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- make clean
|
||||||
|
- go generate ./...
|
||||||
|
- go mod tidy
|
||||||
|
- go mod download
|
||||||
|
|
||||||
|
builds:
|
||||||
|
-
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
- windows
|
||||||
|
- freebsd
|
||||||
|
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
- arm
|
||||||
|
- ppc64le
|
||||||
|
- s390x
|
||||||
|
|
||||||
|
goarm:
|
||||||
|
- 7
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm
|
||||||
|
- goos: darwin
|
||||||
|
goarch: ppc64le
|
||||||
|
- goos: darwin
|
||||||
|
goarch: s390x
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm64
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm
|
||||||
|
- goos: windows
|
||||||
|
goarch: ppc64le
|
||||||
|
- goos: windows
|
||||||
|
goarch: s390x
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm64
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: ppc64le
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: s390x
|
||||||
|
|
||||||
|
flags:
|
||||||
|
- -tags=kqueue
|
||||||
|
- -trimpath
|
||||||
|
|
||||||
|
ldflags:
|
||||||
|
- "-s -w -X github.com/minio/minio/cmd.Version={{.Version}} -X github.com/minio/minio/cmd.ReleaseTag={{.Tag}} -X github.com/minio/minio/cmd.CommitID={{.FullCommit}} -X github.com/minio/minio/cmd.ShortCommitID={{.ShortCommit}}"
|
||||||
|
|
||||||
|
archives:
|
||||||
|
-
|
||||||
|
format: binary
|
||||||
|
name_template: "{{ .Binary }}-release/{{ .Os }}-{{ .Arch }}/{{ .Binary }}.{{ .Version }}"
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
-
|
||||||
|
id: minio
|
||||||
|
package_name: minio
|
||||||
|
vendor: MinIO, Inc.
|
||||||
|
homepage: https://min.io/
|
||||||
|
maintainer: dev@min.io
|
||||||
|
description: MinIO is a High Performance Object Storage released under Apache License v2.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
||||||
|
license: Apache 2.0
|
||||||
|
bindir: /usr/bin
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
overrides:
|
||||||
|
deb:
|
||||||
|
file_name_template: "{{ .Binary }}-release/debs/{{ .ProjectName }}-{{ .Version }}_{{ .Arch }}"
|
||||||
|
replacements:
|
||||||
|
arm: armv7
|
||||||
|
files:
|
||||||
|
"NOTICE": "/usr/share/minio/NOTICE"
|
||||||
|
"CREDITS": "/usr/share/minio/CREDITS"
|
||||||
|
"LICENSE": "/usr/share/minio/LICENSE"
|
||||||
|
"README.md": "/usr/share/minio/README.md"
|
||||||
|
rpm:
|
||||||
|
file_name_template: "{{ .Binary }}-release/rpms/{{ .ProjectName }}-{{ .Version }}.{{ .Arch }}"
|
||||||
|
replacements:
|
||||||
|
amd64: x86_64
|
||||||
|
arm64: aarch64
|
||||||
|
arm: armv7
|
||||||
|
files:
|
||||||
|
"NOTICE": "/usr/share/minio/NOTICE"
|
||||||
|
"CREDITS": "/usr/share/minio/CREDITS"
|
||||||
|
"LICENSE": "/usr/share/minio/LICENSE"
|
||||||
|
"README.md": "/usr/share/minio/README.md"
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
algorithm: sha256
|
||||||
|
|
||||||
|
signs:
|
||||||
|
-
|
||||||
|
signature: "${artifact}.minisig"
|
||||||
|
cmd: "sh"
|
||||||
|
args:
|
||||||
|
- '-c'
|
||||||
|
- 'minisign -s /media/${USER}/minio/minisign.key -qQSm ${artifact} < /media/${USER}/minio/minisign-passphrase'
|
||||||
|
artifacts: all
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^Update yaml files'
|
||||||
|
|
||||||
|
dockers:
|
||||||
|
-
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
dockerfile: Dockerfile.release
|
||||||
|
image_templates:
|
||||||
|
- minio/minio:{{ .Tag }}
|
||||||
|
- minio/minio:latest
|
||||||
|
|
||||||
|
-
|
||||||
|
goos: linux
|
||||||
|
goarch: ppc64le
|
||||||
|
dockerfile: Dockerfile.ppc64le.release
|
||||||
|
image_templates:
|
||||||
|
- minio/minio:{{ .Tag }}-ppc64le
|
||||||
|
|
||||||
|
-
|
||||||
|
goos: linux
|
||||||
|
goarch: s390x
|
||||||
|
dockerfile: Dockerfile.s390x.release
|
||||||
|
image_templates:
|
||||||
|
- minio/minio:{{ .Tag }}-s390x
|
||||||
|
|
||||||
|
-
|
||||||
|
goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
goarm: ''
|
||||||
|
dockerfile: Dockerfile.arm64.release
|
||||||
|
image_templates:
|
||||||
|
- minio/minio:{{ .Tag }}-arm64
|
||||||
|
|
||||||
|
-
|
||||||
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: '7'
|
||||||
|
dockerfile: Dockerfile.arm.release
|
||||||
|
image_templates:
|
||||||
|
- minio/minio:{{ .Tag }}-arm
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"numFilesToCheck": 10,
|
|
||||||
"requiredOrgs": ["minio"]
|
|
||||||
}
|
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
go_import_path: github.com/minio/minio
|
|
||||||
|
|
||||||
language: go
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- shellcheck
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
# this ensures PRs based on a local branch are not built twice
|
|
||||||
# the downside is that a PR targeting a different branch is not built
|
|
||||||
# but as a workaround you can add the branch to this list
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
sudo: required
|
|
||||||
env:
|
|
||||||
- ARCH=x86_64
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
- GO111MODULE=on
|
|
||||||
go: 1.13.x
|
|
||||||
script:
|
|
||||||
- make
|
|
||||||
- diff -au <(gofmt -s -d cmd) <(printf "")
|
|
||||||
- diff -au <(gofmt -s -d pkg) <(printf "")
|
|
||||||
- for d in $(go list ./... | grep -v browser); do CGO_ENABLED=1 go test -v -race --timeout 15m "$d"; done
|
|
||||||
- make verifiers
|
|
||||||
- make crosscompile
|
|
||||||
- make verify
|
|
||||||
- make coverage
|
|
||||||
- cd browser && yarn && yarn test && cd ..
|
|
||||||
- bash -c 'shopt -s globstar; shellcheck mint/**/*.sh'
|
|
||||||
|
|
||||||
- os: windows
|
|
||||||
env:
|
|
||||||
- ARCH=x86_64
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
- GO111MODULE=on
|
|
||||||
go: 1.13.x
|
|
||||||
script:
|
|
||||||
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
|
|
||||||
- bash buildscripts/go-coverage.sh
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
# Add an IPv6 config - see the corresponding Travis issue
|
|
||||||
# https://github.com/travis-ci/travis-ci/issues/8361
|
|
||||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; fi
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then nvm install 11.10.1 ; fi
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# MinIO Contribution Guide [](https://slack.min.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/)
|
# MinIO Contribution Guide [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||||
|
|
||||||
``MinIO`` community welcomes your contribution. To make the process as seamless as possible, we recommend you read this contribution guide.
|
``MinIO`` community welcomes your contribution. To make the process as seamless as possible, we recommend you read this contribution guide.
|
||||||
|
|
||||||
|
|||||||
+7
-6
@@ -1,28 +1,29 @@
|
|||||||
FROM golang:1.13-alpine
|
FROM golang:1.13-alpine as builder
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
ENV CGO_ENABLED 0
|
ENV CGO_ENABLED 0
|
||||||
ENV GO111MODULE on
|
ENV GO111MODULE on
|
||||||
ENV GOPROXY https://proxy.golang.org
|
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache git && \
|
apk add --no-cache git && \
|
||||||
git clone https://github.com/minio/minio && cd minio && \
|
git clone https://github.com/minio/minio && cd minio && \
|
||||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
||||||
|
|
||||||
FROM alpine:3.9
|
FROM alpine:3.12
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
COPY --from=0 /go/bin/minio /usr/bin/minio
|
COPY --from=builder /go/bin/minio /usr/bin/minio
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY --from=builder /go/minio/CREDITS /third_party/
|
||||||
|
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||||
|
FROM arm32v7/alpine:3.12
|
||||||
|
|
||||||
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||||
|
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||||
|
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
||||||
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-arm/minio -o /usr/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-arm/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-arm/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/minio && \
|
||||||
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
CMD ["minio"]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||||
|
FROM arm64v8/alpine:3.12
|
||||||
|
|
||||||
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||||
|
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||||
|
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
||||||
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-arm64/minio -o /usr/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-arm64/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-arm64/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/minio && \
|
||||||
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||||
|
/usr/bin/verify-minio.sh
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
CMD ["minio"]
|
||||||
+3
-1
@@ -1,13 +1,15 @@
|
|||||||
FROM alpine:3.9
|
FROM alpine:3.12
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
COPY minio /usr/bin/
|
COPY minio /usr/bin/
|
||||||
|
COPY CREDITS /third_party/
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM ubuntu
|
||||||
|
|
||||||
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests \
|
||||||
|
git golang make npm && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV PATH=$PATH:/root/go/bin
|
||||||
|
|
||||||
|
RUN go get github.com/go-bindata/go-bindata/go-bindata && \
|
||||||
|
go get github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
|
||||||
+2
-8
@@ -1,17 +1,11 @@
|
|||||||
FROM ubuntu:16.04
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
ENV GOROOT /usr/local/go
|
ENV GOROOT /usr/local/go
|
||||||
|
ENV GOPATH /usr/local/gopath
|
||||||
ENV GOPATH /usr/local
|
|
||||||
|
|
||||||
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
||||||
|
|
||||||
ENV MINT_ROOT_DIR /mint
|
ENV MINT_ROOT_DIR /mint
|
||||||
|
|
||||||
COPY mint /mint
|
COPY mint /mint
|
||||||
|
|
||||||
RUN apt-get --yes update && apt-get --yes upgrade && \
|
RUN apt-get --yes update && apt-get --yes upgrade && \
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
FROM multiarch/qemu-user-static:x86_64-ppc64le as qemu
|
||||||
|
FROM ppc64le/alpine:3.12
|
||||||
|
|
||||||
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
COPY --from=qemu /usr/bin/qemu-ppc64le-static /usr/bin
|
||||||
|
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||||
|
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-ppc64le/minio -o /usr/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-ppc64le/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-ppc64le/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/minio && \
|
||||||
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||||
|
/usr/bin/verify-minio.sh
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
CMD ["minio"]
|
||||||
+14
-17
@@ -1,30 +1,27 @@
|
|||||||
FROM golang:1.13-alpine
|
FROM alpine:3.12
|
||||||
|
|
||||||
ENV GOPATH /go
|
|
||||||
ENV CGO_ENABLED 0
|
|
||||||
ENV GO111MODULE on
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
apk add --no-cache git && \
|
|
||||||
git clone https://github.com/minio/minio
|
|
||||||
|
|
||||||
FROM alpine:3.9
|
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||||
|
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||||
curl https://dl.min.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/minio && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-amd64/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh
|
curl -s -q https://dl.min.io/server/minio/release/linux-amd64/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/minio && \
|
||||||
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||||
|
/usr/bin/verify-minio.sh
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
FROM multiarch/qemu-user-static:x86_64-s390x as qemu
|
||||||
|
FROM s390x/alpine:3.12
|
||||||
|
|
||||||
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
COPY --from=qemu /usr/bin/qemu-s390x-static /usr/bin
|
||||||
|
|
||||||
|
ENV MINIO_UPDATE off
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||||
|
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-s390x/minio -o /usr/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-s390x/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-s390x/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/minio && \
|
||||||
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
|
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||||
|
/usr/bin/verify-minio.sh
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
CMD ["minio"]
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#-------------------------------------------------------------
|
|
||||||
# Stage 1: Build and Unit tests
|
|
||||||
#-------------------------------------------------------------
|
|
||||||
FROM golang:1.13
|
|
||||||
|
|
||||||
COPY . /go/src/github.com/minio/minio
|
|
||||||
WORKDIR /go/src/github.com/minio/minio
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y jq
|
|
||||||
ENV GO111MODULE=on
|
|
||||||
|
|
||||||
RUN git config --global http.cookiefile /gitcookie/.gitcookie
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get -y install sudo
|
|
||||||
RUN touch /etc/sudoers
|
|
||||||
|
|
||||||
RUN echo "root ALL=(ALL) ALL" >> /etc/sudoers
|
|
||||||
RUN echo "ci ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
|
||||||
RUN echo "Defaults env_reset" >> /etc/sudoers
|
|
||||||
RUN echo 'Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go:/usr/local/go/bin"' >> /etc/sudoers
|
|
||||||
|
|
||||||
RUN mkdir -p /home/ci/.cache
|
|
||||||
|
|
||||||
RUN groupadd -g 999 ci && \
|
|
||||||
useradd -r -u 999 -g ci ci && \
|
|
||||||
chown -R ci:ci /go /home/ci && \
|
|
||||||
chmod -R a+rw /go
|
|
||||||
|
|
||||||
USER ci
|
|
||||||
|
|
||||||
# -- tests --
|
|
||||||
RUN make
|
|
||||||
RUN bash -c 'diff -au <(gofmt -s -d cmd) <(printf "")'
|
|
||||||
RUN bash -c 'diff -au <(gofmt -s -d pkg) <(printf "")'
|
|
||||||
RUN for d in $(go list ./... | grep -v browser); do go test -v -race --timeout 20m "$d"; done
|
|
||||||
RUN make verifiers
|
|
||||||
RUN make crosscompile
|
|
||||||
RUN make verify
|
|
||||||
|
|
||||||
#-------------------------------------------------------------
|
|
||||||
# Stage 2: Test Frontend
|
|
||||||
#-------------------------------------------------------------
|
|
||||||
FROM node:10.15-stretch-slim
|
|
||||||
|
|
||||||
COPY browser /minio/browser
|
|
||||||
WORKDIR /minio/browser
|
|
||||||
|
|
||||||
RUN yarn
|
|
||||||
RUN yarn test
|
|
||||||
|
|
||||||
#-------------------------------------------------------------
|
|
||||||
# Stage 3: Run Gateway Tests
|
|
||||||
#-------------------------------------------------------------
|
|
||||||
FROM ubuntu:16.04
|
|
||||||
|
|
||||||
COPY --from=0 /go/src/github.com/minio/minio/minio /usr/bin/minio
|
|
||||||
COPY buildscripts/gateway-tests.sh /usr/bin/gateway-tests.sh
|
|
||||||
COPY mint /mint
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
ENV LANG C.UTF-8
|
|
||||||
ENV GOROOT /usr/local/go
|
|
||||||
ENV GOPATH /usr/local
|
|
||||||
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
|
||||||
ENV MINT_ROOT_DIR /mint
|
|
||||||
|
|
||||||
RUN apt-get --yes update && apt-get --yes upgrade && \
|
|
||||||
apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
|
||||||
cd /mint && /mint/release.sh
|
|
||||||
|
|
||||||
WORKDIR /mint
|
|
||||||
|
|
||||||
RUN /usr/bin/gateway-tests.sh
|
|
||||||
@@ -8,8 +8,6 @@ GOOS := $(shell go env GOOS)
|
|||||||
VERSION ?= $(shell git describe --tags)
|
VERSION ?= $(shell git describe --tags)
|
||||||
TAG ?= "minio/minio:$(VERSION)"
|
TAG ?= "minio/minio:$(VERSION)"
|
||||||
|
|
||||||
BUILD_LDFLAGS := '$(LDFLAGS)'
|
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
checks:
|
checks:
|
||||||
@@ -18,22 +16,12 @@ checks:
|
|||||||
|
|
||||||
getdeps:
|
getdeps:
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@which golint 1>/dev/null || (echo "Installing golint" && GO111MODULE=off go get -u golang.org/x/lint/golint)
|
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
|
||||||
ifeq ($(GOARCH),s390x)
|
|
||||||
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck)
|
|
||||||
else
|
|
||||||
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet https://github.com/dominikh/go-tools/releases/download/2019.2.3/staticcheck_${GOOS}_${GOARCH}.tar.gz && tar xf staticcheck_${GOOS}_${GOARCH}.tar.gz && mv staticcheck/staticcheck ${GOPATH}/bin/staticcheck && chmod +x ${GOPATH}/bin/staticcheck && rm -f staticcheck_${GOOS}_${GOARCH}.tar.gz && rm -rf staticcheck)
|
|
||||||
endif
|
|
||||||
@which misspell 1>/dev/null || (echo "Installing misspell" && GO111MODULE=off go get -u github.com/client9/misspell/cmd/misspell)
|
|
||||||
|
|
||||||
crosscompile:
|
crosscompile:
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
|
|
||||||
verifiers: getdeps vet fmt lint staticcheck spelling
|
verifiers: getdeps fmt lint
|
||||||
|
|
||||||
vet:
|
|
||||||
@echo "Running $@ check"
|
|
||||||
@GO111MODULE=on go vet github.com/minio/minio/...
|
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@@ -42,21 +30,8 @@ fmt:
|
|||||||
|
|
||||||
lint:
|
lint:
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@GO111MODULE=on ${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/cmd/...
|
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
|
||||||
@GO111MODULE=on ${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/pkg/...
|
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
|
||||||
|
|
||||||
staticcheck:
|
|
||||||
@echo "Running $@ check"
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/staticcheck github.com/minio/minio/cmd/...
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/staticcheck github.com/minio/minio/pkg/...
|
|
||||||
|
|
||||||
spelling:
|
|
||||||
@echo "Running $@ check"
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find cmd/`
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find pkg/`
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find docs/`
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find buildscripts/`
|
|
||||||
@GO111MODULE=on ${GOPATH}/bin/misspell -locale US -error `find dockerscripts/`
|
|
||||||
|
|
||||||
# Builds minio, runs the verifiers then runs the tests.
|
# Builds minio, runs the verifiers then runs the tests.
|
||||||
check: test
|
check: test
|
||||||
@@ -64,20 +39,30 @@ test: verifiers build
|
|||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@GO111MODULE=on CGO_ENABLED=0 go test -tags kqueue ./... 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=0 go test -tags kqueue ./... 1>/dev/null
|
||||||
|
|
||||||
verify: build
|
test-race: verifiers build
|
||||||
@echo "Verifying build"
|
@echo "Running unit tests under -race"
|
||||||
|
@(env bash $(PWD)/buildscripts/race.sh)
|
||||||
|
|
||||||
|
# Verify minio binary
|
||||||
|
verify:
|
||||||
|
@echo "Verifying build with race"
|
||||||
|
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||||
|
|
||||||
coverage: build
|
# Verify healing of disks with minio binary
|
||||||
@echo "Running all coverage for minio"
|
verify-healing:
|
||||||
@(env bash $(PWD)/buildscripts/go-coverage.sh)
|
@echo "Verify healing build with race"
|
||||||
|
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
||||||
|
|
||||||
# Builds minio locally.
|
# Builds minio locally.
|
||||||
build: checks
|
build: checks
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|
||||||
docker: build
|
docker: checks
|
||||||
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
|
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||||
|
|
||||||
# Builds minio and installs it to $GOPATH/bin.
|
# Builds minio and installs it to $GOPATH/bin.
|
||||||
@@ -93,3 +78,4 @@ clean:
|
|||||||
@rm -rvf minio
|
@rm -rvf minio
|
||||||
@rm -rvf build
|
@rm -rvf build
|
||||||
@rm -rvf release
|
@rm -rvf release
|
||||||
|
@rm -rvf .verify*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
MinIO Cloud Storage, (C) 2014,2015 MinIO, Inc.
|
MinIO Cloud Storage, (C) 2014-2020 MinIO, Inc.
|
||||||
|
|
||||||
This product includes software developed at MinIO, Inc.
|
This product includes software developed at MinIO, Inc.
|
||||||
(https://min.io/).
|
(https://min.io/).
|
||||||
@@ -6,4 +6,4 @@ This product includes software developed at MinIO, Inc.
|
|||||||
The MinIO project contains unmodified/modified subcomponents too with
|
The MinIO project contains unmodified/modified subcomponents too with
|
||||||
separate copyright notices and license terms. Your use of the source
|
separate copyright notices and license terms. Your use of the source
|
||||||
code for these subcomponents is subject to the terms and conditions
|
code for these subcomponents is subject to the terms and conditions
|
||||||
of the following licenses.
|
of Apache License Version 2.0
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
# MinIO Quickstart Guide
|
# MinIO Quickstart Guide
|
||||||
[](https://slack.min.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/)
|
[](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||||
|
|
||||||
MinIO is an object storage server released under Apache License v2.0. It is compatible[1] with Amazon S3 cloud storage service. It is best suited for storing unstructured data such as photos, videos, log files, backups and container / VM images. Size of an object can range from a few KBs to a maximum of 5TB.
|
[](https://min.io)
|
||||||
|
|
||||||
MinIO server is light enough to be bundled with the application stack, similar to NodeJS, Redis and MySQL.
|
MinIO is a High Performance Object Storage released under Apache License v2.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
||||||
|
|
||||||
[1] MinIO in its default mode is faster and does not calculate MD5Sum unless passed by client. This may lead to incompatibility with some S3 clients like s3ql that heavily depend on MD5Sum. For such applications start MinIO with `--compat` option.
|
|
||||||
```sh
|
|
||||||
minio --compat server /data
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker Container
|
## Docker Container
|
||||||
### Stable
|
### Stable
|
||||||
```
|
```
|
||||||
docker pull minio/minio
|
docker run -p 9000:9000 \
|
||||||
docker run -p 9000:9000 minio/minio server /data
|
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||||
|
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||||
|
minio/minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
### Edge
|
### Edge
|
||||||
```
|
```
|
||||||
docker pull minio/minio:edge
|
docker run -p 9000:9000 \
|
||||||
docker run -p 9000:9000 minio/minio:edge server /data
|
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||||
|
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||||
|
minio/minio:edge server /data
|
||||||
```
|
```
|
||||||
Note: Docker will not display the autogenerated keys unless you start the container with the `-it`(interactive TTY) argument. Generally, it is not recommended to use autogenerated keys with containers. Please visit MinIO Docker quickstart guide for more information [here](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
|
||||||
|
> NOTE: Docker will not display the default keys unless you start the container with the `-it`(interactive TTY) argument. Generally, it is not recommended to use default keys with containers. Please visit MinIO Docker quickstart guide for more information [here](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
### Homebrew
|
### Homebrew (recommended)
|
||||||
Install minio packages using [Homebrew](http://brew.sh/)
|
Install minio packages using [Homebrew](https://brew.sh/)
|
||||||
```sh
|
```sh
|
||||||
brew install minio/stable/minio
|
brew install minio/stable/minio
|
||||||
minio server /data
|
minio server /data
|
||||||
@@ -39,8 +39,8 @@ brew install minio/stable/minio
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Binary Download
|
### Binary Download
|
||||||
| Platform | Architecture | URL |
|
| Platform | Architecture | URL |
|
||||||
| ---------- | -------- | ------ |
|
| ---------- | -------- | ------ |
|
||||||
| Apple macOS | 64-bit Intel | https://dl.min.io/server/minio/release/darwin-amd64/minio |
|
| Apple macOS | 64-bit Intel | https://dl.min.io/server/minio/release/darwin-amd64/minio |
|
||||||
```sh
|
```sh
|
||||||
chmod 755 minio
|
chmod 755 minio
|
||||||
@@ -49,8 +49,8 @@ chmod 755 minio
|
|||||||
|
|
||||||
## GNU/Linux
|
## GNU/Linux
|
||||||
### Binary Download
|
### Binary Download
|
||||||
| Platform | Architecture | URL |
|
| Platform | Architecture | URL |
|
||||||
| ---------- | -------- | ------ |
|
| ---------- | -------- | ------ |
|
||||||
| GNU/Linux | 64-bit Intel | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
| GNU/Linux | 64-bit Intel | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
||||||
```sh
|
```sh
|
||||||
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||||
@@ -58,8 +58,8 @@ chmod +x minio
|
|||||||
./minio server /data
|
./minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
| Platform | Architecture | URL |
|
| Platform | Architecture | URL |
|
||||||
| ---------- | -------- | ------ |
|
| ---------- | -------- | ------ |
|
||||||
| GNU/Linux | ppc64le | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
| GNU/Linux | ppc64le | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
||||||
```sh
|
```sh
|
||||||
wget https://dl.min.io/server/minio/release/linux-ppc64le/minio
|
wget https://dl.min.io/server/minio/release/linux-ppc64le/minio
|
||||||
@@ -69,8 +69,8 @@ chmod +x minio
|
|||||||
|
|
||||||
## Microsoft Windows
|
## Microsoft Windows
|
||||||
### Binary Download
|
### Binary Download
|
||||||
| Platform | Architecture | URL |
|
| Platform | Architecture | URL |
|
||||||
| ---------- | -------- | ------ |
|
| ---------- | -------- | ------ |
|
||||||
| Microsoft Windows | 64-bit | https://dl.min.io/server/minio/release/windows-amd64/minio.exe |
|
| Microsoft Windows | 64-bit | https://dl.min.io/server/minio/release/windows-amd64/minio.exe |
|
||||||
```sh
|
```sh
|
||||||
minio.exe server D:\Photos
|
minio.exe server D:\Photos
|
||||||
@@ -78,7 +78,7 @@ minio.exe server D:\Photos
|
|||||||
|
|
||||||
## FreeBSD
|
## FreeBSD
|
||||||
### Port
|
### Port
|
||||||
Install minio packages using [pkg](https://github.com/freebsd/pkg)
|
Install minio packages using [pkg](https://github.com/freebsd/pkg), MinIO doesn't officially build FreeBSD binaries but is maintained by FreeBSD upstream [here](https://www.freshports.org/www/minio).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg install minio
|
pkg install minio
|
||||||
@@ -88,7 +88,7 @@ service minio start
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Install from Source
|
## Install from Source
|
||||||
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.12](https://golang.org/dl/#stable)
|
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.13](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GO111MODULE=on go get github.com/minio/minio
|
GO111MODULE=on go get github.com/minio/minio
|
||||||
@@ -98,23 +98,6 @@ GO111MODULE=on go get github.com/minio/minio
|
|||||||
|
|
||||||
By default MinIO uses the port 9000 to listen for incoming connections. If your platform blocks the port by default, you may need to enable access to the port.
|
By default MinIO uses the port 9000 to listen for incoming connections. If your platform blocks the port by default, you may need to enable access to the port.
|
||||||
|
|
||||||
### iptables
|
|
||||||
|
|
||||||
For hosts with iptables enabled (RHEL, CentOS, etc), you can use `iptables` command to enable all traffic coming to specific ports. Use below command to allow
|
|
||||||
access to port 9000
|
|
||||||
|
|
||||||
```sh
|
|
||||||
iptables -A INPUT -p tcp --dport 9000 -j ACCEPT
|
|
||||||
service iptables restart
|
|
||||||
```
|
|
||||||
|
|
||||||
Below command enables all incoming traffic to ports ranging from 9000 to 9010.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
|
||||||
service iptables restart
|
|
||||||
```
|
|
||||||
|
|
||||||
### ufw
|
### ufw
|
||||||
|
|
||||||
For hosts with ufw enabled (Debian based distros), you can use `ufw` command to allow traffic to specific ports. Use below command to allow access to port 9000
|
For hosts with ufw enabled (Debian based distros), you can use `ufw` command to allow traffic to specific ports. Use below command to allow access to port 9000
|
||||||
@@ -149,6 +132,23 @@ Note that `permanent` makes sure the rules are persistent across firewall start,
|
|||||||
firewall-cmd --reload
|
firewall-cmd --reload
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### iptables
|
||||||
|
|
||||||
|
For hosts with iptables enabled (RHEL, CentOS, etc), you can use `iptables` command to enable all traffic coming to specific ports. Use below command to allow
|
||||||
|
access to port 9000
|
||||||
|
|
||||||
|
```sh
|
||||||
|
iptables -A INPUT -p tcp --dport 9000 -j ACCEPT
|
||||||
|
service iptables restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Below command enables all incoming traffic to ports ranging from 9000 to 9010.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
||||||
|
service iptables restart
|
||||||
|
```
|
||||||
|
|
||||||
## Test using MinIO Browser
|
## Test using MinIO Browser
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully.
|
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully.
|
||||||
|
|
||||||
@@ -163,20 +163,23 @@ When deployed on a single drive, MinIO server lets clients access any pre-existi
|
|||||||
The above statement is also valid for all gateway backends.
|
The above statement is also valid for all gateway backends.
|
||||||
|
|
||||||
## Upgrading MinIO
|
## Upgrading MinIO
|
||||||
MinIO server supports rolling upgrades, i.e. you can update one MinIO instance at a time in a distributed cluster. This allows upgrades with no downtime. Upgrades can be done manually by replacing the binary with the latest release and restarting all servers in a rolling fashion. However, we recommend all our users to use [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) from the client. This will update all the nodes in the cluster and restart them, as shown in the following command from the MinIO client (mc):
|
MinIO server supports rolling upgrades, i.e. you can update one MinIO instance at a time in a distributed cluster. This allows upgrades with no downtime. Upgrades can be done manually by replacing the binary with the latest release and restarting all servers in a rolling fashion. However, we recommend all our users to use [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) from the client. This will update all the nodes in the cluster simultaneously and restart them, as shown in the following command from the MinIO client (mc):
|
||||||
|
|
||||||
```
|
```
|
||||||
mc admin update <minio alias, e.g., myminio>
|
mc admin update <minio alias, e.g., myminio>
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important things to remember during upgrades**:
|
> NOTE: some releases might not allow rolling upgrades, this is always called out in the release notes and it is generally advised to read release notes before upgrading. In such a situation `mc admin update` is the recommended upgrading mechanism to upgrade all servers at once.
|
||||||
|
|
||||||
|
### Important things to remember during MinIO upgrades
|
||||||
|
|
||||||
- `mc admin update` will only work if the user running MinIO has write access to the parent directory where the binary is located, for example if the current binary is at `/usr/local/bin/minio`, you would need write access to `/usr/local/bin`.
|
- `mc admin update` will only work if the user running MinIO has write access to the parent directory where the binary is located, for example if the current binary is at `/usr/local/bin/minio`, you would need write access to `/usr/local/bin`.
|
||||||
- In the case of federated setups `mc admin update` should be run against each cluster individually. Avoid updating `mc` until all clusters have been updated.
|
- `mc admin update` updates and restarts all servers simultaneously, applications would retry and continue their respective operations upon upgrade.
|
||||||
- If you are updating the server it is always recommended (unless explicitly mentioned in MinIO server release notes), to update `mc` once all the servers have been upgraded using `mc update`.
|
- `mc admin update` is disabled in kubernetes/container environments, container environments provide their own mechanisms to rollout of updates.
|
||||||
- `mc admin update` is disabled in docker/container environments, container environments provide their own mechanisms for updating running containers.
|
- In the case of federated setups `mc admin update` should be run against each cluster individually. Avoid updating `mc` to any new releases until all clusters have been successfully updated.
|
||||||
- If you are using Vault as KMS with MinIO, ensure you have followed the Vault upgrade procedure outlined here: https://www.vaultproject.io/docs/upgrading/index.html
|
- If using `kes` as KMS with MinIO, just replace the binary and restart `kes` more information about `kes` can be found [here](https://github.com/minio/kes/wiki)x
|
||||||
- If you are using etcd with MinIO for the federation, ensure you have followed the etcd upgrade procedure outlined here: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
- If using Vault as KMS with MinIO, ensure you have followed the Vault upgrade procedure outlined here: https://www.vaultproject.io/docs/upgrading/index.html
|
||||||
|
- If using etcd with MinIO for the federation, ensure you have followed the etcd upgrade procedure outlined here: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
||||||
|
|
||||||
## Explore Further
|
## Explore Further
|
||||||
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
||||||
@@ -190,4 +193,4 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fminio%2Fminio?ref=badge_large)
|
Use of MinIO is governed by the Apache 2.0 License found at [LICENSE](./LICENSE).
|
||||||
|
|||||||
+104
-14
@@ -1,4 +1,4 @@
|
|||||||
# MinIO Quickstart Guide [](https://slack.min.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/)
|
# MinIO Quickstart Guide [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||||
|
|
||||||
MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
|
MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
|
||||||
|
|
||||||
@@ -7,29 +7,33 @@ MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合
|
|||||||
## Docker 容器
|
## Docker 容器
|
||||||
### 稳定版
|
### 稳定版
|
||||||
```
|
```
|
||||||
docker pull minio/minio
|
docker run -p 9000:9000 \
|
||||||
docker run -p 9000:9000 minio/minio server /data
|
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||||
|
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||||
|
minio/minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
### 尝鲜版
|
### 尝鲜版
|
||||||
```
|
```
|
||||||
docker pull minio/minio:edge
|
docker run -p 9000:9000 \
|
||||||
docker run -p 9000:9000 minio/minio:edge server /data
|
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||||
|
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||||
|
minio/minio:edge server /data
|
||||||
```
|
```
|
||||||
更多Docker部署信息请访问 [这里](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
|
||||||
|
> 提示:除非你通过`-it`(TTY交互)参数启动容器,否则Docker将不会显示默认的密钥。一般情况下,并不推荐使用容器的默认密钥,更多Docker部署信息请访问 [这里](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
### Homebrew
|
### Homebrew(推荐)
|
||||||
使用 [Homebrew](http://brew.sh/)安装minio
|
使用 [Homebrew](http://brew.sh/)安装minio
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew install minio/stable/minio
|
brew install minio/stable/minio
|
||||||
minio server /data
|
minio server /data
|
||||||
```
|
```
|
||||||
#### Note
|
|
||||||
如果你之前使用 `brew install minio`安装过minio, 可以用 `minio/stable/minio` 官方镜像进行重装. 由于golang 1.8的bug,homebrew版本不太稳定。
|
|
||||||
|
|
||||||
```
|
> 提示:如果你之前使用 `brew install minio`安装过minio, 可以用 `minio/stable/minio` 官方镜像进行重装. 由于golang 1.8的bug,homebrew版本不太稳定。
|
||||||
|
```sh
|
||||||
brew uninstall minio
|
brew uninstall minio
|
||||||
brew install minio/stable/minio
|
brew install minio/stable/minio
|
||||||
```
|
```
|
||||||
@@ -49,6 +53,16 @@ chmod 755 minio
|
|||||||
| ---------- | -------- | ------ |
|
| ---------- | -------- | ------ |
|
||||||
| GNU/Linux | 64-bit Intel | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
| GNU/Linux | 64-bit Intel | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
||||||
```sh
|
```sh
|
||||||
|
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||||
|
chmod +x minio
|
||||||
|
./minio server /data
|
||||||
|
```
|
||||||
|
|
||||||
|
| 操作系统 | CPU架构 | 地址 |
|
||||||
|
| ---------- | -------- | ------ |
|
||||||
|
| GNU/Linux | ppc64le | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
||||||
|
```sh
|
||||||
|
wget https://dl.min.io/server/minio/release/linux-ppc64le/minio
|
||||||
chmod +x minio
|
chmod +x minio
|
||||||
./minio server /data
|
./minio server /data
|
||||||
```
|
```
|
||||||
@@ -64,7 +78,7 @@ minio.exe server D:\Photos
|
|||||||
|
|
||||||
## FreeBSD
|
## FreeBSD
|
||||||
### Port
|
### Port
|
||||||
使用 [pkg](https://github.com/freebsd/pkg)进行安装。
|
使用 [pkg](https://github.com/freebsd/pkg)进行安装,, MinIO官方并没有提供FreeBSD二进制文件, 它由FreeBSD上游维护,点击 [这里](https://www.freshports.org/www/minio)查看。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg install minio
|
pkg install minio
|
||||||
@@ -75,14 +89,68 @@ service minio start
|
|||||||
|
|
||||||
## 使用源码安装
|
## 使用源码安装
|
||||||
|
|
||||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install).
|
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install)。最低需要Golang版本为 [go1.13](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get -u github.com/minio/minio
|
GO111MODULE=on go get github.com/minio/minio
|
||||||
|
```
|
||||||
|
|
||||||
|
## 为防火墙设置允许访问的端口
|
||||||
|
|
||||||
|
默认情况下,MinIO 使用端口9000来侦听传入的连接。如果你的平台默认阻止了该端口,则需要启用对该端口的访问。
|
||||||
|
|
||||||
|
### ufw
|
||||||
|
|
||||||
|
对于启用了ufw的主机(基于Debian的发行版), 你可以通过`ufw`命令允许指定端口上的所有流量连接. 通过如下命令允许访问端口9000
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ufw allow 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
如下命令允许端口9000-9010上的所有传入流量。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ufw allow 9000:9010/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
### firewall-cmd
|
||||||
|
|
||||||
|
对于启用了firewall-cmd的主机(CentOS), 你可以通过`firewall-cmd`命令允许指定端口上的所有流量连接。 通过如下命令允许访问端口9000
|
||||||
|
|
||||||
|
```sh
|
||||||
|
firewall-cmd --get-active-zones
|
||||||
|
```
|
||||||
|
|
||||||
|
这个命令获取当前正在使用的区域。 现在,就可以为以上返回的区域应用端口规则了。 假如返回的区域是 `public`, 使用如下命令
|
||||||
|
|
||||||
|
```sh
|
||||||
|
firewall-cmd --zone=public --add-port=9000/tcp --permanent
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的`permanent`参数表示持久化存储规则,可用于防火墙启动、重启和重新加载。 最后,需要防火墙重新加载,让我们刚刚的修改生效。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### iptables
|
||||||
|
|
||||||
|
对于启用了iptables的主机(RHEL, CentOS, etc), 你可以通过`iptables`命令允许指定端口上的所有流量连接。 通过如下命令允许访问端口9000
|
||||||
|
|
||||||
|
```sh
|
||||||
|
iptables -A INPUT -p tcp --dport 9000 -j ACCEPT
|
||||||
|
service iptables restart
|
||||||
|
```
|
||||||
|
|
||||||
|
如下命令允许端口9000-9010上的所有传入流量。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
||||||
|
service iptables restart
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用MinIO浏览器进行验证
|
## 使用MinIO浏览器进行验证
|
||||||
安装后使用浏览器访问[http://127.0.0.1:9000](http://127.0.0.1:9000),如果可以访问,则表示minio已经安装成功。
|
MinIO Server带有一个嵌入的Web对象浏览器,安装后使用浏览器访问[http://127.0.0.1:9000](http://127.0.0.1:9000),如果可以访问,则表示minio已经安装成功。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -94,6 +162,25 @@ go get -u github.com/minio/minio
|
|||||||
|
|
||||||
上述描述对所有网关后端同样有效。
|
上述描述对所有网关后端同样有效。
|
||||||
|
|
||||||
|
## 升级 MinIO
|
||||||
|
MinIO 服务端支持滚动升级, 也就是说你可以一次更新分布式集群中的一个MinIO实例。 这样可以在不停机的情况下进行升级。可以通过将二进制文件替换为最新版本并以滚动方式重新启动所有服务器来手动完成升级。但是, 我们建议所有用户从客户端使用 [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) 命令升级。 这将同时更新集群中的所有节点并重新启动它们, 如下命令所示:
|
||||||
|
|
||||||
|
```
|
||||||
|
mc admin update <minio alias, e.g., myminio>
|
||||||
|
```
|
||||||
|
|
||||||
|
> 注意: 有些发行版可能不允许滚动升级,这通常在发行说明中提到,所以建议在升级之前阅读发行说明。在这种情况下,建议使用`mc admin update`升级机制来一次升级所有服务器。
|
||||||
|
|
||||||
|
### MinIO升级时要记住的重要事项
|
||||||
|
|
||||||
|
- `mc admin update` 命令仅当运行MinIO的用户对二进制文件所在的父目录具有写权限时才工作, 比如当前二进制文件位于`/usr/local/bin/minio`, 你需要具备`/usr/local/bin`目录的写权限.
|
||||||
|
- `mc admin update` 命令同时更新并重新启动所有服务器,应用程序将在升级后重试并继续各自的操作。
|
||||||
|
- `mc admin update` 命令在 kubernetes/container 环境下是不能用的, 容器环境提供了它自己的更新机制来更新。
|
||||||
|
- 对于联盟部署模式,应分别针对每个群集运行`mc admin update`。 在成功更新所有群集之前,不要将`mc`更新为任何新版本。
|
||||||
|
- 如果将`kes`用作MinIO的KMS,只需替换二进制文件并重新启动`kes`,可以在 [这里](https://github.com/minio/kes/wiki) 找到有关`kes`的更多信息。
|
||||||
|
- 如果将Vault作为MinIO的KMS,请确保已遵循如下Vault升级过程的概述:https://www.vaultproject.io/docs/upgrading/index.html
|
||||||
|
- 如果将MindIO与etcd配合使用, 请确保已遵循如下etcd升级过程的概述: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
||||||
|
|
||||||
## 了解更多
|
## 了解更多
|
||||||
- [MinIO纠删码入门](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
- [MinIO纠删码入门](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
||||||
- [`mc`快速入门](https://docs.min.io/docs/minio-client-quickstart-guide)
|
- [`mc`快速入门](https://docs.min.io/docs/minio-client-quickstart-guide)
|
||||||
@@ -104,3 +191,6 @@ go get -u github.com/minio/minio
|
|||||||
|
|
||||||
## 如何参与到MinIO项目
|
## 如何参与到MinIO项目
|
||||||
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加到MinIO项目中。
|
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加到MinIO项目中。
|
||||||
|
|
||||||
|
## 授权许可
|
||||||
|
MinIO的使用受 Apache 2.0 License 约束,你可以在 [LICENSE](./LICENSE) 查看许可。
|
||||||
|
|||||||
+11
-11
@@ -8,12 +8,12 @@ Whenever there is a security update you just need to upgrade to the latest versi
|
|||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
All security bugs in [minio/minio](https://github,com/minio/minio) (or other minio/* repositories)
|
All security bugs in [minio/minio](https://github,com/minio/minio) (or other minio/* repositories)
|
||||||
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
|
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
|
||||||
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
|
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
|
||||||
in handling your report.
|
in handling your report.
|
||||||
|
|
||||||
Please, provide a detailed explanation of the issue. In particular, outline the type of the security
|
Please, provide a detailed explanation of the issue. In particular, outline the type of the security
|
||||||
issue (DoS, authentication bypass, information disclouse, ...) and the assumptions you're making (e.g. do
|
issue (DoS, authentication bypass, information disclose, ...) and the assumptions you're making (e.g. do
|
||||||
you need access credentials for a successful exploit).
|
you need access credentials for a successful exploit).
|
||||||
|
|
||||||
If you have not received a reply to your email within 48 hours or you have not heard from the security team
|
If you have not received a reply to your email within 48 hours or you have not heard from the security team
|
||||||
@@ -28,14 +28,14 @@ MinIO uses the following disclosure process:
|
|||||||
|
|
||||||
1. Once the security report is received one member of the security team tries to verify and reproduce
|
1. Once the security report is received one member of the security team tries to verify and reproduce
|
||||||
the issue and determines the impact it has.
|
the issue and determines the impact it has.
|
||||||
2. A member of the security team will respond and either confrim or reject the security report.
|
2. A member of the security team will respond and either confirm or reject the security report.
|
||||||
If the report is rejected the repsonse explains why.
|
If the report is rejected the response explains why.
|
||||||
3. Code is audited to find any potential similar problems.
|
3. Code is audited to find any potential similar problems.
|
||||||
4. Fixes are prepared for the latest release.
|
4. Fixes are prepared for the latest release.
|
||||||
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
|
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
|
||||||
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
|
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
|
||||||
the security issue. By default MinIO will **not** publish this information to protect your privacy.
|
the security issue. By default MinIO will **not** publish this information to protect your privacy.
|
||||||
|
|
||||||
This process can take some time, especially when coordination is required with maintainers of other projects.
|
This process can take some time, especially when coordination is required with maintainers of other projects.
|
||||||
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we
|
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we
|
||||||
follow the process described above to ensure that disclosures are handled consistently.
|
follow the process described above to ensure that disclosures are handled consistently.
|
||||||
|
|||||||
+110
-3
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
``MinIO Browser`` provides minimal set of UI to manage buckets and objects on ``minio`` server. ``MinIO Browser`` is written in javascript and released under [Apache 2.0 License](./LICENSE).
|
``MinIO Browser`` provides minimal set of UI to manage buckets and objects on ``minio`` server. ``MinIO Browser`` is written in javascript and released under [Apache 2.0 License](./LICENSE).
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Install node
|
### Install node
|
||||||
@@ -11,6 +12,11 @@ exec -l $SHELL
|
|||||||
nvm install stable
|
nvm install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Install node dependencies
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
### Install `go-bindata` and `go-bindata-assetfs`
|
### Install `go-bindata` and `go-bindata-assetfs`
|
||||||
|
|
||||||
If you do not have a working Golang environment, please follow [Install Golang](https://golang.org/doc/install)
|
If you do not have a working Golang environment, please follow [Install Golang](https://golang.org/doc/install)
|
||||||
@@ -30,13 +36,16 @@ npm run release
|
|||||||
|
|
||||||
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
||||||
|
|
||||||
|
|
||||||
|
## Run MinIO Browser with live reload
|
||||||
|
|
||||||
### Run MinIO Browser with live reload
|
### Run MinIO Browser with live reload
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application
|
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application.
|
||||||
|
|
||||||
### Run MinIO Browser with live reload on custom port
|
### Run MinIO Browser with live reload on custom port
|
||||||
|
|
||||||
@@ -54,7 +63,7 @@ index 3ccdaba..9496c56 100644
|
|||||||
+ port: 8888,
|
+ port: 8888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/minio/webrpc': {
|
'/minio/webrpc': {
|
||||||
target: 'http://localhost:9000',
|
target: 'http://localhost:9000',
|
||||||
@@ -97,7 +98,7 @@ var exports = {
|
@@ -97,7 +98,7 @@ var exports = {
|
||||||
if (process.env.NODE_ENV === 'dev') {
|
if (process.env.NODE_ENV === 'dev') {
|
||||||
exports.entry = [
|
exports.entry = [
|
||||||
@@ -70,4 +79,102 @@ index 3ccdaba..9496c56 100644
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application
|
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application.
|
||||||
|
|
||||||
|
### Run MinIO Browser with live reload on any IP
|
||||||
|
|
||||||
|
Edit `browser/webpack.config.js`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
diff --git a/browser/webpack.config.js b/browser/webpack.config.js
|
||||||
|
index 8bdbba53..139f6049 100644
|
||||||
|
--- a/browser/webpack.config.js
|
||||||
|
+++ b/browser/webpack.config.js
|
||||||
|
@@ -71,6 +71,7 @@ var exports = {
|
||||||
|
historyApiFallback: {
|
||||||
|
index: '/minio/'
|
||||||
|
},
|
||||||
|
+ host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/minio/webrpc': {
|
||||||
|
target: 'http://localhost:9000',
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://IP:8080/minio/](http://IP:8080/minio/) in your browser to play with the application.
|
||||||
|
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
npm run test
|
||||||
|
|
||||||
|
|
||||||
|
## Docker development environment
|
||||||
|
|
||||||
|
This approach will download the sources on your machine such that you are able to use your IDE or editor of choice.
|
||||||
|
A Docker container will be used in order to provide a controlled build environment without messing with your host system.
|
||||||
|
|
||||||
|
### Prepare host system
|
||||||
|
|
||||||
|
Install [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker](https://docs.docker.com/get-docker/).
|
||||||
|
|
||||||
|
### Development within container
|
||||||
|
|
||||||
|
Prepare and build container
|
||||||
|
```
|
||||||
|
git clone git@github.com:minio/minio.git
|
||||||
|
cd minio
|
||||||
|
docker build -t minio-dev -f Dockerfile.dev.browser .
|
||||||
|
```
|
||||||
|
|
||||||
|
Run container, build and run core
|
||||||
|
```sh
|
||||||
|
docker run -it --rm --name minio-dev -v "$PWD":/minio minio-dev
|
||||||
|
|
||||||
|
cd /minio/browser
|
||||||
|
npm install
|
||||||
|
npm run release
|
||||||
|
cd /minio
|
||||||
|
make
|
||||||
|
./minio server /data
|
||||||
|
```
|
||||||
|
Note `Endpoint` IP (the one which is _not_ `127.0.0.1`), `AccessKey` and `SecretKey` (both default to `minioadmin`) in order to enter them in the browser later.
|
||||||
|
|
||||||
|
|
||||||
|
Open another terminal.
|
||||||
|
Connect to container
|
||||||
|
```sh
|
||||||
|
docker exec -it minio-dev bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply patch to allow access from outside container
|
||||||
|
```sh
|
||||||
|
cd /minio
|
||||||
|
git apply --ignore-whitespace <<EOF
|
||||||
|
diff --git a/browser/webpack.config.js b/browser/webpack.config.js
|
||||||
|
index 8bdbba53..139f6049 100644
|
||||||
|
--- a/browser/webpack.config.js
|
||||||
|
+++ b/browser/webpack.config.js
|
||||||
|
@@ -71,6 +71,7 @@ var exports = {
|
||||||
|
historyApiFallback: {
|
||||||
|
index: '/minio/'
|
||||||
|
},
|
||||||
|
+ host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/minio/webrpc': {
|
||||||
|
target: 'http://localhost:9000',
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and run frontend with auto-reload
|
||||||
|
```sh
|
||||||
|
cd /minio/browser
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://IP:8080/minio/](http://IP:8080/minio/) in your browser to play with the application.
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import React from "react"
|
|||||||
import { Route, Switch, Redirect } from "react-router-dom"
|
import { Route, Switch, Redirect } from "react-router-dom"
|
||||||
import Browser from "./browser/Browser"
|
import Browser from "./browser/Browser"
|
||||||
import Login from "./browser/Login"
|
import Login from "./browser/Login"
|
||||||
|
import OpenIDLogin from "./browser/OpenIDLogin"
|
||||||
import web from "./web"
|
import web from "./web"
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Route path={"/login/openid"} component={OpenIDLogin} />
|
||||||
<Route path={"/login"} component={Login} />
|
<Route path={"/login"} component={Login} />
|
||||||
<Route path={"/:bucket?/*"} component={Browser} />
|
<Route path={"/:bucket?/*"} component={Browser} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { Modal } from "react-bootstrap"
|
|||||||
import logo from "../../img/logo.svg"
|
import logo from "../../img/logo.svg"
|
||||||
|
|
||||||
export const AboutModal = ({ serverInfo, hideAbout }) => {
|
export const AboutModal = ({ serverInfo, hideAbout }) => {
|
||||||
const { version, memory, platform, runtime } = serverInfo
|
const { version, platform, runtime } = serverInfo
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="modal-about modal-dark"
|
className="modal-about modal-dark"
|
||||||
@@ -42,10 +42,6 @@ export const AboutModal = ({ serverInfo, hideAbout }) => {
|
|||||||
<div>Version</div>
|
<div>Version</div>
|
||||||
<small>{version}</small>
|
<small>{version}</small>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<div>Memory</div>
|
|
||||||
<small>{memory}</small>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<div>Platform</div>
|
<div>Platform</div>
|
||||||
<small>{platform}</small>
|
<small>{platform}</small>
|
||||||
|
|||||||
@@ -80,13 +80,6 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
|
|
||||||
generateAuth(e) {
|
generateAuth(e) {
|
||||||
const { serverInfo } = this.props
|
const { serverInfo } = this.props
|
||||||
// Generate random access key only for root user
|
|
||||||
if (!serverInfo.userInfo.isIAMUser) {
|
|
||||||
this.setState({
|
|
||||||
newAccessKey: getRandomAccessKey()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
newSecretKey: getRandomSecretKey(),
|
newSecretKey: getRandomSecretKey(),
|
||||||
newSecretKeyVisible: true
|
newSecretKeyVisible: true
|
||||||
@@ -95,15 +88,16 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
|
|
||||||
canChangePassword() {
|
canChangePassword() {
|
||||||
const { serverInfo } = this.props
|
const { serverInfo } = this.props
|
||||||
// Password change is not allowed in WORM mode
|
// Password change is not allowed for temporary users(STS)
|
||||||
if (serverInfo.info.isWorm) {
|
if(serverInfo.userInfo.isTempUser) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// When credentials are set on ENV, password change not allowed for owner
|
// Password change is only allowed for regular users
|
||||||
if (serverInfo.info.isEnvCreds && !serverInfo.userInfo.isIAMUser) {
|
if (!serverInfo.userInfo.isIAMUser) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,24 +180,6 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="has-toggle-password m-t-30">
|
<div className="has-toggle-password m-t-30">
|
||||||
{!serverInfo.userInfo.isIAMUser && (
|
|
||||||
<InputGroup
|
|
||||||
value={this.state.newAccessKey}
|
|
||||||
id="newAccessKey"
|
|
||||||
label={"New Access Key"}
|
|
||||||
name="newAccesskey"
|
|
||||||
type="text"
|
|
||||||
spellCheck="false"
|
|
||||||
required="required"
|
|
||||||
autoComplete="false"
|
|
||||||
align="ig-left"
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({ newAccessKey: e.target.value })
|
|
||||||
}}
|
|
||||||
readonly={serverInfo.userInfo.isIAMUser}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<i
|
<i
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -16,20 +16,22 @@
|
|||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import classNames from "classnames"
|
|
||||||
import logo from "../../img/logo.svg"
|
import logo from "../../img/logo.svg"
|
||||||
import Alert from "../alert/Alert"
|
import Alert from "../alert/Alert"
|
||||||
import * as actionsAlert from "../alert/actions"
|
import * as actionsAlert from "../alert/actions"
|
||||||
import InputGroup from "./InputGroup"
|
import InputGroup from "./InputGroup"
|
||||||
import web from "../web"
|
import web from "../web"
|
||||||
import { Redirect } from "react-router-dom"
|
import { Redirect, Link } from "react-router-dom"
|
||||||
|
import OpenIDLoginButton from './OpenIDLoginButton'
|
||||||
|
|
||||||
export class Login extends React.Component {
|
export class Login extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
accessKey: "",
|
accessKey: "",
|
||||||
secretKey: ""
|
secretKey: "",
|
||||||
|
discoveryDoc: {},
|
||||||
|
clientId: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +85,15 @@ export class Login extends React.Component {
|
|||||||
document.body.classList.add("is-guest")
|
document.body.classList.add("is-guest")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
web.GetDiscoveryDoc().then(({ DiscoveryDoc, clientId }) => {
|
||||||
|
this.setState({
|
||||||
|
clientId,
|
||||||
|
discoveryDoc: DiscoveryDoc
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.body.classList.remove("is-guest")
|
document.body.classList.remove("is-guest")
|
||||||
}
|
}
|
||||||
@@ -95,6 +106,8 @@ export class Login extends React.Component {
|
|||||||
let alertBox = <Alert {...alert} onDismiss={clearAlert} />
|
let alertBox = <Alert {...alert} onDismiss={clearAlert} />
|
||||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||||
if (!alert.message) alertBox = ""
|
if (!alert.message) alertBox = ""
|
||||||
|
|
||||||
|
const showOpenID = Boolean(this.state.discoveryDoc && this.state.discoveryDoc.authorization_endpoint)
|
||||||
return (
|
return (
|
||||||
<div className="login">
|
<div className="login">
|
||||||
{alertBox}
|
{alertBox}
|
||||||
@@ -122,12 +135,32 @@ export class Login extends React.Component {
|
|||||||
type="password"
|
type="password"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
required="required"
|
required="required"
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
/>
|
||||||
<button className="lw-btn" type="submit">
|
<button className="lw-btn" type="submit">
|
||||||
<i className="fas fa-sign-in-alt" />
|
<i className="fas fa-sign-in-alt" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
{showOpenID && (
|
||||||
|
<div className="openid-login">
|
||||||
|
<div className="or">or</div>
|
||||||
|
{
|
||||||
|
this.state.clientId ? (
|
||||||
|
<OpenIDLoginButton
|
||||||
|
className="btn openid-btn"
|
||||||
|
clientId={this.state.clientId}
|
||||||
|
authEp={this.state.discoveryDoc.authorization_endpoint}
|
||||||
|
authScopes={this.state.discoveryDoc.scopes_supported}
|
||||||
|
>
|
||||||
|
Log in with OpenID
|
||||||
|
</OpenIDLoginButton>
|
||||||
|
) : (
|
||||||
|
<Link to={"/login/openid"} className="btn openid-btn">
|
||||||
|
Log in with OpenID
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="l-footer">
|
<div className="l-footer">
|
||||||
<a className="lf-logo" href="">
|
<a className="lf-logo" href="">
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import logo from "../../img/logo.svg"
|
||||||
|
import Alert from "../alert/Alert"
|
||||||
|
import * as actionsAlert from "../alert/actions"
|
||||||
|
import InputGroup from "./InputGroup"
|
||||||
|
import web from "../web"
|
||||||
|
import { Redirect } from "react-router-dom"
|
||||||
|
import qs from "query-string"
|
||||||
|
import { getRandomString } from "../utils"
|
||||||
|
import storage from "local-storage-fallback"
|
||||||
|
import jwtDecode from "jwt-decode"
|
||||||
|
import { buildOpenIDAuthURL, OPEN_ID_NONCE_KEY } from './utils'
|
||||||
|
|
||||||
|
export class OpenIDLogin extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
clientID: "",
|
||||||
|
discoveryDoc: {}
|
||||||
|
}
|
||||||
|
this.clientIDChange = this.clientIDChange.bind(this)
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIDChange(e) {
|
||||||
|
this.setState({
|
||||||
|
clientID: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const { showAlert } = this.props
|
||||||
|
let message = ""
|
||||||
|
if (this.state.clientID === "") {
|
||||||
|
message = "Client ID cannot be empty"
|
||||||
|
}
|
||||||
|
if (message) {
|
||||||
|
showAlert("danger", message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.discoveryDoc && this.state.discoveryDoc.authorization_endpoint) {
|
||||||
|
const redirectURI = window.location.href.split("#")[0]
|
||||||
|
|
||||||
|
// Store nonce in localstorage to check again after the redirect
|
||||||
|
const nonce = getRandomString(16)
|
||||||
|
storage.setItem(OPEN_ID_NONCE_KEY, nonce)
|
||||||
|
|
||||||
|
const authURL = buildOpenIDAuthURL(
|
||||||
|
this.state.discoveryDoc.authorization_endpoint,
|
||||||
|
this.state.discoveryDoc.scopes_supported,
|
||||||
|
redirectURI,
|
||||||
|
this.state.clientID,
|
||||||
|
nonce
|
||||||
|
)
|
||||||
|
window.location = authURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
const { clearAlert } = this.props
|
||||||
|
// Clear out any stale message in the alert of previous page
|
||||||
|
clearAlert()
|
||||||
|
document.body.classList.add("is-guest")
|
||||||
|
|
||||||
|
web.GetDiscoveryDoc().then(({ DiscoveryDoc }) => {
|
||||||
|
this.setState({
|
||||||
|
discoveryDoc: DiscoveryDoc
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const values = qs.parse(this.props.location.hash)
|
||||||
|
if (values.error) {
|
||||||
|
this.props.showAlert("danger", values.error_description)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.id_token) {
|
||||||
|
// Check nonce on the token to prevent replay attacks
|
||||||
|
const tokenJSON = jwtDecode(values.id_token)
|
||||||
|
if (storage.getItem(OPEN_ID_NONCE_KEY) !== tokenJSON.nonce) {
|
||||||
|
this.props.showAlert("danger", "Invalid auth token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
web.LoginSTS({ token: values.id_token }).then(() => {
|
||||||
|
storage.removeItem(OPEN_ID_NONCE_KEY)
|
||||||
|
this.forceUpdate()
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.body.classList.remove("is-guest")
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { clearAlert, alert } = this.props
|
||||||
|
if (web.LoggedIn()) {
|
||||||
|
return <Redirect to={"/"} />
|
||||||
|
}
|
||||||
|
let alertBox = <Alert {...alert} onDismiss={clearAlert} />
|
||||||
|
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||||
|
if (!alert.message) alertBox = ""
|
||||||
|
return (
|
||||||
|
<div className="login">
|
||||||
|
{alertBox}
|
||||||
|
<div className="l-wrap">
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<InputGroup
|
||||||
|
value={this.state.clientID}
|
||||||
|
onChange={this.clientIDChange}
|
||||||
|
className="ig-dark"
|
||||||
|
label="Client ID"
|
||||||
|
id="clientID"
|
||||||
|
name="clientID"
|
||||||
|
type="text"
|
||||||
|
spellCheck="false"
|
||||||
|
required="required"
|
||||||
|
/>
|
||||||
|
<button className="lw-btn" type="submit">
|
||||||
|
<i className="fas fa-sign-in-alt" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="l-footer">
|
||||||
|
<a className="lf-logo" href="">
|
||||||
|
<img src={logo} alt="" />
|
||||||
|
</a>
|
||||||
|
<div className="lf-server">{window.location.host}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
showAlert: (type, message) =>
|
||||||
|
dispatch(actionsAlert.set({ type: type, message: message })),
|
||||||
|
clearAlert: () => dispatch(actionsAlert.clear())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
state => state,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(OpenIDLogin)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { getRandomString } from "../utils"
|
||||||
|
import storage from "local-storage-fallback"
|
||||||
|
import { buildOpenIDAuthURL, OPEN_ID_NONCE_KEY } from './utils'
|
||||||
|
|
||||||
|
export class OpenIDLoginButton extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.handleClick = this.handleClick.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
const { authEp, authScopes, clientId } = this.props
|
||||||
|
|
||||||
|
let redirectURI = window.location.href.split("#")[0]
|
||||||
|
if (redirectURI.endsWith('/')) {
|
||||||
|
redirectURI += 'openid'
|
||||||
|
} else {
|
||||||
|
redirectURI += '/openid'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store nonce in localstorage to check again after the redirect
|
||||||
|
const nonce = getRandomString(16)
|
||||||
|
storage.setItem(OPEN_ID_NONCE_KEY, nonce)
|
||||||
|
|
||||||
|
const authURL = buildOpenIDAuthURL(authEp, authScopes, redirectURI, clientId, nonce)
|
||||||
|
window.location = authURL
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, className } = this.props
|
||||||
|
return (
|
||||||
|
<div onClick={this.handleClick} className={className}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OpenIDLoginButton
|
||||||
@@ -26,6 +26,10 @@ export class StorageInfo extends React.Component {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { used } = this.props.storageInfo
|
const { used } = this.props.storageInfo
|
||||||
|
if (!used || used == 0) {
|
||||||
|
return <noscript />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="feh-used">
|
<div className="feh-used">
|
||||||
<div className="fehu-chart">
|
<div className="fehu-chart">
|
||||||
@@ -54,4 +58,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(StorageInfo)
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(StorageInfo)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { AboutModal } from "../AboutModal"
|
|||||||
describe("AboutModal", () => {
|
describe("AboutModal", () => {
|
||||||
const serverInfo = {
|
const serverInfo = {
|
||||||
version: "test",
|
version: "test",
|
||||||
memory: "test",
|
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test"
|
runtime: "test"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { BrowserDropdown } from "../BrowserDropdown"
|
|||||||
describe("BrowserDropdown", () => {
|
describe("BrowserDropdown", () => {
|
||||||
const serverInfo = {
|
const serverInfo = {
|
||||||
version: "test",
|
version: "test",
|
||||||
memory: "test",
|
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test"
|
runtime: "test"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,32 +54,19 @@ jest.mock("../../utils", () => ({
|
|||||||
describe("ChangePasswordModal", () => {
|
describe("ChangePasswordModal", () => {
|
||||||
const serverInfo = {
|
const serverInfo = {
|
||||||
version: "test",
|
version: "test",
|
||||||
memory: "test",
|
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test",
|
runtime: "test",
|
||||||
info: { isEnvCreds: false },
|
info: {},
|
||||||
userInfo: { isIAMUser: false }
|
userInfo: { isIAMUser: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not allow changing password when isWorm is true", () => {
|
it("should not allow changing password when not IAM user", () => {
|
||||||
const newServerInfo = { ...serverInfo, info: { isWorm: true } }
|
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
|
||||||
expect(
|
|
||||||
wrapper
|
|
||||||
.find("ModalBody")
|
|
||||||
.childAt(0)
|
|
||||||
.text()
|
|
||||||
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not allow changing password when isEnvCreds is true and not IAM user", () => {
|
|
||||||
const newServerInfo = {
|
const newServerInfo = {
|
||||||
...serverInfo,
|
...serverInfo,
|
||||||
info: { isEnvCreds: true },
|
|
||||||
userInfo: { isIAMUser: false }
|
userInfo: { isIAMUser: false }
|
||||||
}
|
}
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
@@ -91,21 +78,22 @@ describe("ChangePasswordModal", () => {
|
|||||||
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should generate accessKey and secretKey when Generate buttons is clicked", () => {
|
it("should not allow changing password for STS user", () => {
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
const newServerInfo = {
|
||||||
wrapper.find("#generate-keys").simulate("click")
|
...serverInfo,
|
||||||
setImmediate(() => {
|
userInfo: { isTempUser: true }
|
||||||
expect(wrapper.state("newAccessKey")).toBe("raccesskey")
|
}
|
||||||
expect(wrapper.state("newSecretKey")).toBe("rsecretkey")
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
})
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find("ModalBody")
|
||||||
|
.childAt(0)
|
||||||
|
.text()
|
||||||
|
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not generate accessKey for IAM User", () => {
|
it("should not generate accessKey for IAM User", () => {
|
||||||
const newServerInfo = {
|
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
...serverInfo,
|
|
||||||
userInfo: { isIAMUser: true }
|
|
||||||
}
|
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
|
||||||
wrapper.find("#generate-keys").simulate("click")
|
wrapper.find("#generate-keys").simulate("click")
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(wrapper.state("newAccessKey")).toBe("minio")
|
expect(wrapper.state("newAccessKey")).toBe("minio")
|
||||||
@@ -114,59 +102,24 @@ describe("ChangePasswordModal", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should not show new accessKey field for IAM User", () => {
|
it("should not show new accessKey field for IAM User", () => {
|
||||||
const newServerInfo = {
|
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
...serverInfo,
|
|
||||||
userInfo: { isIAMUser: true }
|
|
||||||
}
|
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
|
||||||
expect(wrapper.find("#newAccesskey").exists()).toBeFalsy()
|
expect(wrapper.find("#newAccesskey").exists()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should disble Update button for invalid accessKey or secretKey", () => {
|
it("should disable Update button for secretKey", () => {
|
||||||
const showAlert = jest.fn()
|
const showAlert = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
||||||
)
|
)
|
||||||
wrapper
|
|
||||||
.find("#currentAccessKey")
|
|
||||||
.simulate("change", { target: { value: "minio" } })
|
|
||||||
wrapper
|
wrapper
|
||||||
.find("#currentSecretKey")
|
.find("#currentSecretKey")
|
||||||
.simulate("change", { target: { value: "minio123" } })
|
.simulate("change", { target: { value: "minio123" } })
|
||||||
wrapper.find("#newAccessKey").simulate("change", { target: { value: "t" } })
|
|
||||||
wrapper
|
wrapper
|
||||||
.find("#newSecretKey")
|
.find("#newSecretKey")
|
||||||
.simulate("change", { target: { value: "t1" } })
|
.simulate("change", { target: { value: "t1" } })
|
||||||
expect(wrapper.find("#update-keys").prop("disabled")).toBeTruthy()
|
expect(wrapper.find("#update-keys").prop("disabled")).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should update accessKey and secretKey when Update button is clicked", () => {
|
|
||||||
const showAlert = jest.fn()
|
|
||||||
const wrapper = shallow(
|
|
||||||
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
|
||||||
)
|
|
||||||
wrapper
|
|
||||||
.find("#currentAccessKey")
|
|
||||||
.simulate("change", { target: { value: "minio" } })
|
|
||||||
wrapper
|
|
||||||
.find("#currentSecretKey")
|
|
||||||
.simulate("change", { target: { value: "minio123" } })
|
|
||||||
wrapper
|
|
||||||
.find("#newAccessKey")
|
|
||||||
.simulate("change", { target: { value: "test" } })
|
|
||||||
wrapper
|
|
||||||
.find("#newSecretKey")
|
|
||||||
.simulate("change", { target: { value: "test1234" } })
|
|
||||||
expect(wrapper.find("#update-keys").prop("disabled")).toBeFalsy()
|
|
||||||
wrapper.find("#update-keys").simulate("click")
|
|
||||||
setImmediate(() => {
|
|
||||||
expect(showAlert).toHaveBeenCalledWith({
|
|
||||||
type: "success",
|
|
||||||
message: "Credentials updated successfully."
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should call hideChangePassword when Cancel button is clicked", () => {
|
it("should call hideChangePassword when Cancel button is clicked", () => {
|
||||||
const hideChangePassword = jest.fn()
|
const hideChangePassword = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
|
|||||||
@@ -19,21 +19,24 @@ import { shallow, mount } from "enzyme"
|
|||||||
import { Login } from "../Login"
|
import { Login } from "../Login"
|
||||||
import web from "../../web"
|
import web from "../../web"
|
||||||
|
|
||||||
jest.mock('../../web', () => ({
|
jest.mock("../../web", () => ({
|
||||||
Login: jest.fn(() => {
|
Login: jest.fn(() => {
|
||||||
return Promise.resolve({ token: "test", uiVersion: "2018-02-01T01:17:47Z" })
|
return Promise.resolve({ token: "test", uiVersion: "2018-02-01T01:17:47Z" })
|
||||||
}),
|
}),
|
||||||
LoggedIn: jest.fn()
|
LoggedIn: jest.fn(),
|
||||||
|
GetDiscoveryDoc: jest.fn(() => {
|
||||||
|
return Promise.resolve({ DiscoveryDoc: {"authorization_endpoint": "test"} })
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe("Login", () => {
|
describe("Login", () => {
|
||||||
const dispatchMock = jest.fn()
|
const dispatchMock = jest.fn()
|
||||||
const showAlertMock = jest.fn()
|
const showAlertMock = jest.fn()
|
||||||
const clearAlertMock = jest.fn()
|
const clearAlertMock = jest.fn()
|
||||||
|
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<Login
|
shallow(<Login
|
||||||
dispatch={dispatchMock}
|
dispatch={dispatchMock}
|
||||||
alert={{ show: false, type: "danger"}}
|
alert={{ show: false, type: "danger"}}
|
||||||
showAlert={showAlertMock}
|
showAlert={showAlertMock}
|
||||||
clearAlert={clearAlertMock}
|
clearAlert={clearAlertMock}
|
||||||
@@ -42,8 +45,8 @@ describe("Login", () => {
|
|||||||
|
|
||||||
it("should initially have the is-guest class", () => {
|
it("should initially have the is-guest class", () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<Login
|
<Login
|
||||||
dispatch={dispatchMock}
|
dispatch={dispatchMock}
|
||||||
alert={{ show: false, type: "danger"}}
|
alert={{ show: false, type: "danger"}}
|
||||||
showAlert={showAlertMock}
|
showAlert={showAlertMock}
|
||||||
clearAlert={clearAlertMock}
|
clearAlert={clearAlertMock}
|
||||||
@@ -55,8 +58,8 @@ describe("Login", () => {
|
|||||||
|
|
||||||
it("should throw an alert if the keys are empty in login form", () => {
|
it("should throw an alert if the keys are empty in login form", () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Login
|
<Login
|
||||||
dispatch={dispatchMock}
|
dispatch={dispatchMock}
|
||||||
alert={{ show: false, type: "danger"}}
|
alert={{ show: false, type: "danger"}}
|
||||||
showAlert={showAlertMock}
|
showAlert={showAlertMock}
|
||||||
clearAlert={clearAlertMock}
|
clearAlert={clearAlertMock}
|
||||||
@@ -85,8 +88,8 @@ describe("Login", () => {
|
|||||||
|
|
||||||
it("should call web.Login with correct arguments if both keys are entered", () => {
|
it("should call web.Login with correct arguments if both keys are entered", () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Login
|
<Login
|
||||||
dispatch={dispatchMock}
|
dispatch={dispatchMock}
|
||||||
alert={{ show: false, type: "danger"}}
|
alert={{ show: false, type: "danger"}}
|
||||||
showAlert={showAlertMock}
|
showAlert={showAlertMock}
|
||||||
clearAlert={clearAlertMock}
|
clearAlert={clearAlertMock}
|
||||||
@@ -98,7 +101,7 @@ describe("Login", () => {
|
|||||||
})
|
})
|
||||||
wrapper.find("form").simulate("submit")
|
wrapper.find("form").simulate("submit")
|
||||||
expect(web.Login).toHaveBeenCalledWith({
|
expect(web.Login).toHaveBeenCalledWith({
|
||||||
"username": "accessKey",
|
"username": "accessKey",
|
||||||
"password": "secretKey"
|
"password": "secretKey"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ import { StorageInfo } from "../StorageInfo"
|
|||||||
describe("StorageInfo", () => {
|
describe("StorageInfo", () => {
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(
|
shallow(
|
||||||
<StorageInfo
|
<StorageInfo storageInfo={ {used: 60} } fetchStorageInfo={jest.fn()} />
|
||||||
storageInfo={{ used: 60 }}
|
|
||||||
fetchStorageInfo={jest.fn()}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,10 +29,21 @@ describe("StorageInfo", () => {
|
|||||||
const fetchStorageInfo = jest.fn()
|
const fetchStorageInfo = jest.fn()
|
||||||
shallow(
|
shallow(
|
||||||
<StorageInfo
|
<StorageInfo
|
||||||
storageInfo={{ used: 60 }}
|
storageInfo={ {used: 60} }
|
||||||
fetchStorageInfo={fetchStorageInfo}
|
fetchStorageInfo={fetchStorageInfo}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(fetchStorageInfo).toHaveBeenCalled()
|
expect(fetchStorageInfo).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not render anything if used is null", () => {
|
||||||
|
const fetchStorageInfo = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<StorageInfo
|
||||||
|
storageInfo={ {used: 0} }
|
||||||
|
fetchStorageInfo={fetchStorageInfo}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(wrapper.text()).toBe("")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ import * as actionsCommon from "../actions"
|
|||||||
|
|
||||||
jest.mock("../../web", () => ({
|
jest.mock("../../web", () => ({
|
||||||
StorageInfo: jest.fn(() => {
|
StorageInfo: jest.fn(() => {
|
||||||
return Promise.resolve({ storageInfo: { Used: 60 } })
|
return Promise.resolve({
|
||||||
|
used: 60
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
ServerInfo: jest.fn(() => {
|
ServerInfo: jest.fn(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
MinioVersion: "test",
|
MinioVersion: "test",
|
||||||
MinioMemory: "test",
|
|
||||||
MinioPlatform: "test",
|
MinioPlatform: "test",
|
||||||
MinioRuntime: "test",
|
MinioRuntime: "test",
|
||||||
MinioGlobalInfo: "test"
|
MinioGlobalInfo: "test"
|
||||||
@@ -55,7 +56,6 @@ describe("Common actions", () => {
|
|||||||
type: "common/SET_SERVER_INFO",
|
type: "common/SET_SERVER_INFO",
|
||||||
serverInfo: {
|
serverInfo: {
|
||||||
version: "test",
|
version: "test",
|
||||||
memory: "test",
|
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test",
|
runtime: "test",
|
||||||
info: "test"
|
info: "test"
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ describe("common reducer", () => {
|
|||||||
it("should return the initial state", () => {
|
it("should return the initial state", () => {
|
||||||
expect(reducer(undefined, {})).toEqual({
|
expect(reducer(undefined, {})).toEqual({
|
||||||
sidebarOpen: false,
|
sidebarOpen: false,
|
||||||
storageInfo: {
|
storageInfo: {used: 0},
|
||||||
total: 0,
|
|
||||||
free: 0
|
|
||||||
},
|
|
||||||
serverInfo: {}
|
serverInfo: {}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -61,11 +58,11 @@ describe("common reducer", () => {
|
|||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
type: actionsCommon.SET_STORAGE_INFO,
|
type: actionsCommon.SET_STORAGE_INFO,
|
||||||
storageInfo: { total: 100, free: 40 }
|
storageInfo: { }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
storageInfo: { total: 100, free: 40 }
|
storageInfo: { }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -75,7 +72,6 @@ describe("common reducer", () => {
|
|||||||
type: actionsCommon.SET_SERVER_INFO,
|
type: actionsCommon.SET_SERVER_INFO,
|
||||||
serverInfo: {
|
serverInfo: {
|
||||||
version: "test",
|
version: "test",
|
||||||
memory: "test",
|
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test",
|
runtime: "test",
|
||||||
info: "test"
|
info: "test"
|
||||||
@@ -83,7 +79,6 @@ describe("common reducer", () => {
|
|||||||
}).serverInfo
|
}).serverInfo
|
||||||
).toEqual({
|
).toEqual({
|
||||||
version: "test",
|
version: "test",
|
||||||
memory: "test",
|
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test",
|
runtime: "test",
|
||||||
info: "test"
|
info: "test"
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ export const fetchStorageInfo = () => {
|
|||||||
return function(dispatch) {
|
return function(dispatch) {
|
||||||
return web.StorageInfo().then(res => {
|
return web.StorageInfo().then(res => {
|
||||||
const storageInfo = {
|
const storageInfo = {
|
||||||
total: res.storageInfo.Total,
|
used: res.used
|
||||||
used: res.storageInfo.Used
|
|
||||||
}
|
}
|
||||||
dispatch(setStorageInfo(storageInfo))
|
dispatch(setStorageInfo(storageInfo))
|
||||||
})
|
})
|
||||||
@@ -51,7 +50,6 @@ export const fetchServerInfo = () => {
|
|||||||
return web.ServerInfo().then(res => {
|
return web.ServerInfo().then(res => {
|
||||||
const serverInfo = {
|
const serverInfo = {
|
||||||
version: res.MinioVersion,
|
version: res.MinioVersion,
|
||||||
memory: res.MinioMemory,
|
|
||||||
platform: res.MinioPlatform,
|
platform: res.MinioPlatform,
|
||||||
runtime: res.MinioRuntime,
|
runtime: res.MinioRuntime,
|
||||||
info: res.MinioGlobalInfo,
|
info: res.MinioGlobalInfo,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import * as actionsCommon from "./actions"
|
|||||||
export default (
|
export default (
|
||||||
state = {
|
state = {
|
||||||
sidebarOpen: false,
|
sidebarOpen: false,
|
||||||
storageInfo: { total: 0, free: 0 },
|
storageInfo: {used: 0},
|
||||||
serverInfo: {}
|
serverInfo: {}
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createSelector } from "reselect"
|
||||||
|
|
||||||
|
export const getServerInfo = state => state.browser.serverInfo
|
||||||
|
|
||||||
|
export const hasServerPublicDomain = createSelector(
|
||||||
|
getServerInfo,
|
||||||
|
serverInfo => Boolean(serverInfo.info && serverInfo.info.domains && serverInfo.info.domains.length),
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const OPEN_ID_NONCE_KEY = 'openIDKey'
|
||||||
|
|
||||||
|
export const buildOpenIDAuthURL = (authEp, authScopes, redirectURI, clientID, nonce) => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set("response_type", "id_token")
|
||||||
|
params.set("scope", authScopes.join(" "))
|
||||||
|
params.set("client_id", clientID)
|
||||||
|
params.set("redirect_uri", redirectURI)
|
||||||
|
params.set("nonce", nonce)
|
||||||
|
|
||||||
|
return `${authEp}?${params.toString()}`
|
||||||
|
}
|
||||||
@@ -17,14 +17,29 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import { Scrollbars } from "react-custom-scrollbars"
|
import { Scrollbars } from "react-custom-scrollbars"
|
||||||
|
import InfiniteScroll from "react-infinite-scroller"
|
||||||
import * as actionsBuckets from "./actions"
|
import * as actionsBuckets from "./actions"
|
||||||
import { getVisibleBuckets } from "./selectors"
|
import { getFilteredBuckets } from "./selectors"
|
||||||
import BucketContainer from "./BucketContainer"
|
import BucketContainer from "./BucketContainer"
|
||||||
import web from "../web"
|
import web from "../web"
|
||||||
import history from "../history"
|
import history from "../history"
|
||||||
import { pathSlice } from "../utils"
|
import { pathSlice } from "../utils"
|
||||||
|
|
||||||
export class BucketList extends React.Component {
|
export class BucketList extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
this.loadNextPage = this.loadNextPage.bind(this)
|
||||||
|
}
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.filter !== prevProps.filter) {
|
||||||
|
this.setState({
|
||||||
|
page: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const { fetchBuckets, setBucketList, selectBucket } = this.props
|
const { fetchBuckets, setBucketList, selectBucket } = this.props
|
||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
@@ -39,18 +54,33 @@ export class BucketList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadNextPage() {
|
||||||
|
this.setState({
|
||||||
|
page: this.state.page + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
const { visibleBuckets } = this.props
|
const { filteredBuckets } = this.props
|
||||||
|
const visibleBuckets = filteredBuckets.slice(0, this.state.page * 100)
|
||||||
return (
|
return (
|
||||||
<div className="fesl-inner">
|
<div className="fesl-inner">
|
||||||
<Scrollbars
|
<Scrollbars
|
||||||
renderTrackVertical={props => <div className="scrollbar-vertical" />}
|
renderTrackVertical={props => <div className="scrollbar-vertical" />}
|
||||||
>
|
>
|
||||||
<ul>
|
<InfiniteScroll
|
||||||
{visibleBuckets.map(bucket => (
|
pageStart={0}
|
||||||
<BucketContainer key={bucket} bucket={bucket} />
|
loadMore={this.loadNextPage}
|
||||||
))}
|
hasMore={filteredBuckets.length > visibleBuckets.length}
|
||||||
</ul>
|
useWindow={false}
|
||||||
|
element="div"
|
||||||
|
initialLoad={false}
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
{visibleBuckets.map(bucket => (
|
||||||
|
<BucketContainer key={bucket} bucket={bucket} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</InfiniteScroll>
|
||||||
</Scrollbars>
|
</Scrollbars>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -59,7 +89,8 @@ export class BucketList extends React.Component {
|
|||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
visibleBuckets: getVisibleBuckets(state)
|
filteredBuckets: getFilteredBuckets(state),
|
||||||
|
filter: state.buckets.filter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ jest.mock("../../web", () => ({
|
|||||||
describe("BucketList", () => {
|
describe("BucketList", () => {
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
const fetchBuckets = jest.fn()
|
const fetchBuckets = jest.fn()
|
||||||
shallow(<BucketList visibleBuckets={[]} fetchBuckets={fetchBuckets} />)
|
shallow(<BucketList filteredBuckets={[]} fetchBuckets={fetchBuckets} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should call fetchBuckets before component is mounted", () => {
|
it("should call fetchBuckets before component is mounted", () => {
|
||||||
const fetchBuckets = jest.fn()
|
const fetchBuckets = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<BucketList visibleBuckets={[]} fetchBuckets={fetchBuckets} />
|
<BucketList filteredBuckets={[]} fetchBuckets={fetchBuckets} />
|
||||||
)
|
)
|
||||||
expect(fetchBuckets).toHaveBeenCalled()
|
expect(fetchBuckets).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -46,7 +46,7 @@ describe("BucketList", () => {
|
|||||||
history.push("/bk1/pre1")
|
history.push("/bk1/pre1")
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<BucketList
|
<BucketList
|
||||||
visibleBuckets={[]}
|
filteredBuckets={[]}
|
||||||
setBucketList={setBucketList}
|
setBucketList={setBucketList}
|
||||||
selectBucket={selectBucket}
|
selectBucket={selectBucket}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getVisibleBuckets, getCurrentBucket } from "../selectors"
|
import { getFilteredBuckets, getCurrentBucket } from "../selectors"
|
||||||
|
|
||||||
describe("getVisibleBuckets", () => {
|
describe("getFilteredBuckets", () => {
|
||||||
let state
|
let state
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state = {
|
state = {
|
||||||
@@ -28,11 +28,11 @@ describe("getVisibleBuckets", () => {
|
|||||||
|
|
||||||
it("should return all buckets if no filter specified", () => {
|
it("should return all buckets if no filter specified", () => {
|
||||||
state.buckets.filter = ""
|
state.buckets.filter = ""
|
||||||
expect(getVisibleBuckets(state)).toEqual(["test1", "test11", "test2"])
|
expect(getFilteredBuckets(state)).toEqual(["test1", "test11", "test2"])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return all matching buckets if filter is specified", () => {
|
it("should return all matching buckets if filter is specified", () => {
|
||||||
state.buckets.filter = "test1"
|
state.buckets.filter = "test1"
|
||||||
expect(getVisibleBuckets(state)).toEqual(["test1", "test11"])
|
expect(getFilteredBuckets(state)).toEqual(["test1", "test11"])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,19 +31,38 @@ export const SET_POLICIES = "buckets/SET_POLICIES"
|
|||||||
|
|
||||||
export const fetchBuckets = () => {
|
export const fetchBuckets = () => {
|
||||||
return function(dispatch) {
|
return function(dispatch) {
|
||||||
|
const { bucket, prefix } = pathSlice(history.location.pathname)
|
||||||
return web.ListBuckets().then(res => {
|
return web.ListBuckets().then(res => {
|
||||||
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
|
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
|
||||||
dispatch(setList(buckets))
|
|
||||||
if (buckets.length > 0) {
|
if (buckets.length > 0) {
|
||||||
const { bucket, prefix } = pathSlice(history.location.pathname)
|
dispatch(setList(buckets))
|
||||||
if (bucket && buckets.indexOf(bucket) > -1) {
|
if (bucket && buckets.indexOf(bucket) > -1) {
|
||||||
dispatch(selectBucket(bucket, prefix))
|
dispatch(selectBucket(bucket, prefix))
|
||||||
} else {
|
} else {
|
||||||
dispatch(selectBucket(buckets[0]))
|
dispatch(selectBucket(buckets[0]))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dispatch(selectBucket(""))
|
if (bucket) {
|
||||||
history.replace("/")
|
dispatch(setList([bucket]))
|
||||||
|
dispatch(selectBucket(bucket, prefix))
|
||||||
|
} else {
|
||||||
|
dispatch(selectBucket(""))
|
||||||
|
history.replace("/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (bucket && err.message === "Access Denied." || err.message.indexOf('Prefix access is denied') > -1 ) {
|
||||||
|
dispatch(setList([bucket]))
|
||||||
|
dispatch(selectBucket(bucket, prefix))
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "danger",
|
||||||
|
message: err.message,
|
||||||
|
autoClear: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { createSelector } from "reselect"
|
|||||||
const bucketsSelector = state => state.buckets.list
|
const bucketsSelector = state => state.buckets.list
|
||||||
const bucketsFilterSelector = state => state.buckets.filter
|
const bucketsFilterSelector = state => state.buckets.filter
|
||||||
|
|
||||||
export const getVisibleBuckets = createSelector(
|
export const getFilteredBuckets = createSelector(
|
||||||
bucketsSelector,
|
bucketsSelector,
|
||||||
bucketsFilterSelector,
|
bucketsFilterSelector,
|
||||||
(buckets, filter) => buckets.filter(bucket => bucket.indexOf(filter) > -1)
|
(buckets, filter) => buckets.filter(bucket => bucket.indexOf(filter) > -1)
|
||||||
|
|||||||
+72
-34
@@ -14,30 +14,67 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import mimedb from 'mime-types'
|
import mimedb from "mime-types"
|
||||||
|
|
||||||
const isFolder = (name, contentType) => {
|
const isFolder = (name, contentType) => {
|
||||||
if (name.endsWith('/')) return true
|
if (name.endsWith("/")) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPdf = (name, contentType) => {
|
const isPdf = (name, contentType) => {
|
||||||
if (contentType === 'application/pdf') return true
|
if (contentType === "application/pdf") return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const isImage = (name, contentType) => {
|
||||||
|
if (
|
||||||
|
contentType === "image/jpeg" ||
|
||||||
|
contentType === "image/gif" ||
|
||||||
|
contentType === "image/x-icon" ||
|
||||||
|
contentType === "image/png" ||
|
||||||
|
contentType === "image/svg+xml" ||
|
||||||
|
contentType === "image/tiff" ||
|
||||||
|
contentType === "image/webp"
|
||||||
|
)
|
||||||
|
return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const isZip = (name, contentType) => {
|
const isZip = (name, contentType) => {
|
||||||
if (!contentType || !contentType.includes('/')) return false
|
if (!contentType || !contentType.includes("/")) return false
|
||||||
if (contentType.split('/')[1].includes('zip')) return true
|
if (contentType.split("/")[1].includes("zip")) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCode = (name, contentType) => {
|
const isCode = (name, contentType) => {
|
||||||
const codeExt = ['c', 'cpp', 'go', 'py', 'java', 'rb', 'js', 'pl', 'fs',
|
const codeExt = [
|
||||||
'php', 'css', 'less', 'scss', 'coffee', 'net', 'html',
|
"c",
|
||||||
'rs', 'exs', 'scala', 'hs', 'clj', 'el', 'scm', 'lisp',
|
"cpp",
|
||||||
'asp', 'aspx']
|
"go",
|
||||||
const ext = name.split('.').reverse()[0]
|
"py",
|
||||||
|
"java",
|
||||||
|
"rb",
|
||||||
|
"js",
|
||||||
|
"pl",
|
||||||
|
"fs",
|
||||||
|
"php",
|
||||||
|
"css",
|
||||||
|
"less",
|
||||||
|
"scss",
|
||||||
|
"coffee",
|
||||||
|
"net",
|
||||||
|
"html",
|
||||||
|
"rs",
|
||||||
|
"exs",
|
||||||
|
"scala",
|
||||||
|
"hs",
|
||||||
|
"clj",
|
||||||
|
"el",
|
||||||
|
"scm",
|
||||||
|
"lisp",
|
||||||
|
"asp",
|
||||||
|
"aspx",
|
||||||
|
]
|
||||||
|
const ext = name.split(".").reverse()[0]
|
||||||
for (var i in codeExt) {
|
for (var i in codeExt) {
|
||||||
if (ext === codeExt[i]) return true
|
if (ext === codeExt[i]) return true
|
||||||
}
|
}
|
||||||
@@ -45,9 +82,9 @@ const isCode = (name, contentType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isExcel = (name, contentType) => {
|
const isExcel = (name, contentType) => {
|
||||||
if (!contentType || !contentType.includes('/')) return false
|
if (!contentType || !contentType.includes("/")) return false
|
||||||
const types = ['excel', 'spreadsheet']
|
const types = ["excel", "spreadsheet"]
|
||||||
const subType = contentType.split('/')[1]
|
const subType = contentType.split("/")[1]
|
||||||
for (var i in types) {
|
for (var i in types) {
|
||||||
if (subType.includes(types[i])) return true
|
if (subType.includes(types[i])) return true
|
||||||
}
|
}
|
||||||
@@ -55,9 +92,9 @@ const isExcel = (name, contentType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isDoc = (name, contentType) => {
|
const isDoc = (name, contentType) => {
|
||||||
if (!contentType || !contentType.includes('/')) return false
|
if (!contentType || !contentType.includes("/")) return false
|
||||||
const types = ['word', '.document']
|
const types = ["word", ".document"]
|
||||||
const subType = contentType.split('/')[1]
|
const subType = contentType.split("/")[1]
|
||||||
for (var i in types) {
|
for (var i in types) {
|
||||||
if (subType.includes(types[i])) return true
|
if (subType.includes(types[i])) return true
|
||||||
}
|
}
|
||||||
@@ -65,9 +102,9 @@ const isDoc = (name, contentType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isPresentation = (name, contentType) => {
|
const isPresentation = (name, contentType) => {
|
||||||
if (!contentType || !contentType.includes('/')) return false
|
if (!contentType || !contentType.includes("/")) return false
|
||||||
var types = ['powerpoint', 'presentation']
|
var types = ["powerpoint", "presentation"]
|
||||||
const subType = contentType.split('/')[1]
|
const subType = contentType.split("/")[1]
|
||||||
for (var i in types) {
|
for (var i in types) {
|
||||||
if (subType.includes(types[i])) return true
|
if (subType.includes(types[i])) return true
|
||||||
}
|
}
|
||||||
@@ -76,31 +113,32 @@ const isPresentation = (name, contentType) => {
|
|||||||
|
|
||||||
const typeToIcon = (type) => {
|
const typeToIcon = (type) => {
|
||||||
return (name, contentType) => {
|
return (name, contentType) => {
|
||||||
if (!contentType || !contentType.includes('/')) return false
|
if (!contentType || !contentType.includes("/")) return false
|
||||||
if (contentType.split('/')[0] === type) return true
|
if (contentType.split("/")[0] === type) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDataType = (name, contentType) => {
|
export const getDataType = (name, contentType) => {
|
||||||
if (contentType === "") {
|
if (contentType === "") {
|
||||||
contentType = mimedb.lookup(name) || 'application/octet-stream'
|
contentType = mimedb.lookup(name) || "application/octet-stream"
|
||||||
}
|
}
|
||||||
const check = [
|
const check = [
|
||||||
['folder', isFolder],
|
["folder", isFolder],
|
||||||
['code', isCode],
|
["code", isCode],
|
||||||
['audio', typeToIcon('audio')],
|
["audio", typeToIcon("audio")],
|
||||||
['image', typeToIcon('image')],
|
["image", typeToIcon("image")],
|
||||||
['video', typeToIcon('video')],
|
["video", typeToIcon("video")],
|
||||||
['text', typeToIcon('text')],
|
["text", typeToIcon("text")],
|
||||||
['pdf', isPdf],
|
["pdf", isPdf],
|
||||||
['zip', isZip],
|
["image", isImage],
|
||||||
['excel', isExcel],
|
["zip", isZip],
|
||||||
['doc', isDoc],
|
["excel", isExcel],
|
||||||
['presentation', isPresentation]
|
["doc", isDoc],
|
||||||
|
["presentation", isPresentation],
|
||||||
]
|
]
|
||||||
for (var i in check) {
|
for (var i in check) {
|
||||||
if (check[i][1](name, contentType)) return check[i][0]
|
if (check[i][1](name, contentType)) return check[i][0]
|
||||||
}
|
}
|
||||||
return 'other'
|
return "other"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,18 +19,22 @@ import { connect } from "react-redux"
|
|||||||
import { Dropdown } from "react-bootstrap"
|
import { Dropdown } from "react-bootstrap"
|
||||||
import ShareObjectModal from "./ShareObjectModal"
|
import ShareObjectModal from "./ShareObjectModal"
|
||||||
import DeleteObjectConfirmModal from "./DeleteObjectConfirmModal"
|
import DeleteObjectConfirmModal from "./DeleteObjectConfirmModal"
|
||||||
|
import PreviewObjectModal from "./PreviewObjectModal"
|
||||||
|
|
||||||
import * as objectsActions from "./actions"
|
import * as objectsActions from "./actions"
|
||||||
|
import { getDataType } from "../mime.js"
|
||||||
import {
|
import {
|
||||||
SHARE_OBJECT_EXPIRY_DAYS,
|
SHARE_OBJECT_EXPIRY_DAYS,
|
||||||
SHARE_OBJECT_EXPIRY_HOURS,
|
SHARE_OBJECT_EXPIRY_HOURS,
|
||||||
SHARE_OBJECT_EXPIRY_MINUTES
|
SHARE_OBJECT_EXPIRY_MINUTES,
|
||||||
} from "../constants"
|
} from "../constants"
|
||||||
|
|
||||||
export class ObjectActions extends React.Component {
|
export class ObjectActions extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
showDeleteConfirmation: false
|
showDeleteConfirmation: false,
|
||||||
|
showPreview: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shareObject(e) {
|
shareObject(e) {
|
||||||
@@ -43,6 +47,11 @@ export class ObjectActions extends React.Component {
|
|||||||
SHARE_OBJECT_EXPIRY_MINUTES
|
SHARE_OBJECT_EXPIRY_MINUTES
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
handleDownload(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const { object, downloadObject } = this.props
|
||||||
|
downloadObject(object.name)
|
||||||
|
}
|
||||||
deleteObject() {
|
deleteObject() {
|
||||||
const { object, deleteObject } = this.props
|
const { object, deleteObject } = this.props
|
||||||
deleteObject(object.name)
|
deleteObject(object.name)
|
||||||
@@ -53,7 +62,20 @@ export class ObjectActions extends React.Component {
|
|||||||
}
|
}
|
||||||
hideDeleteConfirmModal() {
|
hideDeleteConfirmModal() {
|
||||||
this.setState({
|
this.setState({
|
||||||
showDeleteConfirmation: false
|
showDeleteConfirmation: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getObjectURL(objectname, callback) {
|
||||||
|
const { getObjectURL } = this.props
|
||||||
|
getObjectURL(objectname, callback)
|
||||||
|
}
|
||||||
|
showPreviewModal(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({ showPreview: true })
|
||||||
|
}
|
||||||
|
hidePreviewModal() {
|
||||||
|
this.setState({
|
||||||
|
showPreview: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
@@ -65,26 +87,54 @@ export class ObjectActions extends React.Component {
|
|||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
className="fiad-action"
|
className="fiad-action"
|
||||||
|
title="Share"
|
||||||
onClick={this.shareObject.bind(this)}
|
onClick={this.shareObject.bind(this)}
|
||||||
>
|
>
|
||||||
<i className="fas fa-share-alt" />
|
<i className="fas fa-share-alt" />
|
||||||
</a>
|
</a>
|
||||||
|
{getDataType(object.name, object.contentType) == "image" && (
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
className="fiad-action"
|
||||||
|
title="Preview"
|
||||||
|
onClick={this.showPreviewModal.bind(this)}
|
||||||
|
>
|
||||||
|
<i className="far fa-file-image" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
className="fiad-action"
|
className="fiad-action"
|
||||||
|
title="Download"
|
||||||
|
onClick={this.handleDownload.bind(this)}
|
||||||
|
>
|
||||||
|
<i className="fas fa-cloud-download-alt" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
className="fiad-action"
|
||||||
|
title="Delete"
|
||||||
onClick={this.showDeleteConfirmModal.bind(this)}
|
onClick={this.showDeleteConfirmModal.bind(this)}
|
||||||
>
|
>
|
||||||
<i className="fas fa-trash-alt" />
|
<i className="fas fa-trash-alt" />
|
||||||
</a>
|
</a>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
{(showShareObjectModal && shareObjectName === object.name) &&
|
{showShareObjectModal && shareObjectName === object.name && (
|
||||||
<ShareObjectModal object={object} />}
|
<ShareObjectModal object={object} />
|
||||||
|
)}
|
||||||
{this.state.showDeleteConfirmation && (
|
{this.state.showDeleteConfirmation && (
|
||||||
<DeleteObjectConfirmModal
|
<DeleteObjectConfirmModal
|
||||||
deleteObject={this.deleteObject.bind(this)}
|
deleteObject={this.deleteObject.bind(this)}
|
||||||
hideDeleteConfirmModal={this.hideDeleteConfirmModal.bind(this)}
|
hideDeleteConfirmModal={this.hideDeleteConfirmModal.bind(this)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.state.showPreview && (
|
||||||
|
<PreviewObjectModal
|
||||||
|
object={object}
|
||||||
|
hidePreviewModal={this.hidePreviewModal.bind(this)}
|
||||||
|
getObjectURL={this.getObjectURL.bind(this)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -94,15 +144,18 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
return {
|
return {
|
||||||
object: ownProps.object,
|
object: ownProps.object,
|
||||||
showShareObjectModal: state.objects.shareObject.show,
|
showShareObjectModal: state.objects.shareObject.show,
|
||||||
shareObjectName: state.objects.shareObject.object
|
shareObjectName: state.objects.shareObject.object,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return {
|
return {
|
||||||
|
downloadObject: object => dispatch(objectsActions.downloadObject(object)),
|
||||||
shareObject: (object, days, hours, minutes) =>
|
shareObject: (object, days, hours, minutes) =>
|
||||||
dispatch(objectsActions.shareObject(object, days, hours, minutes)),
|
dispatch(objectsActions.shareObject(object, days, hours, minutes)),
|
||||||
deleteObject: object => dispatch(objectsActions.deleteObject(object))
|
deleteObject: (object) => dispatch(objectsActions.deleteObject(object)),
|
||||||
|
getObjectURL: (object, callback) =>
|
||||||
|
dispatch(objectsActions.getObjectURL(object, callback)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,11 @@ export const ObjectItem = ({
|
|||||||
href={getDataType(name, contentType) === "folder" ? name : "#"}
|
href={getDataType(name, contentType) === "folder" ? name : "#"}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
// onclick function is passed only when we have a prefix
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick()
|
onClick()
|
||||||
|
} else {
|
||||||
|
checked ? uncheckObject(name) : checkObject(name)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import { Dropdown } from "react-bootstrap"
|
||||||
|
import DeleteObjectConfirmModal from "./DeleteObjectConfirmModal"
|
||||||
|
import * as actions from "./actions"
|
||||||
|
|
||||||
|
export class PrefixActions extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
showDeleteConfirmation: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleDownload(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const { object, downloadPrefix } = this.props
|
||||||
|
downloadPrefix(object.name)
|
||||||
|
}
|
||||||
|
deleteObject() {
|
||||||
|
const { object, deleteObject } = this.props
|
||||||
|
deleteObject(object.name)
|
||||||
|
}
|
||||||
|
showDeleteConfirmModal(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({ showDeleteConfirmation: true })
|
||||||
|
}
|
||||||
|
hideDeleteConfirmModal() {
|
||||||
|
this.setState({
|
||||||
|
showDeleteConfirmation: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { object, showShareObjectModal, shareObjectName } = this.props
|
||||||
|
return (
|
||||||
|
<Dropdown id={`obj-actions-${object.name}`}>
|
||||||
|
<Dropdown.Toggle noCaret className="fia-toggle" />
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
className="fiad-action"
|
||||||
|
title="Download as zip"
|
||||||
|
onClick={this.handleDownload.bind(this)}
|
||||||
|
>
|
||||||
|
<i className="fas fa-cloud-download-alt" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
className="fiad-action"
|
||||||
|
title="Delete"
|
||||||
|
onClick={this.showDeleteConfirmModal.bind(this)}
|
||||||
|
>
|
||||||
|
<i className="fas fa-trash-alt" />
|
||||||
|
</a>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
{this.state.showDeleteConfirmation && (
|
||||||
|
<DeleteObjectConfirmModal
|
||||||
|
deleteObject={this.deleteObject.bind(this)}
|
||||||
|
hideDeleteConfirmModal={this.hideDeleteConfirmModal.bind(this)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return {
|
||||||
|
object: ownProps.object,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
downloadPrefix: object => dispatch(actions.downloadPrefix(object)),
|
||||||
|
deleteObject: (object) => dispatch(actions.deleteObject(object)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(PrefixActions)
|
||||||
@@ -17,22 +17,32 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import ObjectItem from "./ObjectItem"
|
import ObjectItem from "./ObjectItem"
|
||||||
|
import PrefixActions from "./PrefixActions"
|
||||||
import * as actionsObjects from "./actions"
|
import * as actionsObjects from "./actions"
|
||||||
|
import { getCheckedList } from "./selectors"
|
||||||
|
|
||||||
export const PrefixContainer = ({ object, currentPrefix, selectPrefix }) => {
|
export const PrefixContainer = ({
|
||||||
|
object,
|
||||||
|
currentPrefix,
|
||||||
|
checkedObjectsCount,
|
||||||
|
selectPrefix
|
||||||
|
}) => {
|
||||||
const props = {
|
const props = {
|
||||||
name: object.name,
|
name: object.name,
|
||||||
contentType: object.contentType,
|
contentType: object.contentType,
|
||||||
onClick: () => selectPrefix(`${currentPrefix}${object.name}`)
|
onClick: () => selectPrefix(`${currentPrefix}${object.name}`)
|
||||||
}
|
}
|
||||||
|
if (checkedObjectsCount == 0) {
|
||||||
|
props.actionButtons = <PrefixActions object={object} />
|
||||||
|
}
|
||||||
return <ObjectItem {...props} />
|
return <ObjectItem {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
return {
|
return {
|
||||||
object: ownProps.object,
|
object: ownProps.object,
|
||||||
currentPrefix: state.objects.currentPrefix
|
currentPrefix: state.objects.currentPrefix,
|
||||||
|
checkedObjectsCount: getCheckedList(state).length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { Modal, ModalHeader, ModalBody } from "react-bootstrap"
|
||||||
|
|
||||||
|
class PreviewObjectModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
url: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.getObjectURL(this.props.object.name, (url) => {
|
||||||
|
this.setState({
|
||||||
|
url: url,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { hidePreviewModal } = this.props
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={true}
|
||||||
|
animation={false}
|
||||||
|
onHide={hidePreviewModal}
|
||||||
|
bsSize="large"
|
||||||
|
>
|
||||||
|
<ModalHeader>Preview</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="input-group">
|
||||||
|
{this.state.url && (
|
||||||
|
<object data={this.state.url} style={{ display: "block", width: "100%" }}>
|
||||||
|
<h3 style={{ textAlign: "center", display: "block", width: "100%" }}>
|
||||||
|
Do not have read permissions to preview "{this.props.object.name}"
|
||||||
|
</h3>
|
||||||
|
</object>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<div className="modal-footer">
|
||||||
|
{
|
||||||
|
<button className="btn btn-link" onClick={hidePreviewModal}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default PreviewObjectModal
|
||||||
@@ -77,7 +77,8 @@ export class ShareObjectModal extends React.Component {
|
|||||||
hideShareObject()
|
hideShareObject()
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { shareObjectDetails, shareObject, hideShareObject } = this.props
|
const { shareObjectDetails, hideShareObject } = this.props
|
||||||
|
const url = `${window.location.protocol}//${shareObjectDetails.url}`
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
show={true}
|
show={true}
|
||||||
@@ -93,11 +94,12 @@ export class ShareObjectModal extends React.Component {
|
|||||||
type="text"
|
type="text"
|
||||||
ref={node => (this.copyTextInput = node)}
|
ref={node => (this.copyTextInput = node)}
|
||||||
readOnly="readOnly"
|
readOnly="readOnly"
|
||||||
value={window.location.protocol + "//" + shareObjectDetails.url}
|
value={url}
|
||||||
onClick={() => this.copyTextInput.select()}
|
onClick={() => this.copyTextInput.select()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
{shareObjectDetails.showExpiryDate && (
|
||||||
|
<div
|
||||||
className="input-group"
|
className="input-group"
|
||||||
style={{ display: web.LoggedIn() ? "block" : "none" }}
|
style={{ display: web.LoggedIn() ? "block" : "none" }}
|
||||||
>
|
>
|
||||||
@@ -174,10 +176,11 @@ export class ShareObjectModal extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
text={window.location.protocol + "//" + shareObjectDetails.url}
|
text={url}
|
||||||
onCopy={this.onUrlCopied.bind(this)}
|
onCopy={this.onUrlCopied.bind(this)}
|
||||||
>
|
>
|
||||||
<button className="btn btn-success">Copy Link</button>
|
<button className="btn btn-success">Copy Link</button>
|
||||||
|
|||||||
@@ -66,6 +66,63 @@ describe("ObjectActions", () => {
|
|||||||
expect(deleteObject).toHaveBeenCalledWith("obj1")
|
expect(deleteObject).toHaveBeenCalledWith("obj1")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it("should call downloadObject when single object is selected and download button is clicked", () => {
|
||||||
|
const downloadObject = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectActions
|
||||||
|
object={{ name: "obj1" }}
|
||||||
|
currentPrefix={"pre1/"}
|
||||||
|
downloadObject={downloadObject} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.at(1)
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
expect(downloadObject).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it("should show PreviewObjectModal when preview action is clicked", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectActions
|
||||||
|
object={{ name: "obj1", contentType: "image/jpeg"}}
|
||||||
|
currentPrefix={"pre1/"} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.at(1)
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
expect(wrapper.state("showPreview")).toBeTruthy()
|
||||||
|
expect(wrapper.find("PreviewObjectModal").length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should hide PreviewObjectModal when cancel button is clicked", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectActions
|
||||||
|
object={{ name: "obj1" , contentType: "image/jpeg"}}
|
||||||
|
currentPrefix={"pre1/"} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.at(1)
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
wrapper.find("PreviewObjectModal").prop("hidePreviewModal")()
|
||||||
|
wrapper.update()
|
||||||
|
expect(wrapper.state("showPreview")).toBeFalsy()
|
||||||
|
expect(wrapper.find("PreviewObjectModal").length).toBe(0)
|
||||||
|
})
|
||||||
|
it("should not show PreviewObjectModal when preview action is clicked if object is not an image", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectActions
|
||||||
|
object={{ name: "obj1"}}
|
||||||
|
currentPrefix={"pre1/"} />
|
||||||
|
)
|
||||||
|
expect(wrapper
|
||||||
|
.find("a")
|
||||||
|
.length).toBe(3) // find only the other 2
|
||||||
|
})
|
||||||
|
|
||||||
it("should call shareObject with object and expiry", () => {
|
it("should call shareObject with object and expiry", () => {
|
||||||
const shareObject = jest.fn()
|
const shareObject = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ describe("ObjectItem", () => {
|
|||||||
|
|
||||||
it("shouldn't call onClick when the object isclicked", () => {
|
it("shouldn't call onClick when the object isclicked", () => {
|
||||||
const onClick = jest.fn()
|
const onClick = jest.fn()
|
||||||
const wrapper = shallow(<ObjectItem name={"test"} />)
|
const checkObject = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectItem name={"test"} checkObject={checkObject} />
|
||||||
|
)
|
||||||
wrapper.find("a").simulate("click", { preventDefault: jest.fn() })
|
wrapper.find("a").simulate("click", { preventDefault: jest.fn() })
|
||||||
expect(onClick).not.toHaveBeenCalled()
|
expect(onClick).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -57,9 +60,15 @@ describe("ObjectItem", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should call uncheckObject when the object/prefix is unchecked", () => {
|
it("should call uncheckObject when the object/prefix is unchecked", () => {
|
||||||
|
const checkObject = jest.fn()
|
||||||
const uncheckObject = jest.fn()
|
const uncheckObject = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectItem name={"test"} checked={true} uncheckObject={uncheckObject} />
|
<ObjectItem
|
||||||
|
name={"test"}
|
||||||
|
checked={true}
|
||||||
|
checkObject={checkObject}
|
||||||
|
uncheckObject={uncheckObject}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
wrapper.find("input[type='checkbox']").simulate("change")
|
wrapper.find("input[type='checkbox']").simulate("change")
|
||||||
expect(uncheckObject).toHaveBeenCalledWith("test")
|
expect(uncheckObject).toHaveBeenCalledWith("test")
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2018 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { shallow } from "enzyme"
|
||||||
|
import { PrefixActions } from "../PrefixActions"
|
||||||
|
|
||||||
|
describe("PrefixActions", () => {
|
||||||
|
it("should render without crashing", () => {
|
||||||
|
shallow(<PrefixActions object={{ name: "abc/" }} currentPrefix={"pre1/"} />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should show DeleteObjectConfirmModal when delete action is clicked", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PrefixActions object={{ name: "abc/" }} currentPrefix={"pre1/"} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.last()
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
expect(wrapper.state("showDeleteConfirmation")).toBeTruthy()
|
||||||
|
expect(wrapper.find("DeleteObjectConfirmModal").length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should hide DeleteObjectConfirmModal when Cancel button is clicked", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PrefixActions object={{ name: "abc/" }} currentPrefix={"pre1/"} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.last()
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
wrapper.find("DeleteObjectConfirmModal").prop("hideDeleteConfirmModal")()
|
||||||
|
wrapper.update()
|
||||||
|
expect(wrapper.state("showDeleteConfirmation")).toBeFalsy()
|
||||||
|
expect(wrapper.find("DeleteObjectConfirmModal").length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call deleteObject with object name", () => {
|
||||||
|
const deleteObject = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PrefixActions
|
||||||
|
object={{ name: "abc/" }}
|
||||||
|
currentPrefix={"pre1/"}
|
||||||
|
deleteObject={deleteObject}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.last()
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
wrapper.find("DeleteObjectConfirmModal").prop("deleteObject")()
|
||||||
|
expect(deleteObject).toHaveBeenCalledWith("abc/")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it("should call downloadPrefix when single object is selected and download button is clicked", () => {
|
||||||
|
const downloadPrefix = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PrefixActions
|
||||||
|
object={{ name: "abc/" }}
|
||||||
|
currentPrefix={"pre1/"}
|
||||||
|
downloadPrefix={downloadPrefix} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("a")
|
||||||
|
.first()
|
||||||
|
.simulate("click", { preventDefault: jest.fn() })
|
||||||
|
expect(downloadPrefix).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -41,4 +41,22 @@ describe("PrefixContainer", () => {
|
|||||||
wrapper.find("Connect(ObjectItem)").prop("onClick")()
|
wrapper.find("Connect(ObjectItem)").prop("onClick")()
|
||||||
expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/")
|
expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should pass actions to ObjectItem", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PrefixContainer object={{ name: "abc/" }} checkedObjectsCount={0} />
|
||||||
|
)
|
||||||
|
expect(wrapper.find("Connect(ObjectItem)").prop("actionButtons")).not.toBe(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should pass empty actions to ObjectItem when checkedObjectCount is more than 0", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PrefixContainer object={{ name: "abc/" }} checkedObjectsCount={1} />
|
||||||
|
)
|
||||||
|
expect(wrapper.find("Connect(ObjectItem)").prop("actionButtons")).toBe(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe("ShareObjectModal", () => {
|
|||||||
shallow(
|
shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -44,7 +44,7 @@ describe("ShareObjectModal", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
hideShareObject={hideShareObject}
|
hideShareObject={hideShareObject}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -59,7 +59,7 @@ describe("ShareObjectModal", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
@@ -76,7 +76,7 @@ describe("ShareObjectModal", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ShareObjectModal
|
<ShareObjectModal
|
||||||
object={{ name: "obj1" }}
|
object={{ name: "obj1" }}
|
||||||
shareObjectDetails={{ show: true, object: "obj1", url: "test" }}
|
shareObjectDetails={{ show: true, object: "obj1", url: "test", showExpiryDate: true }}
|
||||||
hideShareObject={hideShareObject}
|
hideShareObject={hideShareObject}
|
||||||
showCopyAlert={showCopyAlert}
|
showCopyAlert={showCopyAlert}
|
||||||
/>
|
/>
|
||||||
@@ -89,8 +89,15 @@ describe("ShareObjectModal", () => {
|
|||||||
describe("Update expiry values", () => {
|
describe("Update expiry values", () => {
|
||||||
const props = {
|
const props = {
|
||||||
object: { name: "obj1" },
|
object: { name: "obj1" },
|
||||||
shareObjectDetails: { show: true, object: "obj1", url: "test" }
|
shareObjectDetails: { show: true, object: "obj1", url: "test", showExpiryDate: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("should not show expiry values if shared with public link", () => {
|
||||||
|
const shareObjectDetails = { show: true, object: "obj1", url: "test", showExpiryDate: false }
|
||||||
|
const wrapper = shallow(<ShareObjectModal {...props} shareObjectDetails={shareObjectDetails} />)
|
||||||
|
expect(wrapper.find('.set-expire').exists()).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
it("should have default expiry values", () => {
|
it("should have default expiry values", () => {
|
||||||
const wrapper = shallow(<ShareObjectModal {...props} />)
|
const wrapper = shallow(<ShareObjectModal {...props} />)
|
||||||
expect(wrapper.state("expiry")).toEqual({
|
expect(wrapper.state("expiry")).toEqual({
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ jest.mock("../../web", () => ({
|
|||||||
.mockReturnValueOnce(false)
|
.mockReturnValueOnce(false)
|
||||||
.mockReturnValueOnce(true)
|
.mockReturnValueOnce(true)
|
||||||
.mockReturnValueOnce(true)
|
.mockReturnValueOnce(true)
|
||||||
|
.mockReturnValueOnce(true)
|
||||||
.mockReturnValueOnce(false),
|
.mockReturnValueOnce(false),
|
||||||
ListObjects: jest.fn(({ bucketName }) => {
|
ListObjects: jest.fn(({ bucketName }) => {
|
||||||
if (bucketName === "test-deny") {
|
if (bucketName === "test-deny") {
|
||||||
@@ -70,6 +71,16 @@ jest.mock("../../web", () => ({
|
|||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
return Promise.resolve({ token: "test" })
|
return Promise.resolve({ token: "test" })
|
||||||
})
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return Promise.resolve({ token: "test" })
|
||||||
|
}),
|
||||||
|
GetBucketPolicy: jest.fn(({ bucketName, prefix }) => {
|
||||||
|
if (!bucketName) {
|
||||||
|
return Promise.reject({ message: "Invalid bucket" })
|
||||||
|
}
|
||||||
|
if (bucketName === 'test-public') return Promise.resolve({ policy: 'readonly' })
|
||||||
|
return Promise.resolve({})
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const middlewares = [thunk]
|
const middlewares = [thunk]
|
||||||
@@ -295,7 +306,8 @@ describe("Objects actions", () => {
|
|||||||
type: "objects/SET_SHARE_OBJECT",
|
type: "objects/SET_SHARE_OBJECT",
|
||||||
show: true,
|
show: true,
|
||||||
object: "b.txt",
|
object: "b.txt",
|
||||||
url: "test"
|
url: "test",
|
||||||
|
showExpiryDate: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
store.dispatch(actionsObjects.showShareObject("b.txt", "test"))
|
store.dispatch(actionsObjects.showShareObject("b.txt", "test"))
|
||||||
@@ -321,14 +333,16 @@ describe("Objects actions", () => {
|
|||||||
it("creates objects/SET_SHARE_OBJECT when object is shared", () => {
|
it("creates objects/SET_SHARE_OBJECT when object is shared", () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
buckets: { currentBucket: "bk1" },
|
buckets: { currentBucket: "bk1" },
|
||||||
objects: { currentPrefix: "pre1/" }
|
objects: { currentPrefix: "pre1/" },
|
||||||
|
browser: { serverInfo: {} },
|
||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_SHARE_OBJECT",
|
type: "objects/SET_SHARE_OBJECT",
|
||||||
show: true,
|
show: true,
|
||||||
object: "a.txt",
|
object: "a.txt",
|
||||||
url: "https://test.com/bk1/pre1/b.txt"
|
url: "https://test.com/bk1/pre1/b.txt",
|
||||||
|
showExpiryDate: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "alert/SET",
|
type: "alert/SET",
|
||||||
@@ -347,10 +361,42 @@ describe("Objects actions", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates objects/SET_SHARE_OBJECT when object is shared with public link", () => {
|
||||||
|
const store = mockStore({
|
||||||
|
buckets: { currentBucket: "test-public" },
|
||||||
|
objects: { currentPrefix: "pre1/" },
|
||||||
|
browser: { serverInfo: { info: { domains: ['public.com'] }} },
|
||||||
|
})
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: "objects/SET_SHARE_OBJECT",
|
||||||
|
show: true,
|
||||||
|
object: "a.txt",
|
||||||
|
url: "public.com/test-public/pre1/a.txt",
|
||||||
|
showExpiryDate: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "alert/SET",
|
||||||
|
alert: {
|
||||||
|
type: "success",
|
||||||
|
message: "Object shared.",
|
||||||
|
id: alertActions.alertId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return store
|
||||||
|
.dispatch(actionsObjects.shareObject("a.txt", 1, 0, 0))
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("creates alert/SET when shareObject is failed", () => {
|
it("creates alert/SET when shareObject is failed", () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
buckets: { currentBucket: "" },
|
buckets: { currentBucket: "" },
|
||||||
objects: { currentPrefix: "pre1/" }
|
objects: { currentPrefix: "pre1/" },
|
||||||
|
browser: { serverInfo: {} },
|
||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
@@ -442,6 +488,34 @@ describe("Objects actions", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should download prefix", () => {
|
||||||
|
const open = jest.fn()
|
||||||
|
const send = jest.fn()
|
||||||
|
const xhrMockClass = () => ({
|
||||||
|
open: open,
|
||||||
|
send: send
|
||||||
|
})
|
||||||
|
window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
|
||||||
|
|
||||||
|
const store = mockStore({
|
||||||
|
buckets: { currentBucket: "bk1" },
|
||||||
|
objects: { currentPrefix: "pre1/" }
|
||||||
|
})
|
||||||
|
return store.dispatch(actionsObjects.downloadPrefix("pre2/")).then(() => {
|
||||||
|
const requestUrl = `${
|
||||||
|
location.origin
|
||||||
|
}${minioBrowserPrefix}/zip?token=test`
|
||||||
|
expect(open).toHaveBeenCalledWith("POST", requestUrl, true)
|
||||||
|
expect(send).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
bucketName: "bk1",
|
||||||
|
prefix: "pre1/",
|
||||||
|
objects: ["pre2/"]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("creates objects/CHECKED_LIST_ADD action", () => {
|
it("creates objects/CHECKED_LIST_ADD action", () => {
|
||||||
const store = mockStore()
|
const store = mockStore()
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
|
|||||||
+172
-112
@@ -19,20 +19,20 @@ import history from "../history"
|
|||||||
import {
|
import {
|
||||||
sortObjectsByName,
|
sortObjectsByName,
|
||||||
sortObjectsBySize,
|
sortObjectsBySize,
|
||||||
sortObjectsByDate
|
sortObjectsByDate,
|
||||||
} from "../utils"
|
} from "../utils"
|
||||||
import { getCurrentBucket } from "../buckets/selectors"
|
import { getCurrentBucket } from "../buckets/selectors"
|
||||||
import { getCurrentPrefix, getCheckedList } from "./selectors"
|
import { getCurrentPrefix, getCheckedList } from "./selectors"
|
||||||
import * as alertActions from "../alert/actions"
|
import * as alertActions from "../alert/actions"
|
||||||
import * as bucketActions from "../buckets/actions"
|
|
||||||
import {
|
import {
|
||||||
minioBrowserPrefix,
|
minioBrowserPrefix,
|
||||||
SORT_BY_NAME,
|
SORT_BY_NAME,
|
||||||
SORT_BY_SIZE,
|
SORT_BY_SIZE,
|
||||||
SORT_BY_LAST_MODIFIED,
|
SORT_BY_LAST_MODIFIED,
|
||||||
SORT_ORDER_ASC,
|
SORT_ORDER_ASC,
|
||||||
SORT_ORDER_DESC
|
SORT_ORDER_DESC,
|
||||||
} from "../constants"
|
} from "../constants"
|
||||||
|
import { getServerInfo, hasServerPublicDomain } from '../browser/selectors'
|
||||||
|
|
||||||
export const SET_LIST = "objects/SET_LIST"
|
export const SET_LIST = "objects/SET_LIST"
|
||||||
export const RESET_LIST = "objects/RESET_LIST"
|
export const RESET_LIST = "objects/RESET_LIST"
|
||||||
@@ -48,35 +48,35 @@ export const CHECKED_LIST_REMOVE = "objects/CHECKED_LIST_REMOVE"
|
|||||||
export const CHECKED_LIST_RESET = "objects/CHECKED_LIST_RESET"
|
export const CHECKED_LIST_RESET = "objects/CHECKED_LIST_RESET"
|
||||||
export const SET_LIST_LOADING = "objects/SET_LIST_LOADING"
|
export const SET_LIST_LOADING = "objects/SET_LIST_LOADING"
|
||||||
|
|
||||||
export const setList = objects => ({
|
export const setList = (objects) => ({
|
||||||
type: SET_LIST,
|
type: SET_LIST,
|
||||||
objects
|
objects,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const resetList = () => ({
|
export const resetList = () => ({
|
||||||
type: RESET_LIST
|
type: RESET_LIST,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setListLoading = listLoading => ({
|
export const setListLoading = (listLoading) => ({
|
||||||
type: SET_LIST_LOADING,
|
type: SET_LIST_LOADING,
|
||||||
listLoading
|
listLoading,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const fetchObjects = () => {
|
export const fetchObjects = () => {
|
||||||
return function(dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
dispatch(resetList())
|
dispatch(resetList())
|
||||||
const {
|
const {
|
||||||
buckets: { currentBucket },
|
buckets: { currentBucket },
|
||||||
objects: { currentPrefix }
|
objects: { currentPrefix },
|
||||||
} = getState()
|
} = getState()
|
||||||
if (currentBucket) {
|
if (currentBucket) {
|
||||||
dispatch(setListLoading(true))
|
dispatch(setListLoading(true))
|
||||||
return web
|
return web
|
||||||
.ListObjects({
|
.ListObjects({
|
||||||
bucketName: currentBucket,
|
bucketName: currentBucket,
|
||||||
prefix: currentPrefix
|
prefix: currentPrefix,
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then((res) => {
|
||||||
// we need to check if the bucket name and prefix are the same as
|
// we need to check if the bucket name and prefix are the same as
|
||||||
// when the request was made before updating the displayed objects
|
// when the request was made before updating the displayed objects
|
||||||
if (
|
if (
|
||||||
@@ -85,10 +85,10 @@ export const fetchObjects = () => {
|
|||||||
) {
|
) {
|
||||||
let objects = []
|
let objects = []
|
||||||
if (res.objects) {
|
if (res.objects) {
|
||||||
objects = res.objects.map(object => {
|
objects = res.objects.map((object) => {
|
||||||
return {
|
return {
|
||||||
...object,
|
...object,
|
||||||
name: object.name.replace(currentPrefix, "")
|
name: object.name.replace(currentPrefix, ""),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -104,13 +104,13 @@ export const fetchObjects = () => {
|
|||||||
dispatch(setListLoading(false))
|
dispatch(setListLoading(false))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: err.message,
|
message: err.message,
|
||||||
autoClear: true
|
autoClear: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
dispatch(resetList())
|
dispatch(resetList())
|
||||||
@@ -123,8 +123,8 @@ export const fetchObjects = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sortObjects = sortBy => {
|
export const sortObjects = (sortBy) => {
|
||||||
return function(dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const { objects } = getState()
|
const { objects } = getState()
|
||||||
let sortOrder = SORT_ORDER_ASC
|
let sortOrder = SORT_ORDER_ASC
|
||||||
// Reverse sort order if the list is already sorted on same field
|
// Reverse sort order if the list is already sorted on same field
|
||||||
@@ -149,18 +149,18 @@ const sortObjectsList = (list, sortBy, sortOrder) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setSortBy = sortBy => ({
|
export const setSortBy = (sortBy) => ({
|
||||||
type: SET_SORT_BY,
|
type: SET_SORT_BY,
|
||||||
sortBy
|
sortBy,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setSortOrder = sortOrder => ({
|
export const setSortOrder = (sortOrder) => ({
|
||||||
type: SET_SORT_ORDER,
|
type: SET_SORT_ORDER,
|
||||||
sortOrder
|
sortOrder,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const selectPrefix = prefix => {
|
export const selectPrefix = (prefix) => {
|
||||||
return function(dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
dispatch(setCurrentPrefix(prefix))
|
dispatch(setCurrentPrefix(prefix))
|
||||||
dispatch(fetchObjects())
|
dispatch(fetchObjects())
|
||||||
dispatch(resetCheckedList())
|
dispatch(resetCheckedList())
|
||||||
@@ -169,49 +169,49 @@ export const selectPrefix = prefix => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setCurrentPrefix = prefix => {
|
export const setCurrentPrefix = (prefix) => {
|
||||||
return {
|
return {
|
||||||
type: SET_CURRENT_PREFIX,
|
type: SET_CURRENT_PREFIX,
|
||||||
prefix
|
prefix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setPrefixWritable = prefixWritable => ({
|
export const setPrefixWritable = (prefixWritable) => ({
|
||||||
type: SET_PREFIX_WRITABLE,
|
type: SET_PREFIX_WRITABLE,
|
||||||
prefixWritable
|
prefixWritable,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteObject = object => {
|
export const deleteObject = (object) => {
|
||||||
return function(dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const currentBucket = getCurrentBucket(getState())
|
const currentBucket = getCurrentBucket(getState())
|
||||||
const currentPrefix = getCurrentPrefix(getState())
|
const currentPrefix = getCurrentPrefix(getState())
|
||||||
const objectName = `${currentPrefix}${object}`
|
const objectName = `${currentPrefix}${object}`
|
||||||
return web
|
return web
|
||||||
.RemoveObject({
|
.RemoveObject({
|
||||||
bucketName: currentBucket,
|
bucketName: currentBucket,
|
||||||
objects: [objectName]
|
objects: [objectName],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(removeObject(object))
|
dispatch(removeObject(object))
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: e.message
|
message: e.message,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeObject = object => ({
|
export const removeObject = (object) => ({
|
||||||
type: REMOVE,
|
type: REMOVE,
|
||||||
object
|
object,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteCheckedObjects = () => {
|
export const deleteCheckedObjects = () => {
|
||||||
return function(dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const checkedObjects = getCheckedList(getState())
|
const checkedObjects = getCheckedList(getState())
|
||||||
for (let i = 0; i < checkedObjects.length; i++) {
|
for (let i = 0; i < checkedObjects.length; i++) {
|
||||||
dispatch(deleteObject(checkedObjects[i]))
|
dispatch(deleteObject(checkedObjects[i]))
|
||||||
@@ -221,33 +221,52 @@ export const deleteCheckedObjects = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const shareObject = (object, days, hours, minutes) => {
|
export const shareObject = (object, days, hours, minutes) => {
|
||||||
return function(dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
|
const hasServerDomain = hasServerPublicDomain(getState())
|
||||||
const currentBucket = getCurrentBucket(getState())
|
const currentBucket = getCurrentBucket(getState())
|
||||||
const currentPrefix = getCurrentPrefix(getState())
|
const currentPrefix = getCurrentPrefix(getState())
|
||||||
const objectName = `${currentPrefix}${object}`
|
const objectName = `${currentPrefix}${object}`
|
||||||
const expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60
|
const expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60
|
||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
return web
|
return web
|
||||||
.PresignedGet({
|
.GetBucketPolicy({ bucketName: currentBucket, prefix: currentPrefix })
|
||||||
host: location.host,
|
.catch(() => ({ policy: null }))
|
||||||
bucket: currentBucket,
|
.then(({ policy }) => {
|
||||||
object: objectName,
|
if (hasServerDomain && ['readonly', 'readwrite'].includes(policy)) {
|
||||||
expiry: expiry
|
const domain = getServerInfo(getState()).info.domains[0]
|
||||||
|
const url = `${domain}/${currentBucket}/${encodeURI(objectName)}`
|
||||||
|
dispatch(showShareObject(object, url, false))
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "success",
|
||||||
|
message: "Object shared."
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return web
|
||||||
|
.PresignedGet({
|
||||||
|
host: location.host,
|
||||||
|
bucket: currentBucket,
|
||||||
|
object: objectName,
|
||||||
|
expiry: expiry
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(obj => {
|
.then((obj) => {
|
||||||
|
if (!obj) return
|
||||||
dispatch(showShareObject(object, obj.url))
|
dispatch(showShareObject(object, obj.url))
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: `Object shared. Expires in ${days} days ${hours} hours ${minutes} minutes`
|
message: `Object shared. Expires in ${days} days ${hours} hours ${minutes} minutes`,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: err.message
|
message: err.message,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -265,29 +284,29 @@ export const shareObject = (object, days, hours, minutes) => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: `Object shared.`
|
message: `Object shared.`,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showShareObject = (object, url) => ({
|
export const showShareObject = (object, url, showExpiryDate = true) => ({
|
||||||
type: SET_SHARE_OBJECT,
|
type: SET_SHARE_OBJECT,
|
||||||
show: true,
|
show: true,
|
||||||
object,
|
object,
|
||||||
url
|
url,
|
||||||
|
showExpiryDate,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const hideShareObject = (object, url) => ({
|
export const hideShareObject = (object, url) => ({
|
||||||
type: SET_SHARE_OBJECT,
|
type: SET_SHARE_OBJECT,
|
||||||
show: false,
|
show: false,
|
||||||
object: "",
|
object: "",
|
||||||
url: ""
|
url: "",
|
||||||
})
|
})
|
||||||
|
export const getObjectURL = (object, callback) => {
|
||||||
export const downloadObject = object => {
|
return function (dispatch, getState) {
|
||||||
return function(dispatch, getState) {
|
|
||||||
const currentBucket = getCurrentBucket(getState())
|
const currentBucket = getCurrentBucket(getState())
|
||||||
const currentPrefix = getCurrentPrefix(getState())
|
const currentPrefix = getCurrentPrefix(getState())
|
||||||
const objectName = `${currentPrefix}${object}`
|
const objectName = `${currentPrefix}${object}`
|
||||||
@@ -295,78 +314,119 @@ export const downloadObject = object => {
|
|||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
return web
|
return web
|
||||||
.CreateURLToken()
|
.CreateURLToken()
|
||||||
.then(res => {
|
.then((res) => {
|
||||||
const url = `${
|
const url = `${window.location.origin}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=${res.token}`
|
||||||
window.location.origin
|
callback(url)
|
||||||
}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=${
|
|
||||||
res.token
|
|
||||||
}`
|
|
||||||
window.location = url
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: err.message
|
message: err.message,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const url = `${
|
const url = `${window.location.origin}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=`
|
||||||
window.location.origin
|
callback(url)
|
||||||
}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=`
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const downloadObject = (object) => {
|
||||||
|
return function (dispatch, getState) {
|
||||||
|
const currentBucket = getCurrentBucket(getState())
|
||||||
|
const currentPrefix = getCurrentPrefix(getState())
|
||||||
|
const objectName = `${currentPrefix}${object}`
|
||||||
|
const encObjectName = encodeURI(objectName)
|
||||||
|
if (web.LoggedIn()) {
|
||||||
|
return web
|
||||||
|
.CreateURLToken()
|
||||||
|
.then((res) => {
|
||||||
|
const url = `${window.location.origin}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=${res.token}`
|
||||||
|
window.location = url
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "danger",
|
||||||
|
message: err.message,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const url = `${window.location.origin}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=`
|
||||||
window.location = url
|
window.location = url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkObject = object => ({
|
export const downloadPrefix = (object) => {
|
||||||
type: CHECKED_LIST_ADD,
|
return function (dispatch, getState) {
|
||||||
object
|
return downloadObjects(
|
||||||
})
|
getCurrentBucket(getState()),
|
||||||
|
getCurrentPrefix(getState()),
|
||||||
export const uncheckObject = object => ({
|
[object],
|
||||||
type: CHECKED_LIST_REMOVE,
|
`${object.slice(0, -1)}.zip`,
|
||||||
object
|
dispatch
|
||||||
})
|
)
|
||||||
|
|
||||||
export const resetCheckedList = () => ({
|
|
||||||
type: CHECKED_LIST_RESET
|
|
||||||
})
|
|
||||||
|
|
||||||
export const downloadCheckedObjects = () => {
|
|
||||||
return function(dispatch, getState) {
|
|
||||||
const state = getState()
|
|
||||||
const req = {
|
|
||||||
bucketName: getCurrentBucket(state),
|
|
||||||
prefix: getCurrentPrefix(state),
|
|
||||||
objects: getCheckedList(state)
|
|
||||||
}
|
|
||||||
if (!web.LoggedIn()) {
|
|
||||||
const requestUrl = location.origin + "/minio/zip?token="
|
|
||||||
downloadZip(requestUrl, req, dispatch)
|
|
||||||
} else {
|
|
||||||
return web
|
|
||||||
.CreateURLToken()
|
|
||||||
.then(res => {
|
|
||||||
const requestUrl = `${
|
|
||||||
location.origin
|
|
||||||
}${minioBrowserPrefix}/zip?token=${res.token}`
|
|
||||||
downloadZip(requestUrl, req, dispatch)
|
|
||||||
})
|
|
||||||
.catch(err =>
|
|
||||||
dispatch(
|
|
||||||
alertActions.set({
|
|
||||||
type: "danger",
|
|
||||||
message: err.message
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadZip = (url, req, dispatch) => {
|
|
||||||
|
export const checkObject = (object) => ({
|
||||||
|
type: CHECKED_LIST_ADD,
|
||||||
|
object,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const uncheckObject = (object) => ({
|
||||||
|
type: CHECKED_LIST_REMOVE,
|
||||||
|
object,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const resetCheckedList = () => ({
|
||||||
|
type: CHECKED_LIST_RESET,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const downloadCheckedObjects = () => {
|
||||||
|
return function (dispatch, getState) {
|
||||||
|
return downloadObjects(
|
||||||
|
getCurrentBucket(getState()),
|
||||||
|
getCurrentPrefix(getState()),
|
||||||
|
getCheckedList(getState()),
|
||||||
|
null,
|
||||||
|
dispatch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadObjects = (bucketName, prefix, objects, filename, dispatch) => {
|
||||||
|
const req = {
|
||||||
|
bucketName: bucketName,
|
||||||
|
prefix: prefix,
|
||||||
|
objects: objects,
|
||||||
|
}
|
||||||
|
if (web.LoggedIn()) {
|
||||||
|
return web
|
||||||
|
.CreateURLToken()
|
||||||
|
.then((res) => {
|
||||||
|
const requestUrl = `${location.origin}${minioBrowserPrefix}/zip?token=${res.token}`
|
||||||
|
downloadZip(requestUrl, req, filename, dispatch)
|
||||||
|
})
|
||||||
|
.catch((err) =>
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "danger",
|
||||||
|
message: err.message,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const requestUrl = `${location.origin}${minioBrowserPrefix}/zip?token=`
|
||||||
|
downloadZip(requestUrl, req, filename, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadZip = (url, req, filename, dispatch) => {
|
||||||
var anchor = document.createElement("a")
|
var anchor = document.createElement("a")
|
||||||
document.body.appendChild(anchor)
|
document.body.appendChild(anchor)
|
||||||
|
|
||||||
@@ -374,17 +434,17 @@ const downloadZip = (url, req, dispatch) => {
|
|||||||
xhr.open("POST", url, true)
|
xhr.open("POST", url, true)
|
||||||
xhr.responseType = "blob"
|
xhr.responseType = "blob"
|
||||||
|
|
||||||
xhr.onload = function(e) {
|
xhr.onload = function (e) {
|
||||||
if (this.status == 200) {
|
if (this.status == 200) {
|
||||||
dispatch(resetCheckedList())
|
dispatch(resetCheckedList())
|
||||||
var blob = new Blob([this.response], {
|
var blob = new Blob([this.response], {
|
||||||
type: "octet/stream"
|
type: "octet/stream",
|
||||||
})
|
})
|
||||||
var blobUrl = window.URL.createObjectURL(blob)
|
var blobUrl = window.URL.createObjectURL(blob)
|
||||||
var separator = req.prefix.length > 1 ? "-" : ""
|
var separator = req.prefix.length > 1 ? "-" : ""
|
||||||
|
|
||||||
anchor.href = blobUrl
|
anchor.href = blobUrl
|
||||||
anchor.download =
|
anchor.download = filename ||
|
||||||
req.bucketName + separator + req.prefix.slice(0, -1) + ".zip"
|
req.bucketName + separator + req.prefix.slice(0, -1) + ".zip"
|
||||||
|
|
||||||
anchor.click()
|
anchor.click()
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ export default (
|
|||||||
shareObject: {
|
shareObject: {
|
||||||
show: action.show,
|
show: action.show,
|
||||||
object: action.object,
|
object: action.object,
|
||||||
url: action.url
|
url: action.url,
|
||||||
|
showExpiryDate: action.showExpiryDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actionsObjects.CHECKED_LIST_ADD:
|
case actionsObjects.CHECKED_LIST_ADD:
|
||||||
|
|||||||
@@ -48,18 +48,29 @@ export class Dropzone extends React.Component {
|
|||||||
const rejectStyle = {
|
const rejectStyle = {
|
||||||
backgroundColor: "#ffdddd"
|
backgroundColor: "#ffdddd"
|
||||||
}
|
}
|
||||||
|
const getStyle = (isDragActive, isDragAccept, isDragReject) => ({
|
||||||
|
...style,
|
||||||
|
...(isDragActive ? activeStyle : {}),
|
||||||
|
...(isDragReject ? rejectStyle : {})
|
||||||
|
})
|
||||||
|
|
||||||
// disableClick means that it won't trigger a file upload box when
|
// disableClick means that it won't trigger a file upload box when
|
||||||
// the user clicks on a file.
|
// the user clicks on a file.
|
||||||
return (
|
return (
|
||||||
<ReactDropzone
|
<ReactDropzone
|
||||||
style={style}
|
|
||||||
activeStyle={activeStyle}
|
|
||||||
rejectStyle={rejectStyle}
|
|
||||||
disableClick={true}
|
|
||||||
onDrop={this.onDrop.bind(this)}
|
onDrop={this.onDrop.bind(this)}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{({getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject}) => (
|
||||||
|
<div
|
||||||
|
{...getRootProps({
|
||||||
|
onClick: event => event.stopPropagation()
|
||||||
|
})}
|
||||||
|
style={getStyle(isDragActive, isDragAccept, isDragReject)}
|
||||||
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</ReactDropzone>
|
</ReactDropzone>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,11 +89,16 @@ export const uploadFile = file => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentPrefix = getCurrentPrefix(state)
|
const currentPrefix = getCurrentPrefix(state)
|
||||||
const objectName = `${currentPrefix}${file.name}`
|
var _filePath = file.path || file.name
|
||||||
|
if (_filePath.charAt(0) == '/') {
|
||||||
|
_filePath = _filePath.substring(1)
|
||||||
|
}
|
||||||
|
const filePath = _filePath
|
||||||
|
const objectName = `${currentPrefix}${filePath}`
|
||||||
const uploadUrl = `${
|
const uploadUrl = `${
|
||||||
window.location.origin
|
window.location.origin
|
||||||
}${minioBrowserPrefix}/upload/${currentBucket}/${objectName}`
|
}${minioBrowserPrefix}/upload/${currentBucket}/${objectName}`
|
||||||
const slug = `${currentBucket}-${currentPrefix}-${file.name}`
|
const slug = `${currentBucket}-${currentPrefix}-${filePath}`
|
||||||
|
|
||||||
let xhr = new XMLHttpRequest()
|
let xhr = new XMLHttpRequest()
|
||||||
xhr.open("PUT", uploadUrl, true)
|
xhr.open("PUT", uploadUrl, true)
|
||||||
@@ -141,7 +146,7 @@ export const uploadFile = file => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "File '" + file.name + "' uploaded successfully."
|
message: "File '" + filePath + "' uploaded successfully."
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
dispatch(objectsActions.selectPrefix(currentPrefix))
|
dispatch(objectsActions.selectPrefix(currentPrefix))
|
||||||
@@ -153,7 +158,7 @@ export const uploadFile = file => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: "Error occurred uploading '" + file.name + "'."
|
message: "Error occurred uploading '" + filePath + "'."
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -107,3 +107,13 @@ export const getRandomSecretKey = () => {
|
|||||||
const base64Str = btoa(binStr)
|
const base64Str = btoa(binStr)
|
||||||
return base64Str.replace(/\//g, "+").substr(0, 40)
|
return base64Str.replace(/\//g, "+").substr(0, 40)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getRandomString = length => {
|
||||||
|
var text = ""
|
||||||
|
var possible =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length))
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|||||||
+11
-1
@@ -75,6 +75,16 @@ class Web {
|
|||||||
GetToken() {
|
GetToken() {
|
||||||
return storage.getItem('token')
|
return storage.getItem('token')
|
||||||
}
|
}
|
||||||
|
GetDiscoveryDoc() {
|
||||||
|
return this.makeCall("GetDiscoveryDoc")
|
||||||
|
}
|
||||||
|
LoginSTS(args) {
|
||||||
|
return this.makeCall('LoginSTS', args)
|
||||||
|
.then(res => {
|
||||||
|
storage.setItem('token', `${res.token}`)
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
}
|
||||||
ServerInfo() {
|
ServerInfo() {
|
||||||
return this.makeCall('ServerInfo')
|
return this.makeCall('ServerInfo')
|
||||||
}
|
}
|
||||||
@@ -125,4 +135,4 @@ class Web {
|
|||||||
|
|
||||||
const web = new Web(`${window.location.protocol}//${window.location.host}${minioBrowserPrefix}/webrpc`);
|
const web = new Web(`${window.location.protocol}//${window.location.host}${minioBrowserPrefix}/webrpc`);
|
||||||
|
|
||||||
export default web;
|
export default web;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ div.fesl-row {
|
|||||||
|
|
||||||
.fesl-item-name {
|
.fesl-item-name {
|
||||||
a {
|
a {
|
||||||
cursor: default;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +114,6 @@ div.fesl-row {
|
|||||||
----------------------------*/
|
----------------------------*/
|
||||||
&[data-type=folder] {
|
&[data-type=folder] {
|
||||||
.list-type(#a1d6dd, '\f07b');
|
.list-type(#a1d6dd, '\f07b');
|
||||||
|
|
||||||
.fesl-item-name {
|
|
||||||
a {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&[data-type=pdf] {.list-type(#fa7775, '\f1c1'); }
|
&[data-type=pdf] {.list-type(#fa7775, '\f1c1'); }
|
||||||
&[data-type=zip] { .list-type(#427089, '\f1c6'); }
|
&[data-type=zip] { .list-type(#427089, '\f1c6'); }
|
||||||
@@ -355,6 +349,7 @@ div.fesl-row {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
@@ -501,4 +496,4 @@ div.fesl-row {
|
|||||||
.opacity(1);
|
.opacity(1);
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.openid-login {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openid-btn {
|
||||||
|
display: inline-block;
|
||||||
|
color: @link-color;
|
||||||
|
margin-top: 30px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 14px;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.or {
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items: center;
|
||||||
|
color:grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or:after,
|
||||||
|
.or:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
background: grey;
|
||||||
|
width: 10px;
|
||||||
|
height: 1px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/*------------------------------
|
/*------------------------------
|
||||||
Chrome autofill fix
|
Chrome autofill fix
|
||||||
-------------------------------*/
|
-------------------------------*/
|
||||||
@@ -102,4 +137,4 @@ input:-webkit-autofill {
|
|||||||
-webkit-box-shadow:0 0 0 50px #002a37 inset !important;
|
-webkit-box-shadow:0 0 0 50px #002a37 inset !important;
|
||||||
-webkit-text-fill-color: @white !important;
|
-webkit-text-fill-color: @white !important;
|
||||||
caret-color: white;
|
caret-color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+7212
-3616
File diff suppressed because it is too large
Load Diff
+48
-47
@@ -28,69 +28,70 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/minio/minio",
|
"homepage": "https://github.com/minio/minio",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^1.5.2",
|
"async": "^3.2.0",
|
||||||
"babel-cli": "^6.26.0",
|
"babel-cli": "^6.26.0",
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-jest": "^22.1.0",
|
"babel-jest": "^23.6.0",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
"babel-polyfill": "^6.23.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"babel-preset-es2015": "^6.14.0",
|
"babel-preset-es2015": "^6.14.0",
|
||||||
"babel-preset-react": "^6.11.1",
|
"babel-preset-react": "^6.11.1",
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
"copy-webpack-plugin": "^4.6.0",
|
"copy-webpack-plugin": "^6.0.1",
|
||||||
"css-loader": "^0.23.1",
|
"css-loader": "^3.5.3",
|
||||||
"enzyme": "^3.10.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-adapter-react-16": "^1.1.1",
|
"enzyme-adapter-react-16": "^1.15.2",
|
||||||
"esformatter": "^0.10.0",
|
"esformatter": "^0.11.3",
|
||||||
"esformatter-jsx": "^7.4.1",
|
"esformatter-jsx": "^8.0.1",
|
||||||
"esformatter-jsx-ignore": "^1.0.6",
|
"esformatter-jsx-ignore": "^1.0.6",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^4.3.0",
|
||||||
"jest": "^22.1.4",
|
"jest": "^23.6.0",
|
||||||
"jest-enzyme": "^4.0.2",
|
"jest-enzyme": "^7.1.2",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.7",
|
||||||
"less": "^3.9.0",
|
"less": "^3.11.1",
|
||||||
"less-loader": "^4.1.0",
|
"less-loader": "^6.1.0",
|
||||||
"purgecss-webpack-plugin": "^1.4.0",
|
"purgecss-webpack-plugin": "^2.2.0",
|
||||||
"style-loader": "^0.13.1",
|
"style-loader": "^1.2.1",
|
||||||
"url-loader": "^0.5.7",
|
"url-loader": "^4.1.0",
|
||||||
"webpack-cli": "^3.2.0",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-dev-server": "^3.1.14"
|
"webpack-dev-server": "^3.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.10.0",
|
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||||
"bootstrap": "^3.3.6",
|
"bootstrap": "^3.4.1",
|
||||||
"classnames": "^2.2.3",
|
"classnames": "^2.2.6",
|
||||||
"expect": "^1.20.2",
|
"core-js": "^3.6.5",
|
||||||
"glob-all": "^3.1.0",
|
"expect": "^26.0.1",
|
||||||
"history": "^4.7.2",
|
"glob-all": "^3.2.1",
|
||||||
|
"history": "^4.10.1",
|
||||||
"humanize": "0.0.9",
|
"humanize": "0.0.9",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"json-loader": "^0.5.4",
|
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"local-storage-fallback": "^4.0.2",
|
"local-storage-fallback": "^4.1.1",
|
||||||
"material-design-iconic-font": "^2.2.0",
|
"material-design-iconic-font": "^2.2.0",
|
||||||
"mime-db": "^1.25.0",
|
"mime-db": "^1.44.0",
|
||||||
"mime-types": "^2.1.13",
|
"mime-types": "^2.1.27",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.26.0",
|
||||||
"react": "^16.2.0",
|
"query-string": "^6.12.1",
|
||||||
"react-addons-test-utils": "^0.14.8",
|
"react": "^16.13.1",
|
||||||
"react-bootstrap": "^0.32.0",
|
"react-addons-test-utils": "^15.6.2",
|
||||||
"react-copy-to-clipboard": "^5.0.1",
|
"react-bootstrap": "^0.32.4",
|
||||||
|
"react-copy-to-clipboard": "^5.0.2",
|
||||||
"react-custom-scrollbars": "^4.2.1",
|
"react-custom-scrollbars": "^4.2.1",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.13.1",
|
||||||
"react-dropzone": "^4.2.3",
|
"react-dropzone": "^11.0.1",
|
||||||
"react-infinite-scroller": "^1.0.6",
|
"react-infinite-scroller": "^1.2.4",
|
||||||
"react-onclickout": "^2.0.8",
|
"react-onclickout": "^2.0.8",
|
||||||
"react-redux": "^5.0.6",
|
"react-redux": "^5.1.2",
|
||||||
"react-router-dom": "^4.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"redux": "^3.7.2",
|
"redux": "^4.0.5",
|
||||||
"redux-mock-store": "^1.5.1",
|
"redux-mock-store": "^1.5.4",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"reselect": "^3.0.1",
|
"reselect": "^4.0.0",
|
||||||
"superagent": "^3.8.2",
|
"superagent": "^5.2.2",
|
||||||
"superagent-es6-promise": "^1.0.0",
|
"superagent-es6-promise": "^1.0.0",
|
||||||
"webpack": "^4.28.3"
|
"webpack": "^4.43.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
checks = ["all", "-ST1005", "-ST1000", "-SA4000", "-SA9004", "-SA1019", "-SA1008", "-U1000", "-ST1003", "-ST1018"]
|
||||||
+65
-52
File diff suppressed because one or more lines are too long
@@ -73,16 +73,17 @@ var exports = {
|
|||||||
},
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
'/minio/webrpc': {
|
'/minio/webrpc': {
|
||||||
target: 'http://localhost:9000',
|
target: 'http://localhost:9000',
|
||||||
secure: false
|
secure: false,
|
||||||
|
headers: {'Host': "localhost:9000"}
|
||||||
},
|
},
|
||||||
'/minio/upload/*': {
|
'/minio/upload/*': {
|
||||||
target: 'http://localhost:9000',
|
target: 'http://localhost:9000',
|
||||||
secure: false
|
secure: false
|
||||||
},
|
},
|
||||||
'/minio/download/*': {
|
'/minio/download/*': {
|
||||||
target: 'http://localhost:9000',
|
target: 'http://localhost:9000',
|
||||||
secure: false
|
secure: false
|
||||||
},
|
},
|
||||||
'/minio/zip': {
|
'/minio/zip': {
|
||||||
target: 'http://localhost:9000',
|
target: 'http://localhost:9000',
|
||||||
@@ -91,7 +92,7 @@ var exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin({patterns: [
|
||||||
{from: 'app/css/loader.css'},
|
{from: 'app/css/loader.css'},
|
||||||
{from: 'app/img/browsers/chrome.png'},
|
{from: 'app/img/browsers/chrome.png'},
|
||||||
{from: 'app/img/browsers/firefox.png'},
|
{from: 'app/img/browsers/firefox.png'},
|
||||||
@@ -101,7 +102,7 @@ var exports = {
|
|||||||
{from: 'app/img/favicon/favicon-32x32.png'},
|
{from: 'app/img/favicon/favicon-32x32.png'},
|
||||||
{from: 'app/img/favicon/favicon-96x96.png'},
|
{from: 'app/img/favicon/favicon-96x96.png'},
|
||||||
{from: 'app/index.html'}
|
{from: 'app/index.html'}
|
||||||
]),
|
]}),
|
||||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
||||||
new PurgecssPlugin({
|
new PurgecssPlugin({
|
||||||
paths: glob.sync([
|
paths: glob.sync([
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ var exports = {
|
|||||||
fs:'empty'
|
fs:'empty'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin({patterns: [
|
||||||
{from: 'app/css/loader.css'},
|
{from: 'app/css/loader.css'},
|
||||||
{from: 'app/img/browsers/chrome.png'},
|
{from: 'app/img/browsers/chrome.png'},
|
||||||
{from: 'app/img/browsers/firefox.png'},
|
{from: 'app/img/browsers/firefox.png'},
|
||||||
@@ -77,7 +77,7 @@ var exports = {
|
|||||||
{from: 'app/img/favicon/favicon-32x32.png'},
|
{from: 'app/img/favicon/favicon-32x32.png'},
|
||||||
{from: 'app/img/favicon/favicon-96x96.png'},
|
{from: 'app/img/favicon/favicon-96x96.png'},
|
||||||
{from: 'app/index.html'}
|
{from: 'app/index.html'}
|
||||||
]),
|
]}),
|
||||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
||||||
new PurgecssPlugin({
|
new PurgecssPlugin({
|
||||||
paths: glob.sync([
|
paths: glob.sync([
|
||||||
|
|||||||
-9009
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# usage: ./benchcmp.sh <commit-sha1> <commit-sha2>
|
|
||||||
# Exit on any non zero return value on execution of a command.
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# path of benchcmp.
|
|
||||||
benchcmp=${GOPATH}/bin/benchcmp
|
|
||||||
|
|
||||||
# function which runs the benchmark comparison.
|
|
||||||
RunBenchCmp () {
|
|
||||||
# Path for storing output of benchmark at commit 1.
|
|
||||||
commit1Bench=/tmp/minio-$1.bench
|
|
||||||
# Path for storing output of benchmark at commit 2.
|
|
||||||
commit2Bench=/tmp/minio-$2.bench
|
|
||||||
# switch to commit $1.
|
|
||||||
git checkout $1
|
|
||||||
# Check if the benchmark results for given commit 1 already exists.
|
|
||||||
# Benchmarks are time/resource consuming operations, run only if the the results doesn't exist.
|
|
||||||
if [[ ! -f $commit1Bench ]]
|
|
||||||
then
|
|
||||||
|
|
||||||
echo "Running benchmarks at $1"
|
|
||||||
go test -run=NONE -bench=. | tee $commit1Bench
|
|
||||||
fi
|
|
||||||
# get back to the commit from which it was started.
|
|
||||||
git checkout -
|
|
||||||
echo "Checking into commit $2"
|
|
||||||
# switch to commit $2
|
|
||||||
git checkout $2
|
|
||||||
# Check if the benchmark results for given commit 2 already exists.
|
|
||||||
# Benchmarks are time/resource consuming operations, run only if the the results doesn't exist.
|
|
||||||
if [[ ! -f $commit2Bench ]]
|
|
||||||
then
|
|
||||||
# Running benchmarks at $2.
|
|
||||||
echo "Running benchmarks at $2"
|
|
||||||
go test -run=NONE -bench=. | tee $commit2Bench
|
|
||||||
fi
|
|
||||||
|
|
||||||
# get back to the commit from which it was started.
|
|
||||||
git checkout -
|
|
||||||
# Comparing the benchmarks.
|
|
||||||
echo "Running benchmark comparison between $1 and $2 ..."
|
|
||||||
$benchcmp $commit1Bench $commit2Bench
|
|
||||||
echo "Done."
|
|
||||||
}
|
|
||||||
|
|
||||||
# check if 2 commit SHA's of snapshots of code for which benchmp has to be done is provided.
|
|
||||||
if [ ! $# -eq 2 ]
|
|
||||||
then
|
|
||||||
# exit if commit SHA's are not provided.
|
|
||||||
echo $#
|
|
||||||
echo "Need Commit SHA's of 2 snapshots to be supplied to run benchmark comparison."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if benchcmp exists.
|
|
||||||
if [[ -x "$benchcmp" ]]
|
|
||||||
then
|
|
||||||
RunBenchCmp $1 $2
|
|
||||||
else
|
|
||||||
# install benchcmp if doesnt't exist.
|
|
||||||
echo "fetching Benchcmp..."
|
|
||||||
go get -u golang.org/x/tools/cmd/benchcmp
|
|
||||||
echo "Done."
|
|
||||||
RunBenchCmp $1 $2
|
|
||||||
fi
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
# Enable tracing if set.
|
# Enable tracing if set.
|
||||||
[ -n "$BASH_XTRACEFD" ] && set -ex
|
[ -n "$BASH_XTRACEFD" ] && set -x
|
||||||
|
|
||||||
function _init() {
|
function _init() {
|
||||||
## All binaries are static make sure to disable CGO.
|
## All binaries are static make sure to disable CGO.
|
||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
## List of architectures and OS to test coss compilation.
|
## List of architectures and OS to test coss compilation.
|
||||||
SUPPORTED_OSARCH="linux/ppc64le linux/arm64 linux/s390x darwin/amd64 freebsd/amd64"
|
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64"
|
||||||
}
|
}
|
||||||
|
|
||||||
function _build() {
|
function _build() {
|
||||||
@@ -19,11 +20,11 @@ function _build() {
|
|||||||
package=$(go list -f '{{.ImportPath}}')
|
package=$(go list -f '{{.ImportPath}}')
|
||||||
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
||||||
|
|
||||||
# Go build to build the binary.
|
# go build -trimpath to build the binary.
|
||||||
export GOOS=$os
|
export GOOS=$os
|
||||||
export GOARCH=$arch
|
export GOARCH=$arch
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
go build -tags kqueue -o /dev/null
|
go build -trimpath -tags kqueue -o /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function start_minio_server()
|
|||||||
MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 \
|
MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 \
|
||||||
minio --quiet --json server /data --address 127.0.0.1:24242 > server.log 2>&1 &
|
minio --quiet --json server /data --address 127.0.0.1:24242 > server.log 2>&1 &
|
||||||
server_pid=$!
|
server_pid=$!
|
||||||
sleep 3
|
sleep 10
|
||||||
|
|
||||||
echo "$server_pid"
|
echo "$server_pid"
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ function start_minio_gateway_s3()
|
|||||||
minio --quiet --json gateway s3 http://127.0.0.1:24242 \
|
minio --quiet --json gateway s3 http://127.0.0.1:24242 \
|
||||||
--address 127.0.0.1:24240 > gateway.log 2>&1 &
|
--address 127.0.0.1:24240 > gateway.log 2>&1 &
|
||||||
gw_pid=$!
|
gw_pid=$!
|
||||||
sleep 3
|
sleep 10
|
||||||
|
|
||||||
echo "$gw_pid"
|
echo "$gw_pid"
|
||||||
}
|
}
|
||||||
@@ -46,9 +46,10 @@ function main()
|
|||||||
gw_pid="$(start_minio_gateway_s3)"
|
gw_pid="$(start_minio_gateway_s3)"
|
||||||
|
|
||||||
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
|
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
|
||||||
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh aws-sdk-go \
|
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh \
|
||||||
aws-sdk-java aws-sdk-php aws-sdk-ruby awscli healthcheck minio-dotnet \
|
aws-sdk-go aws-sdk-java aws-sdk-php aws-sdk-ruby awscli \
|
||||||
minio-go minio-java minio-js minio-py
|
healthcheck mc minio-dotnet minio-js \
|
||||||
|
minio-py s3cmd s3select security
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
kill "$sr_pid"
|
kill "$sr_pid"
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
GO111MODULE=on CGO_ENABLED=0 go test -v -coverprofile=coverage.txt -covermode=atomic ./...
|
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
for d in $(go list ./... | grep -v browser); do
|
||||||
|
CGO_ENABLED=1 go test -v -race --timeout 50m "$d"
|
||||||
|
done
|
||||||
@@ -69,63 +69,35 @@ function start_minio_erasure_sets()
|
|||||||
echo "$minio_pid"
|
echo "$minio_pid"
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_dist_erasure_sets_ipv6()
|
function start_minio_zone_erasure_sets()
|
||||||
{
|
{
|
||||||
declare -a minio_pids
|
declare -a minio_pids
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
"${MINIO[@]}" server --address="[::1]:9000" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9000.log" 2>&1 &
|
|
||||||
minio_pids[0]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9001.log" 2>&1 &
|
|
||||||
minio_pids[1]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9002" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9002.log" 2>&1 &
|
|
||||||
minio_pids[2]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9003" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9003.log" 2>&1 &
|
|
||||||
minio_pids[3]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9004" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9004.log" 2>&1 &
|
|
||||||
minio_pids[4]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9005" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9005.log" 2>&1 &
|
|
||||||
minio_pids[5]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9006" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9006.log" 2>&1 &
|
|
||||||
minio_pids[6]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9007" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9007.log" 2>&1 &
|
|
||||||
minio_pids[7]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9008" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9008.log" 2>&1 &
|
|
||||||
minio_pids[8]=$!
|
|
||||||
"${MINIO[@]}" server --address="[::1]:9009" "http://[::1]:9000${WORK_DIR}/dist-disk-sets1" "http://[::1]:9001${WORK_DIR}/dist-disk-sets2" "http://[::1]:9002${WORK_DIR}/dist-disk-sets3" "http://[::1]:9003${WORK_DIR}/dist-disk-sets4" "http://[::1]:9004${WORK_DIR}/dist-disk-sets5" "http://[::1]:9005${WORK_DIR}/dist-disk-sets6" "http://[::1]:9006${WORK_DIR}/dist-disk-sets7" "http://[::1]:9007${WORK_DIR}/dist-disk-sets8" "http://[::1]:9008${WORK_DIR}/dist-disk-sets9" "http://[::1]:9009${WORK_DIR}/dist-disk-sets10" "http://[::1]:9000${WORK_DIR}/dist-disk-sets11" "http://[::1]:9001${WORK_DIR}/dist-disk-sets12" "http://[::1]:9002${WORK_DIR}/dist-disk-sets13" "http://[::1]:9003${WORK_DIR}/dist-disk-sets14" "http://[::1]:9004${WORK_DIR}/dist-disk-sets15" "http://[::1]:9005${WORK_DIR}/dist-disk-sets16" "http://[::1]:9006${WORK_DIR}/dist-disk-sets17" "http://[::1]:9007${WORK_DIR}/dist-disk-sets18" "http://[::1]:9008${WORK_DIR}/dist-disk-sets19" "http://[::1]:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-v6-9009.log" 2>&1 &
|
|
||||||
minio_pids[9]=$!
|
|
||||||
|
|
||||||
sleep 35
|
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 &
|
||||||
|
minio_pids[0]=$!
|
||||||
|
|
||||||
|
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 &
|
||||||
|
minio_pids[1]=$!
|
||||||
|
|
||||||
|
sleep 40
|
||||||
echo "${minio_pids[@]}"
|
echo "${minio_pids[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_dist_erasure_sets()
|
function start_minio_zone_erasure_sets_ipv6()
|
||||||
{
|
{
|
||||||
declare -a minio_pids
|
declare -a minio_pids
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9000.log" 2>&1 &
|
|
||||||
minio_pids[0]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9001.log" 2>&1 &
|
|
||||||
minio_pids[1]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9002 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9002.log" 2>&1 &
|
|
||||||
minio_pids[2]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
|
||||||
minio_pids[3]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9004 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9004.log" 2>&1 &
|
|
||||||
minio_pids[4]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9005 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9005.log" 2>&1 &
|
|
||||||
minio_pids[5]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9006 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9006.log" 2>&1 &
|
|
||||||
minio_pids[6]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9007 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9007.log" 2>&1 &
|
|
||||||
minio_pids[7]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9008 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9008.log" 2>&1 &
|
|
||||||
minio_pids[8]=$!
|
|
||||||
"${MINIO[@]}" server --address=:9009 "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets4" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets5" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets6" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets7" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets8" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets9" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets10" "http://127.0.0.1:9000${WORK_DIR}/dist-disk-sets11" "http://127.0.0.1:9001${WORK_DIR}/dist-disk-sets12" "http://127.0.0.1:9002${WORK_DIR}/dist-disk-sets13" "http://127.0.0.1:9003${WORK_DIR}/dist-disk-sets14" "http://127.0.0.1:9004${WORK_DIR}/dist-disk-sets15" "http://127.0.0.1:9005${WORK_DIR}/dist-disk-sets16" "http://127.0.0.1:9006${WORK_DIR}/dist-disk-sets17" "http://127.0.0.1:9007${WORK_DIR}/dist-disk-sets18" "http://127.0.0.1:9008${WORK_DIR}/dist-disk-sets19" "http://127.0.0.1:9009${WORK_DIR}/dist-disk-sets20" >"$WORK_DIR/dist-minio-9009.log" 2>&1 &
|
|
||||||
minio_pids[9]=$!
|
|
||||||
|
|
||||||
sleep 30
|
"${MINIO[@]}" server --address="[::1]:9000" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 &
|
||||||
|
minio_pids[0]=$!
|
||||||
|
|
||||||
|
"${MINIO[@]}" server --address="[::1]:9001" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 &
|
||||||
|
minio_pids[1]=$!
|
||||||
|
|
||||||
|
sleep 40
|
||||||
echo "${minio_pids[@]}"
|
echo "${minio_pids[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +115,7 @@ function start_minio_dist_erasure()
|
|||||||
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
||||||
minio_pids[3]=$!
|
minio_pids[3]=$!
|
||||||
|
|
||||||
sleep 30
|
sleep 40
|
||||||
echo "${minio_pids[@]}"
|
echo "${minio_pids[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +154,35 @@ function run_test_erasure_sets() {
|
|||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_dist_erasure_sets_ipv6()
|
function run_test_zone_erasure_sets()
|
||||||
{
|
{
|
||||||
minio_pids=( $(start_minio_dist_erasure_sets_ipv6) )
|
minio_pids=( $(start_minio_zone_erasure_sets) )
|
||||||
|
|
||||||
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
|
rv=$?
|
||||||
|
|
||||||
|
for pid in "${minio_pids[@]}"; do
|
||||||
|
kill "$pid"
|
||||||
|
done
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
if [ "$rv" -ne 0 ]; then
|
||||||
|
for i in $(seq 0 1); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "$WORK_DIR/zone-minio-900$i.log"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in $(seq 0 1); do
|
||||||
|
rm -f "$WORK_DIR/zone-minio-900$i.log"
|
||||||
|
done
|
||||||
|
|
||||||
|
return "$rv"
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_test_zone_erasure_sets_ipv6()
|
||||||
|
{
|
||||||
|
minio_pids=( $(start_minio_zone_erasure_sets_ipv6) )
|
||||||
|
|
||||||
export SERVER_ENDPOINT="[::1]:9000"
|
export SERVER_ENDPOINT="[::1]:9000"
|
||||||
|
|
||||||
@@ -197,40 +195,14 @@ function run_test_dist_erasure_sets_ipv6()
|
|||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 9); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/dist-minio-v6-900$i.log"
|
cat "$WORK_DIR/zone-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 9); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/dist-minio-v6-900$i.log"
|
rm -f "$WORK_DIR/zone-minio-ipv6-900$i.log"
|
||||||
done
|
|
||||||
|
|
||||||
return "$rv"
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_test_dist_erasure_sets()
|
|
||||||
{
|
|
||||||
minio_pids=( $(start_minio_dist_erasure_sets) )
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
|
||||||
rv=$?
|
|
||||||
|
|
||||||
for pid in "${minio_pids[@]}"; do
|
|
||||||
kill "$pid"
|
|
||||||
done
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
|
||||||
for i in $(seq 0 9); do
|
|
||||||
echo "server$i log:"
|
|
||||||
cat "$WORK_DIR/dist-minio-900$i.log"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in $(seq 0 9); do
|
|
||||||
rm -f "$WORK_DIR/dist-minio-900$i.log"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
@@ -351,15 +323,15 @@ function main()
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure setup as sets"
|
echo "Testing in Distributed Eraure expanded setup"
|
||||||
if ! run_test_dist_erasure_sets; then
|
if ! run_test_zone_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure setup as sets with ipv6"
|
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||||
if ! run_test_dist_erasure_sets_ipv6; then
|
if ! run_test_zone_erasure_sets_ipv6; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
|
function start_minio_3_node() {
|
||||||
|
declare -a minio_pids
|
||||||
|
declare -a ARGS
|
||||||
|
export MINIO_ACCESS_KEY=minio
|
||||||
|
export MINIO_SECRET_KEY=minio123
|
||||||
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
|
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
ARGS+=("http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/")
|
||||||
|
done
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$[$start_port+1]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||||
|
minio_pids[0]=$!
|
||||||
|
disown "${minio_pids[0]}"
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$[$start_port+2]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||||
|
minio_pids[1]=$!
|
||||||
|
disown "${minio_pids[1]}"
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$[$start_port+3]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||||
|
minio_pids[2]=$!
|
||||||
|
disown "${minio_pids[2]}"
|
||||||
|
|
||||||
|
sleep "$1"
|
||||||
|
for pid in "${minio_pids[@]}"; do
|
||||||
|
if ! kill "$pid"; then
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# forcibly killing, to proceed further properly.
|
||||||
|
kill -9 "$pid"
|
||||||
|
sleep 1 # wait 1sec per pid
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function check_online() {
|
||||||
|
if grep -q 'Server switching to safe mode' ${WORK_DIR}/dist-minio-*.log; then
|
||||||
|
echo "1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge()
|
||||||
|
{
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function __init__()
|
||||||
|
{
|
||||||
|
echo "Initializing environment"
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
mkdir -p "$MINIO_CONFIG_DIR"
|
||||||
|
|
||||||
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
|
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
function perform_test() {
|
||||||
|
start_minio_3_node 60
|
||||||
|
|
||||||
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
|
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||||
|
|
||||||
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
|
start_minio_3_node 60
|
||||||
|
|
||||||
|
rv=$(check_online)
|
||||||
|
if [ "$rv" == "1" ]; then
|
||||||
|
pkill -9 minio
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function main()
|
||||||
|
{
|
||||||
|
perform_test "2"
|
||||||
|
perform_test "1"
|
||||||
|
perform_test "3"
|
||||||
|
}
|
||||||
|
|
||||||
|
( __init__ "$@" && main "$@" )
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
+142
-4
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
* MinIO Cloud Storage, (C) 2018-2020 MinIO, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -18,11 +18,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
@@ -50,6 +53,71 @@ type accessControlPolicy struct {
|
|||||||
} `xml:"AccessControlList"`
|
} `xml:"AccessControlList"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutBucketACLHandler - PUT Bucket ACL
|
||||||
|
// -----------------
|
||||||
|
// This operation uses the ACL subresource
|
||||||
|
// to set ACL for a bucket, this is a dummy call
|
||||||
|
// only responds success if the ACL is private.
|
||||||
|
func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "PutBucketACL")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "PutBucketACL", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objAPI := api.ObjectAPI()
|
||||||
|
if objAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow putBucketACL if policy action is set, since this is a dummy call
|
||||||
|
// we are simply re-purposing the bucketPolicyAction.
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before proceeding validate if bucket exists.
|
||||||
|
_, err := objAPI.GetBucketInfo(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aclHeader := r.Header.Get(xhttp.AmzACL)
|
||||||
|
if aclHeader == "" {
|
||||||
|
acl := &accessControlPolicy{}
|
||||||
|
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
|
||||||
|
r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(acl.AccessControlList.Grants) == 0 {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if acl.AccessControlList.Grants[0].Permission != "FULL_CONTROL" {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aclHeader != "" && aclHeader != "private" {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// GetBucketACLHandler - GET Bucket ACL
|
// GetBucketACLHandler - GET Bucket ACL
|
||||||
// -----------------
|
// -----------------
|
||||||
// This operation uses the ACL
|
// This operation uses the ACL
|
||||||
@@ -91,6 +159,7 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
},
|
},
|
||||||
Permission: "FULL_CONTROL",
|
Permission: "FULL_CONTROL",
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := xml.NewEncoder(w).Encode(acl); err != nil {
|
if err := xml.NewEncoder(w).Encode(acl); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@@ -99,6 +168,71 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutObjectACLHandler - PUT Object ACL
|
||||||
|
// -----------------
|
||||||
|
// This operation uses the ACL subresource
|
||||||
|
// to set ACL for a bucket, this is a dummy call
|
||||||
|
// only responds success if the ACL is private.
|
||||||
|
func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "PutObjectACL")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "PutObjectACL", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
object, err := url.PathUnescape(vars["object"])
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objAPI := api.ObjectAPI()
|
||||||
|
if objAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow putObjectACL if policy action is set, since this is a dummy call
|
||||||
|
// we are simply re-purposing the bucketPolicyAction.
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before proceeding validate if object exists.
|
||||||
|
_, err = objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aclHeader := r.Header.Get(xhttp.AmzACL)
|
||||||
|
if aclHeader == "" {
|
||||||
|
acl := &accessControlPolicy{}
|
||||||
|
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(acl.AccessControlList.Grants) == 0 {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if acl.AccessControlList.Grants[0].Permission != "FULL_CONTROL" {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aclHeader != "" && aclHeader != "private" {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// GetObjectACLHandler - GET Object ACL
|
// GetObjectACLHandler - GET Object ACL
|
||||||
// -----------------
|
// -----------------
|
||||||
// This operation uses the ACL
|
// This operation uses the ACL
|
||||||
@@ -110,7 +244,11 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object, err := url.PathUnescape(vars["object"])
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -126,7 +264,7 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Before proceeding validate if object exists.
|
// Before proceeding validate if object exists.
|
||||||
_, err := objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
_, err = objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio/cmd/config"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/env"
|
||||||
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bucketQuotaConfigFile = "quota.json"
|
||||||
|
bucketTargetsFile = "bucket-targets.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PutBucketQuotaConfigHandler - PUT Bucket quota configuration.
|
||||||
|
// ----------
|
||||||
|
// Places a quota configuration on the specified bucket. The quota
|
||||||
|
// specified in the quota configuration will be applied by default
|
||||||
|
// to enforce total quota for the specified bucket.
|
||||||
|
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "PutBucketQuotaConfig")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "PutBucketQuotaConfig", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
// Turn off quota commands if data usage info is unavailable.
|
||||||
|
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOff {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminBucketQuotaDisabled), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = parseBucketQuota(bucket, data); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = globalBucketMetadataSys.Update(bucket, bucketQuotaConfigFile, data); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
||||||
|
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetBucketQuotaConfig")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "GetBucketQuotaConfig", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := globalBucketMetadataSys.GetQuotaConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, configData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRemoteTargetHandler - sets a remote target for bucket
|
||||||
|
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetBucketTarget")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !globalIsErasure {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var target madmin.BucketTarget
|
||||||
|
if err = json.Unmarshal(reqBytes, &target); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
host, port, err := net.SplitHostPort(target.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sameTarget, _ := isLocalHost(host, port, globalMinioPort)
|
||||||
|
if sameTarget && bucket == target.TargetBucket {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target.SourceBucket = bucket
|
||||||
|
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
||||||
|
if target.Arn == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
tgtBytes, err := json.Marshal(&targets)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(target.Arn)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRemoteTargetsHandler - lists remote target(s) for a bucket or gets a target
|
||||||
|
// for a particular ARN type
|
||||||
|
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListBucketTargets")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListBucketTargets", mustGetClaimsFromToken(r))
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
arnType := vars["type"]
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if bucket != "" {
|
||||||
|
if _, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targets := globalBucketTargetSys.ListTargets(ctx, bucket, arnType)
|
||||||
|
data, err := json.Marshal(targets)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRemoteTargetHandler - removes a remote target for bucket with specified ARN
|
||||||
|
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RemoveBucketTarget")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "RemoveBucketTarget", mustGetClaimsFromToken(r))
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
arn := vars["arn"]
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !globalIsErasure {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalBucketTargetSys.RemoveTarget(ctx, bucket, arn); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tgtBytes, err := json.Marshal(&targets)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessNoContent(w)
|
||||||
|
}
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio/cmd/config"
|
||||||
|
"github.com/minio/minio/cmd/config/cache"
|
||||||
|
"github.com/minio/minio/cmd/config/etcd"
|
||||||
|
xldap "github.com/minio/minio/cmd/config/identity/ldap"
|
||||||
|
"github.com/minio/minio/cmd/config/identity/openid"
|
||||||
|
"github.com/minio/minio/cmd/config/policy/opa"
|
||||||
|
"github.com/minio/minio/cmd/config/storageclass"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *http.Request) (auth.Credentials, ObjectLayer) {
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return auth.Credentials{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate request signature.
|
||||||
|
cred, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return cred, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cred, objectAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||||
|
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "DeleteConfigKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "DeleteConfigKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cfg.DelFrom(bytes.NewReader(kvBytes)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
||||||
|
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetConfigKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "SetConfigKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validateConfig(cfg, objectAPI.SetDriveCount()); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the actual server config on disk.
|
||||||
|
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the config input KV to history.
|
||||||
|
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to write backend is encrypted
|
||||||
|
if globalConfigEncrypted {
|
||||||
|
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
||||||
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetConfigKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "GetConfigKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := globalServerConfig
|
||||||
|
if newObjectLayerFn() == nil {
|
||||||
|
var err error
|
||||||
|
cfg, err = getValidConfig(objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
cw := config.NewConfigWriteTo(cfg, vars["key"])
|
||||||
|
if _, err := cw.WriteTo(buf); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
econfigData, err := madmin.EncryptData(password, buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ClearConfigHistoryKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ClearConfigHistoryKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
restoreID := vars["restoreId"]
|
||||||
|
if restoreID == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if restoreID == "all" {
|
||||||
|
chEntries, err := listServerConfigHistory(ctx, objectAPI, false, -1)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, chEntry := range chEntries {
|
||||||
|
if err = delServerConfigHistory(ctx, objectAPI, chEntry.RestoreID); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := delServerConfigHistory(ctx, objectAPI, restoreID); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
||||||
|
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RestoreConfigHistoryKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "RestoreConfigHistoryKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
restoreID := vars["restoreId"]
|
||||||
|
if restoreID == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kvBytes, err := readServerConfigHistory(ctx, objectAPI, restoreID)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validateConfig(cfg, objectAPI.SetDriveCount()); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delServerConfigHistory(ctx, objectAPI, restoreID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConfigHistoryKVHandler - lists all the KV ids.
|
||||||
|
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListConfigHistoryKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListConfigHistoryKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
count, err := strconv.Atoi(vars["count"])
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chEntries, err := listServerConfigHistory(ctx, objectAPI, true, count)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(chEntries)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
econfigData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelpConfigKVHandler - GET /minio/admin/v3/help-config-kv?subSys={subSys}&key={key}
|
||||||
|
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "HelpConfigKV")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "HelpHistoryKV", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
|
subSys := vars["subSys"]
|
||||||
|
key := vars["key"]
|
||||||
|
|
||||||
|
_, envOnly := r.URL.Query()["env"]
|
||||||
|
|
||||||
|
rd, err := GetHelp(subSys, key, envOnly)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(rd)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigHandler - PUT /minio/admin/v3/config
|
||||||
|
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetConfig")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "SetConfig", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := newServerConfig()
|
||||||
|
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validateConfig(cfg, objectAPI.SetDriveCount()); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the actual server config on disk.
|
||||||
|
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the config input KV to history.
|
||||||
|
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to write backend is encrypted
|
||||||
|
if globalConfigEncrypted {
|
||||||
|
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigHandler - GET /minio/admin/v3/config
|
||||||
|
// Get config.json of this minio setup.
|
||||||
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetConfig")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "GetConfig", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var s strings.Builder
|
||||||
|
hkvs := config.HelpSubSysMap[""]
|
||||||
|
var count int
|
||||||
|
for _, hkv := range hkvs {
|
||||||
|
count += len(cfg[hkv.Key])
|
||||||
|
}
|
||||||
|
for _, hkv := range hkvs {
|
||||||
|
v := cfg[hkv.Key]
|
||||||
|
for target, kv := range v {
|
||||||
|
off := kv.Get(config.Enable) == config.EnableOff
|
||||||
|
switch hkv.Key {
|
||||||
|
case config.EtcdSubSys:
|
||||||
|
off = !etcd.Enabled(kv)
|
||||||
|
case config.CacheSubSys:
|
||||||
|
off = !cache.Enabled(kv)
|
||||||
|
case config.StorageClassSubSys:
|
||||||
|
off = !storageclass.Enabled(kv)
|
||||||
|
case config.KmsVaultSubSys:
|
||||||
|
off = !crypto.EnabledVault(kv)
|
||||||
|
case config.KmsKesSubSys:
|
||||||
|
off = !crypto.EnabledKes(kv)
|
||||||
|
case config.PolicyOPASubSys:
|
||||||
|
off = !opa.Enabled(kv)
|
||||||
|
case config.IdentityOpenIDSubSys:
|
||||||
|
off = !openid.Enabled(kv)
|
||||||
|
case config.IdentityLDAPSubSys:
|
||||||
|
off = !xldap.Enabled(kv)
|
||||||
|
}
|
||||||
|
if off {
|
||||||
|
s.WriteString(config.KvComment)
|
||||||
|
s.WriteString(config.KvSpaceSeparator)
|
||||||
|
}
|
||||||
|
s.WriteString(hkv.Key)
|
||||||
|
if target != config.Default {
|
||||||
|
s.WriteString(config.SubSystemSeparator)
|
||||||
|
s.WriteString(target)
|
||||||
|
}
|
||||||
|
s.WriteString(config.KvSpaceSeparator)
|
||||||
|
s.WriteString(kv.String())
|
||||||
|
count--
|
||||||
|
if count > 0 {
|
||||||
|
s.WriteString(config.KvNewline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
econfigData, err := madmin.EncryptData(password, []byte(s.String()))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
@@ -0,0 +1,917 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2019-2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.Request, action iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
||||||
|
var cred auth.Credentials
|
||||||
|
var adminAPIErr APIErrorCode
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return nil, cred
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate request signature.
|
||||||
|
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return nil, cred
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectAPI, cred
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
||||||
|
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RemoveUser")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "RemoveUser", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
accessKey := vars["accessKey"]
|
||||||
|
|
||||||
|
ok, err := globalIAMSys.IsTempUser(accessKey)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to delete user.
|
||||||
|
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsers - GET /minio/admin/v3/list-users
|
||||||
|
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListUsers")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListUsers", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
allCredentials, err := globalIAMSys.ListUsers()
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(allCredentials)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
econfigData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo - GET /minio/admin/v3/user-info
|
||||||
|
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetUserInfo")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "GetUserInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetUserAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
name := vars["accessKey"]
|
||||||
|
|
||||||
|
userInfo, err := globalIAMSys.GetUserInfo(name)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(userInfo)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGroupMembers - PUT /minio/admin/v3/update-group-members
|
||||||
|
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "UpdateGroupMembers")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "UpdateGroupMembers", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var updReq madmin.GroupAddRemove
|
||||||
|
err = json.Unmarshal(data, &updReq)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if updReq.IsRemove {
|
||||||
|
err = globalIAMSys.RemoveUsersFromGroup(updReq.Group, updReq.Members)
|
||||||
|
} else {
|
||||||
|
err = globalIAMSys.AddUsersToGroup(updReq.Group, updReq.Members)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to load group.
|
||||||
|
for _, nerr := range globalNotificationSys.LoadGroup(updReq.Group) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
||||||
|
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetGroup")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "GetGroup", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
group := vars["group"]
|
||||||
|
|
||||||
|
gdesc, err := globalIAMSys.GetGroupDescription(group)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(gdesc)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListGroups - GET /minio/admin/v3/groups
|
||||||
|
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListGroups")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListGroups", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, err := globalIAMSys.ListGroups()
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(groups)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGroupStatus - PUT /minio/admin/v3/set-group-status?group=mygroup1&status=enabled
|
||||||
|
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetGroupStatus")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "SetGroupStatus", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
group := vars["group"]
|
||||||
|
status := vars["status"]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if status == statusEnabled {
|
||||||
|
err = globalIAMSys.SetGroupStatus(group, true)
|
||||||
|
} else if status == statusDisabled {
|
||||||
|
err = globalIAMSys.SetGroupStatus(group, false)
|
||||||
|
} else {
|
||||||
|
err = errInvalidArgument
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to reload user.
|
||||||
|
for _, nerr := range globalNotificationSys.LoadGroup(group) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserStatus - PUT /minio/admin/v3/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
||||||
|
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetUserStatus")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "SetUserStatus", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
accessKey := vars["accessKey"]
|
||||||
|
status := vars["status"]
|
||||||
|
|
||||||
|
// Custom IAM policies not allowed for admin user.
|
||||||
|
if accessKey == globalActiveCred.AccessKey {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalIAMSys.SetUserStatus(accessKey, madmin.AccountStatus(status)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to reload user.
|
||||||
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUser - PUT /minio/admin/v3/add-user?accessKey=<access_key>
|
||||||
|
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AddUser")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.CreateUserAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
accessKey := vars["accessKey"]
|
||||||
|
|
||||||
|
// Custom IAM policies not allowed for admin user.
|
||||||
|
if accessKey == globalActiveCred.AccessKey {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var uinfo madmin.UserInfo
|
||||||
|
if err = json.Unmarshal(configBytes, &uinfo); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = globalIAMSys.SetUser(accessKey, uinfo); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other Minio peers to reload user
|
||||||
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||||
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AddServiceAccount")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "AddServiceAccount", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, _, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var createReq madmin.AddServiceAccountReq
|
||||||
|
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow creating service accounts by root user.
|
||||||
|
if owner {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parentUser := cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
parentUser = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
newCred, err := globalIAMSys.NewServiceAccount(ctx, parentUser, createReq.Policy)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other Minio peers to reload user the service account
|
||||||
|
for _, nerr := range globalNotificationSys.LoadServiceAccount(newCred.AccessKey) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createResp = madmin.AddServiceAccountResp{
|
||||||
|
Credentials: auth.Credentials{
|
||||||
|
AccessKey: newCred.AccessKey,
|
||||||
|
SecretKey: newCred.SecretKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(createResp)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServiceAccounts - GET /minio/admin/v3/list-service-accounts
|
||||||
|
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListServiceAccounts")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListServiceAccounts", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, _, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow creating service accounts by root user.
|
||||||
|
if owner {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parentUser := cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
parentUser = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, parentUser)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var listResp = madmin.ListServiceAccountsResp{
|
||||||
|
Accounts: serviceAccounts,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(listResp)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteServiceAccount - DELETE /minio/admin/v3/delete-service-account
|
||||||
|
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "DeleteServiceAccount")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "DeleteServiceAccount", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, _, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow creating service accounts by root user.
|
||||||
|
if owner {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceAccount := mux.Vars(r)["accessKey"]
|
||||||
|
if serviceAccount == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := globalIAMSys.GetServiceAccountParent(ctx, serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parentUser := cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
parentUser = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentUser != user || user == "" {
|
||||||
|
// The service account belongs to another user but return not
|
||||||
|
// found error to mitigate brute force attacks. or the
|
||||||
|
// serviceAccount doesn't exist.
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServiceAccountNotFound), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = globalIAMSys.DeleteServiceAccount(ctx, serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessNoContent(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountUsageInfoHandler returns usage
|
||||||
|
func (a adminAPIHandlers) AccountUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AccountUsageInfo")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "AccountUsageInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set prefix value for "s3:prefix" policy conditionals.
|
||||||
|
r.Header.Set("prefix", "")
|
||||||
|
|
||||||
|
// Set delimiter value for "s3:delimiter" policy conditionals.
|
||||||
|
r.Header.Set("delimiter", SlashSeparator)
|
||||||
|
|
||||||
|
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
||||||
|
// Use the following trick to filter in place
|
||||||
|
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
||||||
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: iampolicy.ListBucketAction,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
||||||
|
IsOwner: owner,
|
||||||
|
ObjectName: "",
|
||||||
|
Claims: claims,
|
||||||
|
}) {
|
||||||
|
rd = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: iampolicy.PutObjectAction,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
||||||
|
IsOwner: owner,
|
||||||
|
ObjectName: "",
|
||||||
|
Claims: claims,
|
||||||
|
}) {
|
||||||
|
wr = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return rd, wr
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets, err := objectAPI.ListBuckets(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the latest calculated data usage
|
||||||
|
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
// log the error, continue with the accounting response
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountName := cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
accountName = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
acctInfo := madmin.AccountUsageInfo{
|
||||||
|
AccountName: accountName,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
rd, wr := isAllowedAccess(bucket.Name)
|
||||||
|
if rd || wr {
|
||||||
|
var size uint64
|
||||||
|
// Fetch the data usage of the current bucket
|
||||||
|
if !dataUsageInfo.LastUpdate.IsZero() {
|
||||||
|
size = dataUsageInfo.BucketsUsage[bucket.Name].Size
|
||||||
|
}
|
||||||
|
acctInfo.Buckets = append(acctInfo.Buckets, madmin.BucketUsageInfo{
|
||||||
|
Name: bucket.Name,
|
||||||
|
Created: bucket.Created,
|
||||||
|
Size: size,
|
||||||
|
Access: madmin.AccountAccess{
|
||||||
|
Read: rd,
|
||||||
|
Write: wr,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usageInfoJSON, err := json.Marshal(acctInfo)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, usageInfoJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName}
|
||||||
|
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "InfoCannedPolicyV2")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "InfoCannedPolicyV2", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(policy)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(data)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoCannedPolicy - GET /minio/admin/v3/info-canned-policy?name={policyName}
|
||||||
|
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "InfoCannedPolicy")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "InfoCannedPolicy", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(policy)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCannedPoliciesV2 - GET /minio/admin/v2/list-canned-policies
|
||||||
|
func (a adminAPIHandlers) ListCannedPoliciesV2(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListCannedPoliciesV2")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListCannedPoliciesV2", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policies, err := globalIAMSys.ListPolicies()
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policyMap := make(map[string][]byte, len(policies))
|
||||||
|
for k, p := range policies {
|
||||||
|
var err error
|
||||||
|
policyMap[k], err = json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = json.NewEncoder(w).Encode(policyMap); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCannedPolicies - GET /minio/admin/v3/list-canned-policies
|
||||||
|
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListCannedPolicies")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "ListCannedPolicies", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policies, err := globalIAMSys.ListPolicies()
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPolicies = make(map[string]iampolicy.Policy)
|
||||||
|
for name, p := range policies {
|
||||||
|
_, err = json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newPolicies[name] = p
|
||||||
|
}
|
||||||
|
if err = json.NewEncoder(w).Encode(newPolicies); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveCannedPolicy - DELETE /minio/admin/v3/remove-canned-policy?name=<policy_name>
|
||||||
|
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RemoveCannedPolicy")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "RemoveCannedPolicy", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
policyName := vars["name"]
|
||||||
|
|
||||||
|
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to delete policy
|
||||||
|
for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCannedPolicy - PUT /minio/admin/v3/add-canned-policy?name=<policy_name>
|
||||||
|
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AddCannedPolicy")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "AddCannedPolicy", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
policyName := vars["name"]
|
||||||
|
|
||||||
|
// Error out if Content-Length is missing.
|
||||||
|
if r.ContentLength <= 0 {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error out if Content-Length is beyond allowed size.
|
||||||
|
if r.ContentLength > maxBucketPolicySize {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iamPolicy, err := iampolicy.ParseConfig(io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version in policy must not be empty
|
||||||
|
if iamPolicy.Version == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = globalIAMSys.SetPolicy(policyName, *iamPolicy); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to reload policy
|
||||||
|
for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPolicyForUserOrGroup - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
||||||
|
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "SetPolicyForUserOrGroup", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
policyName := vars["policyName"]
|
||||||
|
entityName := vars["userOrGroup"]
|
||||||
|
isGroup := vars["isGroup"] == "true"
|
||||||
|
|
||||||
|
if !isGroup {
|
||||||
|
ok, err := globalIAMSys.IsTempUser(entityName)
|
||||||
|
if err != nil && err != errNoSuchUser {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalIAMSys.PolicyDBSet(entityName, policyName, isGroup); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to reload policy
|
||||||
|
for _, nerr := range globalNotificationSys.LoadPolicyMapping(entityName, isGroup) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+880
-985
File diff suppressed because it is too large
Load Diff
+53
-422
@@ -25,243 +25,35 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// adminErasureTestBed - encapsulates subsystems that need to be setup for
|
||||||
configJSON = []byte(`{
|
|
||||||
"version": "33",
|
|
||||||
"credential": {
|
|
||||||
"accessKey": "minio",
|
|
||||||
"secretKey": "minio123"
|
|
||||||
},
|
|
||||||
"region": "us-east-1",
|
|
||||||
"worm": "off",
|
|
||||||
"storageclass": {
|
|
||||||
"standard": "",
|
|
||||||
"rrs": ""
|
|
||||||
},
|
|
||||||
"cache": {
|
|
||||||
"drives": [],
|
|
||||||
"expiry": 90,
|
|
||||||
"maxuse": 80,
|
|
||||||
"exclude": []
|
|
||||||
},
|
|
||||||
"kms": {
|
|
||||||
"vault": {
|
|
||||||
"endpoint": "",
|
|
||||||
"auth": {
|
|
||||||
"type": "",
|
|
||||||
"approle": {
|
|
||||||
"id": "",
|
|
||||||
"secret": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key-id": {
|
|
||||||
"name": "",
|
|
||||||
"version": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notify": {
|
|
||||||
"amqp": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"url": "",
|
|
||||||
"exchange": "",
|
|
||||||
"routingKey": "",
|
|
||||||
"exchangeType": "",
|
|
||||||
"deliveryMode": 0,
|
|
||||||
"mandatory": false,
|
|
||||||
"immediate": false,
|
|
||||||
"durable": false,
|
|
||||||
"internal": false,
|
|
||||||
"noWait": false,
|
|
||||||
"autoDeleted": false,
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"elasticsearch": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"format": "namespace",
|
|
||||||
"url": "",
|
|
||||||
"index": "",
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kafka": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"brokers": null,
|
|
||||||
"topic": "",
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0,
|
|
||||||
"tls": {
|
|
||||||
"enable": false,
|
|
||||||
"skipVerify": false,
|
|
||||||
"clientAuth": 0
|
|
||||||
},
|
|
||||||
"sasl": {
|
|
||||||
"enable": false,
|
|
||||||
"username": "",
|
|
||||||
"password": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mqtt": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"broker": "",
|
|
||||||
"topic": "",
|
|
||||||
"qos": 0,
|
|
||||||
"username": "",
|
|
||||||
"password": "",
|
|
||||||
"reconnectInterval": 0,
|
|
||||||
"keepAliveInterval": 0,
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mysql": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"format": "namespace",
|
|
||||||
"dsnString": "",
|
|
||||||
"table": "",
|
|
||||||
"host": "",
|
|
||||||
"port": "",
|
|
||||||
"user": "",
|
|
||||||
"password": "",
|
|
||||||
"database": "",
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nats": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"address": "",
|
|
||||||
"subject": "",
|
|
||||||
"username": "",
|
|
||||||
"password": "",
|
|
||||||
"token": "",
|
|
||||||
"secure": false,
|
|
||||||
"pingInterval": 0,
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0,
|
|
||||||
"streaming": {
|
|
||||||
"enable": false,
|
|
||||||
"clusterID": "",
|
|
||||||
"async": false,
|
|
||||||
"maxPubAcksInflight": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nsq": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"nsqdAddress": "",
|
|
||||||
"topic": "",
|
|
||||||
"tls": {
|
|
||||||
"enable": false,
|
|
||||||
"skipVerify": false
|
|
||||||
},
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"postgresql": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"format": "namespace",
|
|
||||||
"connectionString": "",
|
|
||||||
"table": "",
|
|
||||||
"host": "",
|
|
||||||
"port": "",
|
|
||||||
"user": "",
|
|
||||||
"password": "",
|
|
||||||
"database": "",
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"redis": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"format": "namespace",
|
|
||||||
"address": "",
|
|
||||||
"password": "",
|
|
||||||
"key": "",
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"webhook": {
|
|
||||||
"1": {
|
|
||||||
"enable": false,
|
|
||||||
"endpoint": "",
|
|
||||||
"queueDir": "",
|
|
||||||
"queueLimit": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"logger": {
|
|
||||||
"console": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"http": {
|
|
||||||
"1": {
|
|
||||||
"enabled": false,
|
|
||||||
"endpoint": "https://username:password@example.com/api"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compress": {
|
|
||||||
"enabled": false,
|
|
||||||
"extensions":[".txt",".log",".csv",".json"],
|
|
||||||
"mime-types":["text/csv","text/plain","application/json"]
|
|
||||||
},
|
|
||||||
"openid": {
|
|
||||||
"jwks": {
|
|
||||||
"url": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"opa": {
|
|
||||||
"url": "",
|
|
||||||
"authToken": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// adminXLTestBed - encapsulates subsystems that need to be setup for
|
|
||||||
// admin-handler unit tests.
|
// admin-handler unit tests.
|
||||||
type adminXLTestBed struct {
|
type adminErasureTestBed struct {
|
||||||
xlDirs []string
|
erasureDirs []string
|
||||||
objLayer ObjectLayer
|
objLayer ObjectLayer
|
||||||
router *mux.Router
|
router *mux.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareAdminXLTestBed - helper function that setups a single-node
|
// prepareAdminErasureTestBed - helper function that setups a single-node
|
||||||
// XL backend for admin-handler tests.
|
// Erasure backend for admin-handler tests.
|
||||||
func prepareAdminXLTestBed() (*adminXLTestBed, error) {
|
func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, error) {
|
||||||
|
|
||||||
// reset global variables to start afresh.
|
// reset global variables to start afresh.
|
||||||
resetTestGlobals()
|
resetTestGlobals()
|
||||||
|
|
||||||
|
// Set globalIsErasure to indicate that the setup uses an erasure
|
||||||
|
// code backend.
|
||||||
|
globalIsErasure = true
|
||||||
|
|
||||||
// Initializing objectLayer for HealFormatHandler.
|
// Initializing objectLayer for HealFormatHandler.
|
||||||
objLayer, xlDirs, xlErr := initTestXLObjLayer()
|
objLayer, erasureDirs, xlErr := initTestErasureObjLayer(ctx)
|
||||||
if xlErr != nil {
|
if xlErr != nil {
|
||||||
return nil, xlErr
|
return nil, xlErr
|
||||||
}
|
}
|
||||||
@@ -274,66 +66,47 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) {
|
|||||||
// Initialize boot time
|
// Initialize boot time
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
globalEndpoints = mustGetNewEndpointList(xlDirs...)
|
globalEndpoints = mustGetZoneEndpoints(erasureDirs...)
|
||||||
|
|
||||||
// Set globalIsXL to indicate that the setup uses an erasure
|
newAllSubsystems()
|
||||||
// code backend.
|
|
||||||
globalIsXL = true
|
|
||||||
|
|
||||||
// initialize NSLock.
|
initAllSubsystems(ctx, objLayer)
|
||||||
isDistXL := false
|
|
||||||
initNSLock(isDistXL)
|
|
||||||
|
|
||||||
// Init global heal state
|
|
||||||
if globalIsXL {
|
|
||||||
globalAllHealState = initHealState()
|
|
||||||
}
|
|
||||||
|
|
||||||
globalConfigSys = NewConfigSys()
|
|
||||||
|
|
||||||
globalIAMSys = NewIAMSys()
|
|
||||||
globalIAMSys.Init(objLayer)
|
|
||||||
|
|
||||||
globalPolicySys = NewPolicySys()
|
|
||||||
globalPolicySys.Init(objLayer)
|
|
||||||
|
|
||||||
globalNotificationSys = NewNotificationSys(globalServerConfig, globalEndpoints)
|
|
||||||
globalNotificationSys.Init(objLayer)
|
|
||||||
|
|
||||||
// Setup admin mgmt REST API handlers.
|
// Setup admin mgmt REST API handlers.
|
||||||
adminRouter := mux.NewRouter()
|
adminRouter := mux.NewRouter()
|
||||||
registerAdminRouter(adminRouter, true, true)
|
registerAdminRouter(adminRouter, true, true)
|
||||||
|
|
||||||
return &adminXLTestBed{
|
return &adminErasureTestBed{
|
||||||
xlDirs: xlDirs,
|
erasureDirs: erasureDirs,
|
||||||
objLayer: objLayer,
|
objLayer: objLayer,
|
||||||
router: adminRouter,
|
router: adminRouter,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TearDown - method that resets the test bed for subsequent unit
|
// TearDown - method that resets the test bed for subsequent unit
|
||||||
// tests to start afresh.
|
// tests to start afresh.
|
||||||
func (atb *adminXLTestBed) TearDown() {
|
func (atb *adminErasureTestBed) TearDown() {
|
||||||
removeRoots(atb.xlDirs)
|
removeRoots(atb.erasureDirs)
|
||||||
resetTestGlobals()
|
resetTestGlobals()
|
||||||
}
|
}
|
||||||
|
|
||||||
// initTestObjLayer - Helper function to initialize an XL-based object
|
// initTestObjLayer - Helper function to initialize an Erasure-based object
|
||||||
// layer and set globalObjectAPI.
|
// layer and set globalObjectAPI.
|
||||||
func initTestXLObjLayer() (ObjectLayer, []string, error) {
|
func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error) {
|
||||||
xlDirs, err := getRandomDisks(16)
|
erasureDirs, err := getRandomDisks(16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
endpoints := mustGetNewEndpointList(xlDirs...)
|
endpoints := mustGetNewEndpoints(erasureDirs...)
|
||||||
format, err := waitForFormatXL(context.Background(), true, endpoints, 1, 16)
|
storageDisks, format, err := waitForFormatErasure(true, endpoints, 1, 1, 16, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
removeRoots(xlDirs)
|
removeRoots(erasureDirs)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
globalPolicySys = NewPolicySys()
|
globalPolicySys = NewPolicySys()
|
||||||
objLayer, err := newXLSets(endpoints, format, 1, 16)
|
objLayer := &erasureZones{zones: make([]*erasureSets, 1)}
|
||||||
|
objLayer.zones[0], err = newErasureSets(ctx, endpoints, storageDisks, format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -342,7 +115,7 @@ func initTestXLObjLayer() (ObjectLayer, []string, error) {
|
|||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalObjectAPI = objLayer
|
globalObjectAPI = objLayer
|
||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
return objLayer, xlDirs, nil
|
return objLayer, erasureDirs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdType - Represents different service subcomands like status, stop
|
// cmdType - Represents different service subcomands like status, stop
|
||||||
@@ -391,7 +164,7 @@ func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
|
|||||||
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
|
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
|
||||||
queryVal := url.Values{}
|
queryVal := url.Values{}
|
||||||
queryVal.Set("action", string(cmd.toServiceAction()))
|
queryVal.Set("action", string(cmd.toServiceAction()))
|
||||||
resource := "/minio/admin/v1/service?" + queryVal.Encode()
|
resource := adminPathPrefix + adminAPIVersionPrefix + "/service?" + queryVal.Encode()
|
||||||
req, err := newTestRequest(http.MethodPost, resource, 0, nil)
|
req, err := newTestRequest(http.MethodPost, resource, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -408,9 +181,12 @@ func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, er
|
|||||||
// testServicesCmdHandler - parametrizes service subcommand tests on
|
// testServicesCmdHandler - parametrizes service subcommand tests on
|
||||||
// cmdType value.
|
// cmdType value.
|
||||||
func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||||
adminTestBed, err := prepareAdminXLTestBed()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
adminTestBed, err := prepareAdminErasureTestBed(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
t.Fatal("Failed to initialize a single node Erasure backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
@@ -430,7 +206,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
|||||||
testServiceSignalReceiver(cmd, t)
|
testServiceSignalReceiver(cmd, t)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
credentials := globalServerConfig.GetCredential()
|
credentials := globalActiveCred
|
||||||
|
|
||||||
req, err := getServiceCmdRequest(cmd, credentials)
|
req, err := getServiceCmdRequest(cmd, credentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -460,13 +236,13 @@ func buildAdminRequest(queryVal url.Values, method, path string,
|
|||||||
contentLength int64, bodySeeker io.ReadSeeker) (*http.Request, error) {
|
contentLength int64, bodySeeker io.ReadSeeker) (*http.Request, error) {
|
||||||
|
|
||||||
req, err := newTestRequest(method,
|
req, err := newTestRequest(method,
|
||||||
"/minio/admin/v1"+path+"?"+queryVal.Encode(),
|
adminPathPrefix+adminAPIVersionPrefix+path+"?"+queryVal.Encode(),
|
||||||
contentLength, bodySeeker)
|
contentLength, bodySeeker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cred := globalServerConfig.GetCredential()
|
cred := globalActiveCred
|
||||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -475,111 +251,15 @@ func buildAdminRequest(queryVal url.Values, method, path string,
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGetConfigHandler - test for GetConfigHandler.
|
|
||||||
func TestGetConfigHandler(t *testing.T) {
|
|
||||||
adminTestBed, err := prepareAdminXLTestBed()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
|
||||||
}
|
|
||||||
defer adminTestBed.TearDown()
|
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
|
||||||
globalMinioAddr = "127.0.0.1:9000"
|
|
||||||
|
|
||||||
// Prepare query params for get-config mgmt REST API.
|
|
||||||
queryVal := url.Values{}
|
|
||||||
queryVal.Set("config", "")
|
|
||||||
|
|
||||||
req, err := buildAdminRequest(queryVal, http.MethodGet, "/config", 0, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to construct get-config object request - %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
adminTestBed.router.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Errorf("Expected to succeed but failed with %d", rec.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSetConfigHandler - test for SetConfigHandler.
|
|
||||||
func TestSetConfigHandler(t *testing.T) {
|
|
||||||
adminTestBed, err := prepareAdminXLTestBed()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
|
||||||
}
|
|
||||||
defer adminTestBed.TearDown()
|
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
|
||||||
globalMinioAddr = "127.0.0.1:9000"
|
|
||||||
|
|
||||||
// Prepare query params for set-config mgmt REST API.
|
|
||||||
queryVal := url.Values{}
|
|
||||||
queryVal.Set("config", "")
|
|
||||||
|
|
||||||
password := globalServerConfig.GetCredential().SecretKey
|
|
||||||
econfigJSON, err := madmin.EncryptData(password, configJSON)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := buildAdminRequest(queryVal, http.MethodPut, "/config",
|
|
||||||
int64(len(econfigJSON)), bytes.NewReader(econfigJSON))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to construct set-config object request - %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
adminTestBed.router.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Errorf("Expected to succeed but failed with %d, body: %s", rec.Code, rec.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that a very large config file returns an error.
|
|
||||||
{
|
|
||||||
// Make a large enough config string
|
|
||||||
invalidCfg := []byte(strings.Repeat("A", maxEConfigJSONSize+1))
|
|
||||||
req, err := buildAdminRequest(queryVal, http.MethodPut, "/config",
|
|
||||||
int64(len(invalidCfg)), bytes.NewReader(invalidCfg))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to construct set-config object request - %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
adminTestBed.router.ServeHTTP(rec, req)
|
|
||||||
respBody := rec.Body.String()
|
|
||||||
if rec.Code != http.StatusBadRequest ||
|
|
||||||
!strings.Contains(respBody, "Configuration data provided exceeds the allowed maximum of") {
|
|
||||||
t.Errorf("Got unexpected response code or body %d - %s", rec.Code, respBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that a config with duplicate keys in an object return
|
|
||||||
// error.
|
|
||||||
{
|
|
||||||
invalidCfg := append(econfigJSON[:len(econfigJSON)-1], []byte(`, "version": "15"}`)...)
|
|
||||||
req, err := buildAdminRequest(queryVal, http.MethodPut, "/config",
|
|
||||||
int64(len(invalidCfg)), bytes.NewReader(invalidCfg))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to construct set-config object request - %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
adminTestBed.router.ServeHTTP(rec, req)
|
|
||||||
respBody := rec.Body.String()
|
|
||||||
if rec.Code != http.StatusBadRequest ||
|
|
||||||
!strings.Contains(respBody, "JSON configuration provided is of incorrect format") {
|
|
||||||
t.Errorf("Got unexpected response code or body %d - %s", rec.Code, respBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAdminServerInfo(t *testing.T) {
|
func TestAdminServerInfo(t *testing.T) {
|
||||||
adminTestBed, err := prepareAdminXLTestBed()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
adminTestBed, err := prepareAdminErasureTestBed(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
|
t.Fatal("Failed to initialize a single node Erasure backend for admin handler tests.")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
@@ -600,23 +280,14 @@ func TestAdminServerInfo(t *testing.T) {
|
|||||||
t.Errorf("Expected to succeed but failed with %d", rec.Code)
|
t.Errorf("Expected to succeed but failed with %d", rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []ServerInfo{}
|
results := madmin.InfoMessage{}
|
||||||
err = json.NewDecoder(rec.Body).Decode(&results)
|
err = json.NewDecoder(rec.Body).Decode(&results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to decode set config result json %v", err)
|
t.Fatalf("Failed to decode set config result json %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(results) == 0 {
|
if results.Region != globalMinioDefaultRegion {
|
||||||
t.Error("Expected at least one server info result")
|
t.Errorf("Expected %s, got %s", globalMinioDefaultRegion, results.Region)
|
||||||
}
|
|
||||||
|
|
||||||
for _, serverInfo := range results {
|
|
||||||
if serverInfo.Error != "" {
|
|
||||||
t.Errorf("Unexpected error = %v\n", serverInfo.Error)
|
|
||||||
}
|
|
||||||
if serverInfo.Data.Properties.Region != globalMinioDefaultRegion {
|
|
||||||
t.Errorf("Expected %s, got %s", globalMinioDefaultRegion, serverInfo.Data.Properties.Region)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +299,7 @@ func TestToAdminAPIErrCode(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// 1. Server not in quorum.
|
// 1. Server not in quorum.
|
||||||
{
|
{
|
||||||
err: errXLWriteQuorum,
|
err: errErasureWriteQuorum,
|
||||||
expectedAPIErr: ErrAdminConfigNoQuorum,
|
expectedAPIErr: ErrAdminConfigNoQuorum,
|
||||||
},
|
},
|
||||||
// 2. No error.
|
// 2. No error.
|
||||||
@@ -639,12 +310,12 @@ func TestToAdminAPIErrCode(t *testing.T) {
|
|||||||
// 3. Non-admin API specific error.
|
// 3. Non-admin API specific error.
|
||||||
{
|
{
|
||||||
err: errDiskNotFound,
|
err: errDiskNotFound,
|
||||||
expectedAPIErr: toAPIErrorCode(context.Background(), errDiskNotFound),
|
expectedAPIErr: toAPIErrorCode(GlobalContext, errDiskNotFound),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range testCases {
|
for i, test := range testCases {
|
||||||
actualErr := toAdminAPIErrCode(context.Background(), test.err)
|
actualErr := toAdminAPIErrCode(GlobalContext, test.err)
|
||||||
if actualErr != test.expectedAPIErr {
|
if actualErr != test.expectedAPIErr {
|
||||||
t.Errorf("Test %d: Expected %v but received %v",
|
t.Errorf("Test %d: Expected %v but received %v",
|
||||||
i+1, test.expectedAPIErr, actualErr)
|
i+1, test.expectedAPIErr, actualErr)
|
||||||
@@ -652,46 +323,6 @@ func TestToAdminAPIErrCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopLockEntries(t *testing.T) {
|
|
||||||
t1 := UTCNow()
|
|
||||||
t2 := UTCNow().Add(10 * time.Second)
|
|
||||||
peerLocks := []*PeerLocks{
|
|
||||||
{
|
|
||||||
Addr: "1",
|
|
||||||
Locks: map[string][]lockRequesterInfo{
|
|
||||||
"1": {
|
|
||||||
{false, "node2", "ep2", "2", t2, t2, ""},
|
|
||||||
{true, "node1", "ep1", "1", t1, t1, ""},
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
{false, "node2", "ep2", "2", t2, t2, ""},
|
|
||||||
{true, "node1", "ep1", "1", t1, t1, ""},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Addr: "2",
|
|
||||||
Locks: map[string][]lockRequesterInfo{
|
|
||||||
"1": {
|
|
||||||
{false, "node2", "ep2", "2", t2, t2, ""},
|
|
||||||
{true, "node1", "ep1", "1", t1, t1, ""},
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
{false, "node2", "ep2", "2", t2, t2, ""},
|
|
||||||
{true, "node1", "ep1", "1", t1, t1, ""},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
les := topLockEntries(peerLocks)
|
|
||||||
if len(les) != 2 {
|
|
||||||
t.Fatalf("Did not get 2 results")
|
|
||||||
}
|
|
||||||
if les[0].Timestamp.After(les[1].Timestamp) {
|
|
||||||
t.Fatalf("Got wrong sorted value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractHealInitParams(t *testing.T) {
|
func TestExtractHealInitParams(t *testing.T) {
|
||||||
mkParams := func(clientToken string, forceStart, forceStop bool) url.Values {
|
mkParams := func(clientToken string, forceStart, forceStop bool) url.Values {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
|
|||||||
+345
-208
@@ -21,7 +21,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -53,15 +52,18 @@ const (
|
|||||||
// time-duration to keep heal sequence state after it
|
// time-duration to keep heal sequence state after it
|
||||||
// completes.
|
// completes.
|
||||||
keepHealSeqStateDuration = time.Minute * 10
|
keepHealSeqStateDuration = time.Minute * 10
|
||||||
|
|
||||||
|
// nopHeal is a no operating healing action to
|
||||||
|
// wait for the current healing operation to finish
|
||||||
|
nopHeal = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errHealIdleTimeout = fmt.Errorf("healing results were not consumed for too long")
|
errHealIdleTimeout = fmt.Errorf("healing results were not consumed for too long")
|
||||||
errHealPushStopNDiscard = fmt.Errorf("heal push stopped due to heal stop signal")
|
errHealStopSignalled = fmt.Errorf("heal stop signaled")
|
||||||
errHealStopSignalled = fmt.Errorf("heal stop signaled")
|
|
||||||
|
|
||||||
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
||||||
apiErr := toAPIError(ctx, err)
|
apiErr := toAdminAPIErr(ctx, err)
|
||||||
return fmt.Errorf("Heal internal error: %s: %s",
|
return fmt.Errorf("Heal internal error: %s: %s",
|
||||||
apiErr.Code, apiErr.Description)
|
apiErr.Code, apiErr.Description)
|
||||||
}
|
}
|
||||||
@@ -69,18 +71,11 @@ var (
|
|||||||
|
|
||||||
// healSequenceStatus - accumulated status of the heal sequence
|
// healSequenceStatus - accumulated status of the heal sequence
|
||||||
type healSequenceStatus struct {
|
type healSequenceStatus struct {
|
||||||
// lock to update this structure as it is concurrently
|
|
||||||
// accessed
|
|
||||||
updateLock *sync.RWMutex
|
|
||||||
|
|
||||||
// summary and detail for failures
|
// summary and detail for failures
|
||||||
Summary healStatusSummary `json:"Summary"`
|
Summary healStatusSummary `json:"Summary"`
|
||||||
FailureDetail string `json:"Detail,omitempty"`
|
FailureDetail string `json:"Detail,omitempty"`
|
||||||
StartTime time.Time `json:"StartTime"`
|
StartTime time.Time `json:"StartTime"`
|
||||||
|
|
||||||
// disk information
|
|
||||||
NumDisks int `json:"NumDisks"`
|
|
||||||
|
|
||||||
// settings for the heal sequence
|
// settings for the heal sequence
|
||||||
HealSettings madmin.HealOpts `json:"Settings"`
|
HealSettings madmin.HealOpts `json:"Settings"`
|
||||||
|
|
||||||
@@ -93,28 +88,43 @@ type allHealState struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
// map of heal path to heal sequence
|
// map of heal path to heal sequence
|
||||||
healSeqMap map[string]*healSequence
|
healSeqMap map[string]*healSequence
|
||||||
|
healLocalDisks []Endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// initHealState - initialize healing apparatus
|
// newHealState - initialize global heal state management
|
||||||
func initHealState() *allHealState {
|
func newHealState() *allHealState {
|
||||||
healState := &allHealState{
|
healState := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
}
|
}
|
||||||
|
|
||||||
go healState.periodicHealSeqsClean()
|
go healState.periodicHealSeqsClean(GlobalContext)
|
||||||
|
|
||||||
return healState
|
return healState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) periodicHealSeqsClean() {
|
func (ahs *allHealState) getHealLocalDisks() []Endpoints {
|
||||||
|
ahs.Lock()
|
||||||
|
defer ahs.Unlock()
|
||||||
|
|
||||||
|
healLocalDisks := make([]Endpoints, len(ahs.healLocalDisks))
|
||||||
|
copy(healLocalDisks, ahs.healLocalDisks)
|
||||||
|
return healLocalDisks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ahs *allHealState) updateHealLocalDisks(healLocalDisks []Endpoints) {
|
||||||
|
ahs.Lock()
|
||||||
|
defer ahs.Unlock()
|
||||||
|
|
||||||
|
ahs.healLocalDisks = healLocalDisks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
||||||
// Launch clean-up routine to remove this heal sequence (after
|
// Launch clean-up routine to remove this heal sequence (after
|
||||||
// it ends) from the global state after timeout has elapsed.
|
// it ends) from the global state after timeout has elapsed.
|
||||||
ticker := time.NewTicker(time.Minute * 5)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-time.After(time.Minute * 5):
|
||||||
now := UTCNow()
|
now := UTCNow()
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
for path, h := range ahs.healSeqMap {
|
for path, h := range ahs.healSeqMap {
|
||||||
@@ -123,7 +133,7 @@ func (ahs *allHealState) periodicHealSeqsClean() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ahs.Unlock()
|
ahs.Unlock()
|
||||||
case <-GlobalServiceDoneCh:
|
case <-ctx.Done():
|
||||||
// server could be restarting - need
|
// server could be restarting - need
|
||||||
// to exit immediately
|
// to exit immediately
|
||||||
return
|
return
|
||||||
@@ -158,12 +168,17 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
|||||||
he, exists := ahs.getHealSequence(path)
|
he, exists := ahs.getHealSequence(path)
|
||||||
if !exists {
|
if !exists {
|
||||||
hsp = madmin.HealStopSuccess{
|
hsp = madmin.HealStopSuccess{
|
||||||
ClientToken: "invalid",
|
ClientToken: "unknown",
|
||||||
StartTime: UTCNow(),
|
StartTime: UTCNow(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
clientToken := he.clientToken
|
||||||
|
if globalIsDistErasure {
|
||||||
|
clientToken = fmt.Sprintf("%s@%d", he.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
|
}
|
||||||
|
|
||||||
hsp = madmin.HealStopSuccess{
|
hsp = madmin.HealStopSuccess{
|
||||||
ClientToken: he.clientToken,
|
ClientToken: clientToken,
|
||||||
ClientAddress: he.clientAddress,
|
ClientAddress: he.clientAddress,
|
||||||
StartTime: he.startTime,
|
StartTime: he.startTime,
|
||||||
}
|
}
|
||||||
@@ -179,7 +194,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(&hsp)
|
b, err := json.Marshal(&hsp)
|
||||||
return b, toAdminAPIErr(context.Background(), err)
|
return b, toAdminAPIErr(GlobalContext, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LaunchNewHealSequence - launches a background routine that performs
|
// LaunchNewHealSequence - launches a background routine that performs
|
||||||
@@ -195,23 +210,14 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
|||||||
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
||||||
respBytes []byte, apiErr APIError, errMsg string) {
|
respBytes []byte, apiErr APIError, errMsg string) {
|
||||||
|
|
||||||
existsAndLive := false
|
if h.forceStarted {
|
||||||
he, exists := ahs.getHealSequence(h.path)
|
_, apiErr = ahs.stopHealSequence(pathJoin(h.bucket, h.object))
|
||||||
if exists {
|
if apiErr.Code != "" {
|
||||||
if !he.hasEnded() || len(he.currentStatus.Items) > 0 {
|
return respBytes, apiErr, ""
|
||||||
existsAndLive = true
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
oh, exists := ahs.getHealSequence(pathJoin(h.bucket, h.object))
|
||||||
if existsAndLive {
|
if exists && !oh.hasEnded() {
|
||||||
// A heal sequence exists on the given path.
|
|
||||||
if h.forceStarted {
|
|
||||||
// stop the running heal sequence - wait for it to finish.
|
|
||||||
he.stop()
|
|
||||||
for !he.hasEnded() {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errMsg = "Heal is already running on the given path " +
|
errMsg = "Heal is already running on the given path " +
|
||||||
"(use force-start option to stop and start afresh). " +
|
"(use force-start option to stop and start afresh). " +
|
||||||
fmt.Sprintf("The heal was started by IP %s at %s, token is %s",
|
fmt.Sprintf("The heal was started by IP %s at %s, token is %s",
|
||||||
@@ -225,10 +231,9 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
|||||||
|
|
||||||
// Check if new heal sequence to be started overlaps with any
|
// Check if new heal sequence to be started overlaps with any
|
||||||
// existing, running sequence
|
// existing, running sequence
|
||||||
|
hpath := pathJoin(h.bucket, h.object)
|
||||||
for k, hSeq := range ahs.healSeqMap {
|
for k, hSeq := range ahs.healSeqMap {
|
||||||
if !hSeq.hasEnded() && (strings.HasPrefix(k, h.path) ||
|
if !hSeq.hasEnded() && (HasPrefix(k, hpath) || HasPrefix(hpath, k)) {
|
||||||
strings.HasPrefix(h.path, k)) {
|
|
||||||
|
|
||||||
errMsg = "The provided heal sequence path overlaps with an existing " +
|
errMsg = "The provided heal sequence path overlaps with an existing " +
|
||||||
fmt.Sprintf("heal path: %s", k)
|
fmt.Sprintf("heal path: %s", k)
|
||||||
return nil, errorCodes.ToAPIErr(ErrHealOverlappingPaths), errMsg
|
return nil, errorCodes.ToAPIErr(ErrHealOverlappingPaths), errMsg
|
||||||
@@ -236,19 +241,24 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add heal state and start sequence
|
// Add heal state and start sequence
|
||||||
ahs.healSeqMap[h.path] = h
|
ahs.healSeqMap[hpath] = h
|
||||||
|
|
||||||
// Launch top-level background heal go-routine
|
// Launch top-level background heal go-routine
|
||||||
go h.healSequenceStart()
|
go h.healSequenceStart()
|
||||||
|
|
||||||
|
clientToken := h.clientToken
|
||||||
|
if globalIsDistErasure {
|
||||||
|
clientToken = fmt.Sprintf("%s@%d", h.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(madmin.HealStartSuccess{
|
b, err := json.Marshal(madmin.HealStartSuccess{
|
||||||
ClientToken: h.clientToken,
|
ClientToken: clientToken,
|
||||||
ClientAddress: h.clientAddress,
|
ClientAddress: h.clientAddress,
|
||||||
StartTime: h.startTime,
|
StartTime: h.startTime,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(h.ctx, err)
|
logger.LogIf(h.ctx, err)
|
||||||
return nil, toAPIError(h.ctx, err), ""
|
return nil, toAdminAPIErr(h.ctx, err), ""
|
||||||
}
|
}
|
||||||
return b, noError, ""
|
return b, noError, ""
|
||||||
}
|
}
|
||||||
@@ -257,14 +267,17 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
|||||||
// status results from global state and returns its JSON
|
// status results from global state and returns its JSON
|
||||||
// representation. The clientToken helps ensure there aren't
|
// representation. The clientToken helps ensure there aren't
|
||||||
// conflicting clients fetching status.
|
// conflicting clients fetching status.
|
||||||
func (ahs *allHealState) PopHealStatusJSON(path string,
|
func (ahs *allHealState) PopHealStatusJSON(hpath string,
|
||||||
clientToken string) ([]byte, APIErrorCode) {
|
clientToken string) ([]byte, APIErrorCode) {
|
||||||
|
|
||||||
// fetch heal state for given path
|
// fetch heal state for given path
|
||||||
h, exists := ahs.getHealSequence(path)
|
h, exists := ahs.getHealSequence(hpath)
|
||||||
if !exists {
|
if !exists {
|
||||||
// If there is no such heal sequence, return error.
|
// heal sequence doesn't exist, must have finished.
|
||||||
return nil, ErrHealNoSuchProcess
|
jbytes, err := json.Marshal(healSequenceStatus{
|
||||||
|
Summary: healFinishedStatus,
|
||||||
|
})
|
||||||
|
return jbytes, toAdminAPIErrCode(GlobalContext, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if client-token is valid
|
// Check if client-token is valid
|
||||||
@@ -273,8 +286,8 @@ func (ahs *allHealState) PopHealStatusJSON(path string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Take lock to access and update the heal-sequence
|
// Take lock to access and update the heal-sequence
|
||||||
h.currentStatus.updateLock.Lock()
|
h.mutex.Lock()
|
||||||
defer h.currentStatus.updateLock.Unlock()
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
numItems := len(h.currentStatus.Items)
|
numItems := len(h.currentStatus.Items)
|
||||||
|
|
||||||
@@ -285,38 +298,42 @@ func (ahs *allHealState) PopHealStatusJSON(path string,
|
|||||||
lastResultIndex = h.currentStatus.Items[numItems-1].ResultIndex
|
lastResultIndex = h.currentStatus.Items[numItems-1].ResultIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// After sending status to client, and before relinquishing
|
h.lastSentResultIndex = lastResultIndex
|
||||||
// the updateLock, reset Item to nil and record the result
|
|
||||||
// index sent to the client.
|
|
||||||
defer func(i int64) {
|
|
||||||
h.lastSentResultIndex = i
|
|
||||||
h.currentStatus.Items = nil
|
|
||||||
}(lastResultIndex)
|
|
||||||
|
|
||||||
jbytes, err := json.Marshal(h.currentStatus)
|
jbytes, err := json.Marshal(h.currentStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
h.currentStatus.Items = nil
|
||||||
|
|
||||||
logger.LogIf(h.ctx, err)
|
logger.LogIf(h.ctx, err)
|
||||||
return nil, ErrInternalError
|
return nil, ErrInternalError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.currentStatus.Items = nil
|
||||||
|
|
||||||
return jbytes, ErrNone
|
return jbytes, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// healSource denotes single entity and heal option.
|
||||||
|
type healSource struct {
|
||||||
|
bucket string
|
||||||
|
object string
|
||||||
|
versionID string
|
||||||
|
opts *madmin.HealOpts // optional heal option overrides default setting
|
||||||
|
}
|
||||||
|
|
||||||
// healSequence - state for each heal sequence initiated on the
|
// healSequence - state for each heal sequence initiated on the
|
||||||
// server.
|
// server.
|
||||||
type healSequence struct {
|
type healSequence struct {
|
||||||
// bucket, and prefix on which heal seq. was initiated
|
// bucket, and object on which heal seq. was initiated
|
||||||
bucket, objPrefix string
|
bucket, object string
|
||||||
|
|
||||||
// path is just pathJoin(bucket, objPrefix)
|
// A channel of entities (format, buckets, objects) to heal
|
||||||
path string
|
sourceCh chan healSource
|
||||||
|
|
||||||
// List of entities (format, buckets, objects) to heal
|
// A channel of entities with heal result
|
||||||
sourceCh chan string
|
respCh chan healResult
|
||||||
|
|
||||||
// Report healing progress, false if this is a background
|
// Report healing progress
|
||||||
// healing since currently there is no entity which will
|
|
||||||
// receive realtime healing status
|
|
||||||
reportProgress bool
|
reportProgress bool
|
||||||
|
|
||||||
// time at which heal sequence was started
|
// time at which heal sequence was started
|
||||||
@@ -341,59 +358,141 @@ type healSequence struct {
|
|||||||
// completed
|
// completed
|
||||||
traverseAndHealDoneCh chan error
|
traverseAndHealDoneCh chan error
|
||||||
|
|
||||||
// channel to signal heal sequence to stop (e.g. from the
|
// canceler to cancel heal sequence.
|
||||||
// heal-stop API)
|
cancelCtx context.CancelFunc
|
||||||
stopSignalCh chan struct{}
|
|
||||||
|
|
||||||
// the last result index sent to client
|
// the last result index sent to client
|
||||||
lastSentResultIndex int64
|
lastSentResultIndex int64
|
||||||
|
|
||||||
// Number of total items scanned
|
// Number of total items scanned against item type
|
||||||
scannedItemsCount int64
|
scannedItemsMap map[madmin.HealItemType]int64
|
||||||
|
|
||||||
|
// Number of total items healed against item type
|
||||||
|
healedItemsMap map[madmin.HealItemType]int64
|
||||||
|
|
||||||
|
// Number of total items where healing failed against endpoint and drive state
|
||||||
|
healFailedItemsMap map[string]int64
|
||||||
|
|
||||||
// The time of the last scan/heal activity
|
// The time of the last scan/heal activity
|
||||||
lastHealActivity time.Time
|
lastHealActivity time.Time
|
||||||
|
|
||||||
// Holds the request-info for logging
|
// Holds the request-info for logging
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
|
// used to lock this structure as it is concurrently accessed
|
||||||
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHealSequence - creates healSettings, assumes bucket and
|
// NewHealSequence - creates healSettings, assumes bucket and
|
||||||
// objPrefix are already validated.
|
// objPrefix are already validated.
|
||||||
func newHealSequence(bucket, objPrefix, clientAddr string,
|
func newHealSequence(ctx context.Context, bucket, objPrefix, clientAddr string,
|
||||||
numDisks int, hs madmin.HealOpts, forceStart bool) *healSequence {
|
hs madmin.HealOpts, forceStart bool) *healSequence {
|
||||||
|
|
||||||
reqInfo := &logger.ReqInfo{RemoteHost: clientAddr, API: "Heal", BucketName: bucket}
|
reqInfo := &logger.ReqInfo{RemoteHost: clientAddr, API: "Heal", BucketName: bucket}
|
||||||
reqInfo.AppendTags("prefix", objPrefix)
|
reqInfo.AppendTags("prefix", objPrefix)
|
||||||
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
ctx, cancel := context.WithCancel(logger.SetReqInfo(ctx, reqInfo))
|
||||||
|
|
||||||
|
clientToken := mustGetUUID()
|
||||||
|
|
||||||
return &healSequence{
|
return &healSequence{
|
||||||
|
respCh: make(chan healResult),
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
objPrefix: objPrefix,
|
object: objPrefix,
|
||||||
path: pathJoin(bucket, objPrefix),
|
|
||||||
reportProgress: true,
|
reportProgress: true,
|
||||||
startTime: UTCNow(),
|
startTime: UTCNow(),
|
||||||
clientToken: mustGetUUID(),
|
clientToken: clientToken,
|
||||||
clientAddress: clientAddr,
|
clientAddress: clientAddr,
|
||||||
forceStarted: forceStart,
|
forceStarted: forceStart,
|
||||||
settings: hs,
|
settings: hs,
|
||||||
currentStatus: healSequenceStatus{
|
currentStatus: healSequenceStatus{
|
||||||
Summary: healNotStartedStatus,
|
Summary: healNotStartedStatus,
|
||||||
HealSettings: hs,
|
HealSettings: hs,
|
||||||
NumDisks: numDisks,
|
|
||||||
updateLock: &sync.RWMutex{},
|
|
||||||
},
|
},
|
||||||
traverseAndHealDoneCh: make(chan error),
|
traverseAndHealDoneCh: make(chan error),
|
||||||
stopSignalCh: make(chan struct{}),
|
cancelCtx: cancel,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
scannedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
|
healedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
|
healFailedItemsMap: make(map[string]int64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetHealStatusCounters - reset the healSequence status counters between
|
||||||
|
// each monthly background heal scanning activity.
|
||||||
|
// This is used only in case of Background healing scenario, where
|
||||||
|
// we use a single long running healSequence which reactively heals
|
||||||
|
// objects passed to the SourceCh.
|
||||||
|
func (h *healSequence) resetHealStatusCounters() {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
h.currentStatus.Items = []madmin.HealResultItem{}
|
||||||
|
h.lastSentResultIndex = 0
|
||||||
|
h.scannedItemsMap = make(map[madmin.HealItemType]int64)
|
||||||
|
h.healedItemsMap = make(map[madmin.HealItemType]int64)
|
||||||
|
h.healFailedItemsMap = make(map[string]int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getScannedItemsCount - returns a count of all scanned items
|
||||||
|
func (h *healSequence) getScannedItemsCount() int64 {
|
||||||
|
var count int64
|
||||||
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
|
for _, v := range h.scannedItemsMap {
|
||||||
|
count = count + v
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// getScannedItemsMap - returns map of all scanned items against type
|
||||||
|
func (h *healSequence) getScannedItemsMap() map[madmin.HealItemType]int64 {
|
||||||
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Make a copy before returning the value
|
||||||
|
retMap := make(map[madmin.HealItemType]int64, len(h.scannedItemsMap))
|
||||||
|
for k, v := range h.scannedItemsMap {
|
||||||
|
retMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return retMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHealedItemsMap - returns the map of all healed items against type
|
||||||
|
func (h *healSequence) getHealedItemsMap() map[madmin.HealItemType]int64 {
|
||||||
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Make a copy before returning the value
|
||||||
|
retMap := make(map[madmin.HealItemType]int64, len(h.healedItemsMap))
|
||||||
|
for k, v := range h.healedItemsMap {
|
||||||
|
retMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return retMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// gethealFailedItemsMap - returns map of all items where heal failed against
|
||||||
|
// drive endpoint and status
|
||||||
|
func (h *healSequence) gethealFailedItemsMap() map[string]int64 {
|
||||||
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Make a copy before returning the value
|
||||||
|
retMap := make(map[string]int64, len(h.healFailedItemsMap))
|
||||||
|
for k, v := range h.healFailedItemsMap {
|
||||||
|
retMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return retMap
|
||||||
|
}
|
||||||
|
|
||||||
// isQuitting - determines if the heal sequence is quitting (due to an
|
// isQuitting - determines if the heal sequence is quitting (due to an
|
||||||
// external signal)
|
// external signal)
|
||||||
func (h *healSequence) isQuitting() bool {
|
func (h *healSequence) isQuitting() bool {
|
||||||
select {
|
select {
|
||||||
case <-h.stopSignalCh:
|
case <-h.ctx.Done():
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -402,19 +501,19 @@ func (h *healSequence) isQuitting() bool {
|
|||||||
|
|
||||||
// check if the heal sequence has ended
|
// check if the heal sequence has ended
|
||||||
func (h *healSequence) hasEnded() bool {
|
func (h *healSequence) hasEnded() bool {
|
||||||
h.currentStatus.updateLock.RLock()
|
h.mutex.RLock()
|
||||||
summary := h.currentStatus.Summary
|
// background heal never ends
|
||||||
h.currentStatus.updateLock.RUnlock()
|
if h.clientToken == bgHealingUUID {
|
||||||
return summary == healStoppedStatus || summary == healFinishedStatus
|
return false
|
||||||
|
}
|
||||||
|
ended := len(h.currentStatus.Items) == 0 || h.currentStatus.Summary == healStoppedStatus || h.currentStatus.Summary == healFinishedStatus
|
||||||
|
h.mutex.RUnlock()
|
||||||
|
return ended
|
||||||
}
|
}
|
||||||
|
|
||||||
// stops the heal sequence - safe to call multiple times.
|
// stops the heal sequence - safe to call multiple times.
|
||||||
func (h *healSequence) stop() {
|
func (h *healSequence) stop() {
|
||||||
select {
|
h.cancelCtx()
|
||||||
case <-h.stopSignalCh:
|
|
||||||
default:
|
|
||||||
close(h.stopSignalCh)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushHealResultItem - pushes a heal result item for consumption in
|
// pushHealResultItem - pushes a heal result item for consumption in
|
||||||
@@ -441,29 +540,27 @@ func (h *healSequence) pushHealResultItem(r madmin.HealResultItem) error {
|
|||||||
|
|
||||||
var itemsLen int
|
var itemsLen int
|
||||||
for {
|
for {
|
||||||
h.currentStatus.updateLock.Lock()
|
h.mutex.Lock()
|
||||||
itemsLen = len(h.currentStatus.Items)
|
itemsLen = len(h.currentStatus.Items)
|
||||||
if itemsLen == maxUnconsumedHealResultItems {
|
if itemsLen == maxUnconsumedHealResultItems {
|
||||||
// unlock and wait to check again if we can push
|
|
||||||
h.currentStatus.updateLock.Unlock()
|
|
||||||
|
|
||||||
// wait for a second, or quit if an external
|
// wait for a second, or quit if an external
|
||||||
// stop signal is received or the
|
// stop signal is received or the
|
||||||
// unconsumedTimer fires.
|
// unconsumedTimer fires.
|
||||||
select {
|
select {
|
||||||
// Check after a second
|
// Check after a second
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
|
h.mutex.Unlock()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case <-h.stopSignalCh:
|
case <-h.ctx.Done():
|
||||||
|
h.mutex.Unlock()
|
||||||
// discard result and return.
|
// discard result and return.
|
||||||
return errHealPushStopNDiscard
|
return errHealStopSignalled
|
||||||
|
|
||||||
// Timeout if no results consumed for too
|
// Timeout if no results consumed for too long.
|
||||||
// long.
|
|
||||||
case <-unconsumedTimer.C:
|
case <-unconsumedTimer.C:
|
||||||
|
h.mutex.Unlock()
|
||||||
return errHealIdleTimeout
|
return errHealIdleTimeout
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -480,13 +577,7 @@ func (h *healSequence) pushHealResultItem(r madmin.HealResultItem) error {
|
|||||||
h.currentStatus.Items = append(h.currentStatus.Items, r)
|
h.currentStatus.Items = append(h.currentStatus.Items, r)
|
||||||
|
|
||||||
// release lock
|
// release lock
|
||||||
h.currentStatus.updateLock.Unlock()
|
h.mutex.Unlock()
|
||||||
|
|
||||||
// This is a "safe" point for the heal sequence to quit if
|
|
||||||
// signaled externally.
|
|
||||||
if h.isQuitting() {
|
|
||||||
return errHealStopSignalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -500,10 +591,10 @@ func (h *healSequence) pushHealResultItem(r madmin.HealResultItem) error {
|
|||||||
// the heal-sequence.
|
// the heal-sequence.
|
||||||
func (h *healSequence) healSequenceStart() {
|
func (h *healSequence) healSequenceStart() {
|
||||||
// Set status as running
|
// Set status as running
|
||||||
h.currentStatus.updateLock.Lock()
|
h.mutex.Lock()
|
||||||
h.currentStatus.Summary = healRunningStatus
|
h.currentStatus.Summary = healRunningStatus
|
||||||
h.currentStatus.StartTime = UTCNow()
|
h.currentStatus.StartTime = UTCNow()
|
||||||
h.currentStatus.updateLock.Unlock()
|
h.mutex.Unlock()
|
||||||
|
|
||||||
if h.sourceCh == nil {
|
if h.sourceCh == nil {
|
||||||
go h.traverseAndHeal()
|
go h.traverseAndHeal()
|
||||||
@@ -513,25 +604,26 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case err, ok := <-h.traverseAndHealDoneCh:
|
case err, ok := <-h.traverseAndHealDoneCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.mutex.Lock()
|
||||||
h.endTime = UTCNow()
|
h.endTime = UTCNow()
|
||||||
h.currentStatus.updateLock.Lock()
|
|
||||||
defer h.currentStatus.updateLock.Unlock()
|
|
||||||
// Heal traversal is complete.
|
// Heal traversal is complete.
|
||||||
if ok {
|
if err == nil {
|
||||||
|
// heal traversal succeeded.
|
||||||
|
h.currentStatus.Summary = healFinishedStatus
|
||||||
|
} else {
|
||||||
// heal traversal had an error.
|
// heal traversal had an error.
|
||||||
h.currentStatus.Summary = healStoppedStatus
|
h.currentStatus.Summary = healStoppedStatus
|
||||||
h.currentStatus.FailureDetail = err.Error()
|
h.currentStatus.FailureDetail = err.Error()
|
||||||
} else {
|
|
||||||
// heal traversal succeeded.
|
|
||||||
h.currentStatus.Summary = healFinishedStatus
|
|
||||||
}
|
}
|
||||||
|
h.mutex.Unlock()
|
||||||
case <-h.stopSignalCh:
|
case <-h.ctx.Done():
|
||||||
|
h.mutex.Lock()
|
||||||
h.endTime = UTCNow()
|
h.endTime = UTCNow()
|
||||||
h.currentStatus.updateLock.Lock()
|
h.currentStatus.Summary = healFinishedStatus
|
||||||
h.currentStatus.Summary = healStoppedStatus
|
h.mutex.Unlock()
|
||||||
h.currentStatus.FailureDetail = errHealStopSignalled.Error()
|
|
||||||
h.currentStatus.updateLock.Unlock()
|
|
||||||
|
|
||||||
// drain traverse channel so the traversal
|
// drain traverse channel so the traversal
|
||||||
// go-routine does not leak.
|
// go-routine does not leak.
|
||||||
@@ -544,76 +636,110 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(path string, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
var respCh = make(chan healResult)
|
|
||||||
defer close(respCh)
|
|
||||||
// Send heal request
|
// Send heal request
|
||||||
globalBackgroundHealing.queueHealTask(healTask{path: path, responseCh: respCh, opts: h.settings})
|
task := healTask{
|
||||||
// Wait for answer and push result to the client
|
bucket: source.bucket,
|
||||||
res := <-respCh
|
object: source.object,
|
||||||
if !h.reportProgress {
|
versionID: source.versionID,
|
||||||
return nil
|
opts: h.settings,
|
||||||
|
responseCh: h.respCh,
|
||||||
}
|
}
|
||||||
res.result.Type = healType
|
if source.opts != nil {
|
||||||
if res.err != nil {
|
task.opts = *source.opts
|
||||||
// Object might have been deleted, by the time heal
|
}
|
||||||
// was attempted, we should ignore this object and return success.
|
globalBackgroundHealRoutine.queueHealTask(task)
|
||||||
if isErrObjectNotFound(res.err) {
|
|
||||||
return nil
|
select {
|
||||||
}
|
case res := <-h.respCh:
|
||||||
// Only report object error
|
if !h.reportProgress {
|
||||||
if healType != madmin.HealItemObject {
|
// Object might have been deleted, by the time heal
|
||||||
|
// was attempted, we should ignore this object and
|
||||||
|
// return success.
|
||||||
|
if isErrObjectNotFound(res.err) || isErrVersionNotFound(res.err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
// Progress is not reported in case of background heal processing.
|
||||||
|
// Instead we increment relevant counter based on the heal result
|
||||||
|
// for prometheus reporting.
|
||||||
|
if res.err != nil {
|
||||||
|
for _, d := range res.result.After.Drives {
|
||||||
|
// For failed items we report the endpoint and drive state
|
||||||
|
// This will help users take corrective actions for drives
|
||||||
|
h.healFailedItemsMap[d.Endpoint+","+d.State]++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only object type reported for successful healing
|
||||||
|
h.healedItemsMap[res.result.Type]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report caller of any failure
|
||||||
return res.err
|
return res.err
|
||||||
}
|
}
|
||||||
res.result.Detail = res.err.Error()
|
res.result.Type = healType
|
||||||
|
if res.err != nil {
|
||||||
|
// Object might have been deleted, by the time heal
|
||||||
|
// was attempted, we should ignore this object and return success.
|
||||||
|
if isErrObjectNotFound(res.err) || isErrVersionNotFound(res.err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Only report object error
|
||||||
|
if healType != madmin.HealItemObject {
|
||||||
|
return res.err
|
||||||
|
}
|
||||||
|
res.result.Detail = res.err.Error()
|
||||||
|
}
|
||||||
|
return h.pushHealResultItem(res.result)
|
||||||
|
case <-h.ctx.Done():
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return h.pushHealResultItem(res.result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healItemsFromSourceCh() error {
|
func (h *healSequence) healItemsFromSourceCh() error {
|
||||||
h.lastHealActivity = UTCNow()
|
for {
|
||||||
|
select {
|
||||||
|
case source, ok := <-h.sourceCh:
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var itemType madmin.HealItemType
|
||||||
|
switch {
|
||||||
|
case source.bucket == nopHeal:
|
||||||
|
continue
|
||||||
|
case source.bucket == SlashSeparator:
|
||||||
|
itemType = madmin.HealItemMetadata
|
||||||
|
case source.bucket != "" && source.object == "":
|
||||||
|
itemType = madmin.HealItemBucket
|
||||||
|
default:
|
||||||
|
itemType = madmin.HealItemObject
|
||||||
|
}
|
||||||
|
|
||||||
// Start healing the config prefix.
|
if err := h.queueHealTask(source, itemType); err != nil {
|
||||||
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
|
switch err.(type) {
|
||||||
logger.LogIf(h.ctx, err)
|
case ObjectExistsAsDirectory:
|
||||||
}
|
default:
|
||||||
|
logger.LogIf(h.ctx, fmt.Errorf("Heal attempt failed for %s: %w",
|
||||||
|
pathJoin(source.bucket, source.object), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start healing the bucket config prefix.
|
h.scannedItemsMap[itemType]++
|
||||||
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
|
h.lastHealActivity = UTCNow()
|
||||||
logger.LogIf(h.ctx, err)
|
case <-h.ctx.Done():
|
||||||
}
|
return nil
|
||||||
|
|
||||||
for path := range h.sourceCh {
|
|
||||||
|
|
||||||
var itemType madmin.HealItemType
|
|
||||||
switch {
|
|
||||||
case path == SlashSeparator:
|
|
||||||
itemType = madmin.HealItemMetadata
|
|
||||||
case !strings.Contains(path, SlashSeparator):
|
|
||||||
itemType = madmin.HealItemBucket
|
|
||||||
default:
|
|
||||||
itemType = madmin.HealItemObject
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.queueHealTask(path, itemType); err != nil {
|
|
||||||
logger.LogIf(h.ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.scannedItemsCount++
|
|
||||||
h.lastHealActivity = UTCNow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healFromSourceCh() {
|
func (h *healSequence) healFromSourceCh() {
|
||||||
if err := h.healItemsFromSourceCh(); err != nil {
|
h.healItemsFromSourceCh()
|
||||||
h.traverseAndHealDoneCh <- err
|
|
||||||
}
|
|
||||||
close(h.traverseAndHealDoneCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healItems() error {
|
func (h *healSequence) healDiskMeta() error {
|
||||||
// Start with format healing
|
// Start with format healing
|
||||||
if err := h.healDiskFormat(); err != nil {
|
if err := h.healDiskFormat(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -625,12 +751,16 @@ func (h *healSequence) healItems() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start healing the bucket config prefix.
|
// Start healing the bucket config prefix.
|
||||||
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
|
return h.healMinioSysMeta(bucketConfigPrefix)()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) healItems(bucketsOnly bool) error {
|
||||||
|
if err := h.healDiskMeta(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal buckets and objects
|
// Heal buckets and objects
|
||||||
return h.healBuckets()
|
return h.healBuckets(bucketsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// traverseAndHeal - traverses on-disk data and performs healing
|
// traverseAndHeal - traverses on-disk data and performs healing
|
||||||
@@ -641,13 +771,8 @@ func (h *healSequence) healItems() error {
|
|||||||
// has to wait until a safe point is reached, such as between scanning
|
// has to wait until a safe point is reached, such as between scanning
|
||||||
// two objects.
|
// two objects.
|
||||||
func (h *healSequence) traverseAndHeal() {
|
func (h *healSequence) traverseAndHeal() {
|
||||||
if err := h.healItems(); err != nil {
|
bucketsOnly := false // Heals buckets and objects also.
|
||||||
if h.isQuitting() {
|
h.traverseAndHealDoneCh <- h.healItems(bucketsOnly)
|
||||||
err = errHealStopSignalled
|
|
||||||
}
|
|
||||||
h.traverseAndHealDoneCh <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
close(h.traverseAndHealDoneCh)
|
close(h.traverseAndHealDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +781,7 @@ func (h *healSequence) traverseAndHeal() {
|
|||||||
func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
|
func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
|
||||||
return func() error {
|
return func() error {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -664,15 +789,19 @@ func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
|
|||||||
// NOTE: Healing on meta is run regardless
|
// NOTE: Healing on meta is run regardless
|
||||||
// of any bucket being selected, this is to ensure that
|
// of any bucket being selected, this is to ensure that
|
||||||
// meta are always upto date and correct.
|
// meta are always upto date and correct.
|
||||||
return objectAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, func(bucket string, object string) error {
|
return objectAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
herr := h.queueHealTask(pathJoin(bucket, object), madmin.HealItemBucketMetadata)
|
herr := h.queueHealTask(healSource{
|
||||||
|
bucket: bucket,
|
||||||
|
object: object,
|
||||||
|
versionID: versionID,
|
||||||
|
}, madmin.HealItemBucketMetadata)
|
||||||
// Object might have been deleted, by the time heal
|
// Object might have been deleted, by the time heal
|
||||||
// was attempted we ignore this object an move on.
|
// was attempted we ignore this object an move on.
|
||||||
if isErrObjectNotFound(herr) {
|
if isErrObjectNotFound(herr) || isErrVersionNotFound(herr) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return herr
|
return herr
|
||||||
@@ -688,27 +817,27 @@ func (h *healSequence) healDiskFormat() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.queueHealTask(SlashSeparator, madmin.HealItemMetadata)
|
return h.queueHealTask(healSource{bucket: SlashSeparator}, madmin.HealItemMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// healBuckets - check for all buckets heal or just particular bucket.
|
// healBuckets - check for all buckets heal or just particular bucket.
|
||||||
func (h *healSequence) healBuckets() error {
|
func (h *healSequence) healBuckets(bucketsOnly bool) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. If a bucket was specified, heal only the bucket.
|
// 1. If a bucket was specified, heal only the bucket.
|
||||||
if h.bucket != "" {
|
if h.bucket != "" {
|
||||||
return h.healBucket(h.bucket)
|
return h.healBucket(h.bucket, bucketsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -719,7 +848,7 @@ func (h *healSequence) healBuckets() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
if err = h.healBucket(bucket.Name); err != nil {
|
if err = h.healBucket(bucket.Name, bucketsOnly); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -728,24 +857,28 @@ func (h *healSequence) healBuckets() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// healBucket - traverses and heals given bucket
|
// healBucket - traverses and heals given bucket
|
||||||
func (h *healSequence) healBucket(bucket string) error {
|
func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.queueHealTask(bucket, madmin.HealItemBucket); err != nil {
|
if err := h.queueHealTask(healSource{bucket: bucket}, madmin.HealItemBucket); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bucketsOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if !h.settings.Recursive {
|
if !h.settings.Recursive {
|
||||||
if h.objPrefix != "" {
|
if h.object != "" {
|
||||||
// Check if an object named as the objPrefix exists,
|
// Check if an object named as the objPrefix exists,
|
||||||
// and if so heal it.
|
// and if so heal it.
|
||||||
_, err := objectAPI.GetObjectInfo(h.ctx, bucket, h.objPrefix, ObjectOptions{})
|
oi, err := objectAPI.GetObjectInfo(h.ctx, bucket, h.object, ObjectOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = h.healObject(bucket, h.objPrefix); err != nil {
|
if err = h.healObject(bucket, h.object, oi.VersionID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -754,23 +887,27 @@ func (h *healSequence) healBucket(bucket string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := objectAPI.HealObjects(h.ctx, bucket, h.objPrefix, h.healObject); err != nil {
|
if err := objectAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil {
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
return errFnHealFromAPIErr(h.ctx, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// healObject - heal the given object and record result
|
// healObject - heal the given object and record result
|
||||||
func (h *healSequence) healObject(bucket, object string) error {
|
func (h *healSequence) healObject(bucket, object, versionID string) error {
|
||||||
if h.isQuitting() {
|
|
||||||
return errHealStopSignalled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.queueHealTask(pathJoin(bucket, object), madmin.HealItemObject)
|
if h.isQuitting() {
|
||||||
|
return errHealStopSignalled
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.queueHealTask(healSource{
|
||||||
|
bucket: bucket,
|
||||||
|
object: object,
|
||||||
|
versionID: versionID,
|
||||||
|
}, madmin.HealItemObject)
|
||||||
}
|
}
|
||||||
|
|||||||
+178
-91
@@ -20,127 +20,214 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio/cmd/config"
|
||||||
|
"github.com/minio/minio/pkg/env"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
adminAPIPathPrefix = "/minio/admin"
|
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||||
|
adminAPIVersionV2 = madmin.AdminAPIVersionV2
|
||||||
|
adminAPIVersion = madmin.AdminAPIVersion
|
||||||
|
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||||
|
adminAPIVersionV2Prefix = SlashSeparator + adminAPIVersionV2
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
||||||
type adminAPIHandlers struct {
|
type adminAPIHandlers struct{}
|
||||||
}
|
|
||||||
|
|
||||||
// registerAdminRouter - Add handler functions for each service REST API routes.
|
// registerAdminRouter - Add handler functions for each service REST API routes.
|
||||||
func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) {
|
func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) {
|
||||||
|
|
||||||
adminAPI := adminAPIHandlers{}
|
adminAPI := adminAPIHandlers{}
|
||||||
// Admin router
|
// Admin router
|
||||||
adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter()
|
adminRouter := router.PathPrefix(adminPathPrefix).Subrouter()
|
||||||
|
|
||||||
// Version handler
|
|
||||||
adminV1Router := adminRouter.PathPrefix("/v1").Subrouter()
|
|
||||||
|
|
||||||
/// Service operations
|
/// Service operations
|
||||||
|
|
||||||
// Restart and stop MinIO service.
|
adminVersions := []string{
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceActionHandler)).Queries("action", "{action:.*}")
|
adminAPIVersionPrefix,
|
||||||
// Update MinIO servers.
|
adminAPIVersionV2Prefix,
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/update").HandlerFunc(httpTraceAll(adminAPI.ServerUpdateHandler)).Queries("updateURL", "{updateURL:.*}")
|
|
||||||
|
|
||||||
// Info operations
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
|
|
||||||
|
|
||||||
if globalIsDistXL || globalIsXL {
|
|
||||||
/// Heal operations
|
|
||||||
|
|
||||||
// Heal processing endpoint.
|
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/heal/").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/heal/{bucket}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/heal/{bucket}/{prefix:.*}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
|
||||||
|
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/background-heal/status").HandlerFunc(httpTraceAll(adminAPI.BackgroundHealStatusHandler))
|
|
||||||
|
|
||||||
/// Health operations
|
|
||||||
|
|
||||||
}
|
|
||||||
// Performance command - return performance details based on input type
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/performance").HandlerFunc(httpTraceAll(adminAPI.PerfInfoHandler)).Queries("perfType", "{perfType:.*}")
|
|
||||||
|
|
||||||
// Profiling operations
|
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/profiling/start").HandlerFunc(httpTraceAll(adminAPI.StartProfilingHandler)).
|
|
||||||
Queries("profilerType", "{profilerType:.*}")
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/profiling/download").HandlerFunc(httpTraceAll(adminAPI.DownloadProfilingHandler))
|
|
||||||
|
|
||||||
/// Config operations
|
|
||||||
if enableConfigOps {
|
|
||||||
// Get config
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/config").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigHandler))
|
|
||||||
// Set config
|
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/config").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigHandler))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if enableIAMOps {
|
for _, adminVersion := range adminVersions {
|
||||||
// -- IAM APIs --
|
// Restart and stop MinIO service.
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(httpTraceAll(adminAPI.ServiceHandler)).Queries("action", "{action:.*}")
|
||||||
|
// Update MinIO servers.
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(httpTraceAll(adminAPI.ServerUpdateHandler)).Queries("updateURL", "{updateURL:.*}")
|
||||||
|
|
||||||
// Add policy IAM
|
// Info operations
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/add-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.AddCannedPolicy)).Queries("name",
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
|
||||||
"{name:.*}")
|
|
||||||
|
|
||||||
// Add user IAM
|
// StorageInfo operations
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(httpTraceAll(adminAPI.StorageInfoHandler))
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).
|
// DataUsageInfo operations
|
||||||
Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(httpTraceAll(adminAPI.DataUsageInfoHandler))
|
||||||
|
|
||||||
// Info policy IAM
|
if globalIsDistErasure || globalIsErasure {
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
/// Heal operations
|
||||||
|
|
||||||
// Remove policy IAM
|
// Heal processing endpoint.
|
||||||
adminV1Router.Methods(http.MethodDelete).Path("/remove-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
||||||
|
|
||||||
// Set user or group policy
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(httpTraceAll(adminAPI.BackgroundHealStatusHandler))
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/set-user-or-group-policy").
|
|
||||||
HandlerFunc(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup)).
|
|
||||||
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
|
||||||
|
|
||||||
// Remove user IAM
|
/// Health operations
|
||||||
adminV1Router.Methods(http.MethodDelete).Path("/remove-user").HandlerFunc(httpTraceHdrs(adminAPI.RemoveUser)).Queries("accessKey", "{accessKey:.*}")
|
|
||||||
|
|
||||||
// List users
|
}
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListUsers))
|
|
||||||
|
|
||||||
// User info
|
// Profiling operations
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/user-info").HandlerFunc(httpTraceHdrs(adminAPI.GetUserInfo)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(httpTraceAll(adminAPI.StartProfilingHandler)).
|
||||||
|
Queries("profilerType", "{profilerType:.*}")
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(httpTraceAll(adminAPI.DownloadProfilingHandler))
|
||||||
|
|
||||||
// Add/Remove members from group
|
// Config KV operations.
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/update-group-members").HandlerFunc(httpTraceHdrs(adminAPI.UpdateGroupMembers))
|
if enableConfigOps {
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigKVHandler)).Queries("key", "{key:.*}")
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigKVHandler))
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(httpTraceHdrs(adminAPI.DelConfigKVHandler))
|
||||||
|
}
|
||||||
|
|
||||||
// Get Group
|
// Enable config help in all modes.
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/group").HandlerFunc(httpTraceHdrs(adminAPI.GetGroup)).Queries("group", "{group:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(httpTraceAll(adminAPI.HelpConfigKVHandler)).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
||||||
|
|
||||||
// List Groups
|
// Config KV history operations.
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/groups").HandlerFunc(httpTraceHdrs(adminAPI.ListGroups))
|
if enableConfigOps {
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(httpTraceAll(adminAPI.ListConfigHistoryKVHandler)).Queries("count", "{count:[0-9]+}")
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(httpTraceHdrs(adminAPI.ClearConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(httpTraceHdrs(adminAPI.RestoreConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
||||||
|
}
|
||||||
|
|
||||||
// Set Group Status
|
/// Config import/export bulk operations
|
||||||
adminV1Router.Methods(http.MethodPut).Path("/set-group-status").HandlerFunc(httpTraceHdrs(adminAPI.SetGroupStatus)).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
if enableConfigOps {
|
||||||
|
// Get config
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigHandler))
|
||||||
|
// Set config
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigHandler))
|
||||||
|
}
|
||||||
|
|
||||||
// List policies
|
if enableIAMOps {
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
// -- IAM APIs --
|
||||||
|
|
||||||
|
// Add policy IAM
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.AddCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
|
// Add user IAM
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountusageinfo").HandlerFunc(httpTraceAll(adminAPI.AccountUsageInfoHandler))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
|
// Service accounts ops
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(httpTraceHdrs(adminAPI.ListServiceAccounts))
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(httpTraceHdrs(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
if adminVersion == adminAPIVersionV2Prefix {
|
||||||
|
// Info policy IAM v2
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicyV2)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
|
// List policies v2
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPoliciesV2))
|
||||||
|
} else {
|
||||||
|
// Info policy IAM latest
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
|
// List policies latest
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove policy IAM
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
|
// Set user or group policy
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
||||||
|
HandlerFunc(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup)).
|
||||||
|
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
||||||
|
|
||||||
|
// Remove user IAM
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(httpTraceHdrs(adminAPI.RemoveUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
// List users
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListUsers))
|
||||||
|
|
||||||
|
// User info
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(httpTraceHdrs(adminAPI.GetUserInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
// Add/Remove members from group
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(httpTraceHdrs(adminAPI.UpdateGroupMembers))
|
||||||
|
|
||||||
|
// Get Group
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(httpTraceHdrs(adminAPI.GetGroup)).Queries("group", "{group:.*}")
|
||||||
|
|
||||||
|
// List Groups
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(httpTraceHdrs(adminAPI.ListGroups))
|
||||||
|
|
||||||
|
// Set Group Status
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(httpTraceHdrs(adminAPI.SetGroupStatus)).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalIsDistErasure || globalIsErasure {
|
||||||
|
// Quota operations
|
||||||
|
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOn {
|
||||||
|
// GetBucketQuotaConfig
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
// PutBucketQuotaConfig
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
}
|
||||||
|
// Bucket replication operations
|
||||||
|
// GetBucketTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||||
|
// SetRemoteTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
// SetRemoteTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Top APIs --
|
||||||
|
// Top locks
|
||||||
|
if globalIsDistErasure {
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Trace
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(adminAPI.TraceHandler)
|
||||||
|
|
||||||
|
// Console Logs
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(httpTraceAll(adminAPI.ConsoleLogHandler))
|
||||||
|
|
||||||
|
// -- KMS APIs --
|
||||||
|
//
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(httpTraceAll(adminAPI.KMSCreateKeyHandler)).Queries("key-id", "{key-id:.*}")
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
|
||||||
|
|
||||||
|
if !globalIsGateway {
|
||||||
|
// -- OBD API --
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/obdinfo").
|
||||||
|
HandlerFunc(httpTraceHdrs(adminAPI.OBDInfoHandler)).
|
||||||
|
Queries("perfdrive", "{perfdrive:true|false}",
|
||||||
|
"perfnet", "{perfnet:true|false}",
|
||||||
|
"minioinfo", "{minioinfo:true|false}",
|
||||||
|
"minioconfig", "{minioconfig:true|false}",
|
||||||
|
"syscpu", "{syscpu:true|false}",
|
||||||
|
"sysdiskhw", "{sysdiskhw:true|false}",
|
||||||
|
"sysosinfo", "{sysosinfo:true|false}",
|
||||||
|
"sysmem", "{sysmem:true|false}",
|
||||||
|
"sysprocess", "{sysprocess:true|false}",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Top APIs --
|
// If none of the routes match add default error handler routes
|
||||||
// Top locks
|
adminRouter.NotFoundHandler = http.HandlerFunc(httpTraceAll(errorResponseHandler))
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
adminRouter.MethodNotAllowedHandler = http.HandlerFunc(httpTraceAll(errorResponseHandler))
|
||||||
|
|
||||||
// HTTP Trace
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/trace").HandlerFunc(adminAPI.TraceHandler)
|
|
||||||
|
|
||||||
// Console Logs
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/log").HandlerFunc(httpTraceAll(adminAPI.ConsoleLogHandler))
|
|
||||||
|
|
||||||
// -- KMS APIs --
|
|
||||||
//
|
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
|
|
||||||
|
|
||||||
// If none of the routes match, return error.
|
|
||||||
adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON))
|
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-81
@@ -18,97 +18,46 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/set"
|
|
||||||
"github.com/minio/minio/pkg/cpu"
|
|
||||||
"github.com/minio/minio/pkg/disk"
|
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
"github.com/minio/minio/pkg/mem"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getLocalMemUsage - returns ServerMemUsageInfo for only the
|
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func getLocalMemUsage(endpoints EndpointList, r *http.Request) ServerMemUsageInfo {
|
func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin.ServerProperties {
|
||||||
var memUsages []mem.Usage
|
|
||||||
var historicUsages []mem.Usage
|
|
||||||
seenHosts := set.NewStringSet()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
if seenHosts.Contains(endpoint.Host) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seenHosts.Add(endpoint.Host)
|
|
||||||
|
|
||||||
// Only proceed for local endpoints
|
|
||||||
if endpoint.IsLocal {
|
|
||||||
memUsages = append(memUsages, mem.GetUsage())
|
|
||||||
historicUsages = append(historicUsages, mem.GetHistoricUsage())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addr := r.Host
|
addr := r.Host
|
||||||
if globalIsDistXL {
|
if globalIsDistErasure {
|
||||||
addr = GetLocalPeer(endpoints)
|
addr = GetLocalPeer(endpointZones)
|
||||||
}
|
}
|
||||||
return ServerMemUsageInfo{
|
network := make(map[string]string)
|
||||||
Addr: addr,
|
for _, ep := range endpointZones {
|
||||||
Usage: memUsages,
|
for _, endpoint := range ep.Endpoints {
|
||||||
HistoricUsage: historicUsages,
|
nodeName := endpoint.Host
|
||||||
}
|
if nodeName == "" {
|
||||||
}
|
nodeName = r.Host
|
||||||
|
}
|
||||||
// getLocalCPULoad - returns ServerCPULoadInfo for only the
|
if endpoint.IsLocal {
|
||||||
// local endpoints from given list of endpoints
|
// Only proceed for local endpoints
|
||||||
func getLocalCPULoad(endpoints EndpointList, r *http.Request) ServerCPULoadInfo {
|
network[nodeName] = "online"
|
||||||
var cpuLoads []cpu.Load
|
|
||||||
var historicLoads []cpu.Load
|
|
||||||
seenHosts := set.NewStringSet()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
if seenHosts.Contains(endpoint.Host) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seenHosts.Add(endpoint.Host)
|
|
||||||
|
|
||||||
// Only proceed for local endpoints
|
|
||||||
if endpoint.IsLocal {
|
|
||||||
cpuLoads = append(cpuLoads, cpu.GetLoad())
|
|
||||||
historicLoads = append(historicLoads, cpu.GetHistoricLoad())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addr := r.Host
|
|
||||||
if globalIsDistXL {
|
|
||||||
addr = GetLocalPeer(endpoints)
|
|
||||||
}
|
|
||||||
return ServerCPULoadInfo{
|
|
||||||
Addr: addr,
|
|
||||||
Load: cpuLoads,
|
|
||||||
HistoricLoad: historicLoads,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLocalDrivesPerf - returns ServerDrivesPerfInfo for only the
|
|
||||||
// local endpoints from given list of endpoints
|
|
||||||
func getLocalDrivesPerf(endpoints EndpointList, size int64, r *http.Request) madmin.ServerDrivesPerfInfo {
|
|
||||||
var dps []disk.Performance
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
// Only proceed for local endpoints
|
|
||||||
if endpoint.IsLocal {
|
|
||||||
if _, err := os.Stat(endpoint.Path); err != nil {
|
|
||||||
// Since this drive is not available, add relevant details and proceed
|
|
||||||
dps = append(dps, disk.Performance{Path: endpoint.Path, Error: err.Error()})
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dp := disk.GetPerformance(pathJoin(endpoint.Path, minioMetaTmpBucket, mustGetUUID()), size)
|
_, present := network[nodeName]
|
||||||
dp.Path = endpoint.Path
|
if !present {
|
||||||
dps = append(dps, dp)
|
if err := IsServerResolvable(endpoint); err == nil {
|
||||||
|
network[nodeName] = "online"
|
||||||
|
} else {
|
||||||
|
network[nodeName] = "offline"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addr := r.Host
|
|
||||||
if globalIsDistXL {
|
return madmin.ServerProperties{
|
||||||
addr = GetLocalPeer(endpoints)
|
State: "ok",
|
||||||
}
|
Endpoint: addr,
|
||||||
return madmin.ServerDrivesPerfInfo{
|
Uptime: UTCNow().Unix() - globalBootTime.Unix(),
|
||||||
Addr: addr,
|
Version: Version,
|
||||||
Perf: dps,
|
CommitID: CommitID,
|
||||||
Size: size,
|
Network: network,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -20,13 +20,22 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectIdentifier carries key name for the object to delete.
|
// DeletedObject objects deleted
|
||||||
type ObjectIdentifier struct {
|
type DeletedObject struct {
|
||||||
|
DeleteMarker bool `xml:"DeleteMarker,omitempty"`
|
||||||
|
DeleteMarkerVersionID string `xml:"DeleteMarkerVersionId,omitempty"`
|
||||||
|
ObjectName string `xml:"Key,omitempty"`
|
||||||
|
VersionID string `xml:"VersionId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectToDelete carries key name for the object to delete.
|
||||||
|
type ObjectToDelete struct {
|
||||||
ObjectName string `xml:"Key"`
|
ObjectName string `xml:"Key"`
|
||||||
|
VersionID string `xml:"VersionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// createBucketConfiguration container for bucket configuration request from client.
|
// createBucketConfiguration container for bucket configuration request from client.
|
||||||
// Used for parsing the location from the request body for MakeBucketbucket.
|
// Used for parsing the location from the request body for Makebucket.
|
||||||
type createBucketLocationConfiguration struct {
|
type createBucketLocationConfiguration struct {
|
||||||
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
|
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
|
||||||
Location string `xml:"LocationConstraint"`
|
Location string `xml:"LocationConstraint"`
|
||||||
@@ -37,5 +46,5 @@ type DeleteObjectsRequest struct {
|
|||||||
// Element to enable quiet mode for the request
|
// Element to enable quiet mode for the request
|
||||||
Quiet bool
|
Quiet bool
|
||||||
// List of objects to be deleted
|
// List of objects to be deleted
|
||||||
Objects []ObjectIdentifier `xml:"Object"`
|
Objects []ObjectToDelete `xml:"Object"`
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user