mirror of
https://github.com/pgsty/minio.git
synced 2026-11-12 10:35:33 +03:00
Compare commits
643 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8332efa94 | |||
| 3dbef72dc7 | |||
| 2d16e74f38 | |||
| de5070446d | |||
| 374abd1e7d | |||
| 0506d9e83d | |||
| bd3dfad8b9 | |||
| 9fff315555 | |||
| 07b6dce1a5 | |||
| 18fb86b7be | |||
| 58a8275e84 | |||
| 196fab6834 | |||
| 85fc7cea97 | |||
| 328d660106 | |||
| c68910005b | |||
| c79bcc8838 | |||
| 0fe58dbb34 | |||
| 6cb2f56395 | |||
| 59e33b3b21 | |||
| 0e3c92c027 | |||
| db7a9b2c37 | |||
| 44097faec1 | |||
| ff5fca76ab | |||
| bf3da5081f | |||
| 5532982857 | |||
| 783dd875f7 | |||
| 97112c69be | |||
| b0b573052a | |||
| 2939000342 | |||
| 41e1654f9a | |||
| e3cb0278ce | |||
| 0c81f1bdb3 | |||
| 6220875803 | |||
| afd4279cd8 | |||
| 0c8dd8046a | |||
| 3c4ef4338f | |||
| 64cf887b28 | |||
| 927a879052 | |||
| dfe0c96b87 | |||
| e856e10ac2 | |||
| 6d6a731d6d | |||
| 928feb0889 | |||
| b3febe2d24 | |||
| b6b26dba87 | |||
| 5c034e26bd | |||
| cef0fb1434 | |||
| 158d0e26a2 | |||
| 78385bfbeb | |||
| 2a13cc28f2 | |||
| 4d761fda81 | |||
| 4bdf41a6c7 | |||
| 3c605c93fe | |||
| 121f18a443 | |||
| 538aeef27a | |||
| be0d2537b7 | |||
| 3307aa1260 | |||
| 57cfdfd8fb | |||
| dc6733dacc | |||
| f696a221af | |||
| ed5b67720c | |||
| 2aac50571d | |||
| 45edd27ad7 | |||
| c302d1cfc8 | |||
| 6287e8c571 | |||
| f69a98ce49 | |||
| d44f3526dc | |||
| 41b633f5ea | |||
| 4f1ff9c4d9 | |||
| 86bb48792c | |||
| 94dbb4a427 | |||
| 048a46ec2a | |||
| 1480340830 | |||
| 877bd95fa3 | |||
| 8ea6fb368d | |||
| 5fd5ddea23 | |||
| b04c0697e1 | |||
| 50a8ba6a6f | |||
| 334f1ed45a | |||
| 20c89ebbb3 | |||
| 6f56ba80b3 | |||
| 6e84283c66 | |||
| 9d6fddcfdf | |||
| a83105df9d | |||
| 9528b55c25 | |||
| 749ce107ee | |||
| b2a67834ac | |||
| aec2aa3497 | |||
| c7dcbfd6c1 | |||
| ff12080ff5 | |||
| 0b6175b742 | |||
| cf49da387b | |||
| ac714e7e3d | |||
| 79fb79b71c | |||
| 98874c3baf | |||
| d89f6af6c4 | |||
| d4bca00df9 | |||
| 2c68a19dfd | |||
| 9e5853ecc0 | |||
| 124544d834 | |||
| b910904fa6 | |||
| eee1ce305c | |||
| 5c61c3ccdc | |||
| fb8d512f58 | |||
| a0fb0c1835 | |||
| e152b2a975 | |||
| a71629d4dd | |||
| c22f3ca7a8 | |||
| 4a92134235 | |||
| 6b9fd256e1 | |||
| d6132b854f | |||
| ff9a74b91f | |||
| b579163802 | |||
| 87f0c8e7e8 | |||
| bb855499e1 | |||
| 96bfa77856 | |||
| 8e997eba4a | |||
| 228c6686f8 | |||
| 52861d3aea | |||
| cc26911c46 | |||
| 7776d064cf | |||
| 2d9b5a65f1 | |||
| c240da6568 | |||
| 157272dc5b | |||
| 9065274d02 | |||
| f4c56026a2 | |||
| 37e3f5de10 | |||
| 5ea629beb2 | |||
| 240164560f | |||
| cf52691959 | |||
| 10e75116ef | |||
| f649968c69 | |||
| 5ce1448049 | |||
| bcedc2b0d9 | |||
| 8e4a45ec41 | |||
| dec942beb6 | |||
| d4e0f13bb3 | |||
| 1f28a3bb80 | |||
| 3a1d3a7952 | |||
| a9f1ad7924 | |||
| 929b9e164e | |||
| 97376f6e8f | |||
| 92a0a59de2 | |||
| cd18599e7b | |||
| 1f22a16b15 | |||
| 433b6fa8fe | |||
| d7cd857c7c | |||
| 99fbfe2421 | |||
| b1b6264bea | |||
| 1fd72d5aea | |||
| 18dffb26e7 | |||
| edba7c987b | |||
| b737c83a66 | |||
| 97a6322de1 | |||
| 037fe4afdc | |||
| afbb63a197 | |||
| 8902561f3c | |||
| a67116b5bc | |||
| 0f7aa4125f | |||
| b62a5c954c | |||
| b8cdf060c8 | |||
| 9fb937986e | |||
| 2c48f6a02b | |||
| 4155c5b695 | |||
| 471467d310 | |||
| c54c13831a | |||
| ae4ee95d25 | |||
| a2e037f0ec | |||
| e9055e9ef7 | |||
| d350b666ff | |||
| 21831b3fe2 | |||
| 895357607a | |||
| ac240a8477 | |||
| bf38c0c0d1 | |||
| 67cf15d036 | |||
| 701a82642b | |||
| 21fe14201f | |||
| 48640b1de2 | |||
| 5682685c80 | |||
| 9c025b8cce | |||
| eef9f13360 | |||
| fa9b361a3d | |||
| 49862ba347 | |||
| ee2afcf70b | |||
| c7d535c648 | |||
| 9986e103cf | |||
| c5b3666089 | |||
| d265fe7f9e | |||
| 91e6af4470 | |||
| b940fe8fca | |||
| 73fe2e95fe | |||
| 316c492842 | |||
| 74418b542a | |||
| 172e63dbb6 | |||
| 21bf5b4db7 | |||
| a406bb0288 | |||
| 1823ab6808 | |||
| 8eec49304d | |||
| ecdc2f2f5f | |||
| 6a6c772ff2 | |||
| e178c55bc3 | |||
| 2c137c0d04 | |||
| 1d35f2b58f | |||
| 638c57e466 | |||
| 5e4213b3be | |||
| 102295f58a | |||
| a0d14f8ff7 | |||
| e0b0a351c6 | |||
| fcd4b3ba9b | |||
| 1d2ff46a89 | |||
| 8f7c739328 | |||
| 1beea3daba | |||
| 1ffd063939 | |||
| 3d94c38ec4 | |||
| f4af2d3cdc | |||
| b57e7321e7 | |||
| e93867488b | |||
| c08790edd2 | |||
| a46baddbc4 | |||
| 3bd9615d0e | |||
| 2871cb5775 | |||
| a6e0ec4e6f | |||
| e956369c4e | |||
| 76f950c663 | |||
| d774a3309b | |||
| b3edb25377 | |||
| 026b87e39b | |||
| 53a816b17a | |||
| 043aaa792d | |||
| aad9cb208a | |||
| edf081c6a2 | |||
| fd349103e8 | |||
| 10b49eb4fb | |||
| 3856d078d2 | |||
| 6b4cb35f4f | |||
| e6eab2091f | |||
| 3cdb609cca | |||
| d6a7f62ff5 | |||
| 72f170f5d2 | |||
| 824d52a82b | |||
| 067ebab9d8 | |||
| 6be6c0d2e3 | |||
| aa874010e2 | |||
| 8ec888d13d | |||
| 916f274c83 | |||
| 7ac53c07af | |||
| bc72e4226e | |||
| 5e0776e96a | |||
| 2f1ef02d35 | |||
| db8442584e | |||
| d46cf50760 | |||
| 0357121d17 | |||
| aff236e20e | |||
| cbd70d26b5 | |||
| 5e763b71dc | |||
| 7e4e7a66af | |||
| 906947a285 | |||
| bfc70bc74e | |||
| 6b4f833a12 | |||
| 426c902b87 | |||
| e4b51235f8 | |||
| 4c6498d726 | |||
| 0a3b1ad4eb | |||
| f23f442d33 | |||
| e465c3587b | |||
| 7109b6d414 | |||
| 8c97f3e9bc | |||
| 3795b2c8ba | |||
| 7725425e05 | |||
| b2f4948bbe | |||
| f802d2ba83 | |||
| ce8548a1a2 | |||
| 490dec981a | |||
| 39fd7b0b3b | |||
| e83930333b | |||
| b0d70a0e5e | |||
| ff5a5c1ee0 | |||
| 290a53d735 | |||
| 2393a13f86 | |||
| 7d8c8de827 | |||
| 3faef829c5 | |||
| 7560fb6f9a | |||
| 2fddcc6a11 | |||
| 69bf39f42e | |||
| f4d5c861f3 | |||
| 564a0afae1 | |||
| 1e332f0eb1 | |||
| cab8d3d568 | |||
| be8c4cb24a | |||
| 65166e4ce4 | |||
| 8249cd4406 | |||
| c6ecaf68ed | |||
| d3f89fa6e3 | |||
| ce8397f7d9 | |||
| cae9aeca00 | |||
| f939d1c183 | |||
| 242d06274a | |||
| 957e3ed729 | |||
| ed02ee4ef4 | |||
| ba9691a0ad | |||
| e7eb94de6b | |||
| b6eb8dff64 | |||
| 7da9e3a6f8 | |||
| 876970baea | |||
| e68e76e143 | |||
| 2bc7ca2d34 | |||
| e94eb9af10 | |||
| 3018b21ab8 | |||
| 0b605c3383 | |||
| e7ac1ea54c | |||
| 5ac6d91525 | |||
| 1cd6713e24 | |||
| 785b429737 | |||
| 4aecd8d039 | |||
| 1b339ea062 | |||
| 236ef03dbd | |||
| 53cc561048 | |||
| bb4b143f3b | |||
| 3af41cd37d | |||
| 7e32a17742 | |||
| 6c265534a4 | |||
| 1d42133d44 | |||
| df911c9b9e | |||
| a6f40dd574 | |||
| 688215e787 | |||
| 1cfa2e04bc | |||
| b4f6901903 | |||
| 0149382cdc | |||
| 697c9973a7 | |||
| 788fd3df81 | |||
| 996cac5fed | |||
| 0a8b78cb84 | |||
| b4eb74f5ff | |||
| 57d1f31054 | |||
| 9f02f51b87 | |||
| 911a17b149 | |||
| 3d969bd2b4 | |||
| f800cee4fa | |||
| b49fc33cb3 | |||
| 00e235a1ee | |||
| 37a6b2da67 | |||
| 913e977c8d | |||
| c2ddcb3b40 | |||
| ab9544c0d3 | |||
| 4bfe849409 | |||
| 3bdb92fcad | |||
| cf9e3069f2 | |||
| ed0cbfb31e | |||
| 32b2f6117e | |||
| ae92521310 | |||
| 5802df4365 | |||
| c1901f4e12 | |||
| 8d98282afd | |||
| dd839bf295 | |||
| 3af6073576 | |||
| 2518af5f9e | |||
| 7b793d84c8 | |||
| af9bc7ea7d | |||
| ac055b09e9 | |||
| df42914da6 | |||
| 2471bdda00 | |||
| c7e01b139d | |||
| 9d80ff5a05 | |||
| 39b3941892 | |||
| b311abed31 | |||
| ce667ddae0 | |||
| 0fee993a4b | |||
| 0ea5c9d8e8 | |||
| 63ac260bd5 | |||
| a01a39b153 | |||
| f9a4ad7904 | |||
| e60b67d246 | |||
| 9004d69c6f | |||
| 8856a2d77b | |||
| 54a061bdda | |||
| 65b4b100a8 | |||
| 7cc9286e0f | |||
| 2f25639ea0 | |||
| 2070c215a2 | |||
| 94b98222c2 | |||
| 9c605ad153 | |||
| b7c7e59dac | |||
| 767c1436d3 | |||
| 699cf6ff45 | |||
| 9201870f6c | |||
| 6722f58668 | |||
| 7b9b7cef11 | |||
| 7d4fce09dc | |||
| 2075501d86 | |||
| bd099f5e71 | |||
| baf257adcb | |||
| 4fd1986885 | |||
| e1afac9439 | |||
| 580d9db85e | |||
| 42e2fd35d8 | |||
| 1a40c7c27c | |||
| f3bec41eb9 | |||
| 825634d24e | |||
| cb097e6b0a | |||
| 1cfb03fb74 | |||
| f293df647c | |||
| 10522438b7 | |||
| e2e5bd6f19 | |||
| cd7a0a9757 | |||
| b3eda248a3 | |||
| 95b51c48be | |||
| 486888f595 | |||
| 17ab8145b5 | |||
| b3ebc69034 | |||
| 761dde2f1b | |||
| 2bb6a3f4d0 | |||
| e83e947ca3 | |||
| 73733a8fb9 | |||
| ce6c23a360 | |||
| 99d8e6a30f | |||
| 2fa1d8ac48 | |||
| ca7e425ce8 | |||
| 8b9a19eef1 | |||
| 7f629df4d5 | |||
| 98ddc3596c | |||
| 5d23be6242 | |||
| d15d3a524b | |||
| 1e1d9acb1b | |||
| 55ee94bed0 | |||
| d228d29944 | |||
| 013cc66d8e | |||
| c7ed6eee5e | |||
| 8082d1fed6 | |||
| d2a10dbe69 | |||
| 14645142db | |||
| f34b2ef90b | |||
| ce894665a8 | |||
| 0d00f3a55b | |||
| 48ff373ff7 | |||
| e9efee0e64 | |||
| 4b3e7aee0b | |||
| dd53b287f2 | |||
| 21526efe51 | |||
| 7413045f0e | |||
| d76c508566 | |||
| 8fb46de5e4 | |||
| af1944f28d | |||
| 214ea14f29 | |||
| 5fb420c703 | |||
| 2420f6c000 | |||
| b0d7332a0c | |||
| f71b56a5d0 | |||
| d55efc791f | |||
| f63645546d | |||
| e2dd3e3587 | |||
| 27ab780317 | |||
| e2d4d097e7 | |||
| ac8cb6ba0d | |||
| 4ce81fd07f | |||
| df9eeb7f8f | |||
| 31c4fdbf79 | |||
| 48e367ff7d | |||
| fd02492cb7 | |||
| addfa35d93 | |||
| 5afdc56796 | |||
| fb1c333a83 | |||
| c3e1da8e04 | |||
| 20a753e2e5 | |||
| 3a398775fb | |||
| 61a7434379 | |||
| 09f5e29327 | |||
| 29edb4ccfe | |||
| 197d6fb644 | |||
| d4e565e595 | |||
| 1fce2b180f | |||
| be6ccd129d | |||
| f7cecf0945 | |||
| 7b2198f7e5 | |||
| 52221db7ef | |||
| befbf48563 | |||
| 56a61bab56 | |||
| d480022711 | |||
| f1abb92f0c | |||
| 5792be71fa | |||
| c2630bb3a3 | |||
| 5e3010d455 | |||
| ccbf65c8e8 | |||
| 9d07cde385 | |||
| 464b9d7c80 | |||
| 5c81d0d89a | |||
| 62cd643868 | |||
| c0bf02b8b2 | |||
| 1b7dd70f72 | |||
| 372a08be49 | |||
| fd46a1c3b3 | |||
| 5aae7178ad | |||
| dea8220eee | |||
| a4be0b88f6 | |||
| d8101573be | |||
| 41cdb357bb | |||
| 38caddffe7 | |||
| 80fe166902 | |||
| 0e26f983d6 | |||
| fc08fcab52 | |||
| 77dc99e71d | |||
| 5041bfcb5c | |||
| 5be76856bd | |||
| 2a3f5e1ad1 | |||
| f8650a3493 | |||
| 90a52a29c5 | |||
| 8859c92f80 | |||
| 01e5632949 | |||
| 18a4276e25 | |||
| c06032f35f | |||
| 95a6b2c991 | |||
| ee28f6caaa | |||
| 30c9e50701 | |||
| 9aadd725d2 | |||
| 6cfb1cb6fd | |||
| 2dc8ac1e62 | |||
| e952e2a691 | |||
| 040ac5cad8 | |||
| 05685863e3 | |||
| d324c0a1c3 | |||
| 03f8b25b50 | |||
| b0e2c2da78 | |||
| f28a8eca91 | |||
| ca69e54cb6 | |||
| 4629abd5a2 | |||
| dc99f4a7a3 | |||
| 389ec21d0c | |||
| 9341201132 | |||
| 88dd83a365 | |||
| 74285d50c4 | |||
| 60d0611ac2 | |||
| f939222942 | |||
| c293c2e9a3 | |||
| e34ca9acd1 | |||
| 83071a3459 | |||
| edf364bf21 | |||
| 1e037883b0 | |||
| d909f167ff | |||
| 4592aaa3e2 | |||
| 95d1a12422 | |||
| 62aa42cccf | |||
| 5cffd3780a | |||
| def75ffcfe | |||
| ad8e611098 | |||
| 3ec1844e4a | |||
| 523670ba0d | |||
| 35dea24ffd | |||
| e55104a155 | |||
| 111745c564 | |||
| c7df1ffc6f | |||
| 2b7e75e079 | |||
| bcdaa09c75 | |||
| 2fc65dcb99 | |||
| 44a3b58e52 | |||
| 0a256053ee | |||
| 46de9ac03e | |||
| a53dc1d9c8 | |||
| f0462322fd | |||
| c59d2a6288 | |||
| 3e3ff2a70b | |||
| 16bc11e72e | |||
| 2719f1efaa | |||
| cff1be0ae8 | |||
| 39ac62a1a1 | |||
| f427dbbd60 | |||
| c3f689a7d9 | |||
| 85f3a9f3b0 | |||
| 13ba4b433d | |||
| 96f27a4965 | |||
| 0e502899a8 | |||
| 424b44c247 | |||
| 01a71c366d | |||
| 990fbeb3a4 | |||
| 5a9a898ba2 | |||
| fe1fbe0005 | |||
| c56a139fdc | |||
| df50eda811 | |||
| f5d3313210 | |||
| 97fcc9ff99 | |||
| 8a6b2b4447 | |||
| 757eaeae92 | |||
| b7dd61f6bc | |||
| d2a95a04a4 | |||
| 0cc993f403 | |||
| 3a64580663 | |||
| d087e28dce | |||
| 96adfaebe1 | |||
| ddf84f8257 | |||
| 507f993075 | |||
| 73a6a60785 | |||
| cf4cf58faf | |||
| 6bc3c74c0c | |||
| 598ce1e354 | |||
| 4685b76e08 | |||
| 78c9109f6c | |||
| 7e248fc0ba | |||
| c526fa9119 | |||
| 520e0fd985 | |||
| 54a7eba358 | |||
| 1494ba2e6e | |||
| a5b3548ede | |||
| 8318aa0113 | |||
| e69c42956b | |||
| 53ca589c11 | |||
| ca8ff8718e | |||
| e8e48e4c4a | |||
| 67e17ed3f8 | |||
| 2a6a40e93b | |||
| eda34423d7 | |||
| 7ce1f6e736 | |||
| 5c53620a72 | |||
| 5f94cec1e2 | |||
| 646350fa7f | |||
| e162a055cc | |||
| 28d3ad3ada | |||
| 0bd44a7764 | |||
| 8be6d887e2 | |||
| 66b14a0d32 | |||
| 153a612253 | |||
| 1a1b55e133 | |||
| 879de20edf | |||
| e77ad3f9bb | |||
| 4ce86ff5fa | |||
| e290c010e6 | |||
| 33d267fa1b | |||
| 601a744159 | |||
| f630d7c3fa | |||
| 91bfefcf8c | |||
| a1b01e6d5f | |||
| 48594617b5 | |||
| b35b9dcff7 | |||
| a3e317773a | |||
| 16431d222c | |||
| ee49a23220 | |||
| a9eef521ec | |||
| 901d33b59c | |||
| 255116fde7 | |||
| 00ebea2536 | |||
| dedf9774c7 | |||
| 6b1c62133d | |||
| d4251b2545 | |||
| b9d1698d74 | |||
| 7c696e1cb6 | |||
| 165d60421d | |||
| c7962118f8 |
@@ -15,5 +15,6 @@
|
|||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
- [ ] 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 updated
|
|
||||||
- [ ] Unit tests added/updated
|
- [ ] Unit tests added/updated
|
||||||
|
- [ ] Internal documentation updated
|
||||||
|
- [ ] Create a documentation update request [here](https://github.com/minio/docs/issues/new?label=doc-change,title=Doc+Updated+Needed+For+PR+github.com%2fminio%2fminio%2fpull%2fNNNNN)
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Config file for markdownlint-cli
|
||||||
|
MD033:
|
||||||
|
allowed_elements:
|
||||||
|
- details
|
||||||
|
- summary
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
name: 'Dependency Review'
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dependency-review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout Repository'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: 'Dependency Review'
|
||||||
|
uses: actions/dependency-review-action@v1
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: FIPS Build Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
|
# updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Go BoringCrypto ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.18.5b7]
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Setup dockerfile for build test
|
||||||
|
run: |
|
||||||
|
echo "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:${{ matrix.go-version }}" > Dockerfile.fips.test
|
||||||
|
echo "COPY . /minio" >> Dockerfile.fips.test
|
||||||
|
echo "WORKDIR /minio" >> Dockerfile.fips.test
|
||||||
|
echo "RUN make" >> Dockerfile.fips.test
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.fips.test
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: minio/fips-test:latest
|
||||||
|
|
||||||
|
# This should fail if grep returns non-zero exit
|
||||||
|
- name: Test binary
|
||||||
|
run: |
|
||||||
|
docker run --rm minio/fips-test:latest ./minio --version
|
||||||
|
docker run --rm -i minio/fips-test:latest /bin/bash -c 'go tool nm ./minio' | grep -q FIPS
|
||||||
@@ -11,13 +11,16 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -25,14 +28,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
@@ -44,3 +39,6 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make verify-healing
|
make verify-healing
|
||||||
|
make verify-healing-inconsistent-versions
|
||||||
|
make verify-healing-with-root-disks
|
||||||
|
make verify-healing-with-rewrite
|
||||||
|
|||||||
@@ -11,13 +11,16 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -25,24 +28,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
%LocalAppData%\go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
env:
|
env:
|
||||||
@@ -60,9 +45,6 @@ jobs:
|
|||||||
sudo apt install jq -y
|
sudo apt install jq -y
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
|
||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
|
||||||
go list -deps -json ./... | jq -s 'unique_by(.Module.Path)|.[]|select(has("Module"))|.Module' | ./nancy sleuth
|
|
||||||
make
|
make
|
||||||
make test
|
make test
|
||||||
make test-race
|
make test-race
|
||||||
|
|||||||
@@ -11,13 +11,16 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Go ${{ matrix.go-version }} on ${{ matrix.os }} - healing
|
name: Go ${{ matrix.go-version }} on ${{ matrix.os }} - healing
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -25,23 +28,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
MINIO_KMS_KES_CERT_FILE: /home/runner/work/minio/minio/.github/workflows/root.cert
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
MINIO_KMS_KES_KEY_FILE: /home/runner/work/minio/minio/.github/workflows/root.key
|
|
||||||
MINIO_KMS_KES_ENDPOINT: "https://play.min.io:7373"
|
|
||||||
MINIO_KMS_KES_KEY_NAME: "my-minio-key"
|
|
||||||
MINIO_KMS_AUTO_ENCRYPTION: on
|
MINIO_KMS_AUTO_ENCRYPTION: on
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
iam-matrix-test:
|
iam-matrix-test:
|
||||||
name: "[Go=${{ matrix.go-version }}|ldap=${{ matrix.ldap }}|etcd=${{ matrix.etcd }}|openid=${{ matrix.openid }}]"
|
name: "[Go=${{ matrix.go-version }}|ldap=${{ matrix.ldap }}|etcd=${{ matrix.etcd }}|openid=${{ matrix.openid }}]"
|
||||||
@@ -44,13 +47,21 @@ jobs:
|
|||||||
- "5556:5556"
|
- "5556:5556"
|
||||||
env:
|
env:
|
||||||
DEX_LDAP_SERVER: "openldap:389"
|
DEX_LDAP_SERVER: "openldap:389"
|
||||||
|
openid2:
|
||||||
|
image: quay.io/minio/dex
|
||||||
|
ports:
|
||||||
|
- "5557:5557"
|
||||||
|
env:
|
||||||
|
DEX_LDAP_SERVER: "openldap:389"
|
||||||
|
DEX_ISSUER: "http://127.0.0.1:5557/dex"
|
||||||
|
DEX_WEB_HTTP: "0.0.0.0:5557"
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
# When ldap, etcd or openid vars are empty below, those external servers
|
# When ldap, etcd or openid vars are empty below, those external servers
|
||||||
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
||||||
# the tests.
|
# the tests.
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x]
|
||||||
ldap: ["", "localhost:389"]
|
ldap: ["", "localhost:389"]
|
||||||
etcd: ["", "http://localhost:2379"]
|
etcd: ["", "http://localhost:2379"]
|
||||||
openid: ["", "http://127.0.0.1:5556/dex"]
|
openid: ["", "http://127.0.0.1:5556/dex"]
|
||||||
@@ -69,14 +80,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Test LDAP/OpenID/Etcd combo
|
- name: Test LDAP/OpenID/Etcd combo
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
@@ -86,6 +89,28 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-iam
|
make test-iam
|
||||||
|
- name: Test with multiple OpenID providers
|
||||||
|
if: matrix.openid == 'http://127.0.0.1:5556/dex'
|
||||||
|
env:
|
||||||
|
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
|
ETCD_SERVER: ${{ matrix.etcd }}
|
||||||
|
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
|
OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-iam
|
||||||
|
- name: Test with Access Management Plugin enabled
|
||||||
|
env:
|
||||||
|
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
|
ETCD_SERVER: ${{ matrix.etcd }}
|
||||||
|
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
|
POLICY_PLUGIN_ENDPOINT: "http://127.0.0.1:8080"
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
go run docs/iam/access-manager-plugin.go &
|
||||||
|
make test-iam
|
||||||
- name: Test LDAP for automatic site replication
|
- name: Test LDAP for automatic site replication
|
||||||
if: matrix.ldap == 'localhost:389'
|
if: matrix.ldap == 'localhost:389'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Lint all docs
|
name: Lint all docs
|
||||||
@@ -22,4 +25,6 @@ jobs:
|
|||||||
- name: Lint all docs
|
- name: Lint all docs
|
||||||
run: |
|
run: |
|
||||||
npm install -g markdownlint-cli
|
npm install -g markdownlint-cli
|
||||||
markdownlint --fix '**/*.md' --disable MD013 MD040
|
markdownlint --fix '**/*.md' \
|
||||||
|
--config /home/runner/work/minio/minio/.github/markdown-lint-cfg.yaml \
|
||||||
|
--disable MD013 MD040 MD051
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Multi-site replication tests
|
name: MinIO advanced tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -11,14 +11,17 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
replication-test:
|
replication-test:
|
||||||
name: Replication Tests with Go ${{ matrix.go-version }}
|
name: Advanced Tests with Go ${{ matrix.go-version }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -26,19 +29,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
- name: Test Decom
|
||||||
with:
|
run: |
|
||||||
path: |
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
~/.cache/go-build
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
~/go/pkg/mod
|
make test-decom
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Test Replication
|
- name: Test Replication
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-replication
|
make test-replication
|
||||||
|
|
||||||
- name: Test MinIO IDP for automatic site replication
|
- name: Test MinIO IDP for automatic site replication
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
|||||||
@@ -11,13 +11,16 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x]
|
go-version: [1.18.x, 1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: VulnCheck
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
vulncheck:
|
||||||
|
name: Analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.19.x
|
||||||
|
check-latest: true
|
||||||
|
- name: Get official govulncheck
|
||||||
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
shell: bash
|
||||||
|
- name: Run govulncheck
|
||||||
|
run: govulncheck ./...
|
||||||
|
shell: bash
|
||||||
+8
-3
@@ -9,8 +9,7 @@ site/
|
|||||||
/.idea/
|
/.idea/
|
||||||
/Minio.iml
|
/Minio.iml
|
||||||
**/access.log
|
**/access.log
|
||||||
vendor/**/*.js
|
vendor/
|
||||||
vendor/**/*.json
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.syso
|
*.syso
|
||||||
coverage.txt
|
coverage.txt
|
||||||
@@ -32,4 +31,10 @@ hash-set
|
|||||||
minio.RELEASE*
|
minio.RELEASE*
|
||||||
mc
|
mc
|
||||||
nancy
|
nancy
|
||||||
inspects/*
|
inspects/*
|
||||||
|
docs/debugging/s3-verify/s3-verify
|
||||||
|
docs/debugging/xl-meta/xl-meta
|
||||||
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
|
docs/debugging/hash-set/hash-set
|
||||||
|
docs/debugging/healing-bin/healing-bin
|
||||||
|
docs/debugging/inspect/inspect
|
||||||
|
|||||||
+5
-16
@@ -1,6 +1,6 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
golint:
|
gofumpt:
|
||||||
min-confidence: 0
|
lang-version: "1.18"
|
||||||
|
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
@@ -14,25 +14,14 @@ linters:
|
|||||||
- govet
|
- govet
|
||||||
- revive
|
- revive
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- gosimple
|
|
||||||
- deadcode
|
|
||||||
- structcheck
|
|
||||||
- gomodguard
|
- gomodguard
|
||||||
- gofmt
|
- gofmt
|
||||||
- unused
|
|
||||||
- structcheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- varcheck
|
- unused
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
- gofumpt
|
||||||
|
- tenv
|
||||||
linters-settings:
|
- durationcheck
|
||||||
gofumpt:
|
|
||||||
lang-version: "1.17"
|
|
||||||
|
|
||||||
# Choose whether or not to use the extra rules that are disabled
|
|
||||||
# by default
|
|
||||||
extra-rules: false
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
CVE-2020-26160
|
|
||||||
CVE-2020-15136
|
|
||||||
CVE-2020-15115
|
|
||||||
CVE-2020-15114
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ checks: ## check dependencies
|
|||||||
@(env bash $(PWD)/buildscripts/checkdeps.sh)
|
@(env bash $(PWD)/buildscripts/checkdeps.sh)
|
||||||
|
|
||||||
help: ## print this help
|
help: ## print this help
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-40s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
getdeps: ## fetch necessary dependencies
|
getdeps: ## fetch necessary dependencies
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.43.0
|
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
|
||||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.7-0.20211026165309-e818a1881b0e
|
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@f3635b96e4838a6c773babb65ef35297fe5fe2f9
|
||||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
crosscompile: ## cross compile minio
|
crosscompile: ## cross compile minio
|
||||||
@@ -39,7 +39,14 @@ lint: ## runs golangci-lint suite of linters
|
|||||||
check: test
|
check: test
|
||||||
test: verifiers build ## builds minio, runs linters, tests
|
test: verifiers build ## builds minio, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@CGO_ENABLED=0 go test -tags kqueue ./...
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
||||||
|
|
||||||
|
test-decom: install
|
||||||
|
@echo "Running minio decom tests"
|
||||||
|
@env bash $(PWD)/docs/distributed/decom.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
|
||||||
test-upgrade: build
|
test-upgrade: build
|
||||||
@echo "Running minio upgrade tests"
|
@echo "Running minio upgrade tests"
|
||||||
@@ -51,13 +58,14 @@ test-race: verifiers build ## builds minio, runs linters, tests (race)
|
|||||||
|
|
||||||
test-iam: build ## verify IAM (external IDP, etcd backends)
|
test-iam: build ## verify IAM (external IDP, etcd backends)
|
||||||
@echo "Running tests for IAM (external IDP, etcd backends)"
|
@echo "Running tests for IAM (external IDP, etcd backends)"
|
||||||
@CGO_ENABLED=0 go test -tags kqueue -v -run TestIAM* ./cmd
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue -v -run TestIAM* ./cmd
|
||||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||||
@CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
||||||
|
|
||||||
test-replication: install ## verify multi site replication
|
test-replication: install ## verify multi site replication
|
||||||
@echo "Running tests for replicating three sites"
|
@echo "Running tests for replicating three sites"
|
||||||
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
||||||
|
|
||||||
test-site-replication-ldap: install ## verify automatic site replication
|
test-site-replication-ldap: install ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||||
@@ -73,14 +81,30 @@ test-site-replication-minio: install ## verify automatic site replication
|
|||||||
|
|
||||||
verify: ## verify minio various setups
|
verify: ## verify minio various setups
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 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)
|
||||||
|
|
||||||
verify-healing: ## verify healing and replacing disks with minio binary
|
verify-healing: ## verify healing and replacing disks with minio binary
|
||||||
@echo "Verify healing build with race"
|
@echo "Verify healing build with race"
|
||||||
@CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
||||||
@(env bash $(PWD)/buildscripts/unaligned-healing.sh)
|
@(env bash $(PWD)/buildscripts/unaligned-healing.sh)
|
||||||
|
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
||||||
|
|
||||||
|
verify-healing-with-root-disks: ## verify healing root disks
|
||||||
|
@echo "Verify healing with root drives"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
|
||||||
|
|
||||||
|
verify-healing-with-rewrite: ## verify healing to rewrite old xl.meta -> new xl.meta
|
||||||
|
@echo "Verify healing with rewrite"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
|
||||||
|
|
||||||
|
verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
|
||||||
|
@echo "Verify resolving inconsistent versions build with race"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
||||||
|
|
||||||
build: checks ## builds minio to $(PWD)
|
build: checks ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@@ -122,6 +146,8 @@ clean: ## cleanup all generated assets
|
|||||||
@echo "Cleaning up all the generated files"
|
@echo "Cleaning up all the generated files"
|
||||||
@find . -name '*.test' | xargs rm -fv
|
@find . -name '*.test' | xargs rm -fv
|
||||||
@find . -name '*~' | xargs rm -fv
|
@find . -name '*~' | xargs rm -fv
|
||||||
|
@find . -name '.#*#' | xargs rm -fv
|
||||||
|
@find . -name '#*#' | xargs rm -fv
|
||||||
@rm -rvf minio
|
@rm -rvf minio
|
||||||
@rm -rvf build
|
@rm -rvf build
|
||||||
@rm -rvf release
|
@rm -rvf release
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# MinIO FIPS Builds
|
||||||
|
|
||||||
|
MinIO creates FIPS builds using a patched version of the Go compiler (that uses BoringCrypto, from BoringSSL, which is [FIPS 140-2 validated](https://csrc.nist.gov/csrc/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp2964.pdf)) published by the Golang Team [here](https://github.com/golang/go/tree/dev.boringcrypto/misc/boring).
|
||||||
|
|
||||||
|
MinIO FIPS executables are available at <http://dl.min.io> - they are only published for `linux-amd64` architecture as binary files with the suffix `.fips`. We also publish corresponding container images to our official image repositories.
|
||||||
|
|
||||||
|
We are not making any statements or representations about the suitability of this code or build in relation to the FIPS 140-2 standard. Interested users will have to evaluate for themselves whether this is useful for their own purposes.
|
||||||
@@ -14,7 +14,7 @@ Use the following commands to run a standalone MinIO server as a container.
|
|||||||
|
|
||||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||||
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||||
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html)
|
||||||
for more complete documentation.
|
for more complete documentation.
|
||||||
|
|
||||||
### Stable
|
### Stable
|
||||||
@@ -32,7 +32,7 @@ root credentials. You can use the Browser to create buckets, upload objects, and
|
|||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
> NOTE: To deploy MinIO on with persistent storage, you must map local persistent directories from the host OS to the container using the `podman -v` option. For example, `-v /mnt/data:/data` maps the host OS drive at `/mnt/data` to `/data` on the container.
|
> NOTE: To deploy MinIO on with persistent storage, you must map local persistent directories from the host OS to the container using the `podman -v` option. For example, `-v /mnt/data:/data` maps the host OS drive at `/mnt/data` to `/data` on the container.
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to vi
|
|||||||
|
|
||||||
Use the following commands to run a standalone MinIO server on macOS.
|
Use the following commands to run a standalone MinIO server on macOS.
|
||||||
|
|
||||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html) for more complete documentation.
|
||||||
|
|
||||||
### Homebrew (recommended)
|
### Homebrew (recommended)
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ brew install minio/stable/minio
|
|||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html/> to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
### Binary Download
|
### Binary Download
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ chmod +x minio
|
|||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
## GNU/Linux
|
## GNU/Linux
|
||||||
|
|
||||||
@@ -99,9 +99,9 @@ The following table lists supported architectures. Replace the `wget` URL with t
|
|||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html#) for more complete documentation.
|
||||||
|
|
||||||
## Microsoft Windows
|
## Microsoft Windows
|
||||||
|
|
||||||
@@ -119,23 +119,23 @@ minio.exe server D:\
|
|||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html#) for more complete documentation.
|
||||||
|
|
||||||
## Install from Source
|
## Install from Source
|
||||||
|
|
||||||
Use the following commands to compile and run a standalone MinIO server 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.17](https://golang.org/dl/#stable)
|
Use the following commands to compile and run a standalone MinIO server 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.18](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GO111MODULE=on go install github.com/minio/minio@latest
|
go install github.com/minio/minio@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to <http://127.0.0.1:9000> and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://docs.min.io/docs/> and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see <https://min.io/docs/minio/linux/developers/minio-drivers.html> to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html) for more complete documentation.
|
||||||
|
|
||||||
MinIO strongly recommends *against* using compiled-from-source MinIO servers for production environments.
|
MinIO strongly recommends *against* using compiled-from-source MinIO servers for production environments.
|
||||||
|
|
||||||
@@ -196,12 +196,6 @@ iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
|||||||
service iptables restart
|
service iptables restart
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pre-existing data
|
|
||||||
|
|
||||||
When deployed on a single drive, MinIO server lets clients access any pre-existing data in the data directory. For example, if MinIO is started with the command `minio server /mnt/data`, any pre-existing data in the `/mnt/data` directory would be accessible to the clients.
|
|
||||||
|
|
||||||
The above statement is also valid for all gateway backends.
|
|
||||||
|
|
||||||
## Test MinIO Connectivity
|
## Test MinIO Connectivity
|
||||||
|
|
||||||
### Test using MinIO Console
|
### Test using MinIO Console
|
||||||
@@ -228,7 +222,7 @@ For example: `export MINIO_SERVER_URL="https://minio.example.net"`
|
|||||||
|
|
||||||
## Test using MinIO Client `mc`
|
## Test using MinIO Client `mc`
|
||||||
|
|
||||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide) for further instructions.
|
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://min.io/docs/minio/linux/reference/minio-mc.html#quickstart) for further instructions.
|
||||||
|
|
||||||
## Upgrading MinIO
|
## Upgrading MinIO
|
||||||
|
|
||||||
@@ -236,32 +230,30 @@ Upgrades require zero downtime in MinIO, all upgrades are non-disruptive, all tr
|
|||||||
|
|
||||||
> NOTE: requires internet access to update directly from <https://dl.min.io>, optionally you can host any mirrors at <https://my-artifactory.example.com/minio/>
|
> NOTE: requires internet access to update directly from <https://dl.min.io>, optionally you can host any mirrors at <https://my-artifactory.example.com/minio/>
|
||||||
|
|
||||||
- For deployments that installed the MinIO server binary by hand, use [`mc admin update`](https://docs.min.io/minio/baremetal/reference/minio-mc-admin/mc-admin-update.html)
|
- For deployments that installed the MinIO server binary by hand, use [`mc admin update`](https://min.io/docs/minio/linux/reference/minio-mc-admin/mc-admin-update.html)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mc admin update <minio alias, e.g., myminio>
|
mc admin update <minio alias, e.g., myminio>
|
||||||
```
|
```
|
||||||
|
|
||||||
- For deployments without external internet access (e.g. airgapped environments), download the binary from <https://dl.min.io> and replace the existing MinIO binary let's say for example `/opt/bin/minio`, apply executable permissions `chmod +x /opt/bin/minio` and do `mc admin service restart alias/`.
|
- For deployments without external internet access (e.g. airgapped environments), download the binary from <https://dl.min.io> and replace the existing MinIO binary let's say for example `/opt/bin/minio`, apply executable permissions `chmod +x /opt/bin/minio` and proceed to perform `mc admin service restart alias/`.
|
||||||
|
|
||||||
- For RPM/DEB installations, upgrade packages **parallelly** on all servers. Once upgraded, perform `systemctl restart minio` across all nodes in **parallel**. RPM/DEB based installations are usually automated using [`ansible`](https://github.com/minio/ansible-minio).
|
- For installations using Systemd MinIO service, upgrade via RPM/DEB packages **parallelly** on all servers or replace the binary lets say `/opt/bin/minio` on all nodes, apply executable permissions `chmod +x /opt/bin/minio` and process to perform `mc admin service restart alias/`.
|
||||||
|
|
||||||
### Upgrade Checklist
|
### Upgrade Checklist
|
||||||
|
|
||||||
- Test all upgrades in a lower environment (DEV, QA, UAT) before applying to production. Performing blind upgrades in production environments carries significant risk.
|
- Test all upgrades in a lower environment (DEV, QA, UAT) before applying to production. Performing blind upgrades in production environments carries significant risk.
|
||||||
- Read the release notes for the targeted MinIO release *before* performing any installation, there is no forced requirement to upgrade to latest releases every week. If it has a bug fix you are looking for then yes, else avoid actively upgrading a running production system.
|
- Read the release notes for MinIO *before* performing any upgrade, there is no forced requirement to upgrade to latest releases upon every releases. Some releases may not be relevant to your setup, avoid upgrading production environments unnecessarily.
|
||||||
- Make sure MinIO process has write access to `/opt/bin` if you plan to use `mc admin update`. This is needed for MinIO to download the latest binary from <https://dl.min.io> and save it locally for upgrades.
|
- If you plan to use `mc admin update`, MinIO process must have write access to the parent directory where the binary is present on the host system.
|
||||||
- `mc admin update` is not supported in kubernetes/container environments, container environments provide their own mechanisms for container updates.
|
- `mc admin update` is not supported and should be avoided in kubernetes/container environments, please upgrade containers by upgrading relevant container images.
|
||||||
- **We do not recommend upgrading one MinIO server at a time, the product is designed to support parallel upgrades please follow our recommended guidelines.**
|
- **We do not recommend upgrading one MinIO server at a time, the product is designed to support parallel upgrades please follow our recommended guidelines.**
|
||||||
|
|
||||||
## Explore Further
|
## Explore Further
|
||||||
|
|
||||||
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
- [MinIO Erasure Code Overview](https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html)
|
||||||
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
|
- [Use `mc` with MinIO Server](https://min.io/docs/minio/linux/reference/minio-mc.html)
|
||||||
- [Use `aws-cli` with MinIO Server](https://docs.min.io/docs/aws-cli-with-minio)
|
- [Use `minio-go` SDK with MinIO Server](https://min.io/docs/minio/linux/developers/go/minio-go.html)
|
||||||
- [Use `s3cmd` with MinIO Server](https://docs.min.io/docs/s3cmd-with-minio)
|
- [The MinIO documentation website](https://min.io/docs/minio/linux/index.html)
|
||||||
- [Use `minio-go` SDK with MinIO Server](https://docs.min.io/docs/golang-client-quickstart-guide)
|
|
||||||
- [The MinIO documentation website](https://docs.min.io)
|
|
||||||
|
|
||||||
## Contribute to MinIO Project
|
## Contribute to MinIO Project
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -24,14 +24,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genLDFlags(version string) string {
|
func genLDFlags(version string) string {
|
||||||
|
releaseTag, date := releaseTag(version)
|
||||||
|
copyrightYear := strconv.Itoa(date.Year())
|
||||||
ldflagsStr := "-s -w"
|
ldflagsStr := "-s -w"
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.Version=" + version
|
ldflagsStr += " -X github.com/minio/minio/cmd.Version=" + version
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.ReleaseTag=" + releaseTag(version)
|
ldflagsStr += " -X github.com/minio/minio/cmd.CopyrightYear=" + copyrightYear
|
||||||
|
ldflagsStr += " -X github.com/minio/minio/cmd.ReleaseTag=" + releaseTag
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.CommitID=" + commitID()
|
ldflagsStr += " -X github.com/minio/minio/cmd.CommitID=" + commitID()
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.ShortCommitID=" + commitID()[:12]
|
ldflagsStr += " -X github.com/minio/minio/cmd.ShortCommitID=" + commitID()[:12]
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.GOPATH=" + os.Getenv("GOPATH")
|
ldflagsStr += " -X github.com/minio/minio/cmd.GOPATH=" + os.Getenv("GOPATH")
|
||||||
@@ -40,7 +44,7 @@ func genLDFlags(version string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// genReleaseTag prints release tag to the console for easy git tagging.
|
// genReleaseTag prints release tag to the console for easy git tagging.
|
||||||
func releaseTag(version string) string {
|
func releaseTag(version string) (string, time.Time) {
|
||||||
relPrefix := "DEVELOPMENT"
|
relPrefix := "DEVELOPMENT"
|
||||||
if prefix := os.Getenv("MINIO_RELEASE"); prefix != "" {
|
if prefix := os.Getenv("MINIO_RELEASE"); prefix != "" {
|
||||||
relPrefix = prefix
|
relPrefix = prefix
|
||||||
@@ -53,14 +57,17 @@ func releaseTag(version string) string {
|
|||||||
|
|
||||||
relTag := strings.Replace(version, " ", "-", -1)
|
relTag := strings.Replace(version, " ", "-", -1)
|
||||||
relTag = strings.Replace(relTag, ":", "-", -1)
|
relTag = strings.Replace(relTag, ":", "-", -1)
|
||||||
|
t, err := time.Parse("2006-01-02T15-04-05Z", relTag)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
relTag = strings.Replace(relTag, ",", "", -1)
|
relTag = strings.Replace(relTag, ",", "", -1)
|
||||||
relTag = relPrefix + "." + relTag
|
relTag = relPrefix + "." + relTag
|
||||||
|
|
||||||
if relSuffix != "" {
|
if relSuffix != "" {
|
||||||
relTag += "." + relSuffix
|
relTag += "." + relSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
return relTag
|
return relTag, t
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitID returns the abbreviated commit-id hash of the last commit.
|
// commitID returns the abbreviated commit-id hash of the last commit.
|
||||||
|
|||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function start_minio_4drive() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=minio
|
||||||
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir ${WORK_DIR}
|
||||||
|
C_PWD=${PWD}
|
||||||
|
if [ ! -x "$PWD/mc" ]; then
|
||||||
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${PWD}/mc" mb --with-versioning minio/bucket
|
||||||
|
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||||
|
|
||||||
|
sudo chown -R root. "${WORK_DIR}/disk${i}"
|
||||||
|
|
||||||
|
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||||
|
|
||||||
|
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for vid in $("${PWD}/mc" ls --json --versions minio/bucket/testobj | jq -r .versionId); do
|
||||||
|
"${PWD}/mc" cat --vid "${vid}" minio/bucket/testobj | md5sum
|
||||||
|
done
|
||||||
|
|
||||||
|
pkill minio
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
start_minio_4drive ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge()
|
||||||
|
{
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
( main "$@" )
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
//
|
||||||
|
// MinIO Object Storage (c) 2022 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||||
|
// dummy values, please replace them with original values.
|
||||||
|
|
||||||
|
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||||
|
// New returns an MinIO Admin client object.
|
||||||
|
madmClnt, err := madmin.New(os.Args[1], os.Args[2], os.Args[3], false)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := madmin.HealOpts{
|
||||||
|
Recursive: true, // recursively heal all objects at 'prefix'
|
||||||
|
Remove: true, // remove content that has lost quorum and not recoverable
|
||||||
|
Recreate: true, // rewrite all old non-inlined xl.meta to new xl.meta
|
||||||
|
ScanMode: madmin.HealNormalScan, // by default do not do 'deep' scanning
|
||||||
|
}
|
||||||
|
|
||||||
|
start, _, err := madmClnt.Heal(context.Background(), "healing-rewrite-bucket", "", opts, "", false, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Healstart sequence ===")
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
if err = enc.Encode(&start); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
for {
|
||||||
|
_, status, err := madmClnt.Heal(context.Background(), "healing-rewrite-bucket", "", opts, start.ClientToken, false, false)
|
||||||
|
if status.Summary == "finished" {
|
||||||
|
fmt.Println("Healstatus on items ===")
|
||||||
|
for _, item := range status.Items {
|
||||||
|
if err = enc.Encode(&item); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if status.Summary == "stopped" {
|
||||||
|
fmt.Println("Healstatus on items ===")
|
||||||
|
fmt.Println("Heal failed with", status.FailureDetail)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range status.Items {
|
||||||
|
if err = enc.Encode(&item); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ __init__() {
|
|||||||
mc mb minio/minio-test/
|
mc mb minio/minio-test/
|
||||||
mc cp ./minio minio/minio-test/to-read/
|
mc cp ./minio minio/minio-test/to-read/
|
||||||
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
||||||
mc policy set download minio/minio-test
|
mc anonymous set download minio/minio-test
|
||||||
|
|
||||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
export GORACE="history_size=7"
|
||||||
|
export MINIO_API_REQUESTS_MAX=10000
|
||||||
|
|
||||||
## TODO remove `dsync` from race detector once this is merged and released https://go-review.googlesource.com/c/go/+/333529/
|
## TODO remove `dsync` from race detector once this is merged and released https://go-review.googlesource.com/c/go/+/333529/
|
||||||
for d in $(go list ./... | grep -v dsync); do
|
for d in $(go list ./... | grep -v dsync); do
|
||||||
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
||||||
|
|||||||
Executable
+72
@@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function start_minio_5drive() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=minio
|
||||||
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" stat minio/bucket/testobj
|
||||||
|
|
||||||
|
pkill minio
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
start_minio_5drive ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge()
|
||||||
|
{
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
( main "$@" )
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
Executable
+151
@@ -0,0 +1,151 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO_OLD=( "$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function download_old_release() {
|
||||||
|
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
|
||||||
|
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
|
||||||
|
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function verify_rewrite() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ACCESS_KEY=minio
|
||||||
|
export MINIO_SECRET_KEY=minio123
|
||||||
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
|
||||||
|
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
|
||||||
|
"${WORK_DIR}/mc" cp \
|
||||||
|
buildscripts/verify-build.sh \
|
||||||
|
minio/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" cp \
|
||||||
|
buildscripts/verify-build.sh \
|
||||||
|
minio/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" cp \
|
||||||
|
buildscripts/verify-build.sh \
|
||||||
|
minio/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
|
kill ${pid}
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go build ./docs/debugging/s3-check-md5/
|
||||||
|
if ! ./s3-check-md5 \
|
||||||
|
-debug \
|
||||||
|
-versions \
|
||||||
|
-access-key minio \
|
||||||
|
-secret-key minio123 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if ! ./s3-check-md5 \
|
||||||
|
-debug \
|
||||||
|
-versions \
|
||||||
|
-access-key minio \
|
||||||
|
-secret-key minio123 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
kill ${pid}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
download_old_release
|
||||||
|
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
verify_rewrite ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge()
|
||||||
|
{
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
( main "$@" )
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
@@ -120,7 +120,7 @@ function start_minio_16drive() {
|
|||||||
cat "${WORK_DIR}/server1.log"
|
cat "${WORK_DIR}/server1.log"
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
mkdir -p inspects
|
mkdir -p inspects
|
||||||
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-shard-bucket/unaligned/**)
|
(cd inspects; "${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**)
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb play/inspects
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
@@ -140,7 +140,7 @@ function start_minio_16drive() {
|
|||||||
cat "${WORK_DIR}/server1.log"
|
cat "${WORK_DIR}/server1.log"
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
mkdir -p inspects
|
mkdir -p inspects
|
||||||
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-shard-bucket/unaligned/**)
|
(cd inspects; "${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**)
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb play/inspects
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$(mktemp -d)"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
|
|
||||||
|
function start_minio() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=minio
|
||||||
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
unset MINIO_CI_CD
|
||||||
|
unset CI
|
||||||
|
|
||||||
|
args=()
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
args+=("http://localhost:$[${start_port}+$i]${WORK_DIR}/mnt/disk$i/ ")
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
"${MINIO[@]}" --address ":$[$start_port+$i]" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait until all nodes return 403
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
while [ "$(curl -m 1 -s -o /dev/null -w "%{http_code}" http://localhost:$[$start_port+$i])" -ne "403" ]; do
|
||||||
|
echo -n ".";
|
||||||
|
sleep 1;
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare fake disks with losetup
|
||||||
|
function prepare_block_devices() {
|
||||||
|
mkdir -p ${WORK_DIR}/disks/ ${WORK_DIR}/mnt/
|
||||||
|
for i in 1 2 3 4; do
|
||||||
|
dd if=/dev/zero of=${WORK_DIR}/disks/img.$i bs=1M count=2048
|
||||||
|
mkfs.ext4 -F ${WORK_DIR}/disks/img.$i
|
||||||
|
sudo mknod /dev/minio-loopdisk$i b 7 $[256-$i]
|
||||||
|
sudo losetup /dev/minio-loopdisk$i ${WORK_DIR}/disks/img.$i
|
||||||
|
mkdir -p ${WORK_DIR}/mnt/disk$i/
|
||||||
|
sudo mount /dev/minio-loopdisk$i ${WORK_DIR}/mnt/disk$i/
|
||||||
|
sudo chown "$(id -u):$(id -g)" /dev/minio-loopdisk$i ${WORK_DIR}/mnt/disk$i/
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start a distributed MinIO setup, unmount one disk and check if it is formatted
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
start_minio ${start_port}
|
||||||
|
|
||||||
|
# Unmount the disk, after the unmount the device id
|
||||||
|
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
|
||||||
|
# will be detected as root disk
|
||||||
|
while [ "$u" != "0" ]; do
|
||||||
|
sudo umount ${WORK_DIR}/mnt/disk4/
|
||||||
|
u=$?
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait until MinIO self heal kicks in
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
if [ -f ${WORK_DIR}/mnt/disk4/.minio.sys/format.json ]; then
|
||||||
|
echo "A root disk is formatted unexpectedely"
|
||||||
|
cat "${WORK_DIR}/server4.log"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
pkill minio
|
||||||
|
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
|
||||||
|
sudo rm /dev/minio-loopdisk*
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
( prepare_block_devices )
|
||||||
|
( main "$@" )
|
||||||
|
rv=$?
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
exit "$rv"
|
||||||
|
|
||||||
+2
-2
@@ -80,7 +80,7 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Before proceeding validate if bucket exists.
|
// Before proceeding validate if bucket exists.
|
||||||
_, err := objAPI.GetBucketInfo(ctx, bucket)
|
_, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -142,7 +142,7 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Before proceeding validate if bucket exists.
|
// Before proceeding validate if bucket exists.
|
||||||
_, err := objAPI.GetBucketInfo(ctx, bucket)
|
_, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
+820
-23
@@ -18,15 +18,30 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/klauspost/compress/zip"
|
||||||
|
"github.com/minio/kes"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
|
"github.com/minio/minio/internal/event"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/bucket/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,19 +62,18 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
data, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
return
|
return
|
||||||
@@ -76,15 +90,17 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketMeta := madmin.SRBucketMeta{
|
bucketMeta := madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeQuotaConfig,
|
Type: madmin.SRBucketMetaTypeQuotaConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Quota: data,
|
Quota: data,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
if quotaConfig.Quota == 0 {
|
if quotaConfig.Quota == 0 {
|
||||||
bucketMeta.Quota = nil
|
bucketMeta.Quota = nil
|
||||||
@@ -108,19 +124,18 @@ func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
config, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -145,7 +160,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
update := r.Form.Get("update") == "true"
|
update := r.Form.Get("update") == "true"
|
||||||
|
|
||||||
if !globalIsErasure {
|
if globalIsGateway {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -153,12 +168,11 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -234,7 +248,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, update); err != nil {
|
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, update); err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case BucketRemoteConnectionErr:
|
case RemoteTargetConnectionErr:
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL)
|
||||||
default:
|
default:
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -251,7 +265,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -274,19 +288,19 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
if !globalIsErasure {
|
|
||||||
|
if globalIsGateway {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bucket != "" {
|
if bucket != "" {
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -314,19 +328,18 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|
||||||
if !globalIsErasure {
|
if globalIsGateway {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -345,7 +358,7 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -353,3 +366,787 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExportBucketMetadataHandler - exports all bucket metadata as a zipped file
|
||||||
|
func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ExportBucketMetadata")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
bucket := pathClean(r.Form.Get("bucket"))
|
||||||
|
if globalIsGateway {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
buckets []BucketInfo
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if bucket != "" {
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buckets = append(buckets, BucketInfo{Name: bucket})
|
||||||
|
} else {
|
||||||
|
buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a zip writer which will provide a zipped content
|
||||||
|
// of bucket metadata
|
||||||
|
zipWriter := zip.NewWriter(w)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
rawDataFn := func(r io.Reader, filename string, sz int) error {
|
||||||
|
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
||||||
|
name: filename,
|
||||||
|
size: int64(sz),
|
||||||
|
mode: 0o600,
|
||||||
|
modTime: time.Now(),
|
||||||
|
isDir: false,
|
||||||
|
sys: nil,
|
||||||
|
})
|
||||||
|
if zerr != nil {
|
||||||
|
logger.LogIf(ctx, zerr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
zwriter, zerr := zipWriter.CreateHeader(header)
|
||||||
|
if zerr != nil {
|
||||||
|
logger.LogIf(ctx, zerr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(zwriter, r); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgFiles := []string{
|
||||||
|
bucketPolicyConfig,
|
||||||
|
bucketNotificationConfig,
|
||||||
|
bucketLifecycleConfig,
|
||||||
|
bucketSSEConfig,
|
||||||
|
bucketTaggingConfig,
|
||||||
|
bucketQuotaConfigFile,
|
||||||
|
objectLockConfig,
|
||||||
|
bucketVersioningConfig,
|
||||||
|
bucketReplicationConfig,
|
||||||
|
bucketTargetsFile,
|
||||||
|
}
|
||||||
|
for _, bi := range buckets {
|
||||||
|
for _, cfgFile := range cfgFiles {
|
||||||
|
cfgPath := pathJoin(bi.Name, cfgFile)
|
||||||
|
bucket := bi.Name
|
||||||
|
switch cfgFile {
|
||||||
|
case bucketNotificationConfig:
|
||||||
|
config, err := globalBucketMetadataSys.GetNotificationConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketLifecycleConfig:
|
||||||
|
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketQuotaConfigFile:
|
||||||
|
config, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketQuotaConfigNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketSSEConfig:
|
||||||
|
config, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketSSEConfigNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketTaggingConfig:
|
||||||
|
config, _, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketTaggingNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case objectLockConfig:
|
||||||
|
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketVersioningConfig:
|
||||||
|
config, _, err := globalBucketMetadataSys.GetVersioningConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// ignore empty versioning configs
|
||||||
|
if config.Status != versioning.Enabled && config.Status != versioning.Suspended {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketReplicationConfig:
|
||||||
|
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketReplicationConfigNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case bucketTargetsFile:
|
||||||
|
config, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, BucketRemoteTargetNotFound{Bucket: bucket}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type importMetaReport struct {
|
||||||
|
madmin.BucketMetaImportErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *importMetaReport) SetStatus(bucket, fname string, err error) {
|
||||||
|
st := i.Buckets[bucket]
|
||||||
|
var errMsg string
|
||||||
|
if err != nil {
|
||||||
|
errMsg = err.Error()
|
||||||
|
}
|
||||||
|
switch fname {
|
||||||
|
case bucketPolicyConfig:
|
||||||
|
st.Policy = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case bucketNotificationConfig:
|
||||||
|
st.Notification = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case bucketLifecycleConfig:
|
||||||
|
st.Lifecycle = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case bucketSSEConfig:
|
||||||
|
st.SSEConfig = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case bucketTaggingConfig:
|
||||||
|
st.Tagging = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case bucketQuotaConfigFile:
|
||||||
|
st.Quota = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case objectLockConfig:
|
||||||
|
st.ObjectLock = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
case bucketVersioningConfig:
|
||||||
|
st.Versioning = madmin.MetaStatus{IsSet: true, Err: errMsg}
|
||||||
|
default:
|
||||||
|
st.Err = errMsg
|
||||||
|
}
|
||||||
|
i.Buckets[bucket] = st
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportBucketMetadataHandler - imports all bucket metadata from a zipped file and overwrite bucket metadata config
|
||||||
|
// There are some caveats regarding the following:
|
||||||
|
// 1. object lock config - object lock should have been specified at time of bucket creation. Only default retention settings are imported here.
|
||||||
|
// 2. Replication config - is omitted from import as remote target credentials are not available from exported data for security reasons.
|
||||||
|
// 3. lifecycle config - if transition rules are present, tier name needs to have been defined.
|
||||||
|
func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ImportBucketMetadata")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
if globalIsGateway {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
zr, err := zip.NewReader(reader, int64(len(data)))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bucketMap := make(map[string]struct{}, 1)
|
||||||
|
rpt := importMetaReport{
|
||||||
|
madmin.BucketMetaImportErrs{
|
||||||
|
Buckets: make(map[string]madmin.BucketStatus, len(zr.File)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// import object lock config if any - order of import matters here.
|
||||||
|
for _, file := range zr.File {
|
||||||
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
|
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
|
||||||
|
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bucket, fileName := slc[0], slc[1]
|
||||||
|
switch fileName {
|
||||||
|
case objectLockConfig:
|
||||||
|
reader, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config, err := objectlock.ParseObjectLockConfig(reader)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
|
opts := MakeBucketOptions{
|
||||||
|
LockEnabled: config.ObjectLockEnabled == "Enabled",
|
||||||
|
}
|
||||||
|
err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(BucketExists); !ok {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bucketMap[bucket] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
||||||
|
if _, _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, configData)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
|
// Call site replication hook.
|
||||||
|
//
|
||||||
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
|
// errors.
|
||||||
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
|
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
||||||
|
Bucket: bucket,
|
||||||
|
ObjectLockConfig: &cfgStr,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// import versioning metadata
|
||||||
|
for _, file := range zr.File {
|
||||||
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
|
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
|
||||||
|
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bucket, fileName := slc[0], slc[1]
|
||||||
|
switch fileName {
|
||||||
|
case bucketVersioningConfig:
|
||||||
|
reader, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v, err := versioning.ParseConfig(io.LimitReader(reader, maxBucketVersioningConfigSize))
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
|
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}); err != nil {
|
||||||
|
if _, ok := err.(BucketExists); !ok {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bucketMap[bucket] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalSiteReplicationSys.isEnabled() && v.Suspended() {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("Cluster replication is enabled for this site, so the versioning state cannot be suspended."))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled && v.Suspended() {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("An Object Lock configuration is present on this bucket, so the versioning state cannot be suspended."))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := getReplicationConfig(ctx, bucket); err == nil && v.Suspended() {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("A replication configuration is present on this bucket, so the versioning state cannot be suspended."))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, configData); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range zr.File {
|
||||||
|
reader, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(file.Name, "", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sz := file.FileInfo().Size()
|
||||||
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
|
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
|
||||||
|
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bucket, fileName := slc[0], slc[1]
|
||||||
|
// create bucket if it does not exist yet.
|
||||||
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
|
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(BucketExists); !ok {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bucketMap[bucket] = struct{}{}
|
||||||
|
}
|
||||||
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch fileName {
|
||||||
|
case bucketNotificationConfig:
|
||||||
|
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region, globalEventNotifier.targetList)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketNotificationConfig, configData); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rulesMap := config.ToRulesMap()
|
||||||
|
globalEventNotifier.AddRulesMap(bucket, rulesMap)
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
case bucketPolicyConfig:
|
||||||
|
// Error out if Content-Length is beyond allowed size.
|
||||||
|
if sz > maxBucketPolicySize {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrPolicyTooLarge.String()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketPolicyBytes, err := io.ReadAll(io.LimitReader(reader, sz))
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version in policy must not be empty
|
||||||
|
if bucketPolicy.Version == "" {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrMalformedPolicy.String()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := json.Marshal(bucketPolicy)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, configData)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
// Call site replication hook.
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
|
Bucket: bucket,
|
||||||
|
Policy: bucketPolicyBytes,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case bucketLifecycleConfig:
|
||||||
|
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(reader, sz))
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the received bucket policy document
|
||||||
|
if err = bucketLifecycle.Validate(); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the transition storage ARNs
|
||||||
|
if err = validateTransitionTier(bucketLifecycle); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(bucketLifecycle)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, configData); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
case bucketSSEConfig:
|
||||||
|
// Parse bucket encryption xml
|
||||||
|
encConfig, err := validateBucketSSEConfig(io.LimitReader(reader, maxBucketSSEConfigSize))
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error if KMS is not initialized
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s", errorCodes[ErrKMSNotConfigured].Description))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kmsKey := encConfig.KeyID()
|
||||||
|
if kmsKey != "" {
|
||||||
|
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||||
|
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
|
rpt.SetStatus(bucket, fileName, errKMSKeyNotFound)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(encConfig)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the bucket encryption configuration in the object layer
|
||||||
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, configData)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
|
// Call site replication hook.
|
||||||
|
//
|
||||||
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
|
// errors.
|
||||||
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
|
Bucket: bucket,
|
||||||
|
SSEConfig: &cfgStr,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
case bucketTaggingConfig:
|
||||||
|
tags, err := tags.ParseBucketXML(io.LimitReader(reader, sz))
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := xml.Marshal(tags)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, configData)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
|
// Call site replication hook.
|
||||||
|
//
|
||||||
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
|
// errors.
|
||||||
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
|
Bucket: bucket,
|
||||||
|
Tags: &cfgStr,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case bucketQuotaConfigFile:
|
||||||
|
data, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
quotaConfig, err := parseBucketQuota(bucket, data)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if quotaConfig.Type == "fifo" {
|
||||||
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
|
bucketMeta := madmin.SRBucketMeta{
|
||||||
|
Type: madmin.SRBucketMetaTypeQuotaConfig,
|
||||||
|
Bucket: bucket,
|
||||||
|
Quota: data,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
if quotaConfig.Quota == 0 {
|
||||||
|
bucketMeta.Quota = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call site replication hook.
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, rptData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
||||||
|
// to the connected HTTP client.
|
||||||
|
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ReplicationDiff")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
if globalIsGateway {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts := extractReplicateDiffOpts(r.Form)
|
||||||
|
if opts.ARN != "" {
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, opts.ARN)
|
||||||
|
if tgt.Empty() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrInvalidRequest, fmt.Errorf("invalid arn : '%s'", opts.ARN)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer keepAliveTicker.Stop()
|
||||||
|
|
||||||
|
diffCh, err := getReplicationDiff(ctx, objectAPI, bucket, opts)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry, ok := <-diffCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := enc.Encode(entry); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(diffCh) == 0 {
|
||||||
|
// Flush if nothing is queued
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
case <-keepAliveTicker.C:
|
||||||
|
if len(diffCh) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes"
|
||||||
@@ -29,6 +30,10 @@ import (
|
|||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
|
// If any of the supplied actions are allowed it will be successful.
|
||||||
|
// If nil ObjectLayer is returned, the operation is not permitted.
|
||||||
|
// When nil ObjectLayer has been returned an error has always been sent to w.
|
||||||
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -40,11 +45,16 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr := checkAdminRequestAuth(ctx, r, action, "")
|
cred, adminAPIErr := checkAdminRequestAuth(ctx, r, action, "")
|
||||||
if adminAPIErr != ErrNone {
|
switch adminAPIErr {
|
||||||
|
case ErrNone:
|
||||||
|
return objectAPI, cred
|
||||||
|
case ErrAccessDenied:
|
||||||
|
// Try another
|
||||||
|
continue
|
||||||
|
default:
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
}
|
}
|
||||||
return objectAPI, cred
|
|
||||||
}
|
}
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
return nil, auth.Credentials{}
|
return nil, auth.Credentials{}
|
||||||
@@ -96,6 +106,12 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, errTooManyPolicies):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioAdminInvalidRequest",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, errDecommissionAlreadyRunning):
|
case errors.Is(err, errDecommissionAlreadyRunning):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioDecommissionNotAllowed",
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
@@ -180,7 +196,7 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioAdminTierBackendInUse",
|
Code: "XMinioAdminTierBackendInUse",
|
||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case errors.Is(err, errTierBackendNotEmpty):
|
case errors.Is(err, errTierBackendNotEmpty):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
@@ -217,3 +233,27 @@ func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
|||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wraps export error for more context
|
||||||
|
func exportError(ctx context.Context, err error, fname, entity string) APIError {
|
||||||
|
if entity == "" {
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error exporting %s with: %w", fname, err))
|
||||||
|
}
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error exporting %s from %s with: %w", entity, fname, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// wraps import error for more context
|
||||||
|
func importError(ctx context.Context, err error, fname, entity string) APIError {
|
||||||
|
if entity == "" {
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error importing %s with: %w", fname, err))
|
||||||
|
}
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error importing %s from %s with: %w", entity, fname, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// wraps import error for more context
|
||||||
|
func importErrorWithAPIErr(ctx context.Context, apiErr APIErrorCode, err error, fname, entity string) APIError {
|
||||||
|
if entity == "" {
|
||||||
|
return errorCodes.ToAPIErrWithErr(apiErr, fmt.Errorf("error importing %s with: %w", fname, err))
|
||||||
|
}
|
||||||
|
return errorCodes.ToAPIErrWithErr(apiErr, fmt.Errorf("error importing %s from %s with: %w", entity, fname, err))
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/etcd"
|
"github.com/minio/minio/internal/config/etcd"
|
||||||
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/config/policy/opa"
|
idplugin "github.com/minio/minio/internal/config/identity/plugin"
|
||||||
|
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
@@ -80,6 +81,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
if err = validateConfig(cfg, subSys); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
@@ -90,6 +92,12 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// freshly retrieve the config so that default values are loaded for reset config
|
||||||
|
if cfg, err = getValidConfig(objectAPI); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
dynamic := config.SubSystemsDynamic.Contains(subSys)
|
dynamic := config.SubSystemsDynamic.Contains(subSys)
|
||||||
if dynamic {
|
if dynamic {
|
||||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||||
@@ -97,13 +105,14 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyDynamic(ctx context.Context, objectAPI ObjectLayer, cfg config.Config, subSys string,
|
func applyDynamic(ctx context.Context, objectAPI ObjectLayer, cfg config.Config, subSys string,
|
||||||
r *http.Request, w http.ResponseWriter) {
|
r *http.Request, w http.ResponseWriter,
|
||||||
|
) {
|
||||||
// Apply dynamic values.
|
// Apply dynamic values.
|
||||||
if err := applyDynamicConfigForSubSys(GlobalContext, objectAPI, cfg, subSys); err != nil {
|
if err := applyDynamicConfigForSubSys(GlobalContext, objectAPI, cfg, subSys); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
globalNotificationSys.SignalService(serviceReloadDynamic)
|
globalNotificationSys.SignalConfigReload(subSys)
|
||||||
// Tell the client that dynamic config was applied.
|
// Tell the client that dynamic config was applied.
|
||||||
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||||
}
|
}
|
||||||
@@ -171,10 +180,16 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
if dynamic {
|
if dynamic {
|
||||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
||||||
|
//
|
||||||
|
// `key` can be one of three forms:
|
||||||
|
// 1. `subsys:target` -> request for config of a single subsystem and target pair.
|
||||||
|
// 2. `subsys:` -> request for config of a single subsystem and the default target.
|
||||||
|
// 3. `subsys` -> request for config of all targets for the given subsystem.
|
||||||
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfigKV")
|
ctx := newContext(r, w, "GetConfigKV")
|
||||||
|
|
||||||
@@ -187,15 +202,34 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
cfg := globalServerConfig.Clone()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
buf := &bytes.Buffer{}
|
key := vars["key"]
|
||||||
cw := config.NewConfigWriteTo(cfg, vars["key"])
|
|
||||||
if _, err := cw.WriteTo(buf); err != nil {
|
var subSys, target string
|
||||||
|
{
|
||||||
|
ws := strings.SplitN(key, madmin.SubSystemSeparator, 2)
|
||||||
|
subSys = ws[0]
|
||||||
|
if len(ws) == 2 {
|
||||||
|
if ws[1] == "" {
|
||||||
|
target = madmin.Default
|
||||||
|
} else {
|
||||||
|
target = ws[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subSysConfigs, err := cfg.GetSubsysInfo(subSys, target)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var s strings.Builder
|
||||||
|
for _, subSysConfig := range subSysConfigs {
|
||||||
|
subSysConfig.AddString(&s, false)
|
||||||
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
econfigData, err := madmin.EncryptData(password, buf.Bytes())
|
econfigData, err := madmin.EncryptData(password, []byte(s.String()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -425,40 +459,30 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
count += len(cfg[hkv.Key])
|
count += len(cfg[hkv.Key])
|
||||||
}
|
}
|
||||||
for _, hkv := range hkvs {
|
for _, hkv := range hkvs {
|
||||||
v := cfg[hkv.Key]
|
// We ignore the error below, as we cannot get one.
|
||||||
for target, kv := range v {
|
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "")
|
||||||
off := kv.Get(config.Enable) == config.EnableOff
|
|
||||||
|
for _, item := range cfgSubsysItems {
|
||||||
|
off := item.Config.Get(config.Enable) == config.EnableOff
|
||||||
switch hkv.Key {
|
switch hkv.Key {
|
||||||
case config.EtcdSubSys:
|
case config.EtcdSubSys:
|
||||||
off = !etcd.Enabled(kv)
|
off = !etcd.Enabled(item.Config)
|
||||||
case config.CacheSubSys:
|
case config.CacheSubSys:
|
||||||
off = !cache.Enabled(kv)
|
off = !cache.Enabled(item.Config)
|
||||||
case config.StorageClassSubSys:
|
case config.StorageClassSubSys:
|
||||||
off = !storageclass.Enabled(kv)
|
off = !storageclass.Enabled(item.Config)
|
||||||
case config.PolicyOPASubSys:
|
case config.PolicyPluginSubSys:
|
||||||
off = !opa.Enabled(kv)
|
off = !polplugin.Enabled(item.Config)
|
||||||
case config.IdentityOpenIDSubSys:
|
case config.IdentityOpenIDSubSys:
|
||||||
off = !openid.Enabled(kv)
|
off = !openid.Enabled(item.Config)
|
||||||
case config.IdentityLDAPSubSys:
|
case config.IdentityLDAPSubSys:
|
||||||
off = !xldap.Enabled(kv)
|
off = !xldap.Enabled(item.Config)
|
||||||
case config.IdentityTLSSubSys:
|
case config.IdentityTLSSubSys:
|
||||||
off = !globalSTSTLSConfig.Enabled
|
off = !globalSTSTLSConfig.Enabled
|
||||||
|
case config.IdentityPluginSubSys:
|
||||||
|
off = !idplugin.Enabled(item.Config)
|
||||||
}
|
}
|
||||||
if off {
|
item.AddString(&s, 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio/internal/config"
|
||||||
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
|
"github.com/minio/pkg/ldap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
|
||||||
|
func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetIdentityCfg")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||||
|
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
|
||||||
|
reqBytes, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfgDataBuilder strings.Builder
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
fmt.Fprintf(&cfgDataBuilder, "identity_openid")
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
fmt.Fprintf(&cfgDataBuilder, "identity_ldap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure body content type is opaque.
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subsystem configuration name could be empty.
|
||||||
|
cfgName := mux.Vars(r)["name"]
|
||||||
|
if cfgName != "" {
|
||||||
|
if idpCfgType == madmin.LDAPIDPCfg {
|
||||||
|
// LDAP does not support multiple configurations. So this must be
|
||||||
|
// empty.
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.KvSpaceSeparator, string(reqBytes))
|
||||||
|
|
||||||
|
cfgData := cfgDataBuilder.String()
|
||||||
|
subSys, _, _, err := config.GetSubSys(cfgData)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic, err := cfg.ReadConfig(strings.NewReader(cfgData))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDP config is not dynamic. Sanity check.
|
||||||
|
if dynamic {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validateConfig(cfg, subSys); err != nil {
|
||||||
|
|
||||||
|
var validationErr ldap.Validation
|
||||||
|
if errors.As(err, &validationErr) {
|
||||||
|
// If we got an LDAP validation error, we need to send appropriate
|
||||||
|
// error message back to client (likely mc).
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPValidation),
|
||||||
|
validationErr.FormatError(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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, []byte(cfgData)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// GET <admin-prefix>/id-cfg?type=openid&name=dex_test
|
||||||
|
//
|
||||||
|
// GetIdentityProviderCfg returns a list of configured IDPs on the server if
|
||||||
|
// name is empty. If name is non-empty, returns the configuration details for
|
||||||
|
// the IDP of the given type and configuration name. The configuration name for
|
||||||
|
// the default ("un-named") configuration target is `_`.
|
||||||
|
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetIdentityProviderCfg")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
|
cfgName := r.Form.Get("name")
|
||||||
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no cfgName is provided, we list.
|
||||||
|
if cfgName == "" {
|
||||||
|
a.listIdentityProviders(ctx, w, r, idpCfgType, password)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
var cfgInfos []madmin.IDPCfgInfo
|
||||||
|
var err error
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := madmin.IDPConfig{
|
||||||
|
Type: idpCfgType,
|
||||||
|
Name: cfgName,
|
||||||
|
Info: cfgInfos,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(res)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, idpCfgType, password string) {
|
||||||
|
var cfgList []madmin.IDPListItem
|
||||||
|
var err error
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
cfgList, err = globalOpenIDConfig.GetConfigList(cfg)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
cfgList, err = globalLDAPConfig.GetConfigList(cfg)
|
||||||
|
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(cfgList)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// DELETE <admin-prefix>/id-cfg?type=openid&name=dex_test
|
||||||
|
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "DeleteIdentityProviderCfg")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
|
cfgName := mux.Vars(r)["name"]
|
||||||
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgCopy := globalServerConfig.Clone()
|
||||||
|
var subSys string
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
subSys = config.IdentityOpenIDSubSys
|
||||||
|
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnv := false
|
||||||
|
for _, ci := range cfgInfos {
|
||||||
|
if ci.IsCfg && ci.IsEnv {
|
||||||
|
hasEnv = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasEnv {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
subSys = config.IdentityLDAPSubSys
|
||||||
|
cfgInfos, err := globalLDAPConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnv := false
|
||||||
|
for _, ci := range cfgInfos {
|
||||||
|
if ci.IsCfg && ci.IsEnv {
|
||||||
|
hasEnv = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasEnv {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgKey := fmt.Sprintf("%s:%s", subSys, cfgName)
|
||||||
|
if cfgName == madmin.Default {
|
||||||
|
cfgKey = subSys
|
||||||
|
}
|
||||||
|
if err = cfg.DelKVS(cfgKey); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = validateConfig(cfg, subSys); 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
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic := config.SubSystemsDynamic.Contains(subSys)
|
||||||
|
if dynamic {
|
||||||
|
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -58,6 +59,16 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := pools.Decommission(r.Context(), idx); err != nil {
|
if err := pools.Decommission(r.Context(), idx); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -96,6 +107,16 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := pools.DecommissionCancel(ctx, idx); err != nil {
|
if err := pools.DecommissionCancel(ctx, idx); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -129,8 +150,10 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
idx := globalEndpoints.GetPoolIdx(v)
|
idx := globalEndpoints.GetPoolIdx(v)
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
|
apiErr := toAdminAPIErr(ctx, errInvalidArgument)
|
||||||
|
apiErr.Description = fmt.Sprintf("specified pool '%s' not found, please specify a valid pool", v)
|
||||||
// We didn't find any matching pools, invalid input
|
// We didn't find any matching pools, invalid input
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
@@ -115,18 +116,39 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
case madmin.MakeWithVersioningBktOp:
|
case madmin.MakeWithVersioningBktOp:
|
||||||
_, isLockEnabled := r.Form["lockEnabled"]
|
_, isLockEnabled := r.Form["lockEnabled"]
|
||||||
_, isVersioningEnabled := r.Form["versioningEnabled"]
|
_, isVersioningEnabled := r.Form["versioningEnabled"]
|
||||||
opts := BucketOptions{
|
_, isForceCreate := r.Form["forceCreate"]
|
||||||
|
createdAtStr := strings.TrimSpace(r.Form.Get("createdAt"))
|
||||||
|
createdAt, cerr := time.Parse(time.RFC3339Nano, createdAtStr)
|
||||||
|
if cerr != nil {
|
||||||
|
createdAt = timeSentinel
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := MakeBucketOptions{
|
||||||
Location: r.Form.Get("location"),
|
Location: r.Form.Get("location"),
|
||||||
LockEnabled: isLockEnabled,
|
LockEnabled: isLockEnabled,
|
||||||
VersioningEnabled: isVersioningEnabled,
|
VersioningEnabled: isVersioningEnabled,
|
||||||
|
ForceCreate: isForceCreate,
|
||||||
|
CreatedAt: createdAt,
|
||||||
}
|
}
|
||||||
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
|
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
|
||||||
case madmin.ConfigureReplBktOp:
|
case madmin.ConfigureReplBktOp:
|
||||||
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
|
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
|
||||||
case madmin.DeleteBucketBktOp:
|
case madmin.DeleteBucketBktOp:
|
||||||
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, false)
|
_, noRecreate := r.Form["noRecreate"]
|
||||||
|
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
||||||
|
Force: false,
|
||||||
|
NoRecreate: noRecreate,
|
||||||
|
SRDeleteOp: getSRBucketDeleteOp(true),
|
||||||
|
})
|
||||||
case madmin.ForceDeleteBucketBktOp:
|
case madmin.ForceDeleteBucketBktOp:
|
||||||
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, true)
|
_, noRecreate := r.Form["noRecreate"]
|
||||||
|
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
||||||
|
Force: true,
|
||||||
|
NoRecreate: noRecreate,
|
||||||
|
SRDeleteOp: getSRBucketDeleteOp(true),
|
||||||
|
})
|
||||||
|
case madmin.PurgeDeletedBucketOp:
|
||||||
|
globalSiteReplicationSys.purgeDeletedBucket(ctx, objectAPI, bucket)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -158,7 +180,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
err = errSRInvalidRequest(errInvalidArgument)
|
err = errSRInvalidRequest(errInvalidArgument)
|
||||||
case madmin.SRIAMItemPolicy:
|
case madmin.SRIAMItemPolicy:
|
||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil)
|
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
|
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
@@ -166,21 +188,21 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if policy.IsEmpty() {
|
if policy.IsEmpty() {
|
||||||
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil)
|
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy)
|
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, policy, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case madmin.SRIAMItemSvcAcc:
|
case madmin.SRIAMItemSvcAcc:
|
||||||
err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange)
|
err = globalSiteReplicationSys.PeerSvcAccChangeHandler(ctx, item.SvcAccChange, item.UpdatedAt)
|
||||||
case madmin.SRIAMItemPolicyMapping:
|
case madmin.SRIAMItemPolicyMapping:
|
||||||
err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping)
|
err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping, item.UpdatedAt)
|
||||||
case madmin.SRIAMItemSTSAcc:
|
case madmin.SRIAMItemSTSAcc:
|
||||||
err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential)
|
err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential, item.UpdatedAt)
|
||||||
case madmin.SRIAMItemIAMUser:
|
case madmin.SRIAMItemIAMUser:
|
||||||
err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser)
|
err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser, item.UpdatedAt)
|
||||||
case madmin.SRIAMItemGroupInfo:
|
case madmin.SRIAMItemGroupInfo:
|
||||||
err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo)
|
err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -212,7 +234,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
err = errSRInvalidRequest(errInvalidArgument)
|
err = errSRInvalidRequest(errInvalidArgument)
|
||||||
case madmin.SRBucketMetaTypePolicy:
|
case madmin.SRBucketMetaTypePolicy:
|
||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil)
|
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket)
|
bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket)
|
||||||
if berr != nil {
|
if berr != nil {
|
||||||
@@ -220,31 +242,33 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bktPolicy.IsEmpty() {
|
if bktPolicy.IsEmpty() {
|
||||||
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil)
|
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, bktPolicy)
|
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, bktPolicy, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case madmin.SRBucketMetaTypeQuotaConfig:
|
case madmin.SRBucketMetaTypeQuotaConfig:
|
||||||
if item.Quota == nil {
|
if item.Quota == nil {
|
||||||
err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, nil)
|
err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
quotaConfig, err := parseBucketQuota(item.Bucket, item.Quota)
|
quotaConfig, err := parseBucketQuota(item.Bucket, item.Quota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig); err != nil {
|
if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig, item.UpdatedAt); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case madmin.SRBucketMetaTypeVersionConfig:
|
||||||
|
err = globalSiteReplicationSys.PeerBucketVersioningHandler(ctx, item.Bucket, item.Versioning, item.UpdatedAt)
|
||||||
case madmin.SRBucketMetaTypeTags:
|
case madmin.SRBucketMetaTypeTags:
|
||||||
err = globalSiteReplicationSys.PeerBucketTaggingHandler(ctx, item.Bucket, item.Tags)
|
err = globalSiteReplicationSys.PeerBucketTaggingHandler(ctx, item.Bucket, item.Tags, item.UpdatedAt)
|
||||||
case madmin.SRBucketMetaTypeObjectLockConfig:
|
case madmin.SRBucketMetaTypeObjectLockConfig:
|
||||||
err = globalSiteReplicationSys.PeerBucketObjectLockConfigHandler(ctx, item.Bucket, item.ObjectLockConfig)
|
err = globalSiteReplicationSys.PeerBucketObjectLockConfigHandler(ctx, item.Bucket, item.ObjectLockConfig, item.UpdatedAt)
|
||||||
case madmin.SRBucketMetaTypeSSEConfig:
|
case madmin.SRBucketMetaTypeSSEConfig:
|
||||||
err = globalSiteReplicationSys.PeerBucketSSEConfigHandler(ctx, item.Bucket, item.SSEConfig)
|
err = globalSiteReplicationSys.PeerBucketSSEConfigHandler(ctx, item.Bucket, item.SSEConfig, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -294,7 +318,7 @@ func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptionKey string) error {
|
func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptionKey string) error {
|
||||||
data, err := ioutil.ReadAll(body)
|
data, err := io.ReadAll(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SRError{
|
return SRError{
|
||||||
Cause: err,
|
Cause: err,
|
||||||
@@ -432,6 +456,7 @@ func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
|||||||
opts.Users = q.Get("users") == "true"
|
opts.Users = q.Get("users") == "true"
|
||||||
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
||||||
opts.EntityValue = q.Get("entityvalue")
|
opts.EntityValue = q.Get("entityvalue")
|
||||||
|
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -41,11 +42,15 @@ func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIAMInternalIDPConcurrencyServerSuite(t *testing.T) {
|
func TestIAMInternalIDPConcurrencyServerSuite(t *testing.T) {
|
||||||
|
if runtime.GOOS == globalWindowsOSName {
|
||||||
|
t.Skip("windows is clunky")
|
||||||
|
}
|
||||||
|
|
||||||
baseTestCases := []TestSuiteCommon{
|
baseTestCases := []TestSuiteCommon{
|
||||||
// Init and run test on FS backend with signature v4.
|
// Init and run test on ErasureSD backend with signature v4.
|
||||||
{serverType: "FS", signer: signerV4},
|
{serverType: "ErasureSD", signer: signerV4},
|
||||||
// Init and run test on FS backend, with tls enabled.
|
// Init and run test on ErasureSD backend, with tls enabled.
|
||||||
{serverType: "FS", signer: signerV4, secure: true},
|
{serverType: "ErasureSD", signer: signerV4, secure: true},
|
||||||
// Init and run test on Erasure backend.
|
// Init and run test on Erasure backend.
|
||||||
{serverType: "Erasure", signer: signerV4},
|
{serverType: "Erasure", signer: signerV4},
|
||||||
// Init and run test on ErasureSet backend.
|
// Init and run test on ErasureSet backend.
|
||||||
@@ -73,7 +78,7 @@ func TestIAMInternalIDPConcurrencyServerSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) TestDeleteUserRace(c *check) {
|
func (s *TestSuiteIAM) TestDeleteUserRace(c *check) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
|
|||||||
+859
-72
File diff suppressed because it is too large
Load Diff
@@ -24,16 +24,17 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
minio "github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||||
@@ -50,6 +51,8 @@ const (
|
|||||||
type TestSuiteIAM struct {
|
type TestSuiteIAM struct {
|
||||||
TestSuiteCommon
|
TestSuiteCommon
|
||||||
|
|
||||||
|
ServerTypeDescription string
|
||||||
|
|
||||||
// Flag to turn on tests for etcd backend IAM
|
// Flag to turn on tests for etcd backend IAM
|
||||||
withEtcdBackend bool
|
withEtcdBackend bool
|
||||||
|
|
||||||
@@ -59,7 +62,15 @@ type TestSuiteIAM struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestSuiteIAM(c TestSuiteCommon, withEtcdBackend bool) *TestSuiteIAM {
|
func newTestSuiteIAM(c TestSuiteCommon, withEtcdBackend bool) *TestSuiteIAM {
|
||||||
return &TestSuiteIAM{TestSuiteCommon: c, withEtcdBackend: withEtcdBackend}
|
etcdStr := ""
|
||||||
|
if withEtcdBackend {
|
||||||
|
etcdStr = " (with etcd backend)"
|
||||||
|
}
|
||||||
|
return &TestSuiteIAM{
|
||||||
|
TestSuiteCommon: c,
|
||||||
|
ServerTypeDescription: fmt.Sprintf("%s%s", c.serverType, etcdStr),
|
||||||
|
withEtcdBackend: withEtcdBackend,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) iamSetup(c *check) {
|
func (s *TestSuiteIAM) iamSetup(c *check) {
|
||||||
@@ -87,6 +98,29 @@ func (s *TestSuiteIAM) iamSetup(c *check) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of all IAM test suites (i.e. test server configuration combinations)
|
||||||
|
// common to tests.
|
||||||
|
var iamTestSuites = func() []*TestSuiteIAM {
|
||||||
|
baseTestCases := []TestSuiteCommon{
|
||||||
|
// Init and run test on ErasureSD backend with signature v4.
|
||||||
|
{serverType: "ErasureSD", signer: signerV4},
|
||||||
|
// Init and run test on ErasureSD backend, with tls enabled.
|
||||||
|
{serverType: "ErasureSD", signer: signerV4, secure: true},
|
||||||
|
// Init and run test on Erasure backend.
|
||||||
|
{serverType: "Erasure", signer: signerV4},
|
||||||
|
// Init and run test on ErasureSet backend.
|
||||||
|
{serverType: "ErasureSet", signer: signerV4},
|
||||||
|
}
|
||||||
|
testCases := []*TestSuiteIAM{}
|
||||||
|
for _, bt := range baseTestCases {
|
||||||
|
testCases = append(testCases,
|
||||||
|
newTestSuiteIAM(bt, false),
|
||||||
|
newTestSuiteIAM(bt, true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return testCases
|
||||||
|
}()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnvTestEtcdBackend = "ETCD_SERVER"
|
EnvTestEtcdBackend = "ETCD_SERVER"
|
||||||
)
|
)
|
||||||
@@ -156,30 +190,12 @@ func (s *TestSuiteIAM) getUserClient(c *check, accessKey, secretKey, sessionToke
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIAMInternalIDPServerSuite(t *testing.T) {
|
func TestIAMInternalIDPServerSuite(t *testing.T) {
|
||||||
baseTestCases := []TestSuiteCommon{
|
if runtime.GOOS == globalWindowsOSName {
|
||||||
// Init and run test on FS backend with signature v4.
|
t.Skip("windows is clunky disable these tests")
|
||||||
{serverType: "FS", signer: signerV4},
|
|
||||||
// Init and run test on FS backend, with tls enabled.
|
|
||||||
{serverType: "FS", signer: signerV4, secure: true},
|
|
||||||
// Init and run test on Erasure backend.
|
|
||||||
{serverType: "Erasure", signer: signerV4},
|
|
||||||
// Init and run test on ErasureSet backend.
|
|
||||||
{serverType: "ErasureSet", signer: signerV4},
|
|
||||||
}
|
}
|
||||||
testCases := []*TestSuiteIAM{}
|
for i, testCase := range iamTestSuites {
|
||||||
for _, bt := range baseTestCases {
|
|
||||||
testCases = append(testCases,
|
|
||||||
newTestSuiteIAM(bt, false),
|
|
||||||
newTestSuiteIAM(bt, true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
etcdStr := ""
|
|
||||||
if testCase.withEtcdBackend {
|
|
||||||
etcdStr = " (with etcd backend)"
|
|
||||||
}
|
|
||||||
t.Run(
|
t.Run(
|
||||||
fmt.Sprintf("Test: %d, ServerType: %s%s", i+1, testCase.serverType, etcdStr),
|
fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.ServerTypeDescription),
|
||||||
func(t *testing.T) {
|
func(t *testing.T) {
|
||||||
suite := testCase
|
suite := testCase
|
||||||
c := &check{t, testCase.serverType}
|
c := &check{t, testCase.serverType}
|
||||||
@@ -226,6 +242,7 @@ func (s *TestSuiteIAM) TestUserCreate(c *check) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("unable to set policy: %v", err)
|
c.Fatalf("unable to set policy: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := s.getUserClient(c, accessKey, secretKey, "")
|
client := s.getUserClient(c, accessKey, secretKey, "")
|
||||||
err = client.MakeBucket(ctx, getRandomBucketName(), minio.MakeBucketOptions{})
|
err = client.MakeBucket(ctx, getRandomBucketName(), minio.MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -371,7 +388,7 @@ func (s *TestSuiteIAM) TestUserPolicyEscalationBug(c *check) {
|
|||||||
req.ContentLength = int64(len(buf))
|
req.ContentLength = int64(len(buf))
|
||||||
sum := sha256.Sum256(buf)
|
sum := sha256.Sum256(buf)
|
||||||
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
|
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
|
||||||
req.Body = ioutil.NopCloser(bytes.NewReader(buf))
|
req.Body = io.NopCloser(bytes.NewReader(buf))
|
||||||
req = signer.SignV4(*req, accessKey, secretKey, "", "")
|
req = signer.SignV4(*req, accessKey, secretKey, "", "")
|
||||||
|
|
||||||
// 3.1 Execute the request.
|
// 3.1 Execute the request.
|
||||||
@@ -890,6 +907,9 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
|||||||
|
|
||||||
// 5. Check that service account can be deleted.
|
// 5. Check that service account can be deleted.
|
||||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||||
|
|
||||||
|
// 6. Check that service account cannot be created for some other user.
|
||||||
|
c.mustNotCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
||||||
@@ -960,7 +980,168 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
|||||||
c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
|
c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
pluginEndpoint := os.Getenv("POLICY_PLUGIN_ENDPOINT")
|
||||||
|
if pluginEndpoint == "" {
|
||||||
|
c.Skip("POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
||||||
|
}
|
||||||
|
|
||||||
|
configCmds := []string{
|
||||||
|
"policy_plugin",
|
||||||
|
"url=" + pluginEndpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.adm.SetConfigKV(ctx, strings.Join(configCmds, " "))
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("unable to setup access management plugin for tests: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.RestartIAMSuite(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIAM_AMPInternalIDPServerSuite - tests for access management plugin
|
||||||
|
func TestIAM_AMPInternalIDPServerSuite(t *testing.T) {
|
||||||
|
for i, testCase := range iamTestSuites {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.ServerTypeDescription),
|
||||||
|
func(t *testing.T) {
|
||||||
|
suite := testCase
|
||||||
|
c := &check{t, testCase.serverType}
|
||||||
|
|
||||||
|
suite.SetUpSuite(c)
|
||||||
|
defer suite.TearDownSuite(c)
|
||||||
|
|
||||||
|
suite.SetUpAccMgmtPlugin(c)
|
||||||
|
|
||||||
|
suite.TestAccMgmtPlugin(c)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAccMgmtPlugin - this test assumes that the access-management-plugin is
|
||||||
|
// the same as the example in `docs/iam/access-manager-plugin.go` -
|
||||||
|
// specifically, it denies only `s3:Put*` operations on non-root accounts.
|
||||||
|
func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// 0. Check that owner is able to make-bucket.
|
||||||
|
bucket := getRandomBucketName()
|
||||||
|
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("bucket creat error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Create a user.
|
||||||
|
accessKey, secretKey := mustGenerateCredentials(c)
|
||||||
|
err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to set user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check new user appears in listing
|
||||||
|
usersMap, err := s.adm.ListUsers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("error listing: %v", err)
|
||||||
|
}
|
||||||
|
v, ok := usersMap[accessKey]
|
||||||
|
if !ok {
|
||||||
|
c.Fatalf("user not listed: %s", accessKey)
|
||||||
|
}
|
||||||
|
c.Assert(v.Status, madmin.AccountEnabled)
|
||||||
|
|
||||||
|
// 3. Check that user is able to make a bucket.
|
||||||
|
client := s.getUserClient(c, accessKey, secretKey, "")
|
||||||
|
err = client.MakeBucket(ctx, getRandomBucketName(), minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user not create bucket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.1 check user has access to bucket
|
||||||
|
c.mustListObjects(ctx, client, bucket)
|
||||||
|
|
||||||
|
// 3.2 check that user cannot upload an object.
|
||||||
|
_, err = client.PutObject(ctx, bucket, "objectName", bytes.NewBuffer([]byte("some content")), 12, minio.PutObjectOptions{})
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("user was able to upload unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an madmin client with user creds
|
||||||
|
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
|
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
||||||
|
Secure: s.secure,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Err creating user admin client: %v", err)
|
||||||
|
}
|
||||||
|
userAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
|
||||||
|
|
||||||
|
// Create svc acc
|
||||||
|
cr := c.mustCreateSvcAccount(ctx, accessKey, userAdmClient)
|
||||||
|
|
||||||
|
// 1. Check that svc account appears in listing
|
||||||
|
c.assertSvcAccAppearsInListing(ctx, userAdmClient, accessKey, cr.AccessKey)
|
||||||
|
|
||||||
|
// 2. Check that svc account info can be queried
|
||||||
|
c.assertSvcAccInfoQueryable(ctx, userAdmClient, accessKey, cr.AccessKey, false)
|
||||||
|
|
||||||
|
// 3. Check S3 access
|
||||||
|
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||||
|
|
||||||
|
// Check that session policies do not apply - as policy enforcement is
|
||||||
|
// delegated to plugin.
|
||||||
|
{
|
||||||
|
svcAK, svcSK := mustGenerateCredentials(c)
|
||||||
|
|
||||||
|
// This policy does not allow listing objects.
|
||||||
|
policyBytes := []byte(fmt.Sprintf(`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:GetObject"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::%s/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, bucket))
|
||||||
|
cr, err := userAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
|
Policy: policyBytes,
|
||||||
|
TargetUser: accessKey,
|
||||||
|
AccessKey: svcAK,
|
||||||
|
SecretKey: svcSK,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to create svc acc: %v", err)
|
||||||
|
}
|
||||||
|
svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
|
||||||
|
// Though the attached policy does not allow listing, it will be
|
||||||
|
// ignored because the plugin allows it.
|
||||||
|
c.mustListObjects(ctx, svcClient, bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Check that service account's secret key and account status can be
|
||||||
|
// updated.
|
||||||
|
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, accessKey, bucket)
|
||||||
|
|
||||||
|
// 5. Check that service account can be deleted.
|
||||||
|
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||||
|
|
||||||
|
// 6. Check that service account **can** be created for some other user.
|
||||||
|
// This is possible because of the policy enforced in the plugin.
|
||||||
|
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) mustCreateIAMUser(ctx context.Context, admClnt *madmin.AdminClient) madmin.Credentials {
|
func (c *check) mustCreateIAMUser(ctx context.Context, admClnt *madmin.AdminClient) madmin.Credentials {
|
||||||
|
c.Helper()
|
||||||
randUser := mustGetUUID()
|
randUser := mustGetUUID()
|
||||||
randPass := mustGetUUID()
|
randPass := mustGetUUID()
|
||||||
err := admClnt.AddUser(ctx, randUser, randPass)
|
err := admClnt.AddUser(ctx, randUser, randPass)
|
||||||
@@ -974,6 +1155,7 @@ func (c *check) mustCreateIAMUser(ctx context.Context, admClnt *madmin.AdminClie
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) mustGetIAMUserInfo(ctx context.Context, admClnt *madmin.AdminClient, accessKey string) madmin.UserInfo {
|
func (c *check) mustGetIAMUserInfo(ctx context.Context, admClnt *madmin.AdminClient, accessKey string) madmin.UserInfo {
|
||||||
|
c.Helper()
|
||||||
ui, err := admClnt.GetUserInfo(ctx, accessKey)
|
ui, err := admClnt.GetUserInfo(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("should be able to get user info: %v", err)
|
c.Fatalf("should be able to get user info: %v", err)
|
||||||
@@ -982,6 +1164,7 @@ func (c *check) mustGetIAMUserInfo(ctx context.Context, admClnt *madmin.AdminCli
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) mustNotCreateIAMUser(ctx context.Context, admClnt *madmin.AdminClient) {
|
func (c *check) mustNotCreateIAMUser(ctx context.Context, admClnt *madmin.AdminClient) {
|
||||||
|
c.Helper()
|
||||||
randUser := mustGetUUID()
|
randUser := mustGetUUID()
|
||||||
randPass := mustGetUUID()
|
randPass := mustGetUUID()
|
||||||
err := admClnt.AddUser(ctx, randUser, randPass)
|
err := admClnt.AddUser(ctx, randUser, randPass)
|
||||||
@@ -991,6 +1174,7 @@ func (c *check) mustNotCreateIAMUser(ctx context.Context, admClnt *madmin.AdminC
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) mustCreateSvcAccount(ctx context.Context, tgtUser string, admClnt *madmin.AdminClient) madmin.Credentials {
|
func (c *check) mustCreateSvcAccount(ctx context.Context, tgtUser string, admClnt *madmin.AdminClient) madmin.Credentials {
|
||||||
|
c.Helper()
|
||||||
cr, err := admClnt.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
cr, err := admClnt.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
TargetUser: tgtUser,
|
TargetUser: tgtUser,
|
||||||
})
|
})
|
||||||
@@ -1001,6 +1185,7 @@ func (c *check) mustCreateSvcAccount(ctx context.Context, tgtUser string, admCln
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) mustNotCreateSvcAccount(ctx context.Context, tgtUser string, admClnt *madmin.AdminClient) {
|
func (c *check) mustNotCreateSvcAccount(ctx context.Context, tgtUser string, admClnt *madmin.AdminClient) {
|
||||||
|
c.Helper()
|
||||||
_, err := admClnt.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
_, err := admClnt.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
TargetUser: tgtUser,
|
TargetUser: tgtUser,
|
||||||
})
|
})
|
||||||
@@ -1010,28 +1195,90 @@ func (c *check) mustNotCreateSvcAccount(ctx context.Context, tgtUser string, adm
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) mustNotListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustNotListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
||||||
v, ok := <-res
|
v, ok := <-res
|
||||||
if !ok || v.Err == nil {
|
if !ok || v.Err == nil {
|
||||||
c.Fatalf("user was able to list unexpectedly!")
|
c.Fatalf("user was able to list unexpectedly! on %s", bucket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustPutObjectWithTags(ctx context.Context, client *minio.Client, bucket, object string) {
|
||||||
|
c.Helper()
|
||||||
|
_, err := client.PutObject(ctx, bucket, object, bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{
|
||||||
|
UserTags: map[string]string{
|
||||||
|
"security": "public",
|
||||||
|
"virus": "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to upload the object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustGetObject(ctx context.Context, client *minio.Client, bucket, object string) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
r, err := client.GetObject(ctx, bucket, object, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(io.Discard, r)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustHeadObject(ctx context.Context, client *minio.Client, bucket, object string, tagCount int) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
oinfo, err := client.StatObject(ctx, bucket, object, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oinfo.UserTagCount != tagCount {
|
||||||
|
c.Fatalf("expected tagCount: %d, got %d", tagCount, oinfo.UserTagCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
||||||
v, ok := <-res
|
v, ok := <-res
|
||||||
if ok && v.Err != nil {
|
if ok && v.Err != nil {
|
||||||
msg := fmt.Sprintf("user was unable to list: %v", v.Err)
|
c.Fatalf("user was unable to list: %v", v.Err)
|
||||||
c.Fatalf(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *check) mustListBuckets(ctx context.Context, client *minio.Client) {
|
||||||
|
c.Helper()
|
||||||
|
_, err := client.ListBuckets(ctx)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to list buckets: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
|
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
|
if e, ok := err.(minio.ErrorResponse); ok {
|
||||||
|
if e.Code == "AccessDenied" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Fatalf("upload did not get an AccessDenied error - got %#v instead", err)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) assertSvcAccS3Access(ctx context.Context, s *TestSuiteIAM, cr madmin.Credentials, bucket string) {
|
func (c *check) assertSvcAccS3Access(ctx context.Context, s *TestSuiteIAM, cr madmin.Credentials, bucket string) {
|
||||||
svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
|
svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
|
||||||
c.mustListObjects(ctx, svcClient, bucket)
|
c.mustListObjects(ctx, svcClient, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) assertSvcAccAppearsInListing(ctx context.Context, madmClient *madmin.AdminClient, parentAK, svcAK string) {
|
func (c *check) assertSvcAccAppearsInListing(ctx context.Context, madmClient *madmin.AdminClient, parentAK, svcAK string) {
|
||||||
|
c.Helper()
|
||||||
listResp, err := madmClient.ListServiceAccounts(ctx, parentAK)
|
listResp, err := madmClient.ListServiceAccounts(ctx, parentAK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("unable to list svc accounts: %v", err)
|
c.Fatalf("unable to list svc accounts: %v", err)
|
||||||
@@ -1057,6 +1304,7 @@ func (c *check) assertSvcAccInfoQueryable(ctx context.Context, madmClient *madmi
|
|||||||
// bucket. It creates a session policy that restricts listing on the bucket and
|
// bucket. It creates a session policy that restricts listing on the bucket and
|
||||||
// then enables it again in a session policy update call.
|
// then enables it again in a session policy update call.
|
||||||
func (c *check) assertSvcAccSessionPolicyUpdate(ctx context.Context, s *TestSuiteIAM, madmClient *madmin.AdminClient, accessKey, bucket string) {
|
func (c *check) assertSvcAccSessionPolicyUpdate(ctx context.Context, s *TestSuiteIAM, madmClient *madmin.AdminClient, accessKey, bucket string) {
|
||||||
|
c.Helper()
|
||||||
svcAK, svcSK := mustGenerateCredentials(c)
|
svcAK, svcSK := mustGenerateCredentials(c)
|
||||||
|
|
||||||
// This policy does not allow listing objects.
|
// This policy does not allow listing objects.
|
||||||
@@ -1112,6 +1360,7 @@ func (c *check) assertSvcAccSessionPolicyUpdate(ctx context.Context, s *TestSuit
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) assertSvcAccSecretKeyAndStatusUpdate(ctx context.Context, s *TestSuiteIAM, madmClient *madmin.AdminClient, accessKey, bucket string) {
|
func (c *check) assertSvcAccSecretKeyAndStatusUpdate(ctx context.Context, s *TestSuiteIAM, madmClient *madmin.AdminClient, accessKey, bucket string) {
|
||||||
|
c.Helper()
|
||||||
svcAK, svcSK := mustGenerateCredentials(c)
|
svcAK, svcSK := mustGenerateCredentials(c)
|
||||||
cr, err := madmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
cr, err := madmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
TargetUser: accessKey,
|
TargetUser: accessKey,
|
||||||
@@ -1148,6 +1397,7 @@ func (c *check) assertSvcAccSecretKeyAndStatusUpdate(ctx context.Context, s *Tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *check) assertSvcAccDeletion(ctx context.Context, s *TestSuiteIAM, madmClient *madmin.AdminClient, accessKey, bucket string) {
|
func (c *check) assertSvcAccDeletion(ctx context.Context, s *TestSuiteIAM, madmClient *madmin.AdminClient, accessKey, bucket string) {
|
||||||
|
c.Helper()
|
||||||
svcAK, svcSK := mustGenerateCredentials(c)
|
svcAK, svcSK := mustGenerateCredentials(c)
|
||||||
cr, err := madmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
cr, err := madmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
TargetUser: accessKey,
|
TargetUser: accessKey,
|
||||||
@@ -1168,6 +1418,7 @@ func (c *check) assertSvcAccDeletion(ctx context.Context, s *TestSuiteIAM, madmC
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mustGenerateCredentials(c *check) (string, string) {
|
func mustGenerateCredentials(c *check) (string, string) {
|
||||||
|
c.Helper()
|
||||||
ak, sk, err := auth.GenerateCredentials()
|
ak, sk, err := auth.GenerateCredentials()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("unable to generate credentials: %v", err)
|
c.Fatalf("unable to generate credentials: %v", err)
|
||||||
|
|||||||
+567
-238
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -74,7 +73,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
|||||||
|
|
||||||
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
@@ -220,7 +219,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
|||||||
adminTestBed.router.ServeHTTP(rec, req)
|
adminTestBed.router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
resp, _ := ioutil.ReadAll(rec.Body)
|
resp, _ := io.ReadAll(rec.Body)
|
||||||
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
||||||
http.StatusOK, rec.Code, string(resp))
|
http.StatusOK, rec.Code, string(resp))
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-34
@@ -91,31 +91,27 @@ type allHealState struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
// map of heal path to heal sequence
|
// map of heal path to heal sequence
|
||||||
healSeqMap map[string]*healSequence // Indexed by endpoint
|
healSeqMap map[string]*healSequence // Indexed by endpoint
|
||||||
healLocalDisks map[Endpoint]struct{}
|
// keep track of the healing status of disks in the memory
|
||||||
|
// false: the disk needs to be healed but no healing routine is started
|
||||||
|
// true: the disk is currently healing
|
||||||
|
healLocalDisks map[Endpoint]bool
|
||||||
healStatus map[string]healingTracker // Indexed by disk ID
|
healStatus map[string]healingTracker // Indexed by disk ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHealState - initialize global heal state management
|
// newHealState - initialize global heal state management
|
||||||
func newHealState(cleanup bool) *allHealState {
|
func newHealState(ctx context.Context, cleanup bool) *allHealState {
|
||||||
hstate := &allHealState{
|
hstate := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
healLocalDisks: map[Endpoint]struct{}{},
|
healLocalDisks: make(map[Endpoint]bool),
|
||||||
healStatus: make(map[string]healingTracker),
|
healStatus: make(map[string]healingTracker),
|
||||||
}
|
}
|
||||||
if cleanup {
|
if cleanup {
|
||||||
go hstate.periodicHealSeqsClean(GlobalContext)
|
go hstate.periodicHealSeqsClean(ctx)
|
||||||
}
|
}
|
||||||
return hstate
|
return hstate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) healDriveCount() int {
|
|
||||||
ahs.RLock()
|
|
||||||
defer ahs.RUnlock()
|
|
||||||
|
|
||||||
return len(ahs.healLocalDisks)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
defer ahs.Unlock()
|
defer ahs.Unlock()
|
||||||
@@ -165,23 +161,34 @@ func (ahs *allHealState) getLocalHealingDisks() map[string]madmin.HealingDisk {
|
|||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHealLocalDiskEndpoints() returns the list of disks that need
|
||||||
|
// to be healed but there is no healing routine in progress on them.
|
||||||
func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
|
func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
|
||||||
ahs.RLock()
|
ahs.RLock()
|
||||||
defer ahs.RUnlock()
|
defer ahs.RUnlock()
|
||||||
|
|
||||||
var endpoints Endpoints
|
var endpoints Endpoints
|
||||||
for ep := range ahs.healLocalDisks {
|
for ep, healing := range ahs.healLocalDisks {
|
||||||
endpoints = append(endpoints, ep)
|
if !healing {
|
||||||
|
endpoints = append(endpoints, ep)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return endpoints
|
return endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ahs *allHealState) markDiskForHealing(ep Endpoint) {
|
||||||
|
ahs.Lock()
|
||||||
|
defer ahs.Unlock()
|
||||||
|
|
||||||
|
ahs.healLocalDisks[ep] = true
|
||||||
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
defer ahs.Unlock()
|
defer ahs.Unlock()
|
||||||
|
|
||||||
for _, ep := range healLocalDisks {
|
for _, ep := range healLocalDisks {
|
||||||
ahs.healLocalDisks[ep] = struct{}{}
|
ahs.healLocalDisks[ep] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +201,6 @@ func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-periodicTimer.C:
|
case <-periodicTimer.C:
|
||||||
periodicTimer.Reset(time.Minute * 5)
|
|
||||||
now := UTCNow()
|
now := UTCNow()
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
for path, h := range ahs.healSeqMap {
|
for path, h := range ahs.healSeqMap {
|
||||||
@@ -203,6 +209,8 @@ func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ahs.Unlock()
|
ahs.Unlock()
|
||||||
|
|
||||||
|
periodicTimer.Reset(time.Minute * 5)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// server could be restarting - need
|
// server could be restarting - need
|
||||||
// to exit immediately
|
// to exit immediately
|
||||||
@@ -581,12 +589,7 @@ func (h *healSequence) pushHealResultItem(r madmin.HealResultItem) error {
|
|||||||
// heal-results in memory and the client has not consumed it
|
// heal-results in memory and the client has not consumed it
|
||||||
// for too long.
|
// for too long.
|
||||||
unconsumedTimer := time.NewTimer(healUnconsumedTimeout)
|
unconsumedTimer := time.NewTimer(healUnconsumedTimeout)
|
||||||
defer func() {
|
defer unconsumedTimer.Stop()
|
||||||
// stop the timeout timer so it is garbage collected.
|
|
||||||
if !unconsumedTimer.Stop() {
|
|
||||||
<-unconsumedTimer.C
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var itemsLen int
|
var itemsLen int
|
||||||
for {
|
for {
|
||||||
@@ -701,7 +704,7 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
if source.opts != nil {
|
if source.opts != nil {
|
||||||
task.opts = *source.opts
|
task.opts = *source.opts
|
||||||
} else {
|
} else {
|
||||||
task.opts.ScanMode = globalHealConfig.ScanMode()
|
task.opts.ScanMode = madmin.HealNormalScan
|
||||||
}
|
}
|
||||||
|
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
@@ -808,16 +811,6 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// healDiskFormat - heals format.json, return value indicates if a
|
|
||||||
// failure error occurred.
|
|
||||||
func (h *healSequence) healDiskFormat() error {
|
|
||||||
if h.isQuitting() {
|
|
||||||
return errHealStopSignalled
|
|
||||||
}
|
|
||||||
|
|
||||||
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(objAPI ObjectLayer, bucketsOnly bool) error {
|
func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
@@ -829,7 +822,7 @@ func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
|
|||||||
return h.healBucket(objAPI, h.bucket, bucketsOnly)
|
return h.healBucket(objAPI, h.bucket, bucketsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, err := objAPI.ListBuckets(h.ctx)
|
buckets, err := objAPI.ListBuckets(h.ctx, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
return errFnHealFromAPIErr(h.ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-43
@@ -66,6 +66,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
||||||
// DataUsageInfo operations
|
// DataUsageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.DataUsageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.DataUsageInfoHandler)))
|
||||||
|
// Metrics operation
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/metrics").HandlerFunc(gz(httpTraceAll(adminAPI.MetricsHandler)))
|
||||||
|
|
||||||
if globalIsDistErasure || globalIsErasure {
|
if globalIsDistErasure || globalIsErasure {
|
||||||
// Heal operations
|
// Heal operations
|
||||||
@@ -84,10 +86,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profiling operations
|
// Profiling operations - deprecated API
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(gz(httpTraceAll(adminAPI.StartProfilingHandler))).
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(gz(httpTraceAll(adminAPI.StartProfilingHandler))).
|
||||||
Queries("profilerType", "{profilerType:.*}")
|
Queries("profilerType", "{profilerType:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(gz(httpTraceAll(adminAPI.DownloadProfilingHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(gz(httpTraceAll(adminAPI.DownloadProfilingHandler)))
|
||||||
|
// Profiling operations
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/profile").HandlerFunc(gz(httpTraceAll(adminAPI.ProfileHandler)))
|
||||||
|
|
||||||
// Config KV operations.
|
// Config KV operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
@@ -168,50 +172,82 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
// Set Group Status
|
// Set Group Status
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetGroupStatus))).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetGroupStatus))).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
if globalIsDistErasure || globalIsErasure {
|
// Export IAM info to zipped file
|
||||||
// GetBucketQuotaConfig
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-iam").HandlerFunc(httpTraceHdrs(adminAPI.ExportIAM))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
|
||||||
gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
|
||||||
// PutBucketQuotaConfig
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
|
||||||
gz(httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
|
||||||
|
|
||||||
// Bucket replication operations
|
// Import IAM info
|
||||||
// GetBucketTargetHandler
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
|
||||||
gz(httpTraceHdrs(adminAPI.ListRemoteTargetsHandler))).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
|
||||||
// SetRemoteTargetHandler
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
|
||||||
gz(httpTraceHdrs(adminAPI.SetRemoteTargetHandler))).Queries("bucket", "{bucket:.*}")
|
|
||||||
// RemoveRemoteTargetHandler
|
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
|
||||||
gz(httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler))).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
|
||||||
|
|
||||||
// Remote Tier management operations
|
// IDentity Provider configuration APIs
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddTierHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}")
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.EditTierHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg))).Queries("type", "{type:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListTierHandler)))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}")
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveTierHandler)))
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.VerifyTierHandler)))
|
|
||||||
// Tier stats
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier-stats").HandlerFunc(gz(httpTraceHdrs(adminAPI.TierStatsHandler)))
|
|
||||||
|
|
||||||
// Cluster Replication APIs
|
// -- END IAM APIs --
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationAdd)))
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationRemove)))
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationInfo)))
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationMetaInfo)))
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationStatus)))
|
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/join").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerJoin)))
|
// GetBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/peer/bucket-ops").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerBucketOps))).Queries("bucket", "{bucket:.*}").Queries("operation", "{operation:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/iam-item").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateIAMItem)))
|
gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/bucket-meta").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateBucketItem)))
|
// PutBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerGetIDPSettings)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
gz(httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
|
// Bucket replication operations
|
||||||
}
|
// GetBucketTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.ListRemoteTargetsHandler))).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||||
|
// SetRemoteTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.SetRemoteTargetHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
|
// RemoveRemoteTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler))).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
|
// ReplicationDiff - MinIO extension API
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.ReplicationDiffHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
|
// Batch job operations
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/start-job").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.StartBatchJob)))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.ListBatchJobs)))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.DescribeBatchJob)))
|
||||||
|
|
||||||
|
// Bucket migration operations
|
||||||
|
// ExportBucketMetaHandler
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.ExportBucketMetadataHandler)))
|
||||||
|
// ImportBucketMetaHandler
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-bucket-metadata").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.ImportBucketMetadataHandler)))
|
||||||
|
|
||||||
|
// Remote Tier management operations
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddTierHandler)))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.EditTierHandler)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListTierHandler)))
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveTierHandler)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.VerifyTierHandler)))
|
||||||
|
// Tier stats
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier-stats").HandlerFunc(gz(httpTraceHdrs(adminAPI.TierStatsHandler)))
|
||||||
|
|
||||||
|
// Cluster Replication APIs
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationAdd)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationRemove)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationInfo)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationMetaInfo)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationStatus)))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/join").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerJoin)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/peer/bucket-ops").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerBucketOps))).Queries("bucket", "{bucket:.*}").Queries("operation", "{operation:.*}")
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/iam-item").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateIAMItem)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/bucket-meta").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateBucketItem)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerGetIDPSettings)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Top locks
|
// Top locks
|
||||||
@@ -221,8 +257,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
Queries("paths", "{paths:.*}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ForceUnlockHandler)))
|
Queries("paths", "{paths:.*}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ForceUnlockHandler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(httpTraceHdrs(adminAPI.SpeedtestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(httpTraceHdrs(adminAPI.SpeedTestHandler))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(httpTraceHdrs(adminAPI.ObjectSpeedtestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(httpTraceHdrs(adminAPI.ObjectSpeedTestHandler))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(httpTraceHdrs(adminAPI.DriveSpeedtestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(httpTraceHdrs(adminAPI.DriveSpeedtestHandler))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(httpTraceHdrs(adminAPI.NetperfHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(httpTraceHdrs(adminAPI.NetperfHandler))
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +35,10 @@ import (
|
|||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
||||||
var localEndpoints Endpoints
|
var localEndpoints Endpoints
|
||||||
addr := r.Host
|
addr := globalLocalNodeName
|
||||||
|
if r != nil {
|
||||||
|
addr = r.Host
|
||||||
|
}
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
addr = globalLocalNodeName
|
addr = globalLocalNodeName
|
||||||
}
|
}
|
||||||
@@ -40,7 +47,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
for _, endpoint := range ep.Endpoints {
|
for _, endpoint := range ep.Endpoints {
|
||||||
nodeName := endpoint.Host
|
nodeName := endpoint.Host
|
||||||
if nodeName == "" {
|
if nodeName == "" {
|
||||||
nodeName = r.Host
|
nodeName = addr
|
||||||
}
|
}
|
||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
@@ -64,6 +71,22 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
var memstats runtime.MemStats
|
var memstats runtime.MemStats
|
||||||
runtime.ReadMemStats(&memstats)
|
runtime.ReadMemStats(&memstats)
|
||||||
|
|
||||||
|
gcStats := debug.GCStats{
|
||||||
|
// If stats.PauseQuantiles is non-empty, ReadGCStats fills
|
||||||
|
// it with quantiles summarizing the distribution of pause time.
|
||||||
|
// For example, if len(stats.PauseQuantiles) is 5, it will be
|
||||||
|
// filled with the minimum, 25%, 50%, 75%, and maximum pause times.
|
||||||
|
PauseQuantiles: make([]time.Duration, 5),
|
||||||
|
}
|
||||||
|
debug.ReadGCStats(&gcStats)
|
||||||
|
// Truncate GC stats to max 5 entries.
|
||||||
|
if len(gcStats.PauseEnd) > 5 {
|
||||||
|
gcStats.PauseEnd = gcStats.PauseEnd[len(gcStats.PauseEnd)-5:]
|
||||||
|
}
|
||||||
|
if len(gcStats.Pause) > 5 {
|
||||||
|
gcStats.Pause = gcStats.Pause[len(gcStats.Pause)-5:]
|
||||||
|
}
|
||||||
|
|
||||||
props := madmin.ServerProperties{
|
props := madmin.ServerProperties{
|
||||||
State: string(madmin.ItemInitializing),
|
State: string(madmin.ItemInitializing),
|
||||||
Endpoint: addr,
|
Endpoint: addr,
|
||||||
@@ -78,6 +101,44 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
Frees: memstats.Frees,
|
Frees: memstats.Frees,
|
||||||
HeapAlloc: memstats.HeapAlloc,
|
HeapAlloc: memstats.HeapAlloc,
|
||||||
},
|
},
|
||||||
|
GoMaxProcs: runtime.GOMAXPROCS(0),
|
||||||
|
NumCPU: runtime.NumCPU(),
|
||||||
|
RuntimeVersion: runtime.Version(),
|
||||||
|
GCStats: &madmin.GCStats{
|
||||||
|
LastGC: gcStats.LastGC,
|
||||||
|
NumGC: gcStats.NumGC,
|
||||||
|
PauseTotal: gcStats.PauseTotal,
|
||||||
|
Pause: gcStats.Pause,
|
||||||
|
PauseEnd: gcStats.PauseEnd,
|
||||||
|
},
|
||||||
|
MinioEnvVars: make(map[string]string, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
sensitive := map[string]struct{}{
|
||||||
|
config.EnvAccessKey: {},
|
||||||
|
config.EnvSecretKey: {},
|
||||||
|
config.EnvRootUser: {},
|
||||||
|
config.EnvRootPassword: {},
|
||||||
|
config.EnvMinIOSubnetAPIKey: {},
|
||||||
|
config.EnvKMSSecretKey: {},
|
||||||
|
}
|
||||||
|
for _, v := range os.Environ() {
|
||||||
|
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
split := strings.SplitN(v, "=", 2)
|
||||||
|
key := split[0]
|
||||||
|
value := ""
|
||||||
|
if len(split) > 1 {
|
||||||
|
value = split[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not send sensitive creds.
|
||||||
|
if _, ok := sensitive[key]; ok || strings.Contains(strings.ToLower(key), "password") || strings.HasSuffix(strings.ToLower(key), "key") {
|
||||||
|
props.MinioEnvVars[key] = "*** EXISTS, REDACTED ***"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
props.MinioEnvVars[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
|
|||||||
+92
-13
@@ -30,7 +30,8 @@ import (
|
|||||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v7"
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
@@ -131,7 +132,7 @@ const (
|
|||||||
ErrReplicationNeedsVersioningError
|
ErrReplicationNeedsVersioningError
|
||||||
ErrReplicationBucketNeedsVersioningError
|
ErrReplicationBucketNeedsVersioningError
|
||||||
ErrReplicationDenyEditError
|
ErrReplicationDenyEditError
|
||||||
ErrReplicationNoMatchingRuleError
|
ErrReplicationNoExistingObjects
|
||||||
ErrObjectRestoreAlreadyInProgress
|
ErrObjectRestoreAlreadyInProgress
|
||||||
ErrNoSuchKey
|
ErrNoSuchKey
|
||||||
ErrNoSuchUpload
|
ErrNoSuchUpload
|
||||||
@@ -176,6 +177,7 @@ const (
|
|||||||
ErrBucketAlreadyOwnedByYou
|
ErrBucketAlreadyOwnedByYou
|
||||||
ErrInvalidDuration
|
ErrInvalidDuration
|
||||||
ErrBucketAlreadyExists
|
ErrBucketAlreadyExists
|
||||||
|
ErrTooManyBuckets
|
||||||
ErrMetadataTooLarge
|
ErrMetadataTooLarge
|
||||||
ErrUnsupportedMetadata
|
ErrUnsupportedMetadata
|
||||||
ErrMaximumExpires
|
ErrMaximumExpires
|
||||||
@@ -196,8 +198,9 @@ const (
|
|||||||
ErrInvalidTagDirective
|
ErrInvalidTagDirective
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
|
|
||||||
// SSE-S3 related API errors
|
// SSE-S3/SSE-KMS related API errors
|
||||||
ErrInvalidEncryptionMethod
|
ErrInvalidEncryptionMethod
|
||||||
|
ErrInvalidEncryptionKeyID
|
||||||
|
|
||||||
// Server-Side-Encryption (with Customer provided key) related API errors.
|
// Server-Side-Encryption (with Customer provided key) related API errors.
|
||||||
ErrInsecureSSECustomerRequest
|
ErrInsecureSSECustomerRequest
|
||||||
@@ -231,6 +234,7 @@ const (
|
|||||||
|
|
||||||
// S3 extended errors.
|
// S3 extended errors.
|
||||||
ErrContentSHA256Mismatch
|
ErrContentSHA256Mismatch
|
||||||
|
ErrContentChecksumMismatch
|
||||||
|
|
||||||
// Add new extended error codes here.
|
// Add new extended error codes here.
|
||||||
|
|
||||||
@@ -260,6 +264,7 @@ const (
|
|||||||
ErrAdminNoSuchUser
|
ErrAdminNoSuchUser
|
||||||
ErrAdminNoSuchGroup
|
ErrAdminNoSuchGroup
|
||||||
ErrAdminGroupNotEmpty
|
ErrAdminGroupNotEmpty
|
||||||
|
ErrAdminNoSuchJob
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
ErrAdminInvalidArgument
|
ErrAdminInvalidArgument
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
@@ -267,7 +272,11 @@ const (
|
|||||||
ErrAdminConfigNoQuorum
|
ErrAdminConfigNoQuorum
|
||||||
ErrAdminConfigTooLarge
|
ErrAdminConfigTooLarge
|
||||||
ErrAdminConfigBadJSON
|
ErrAdminConfigBadJSON
|
||||||
|
ErrAdminNoSuchConfigTarget
|
||||||
|
ErrAdminConfigEnvOverridden
|
||||||
ErrAdminConfigDuplicateKeys
|
ErrAdminConfigDuplicateKeys
|
||||||
|
ErrAdminConfigInvalidIDPType
|
||||||
|
ErrAdminConfigLDAPValidation
|
||||||
ErrAdminCredentialsMismatch
|
ErrAdminCredentialsMismatch
|
||||||
ErrInsecureClientRequest
|
ErrInsecureClientRequest
|
||||||
ErrObjectTampered
|
ErrObjectTampered
|
||||||
@@ -280,6 +289,7 @@ const (
|
|||||||
ErrSiteReplicationBucketConfigError
|
ErrSiteReplicationBucketConfigError
|
||||||
ErrSiteReplicationBucketMetaError
|
ErrSiteReplicationBucketMetaError
|
||||||
ErrSiteReplicationIAMError
|
ErrSiteReplicationIAMError
|
||||||
|
ErrSiteReplicationConfigMissing
|
||||||
|
|
||||||
// Bucket Quota error codes
|
// Bucket Quota error codes
|
||||||
ErrAdminBucketQuotaExceeded
|
ErrAdminBucketQuotaExceeded
|
||||||
@@ -383,10 +393,13 @@ const (
|
|||||||
ErrAdminProfilerNotEnabled
|
ErrAdminProfilerNotEnabled
|
||||||
ErrInvalidDecompressedSize
|
ErrInvalidDecompressedSize
|
||||||
ErrAddUserInvalidArgument
|
ErrAddUserInvalidArgument
|
||||||
|
ErrAdminResourceInvalidArgument
|
||||||
ErrAdminAccountNotEligible
|
ErrAdminAccountNotEligible
|
||||||
ErrAccountNotEligible
|
ErrAccountNotEligible
|
||||||
ErrAdminServiceAccountNotFound
|
ErrAdminServiceAccountNotFound
|
||||||
ErrPostPolicyConditionInvalidFormat
|
ErrPostPolicyConditionInvalidFormat
|
||||||
|
|
||||||
|
ErrInvalidChecksum
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorCodeMap map[APIErrorCode]APIError
|
type errorCodeMap map[APIErrorCode]APIError
|
||||||
@@ -676,6 +689,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrTooManyBuckets: {
|
||||||
|
Code: "TooManyBuckets",
|
||||||
|
Description: "You have attempted to create more buckets than allowed",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrBucketNotEmpty: {
|
ErrBucketNotEmpty: {
|
||||||
Code: "BucketNotEmpty",
|
Code: "BucketNotEmpty",
|
||||||
Description: "The bucket you tried to delete is not empty",
|
Description: "The bucket you tried to delete is not empty",
|
||||||
@@ -883,7 +901,7 @@ var errorCodes = errorCodeMap{
|
|||||||
},
|
},
|
||||||
ErrReplicationRemoteConnectionError: {
|
ErrReplicationRemoteConnectionError: {
|
||||||
Code: "XMinioAdminReplicationRemoteConnectionError",
|
Code: "XMinioAdminReplicationRemoteConnectionError",
|
||||||
Description: "Remote service connection error - please check remote service credentials and target bucket",
|
Description: "Remote service connection error",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrReplicationBandwidthLimitError: {
|
ErrReplicationBandwidthLimitError: {
|
||||||
@@ -891,15 +909,15 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Bandwidth limit for remote target must be atleast 100MBps",
|
Description: "Bandwidth limit for remote target must be atleast 100MBps",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrReplicationNoMatchingRuleError: {
|
ErrReplicationNoExistingObjects: {
|
||||||
Code: "XMinioReplicationNoMatchingRule",
|
Code: "XMinioReplicationNoExistingObjects",
|
||||||
Description: "No matching replication rule found for this object prefix",
|
Description: "No matching ExistingsObjects rule enabled",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrReplicationDenyEditError: {
|
ErrReplicationDenyEditError: {
|
||||||
Code: "XMinioReplicationDenyEdit",
|
Code: "XMinioReplicationDenyEdit",
|
||||||
Description: "Cannot alter local replication config since this server is in a cluster replication setup",
|
Description: "Cannot alter local replication config since this server is in a cluster replication setup",
|
||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrBucketRemoteIdenticalToSource: {
|
ErrBucketRemoteIdenticalToSource: {
|
||||||
Code: "XMinioAdminRemoteIdenticalToSource",
|
Code: "XMinioAdminRemoteIdenticalToSource",
|
||||||
@@ -1068,6 +1086,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The encryption method specified is not supported",
|
Description: "The encryption method specified is not supported",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidEncryptionKeyID: {
|
||||||
|
Code: "InvalidRequest",
|
||||||
|
Description: "The specified KMS KeyID contains unsupported characters",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInsecureSSECustomerRequest: {
|
ErrInsecureSSECustomerRequest: {
|
||||||
Code: "InvalidRequest",
|
Code: "InvalidRequest",
|
||||||
Description: "Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection.",
|
Description: "Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection.",
|
||||||
@@ -1150,11 +1173,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrContentChecksumMismatch: {
|
||||||
|
Code: "XAmzContentChecksumMismatch",
|
||||||
|
Description: "The provided 'x-amz-checksum' header does not match what was computed.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
// MinIO extensions.
|
// MinIO extensions.
|
||||||
ErrStorageFull: {
|
ErrStorageFull: {
|
||||||
Code: "XMinioStorageFull",
|
Code: "XMinioStorageFull",
|
||||||
Description: "Storage backend has reached its minimum free disk threshold. Please delete a few objects to proceed.",
|
Description: "Storage backend has reached its minimum free drive threshold. Please delete a few objects to proceed.",
|
||||||
HTTPStatusCode: http.StatusInsufficientStorage,
|
HTTPStatusCode: http.StatusInsufficientStorage,
|
||||||
},
|
},
|
||||||
ErrRequestBodyParse: {
|
ErrRequestBodyParse: {
|
||||||
@@ -1165,7 +1193,7 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrObjectExistsAsDirectory: {
|
ErrObjectExistsAsDirectory: {
|
||||||
Code: "XMinioObjectExistsAsDirectory",
|
Code: "XMinioObjectExistsAsDirectory",
|
||||||
Description: "Object name already exists as a directory.",
|
Description: "Object name already exists as a directory.",
|
||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrInvalidObjectName: {
|
ErrInvalidObjectName: {
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: "XMinioInvalidObjectName",
|
||||||
@@ -1202,6 +1230,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The specified group does not exist.",
|
Description: "The specified group does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoSuchJob: {
|
||||||
|
Code: "XMinioAdminNoSuchJob",
|
||||||
|
Description: "The specified job does not exist.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrAdminGroupNotEmpty: {
|
ErrAdminGroupNotEmpty: {
|
||||||
Code: "XMinioAdminGroupNotEmpty",
|
Code: "XMinioAdminGroupNotEmpty",
|
||||||
Description: "The specified group is not empty - cannot remove it.",
|
Description: "The specified group is not empty - cannot remove it.",
|
||||||
@@ -1238,16 +1271,36 @@ var errorCodes = errorCodeMap{
|
|||||||
maxEConfigJSONSize),
|
maxEConfigJSONSize),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoSuchConfigTarget: {
|
||||||
|
Code: "XMinioAdminNoSuchConfigTarget",
|
||||||
|
Description: "No such named configuration target exists",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigBadJSON: {
|
ErrAdminConfigBadJSON: {
|
||||||
Code: "XMinioAdminConfigBadJSON",
|
Code: "XMinioAdminConfigBadJSON",
|
||||||
Description: "JSON configuration provided is of incorrect format",
|
Description: "JSON configuration provided is of incorrect format",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigEnvOverridden: {
|
||||||
|
Code: "XMinioAdminConfigEnvOverridden",
|
||||||
|
Description: "Unable to update config via Admin API due to environment variable override",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigDuplicateKeys: {
|
ErrAdminConfigDuplicateKeys: {
|
||||||
Code: "XMinioAdminConfigDuplicateKeys",
|
Code: "XMinioAdminConfigDuplicateKeys",
|
||||||
Description: "JSON configuration provided has objects with duplicate keys",
|
Description: "JSON configuration provided has objects with duplicate keys",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigInvalidIDPType: {
|
||||||
|
Code: "XMinioAdminConfigInvalidIDPType",
|
||||||
|
Description: fmt.Sprintf("Invalid IDP configuration type - must be one of %v", madmin.ValidIDPConfigTypes),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigLDAPValidation: {
|
||||||
|
Code: "XMinioAdminConfigLDAPValidation",
|
||||||
|
Description: "LDAP Configuration validation failed",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigNotificationTargetsFailed: {
|
ErrAdminConfigNotificationTargetsFailed: {
|
||||||
Code: "XMinioAdminNotificationTargetsTestFailed",
|
Code: "XMinioAdminNotificationTargetsTestFailed",
|
||||||
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
||||||
@@ -1339,7 +1392,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Error while replicating an IAM item",
|
Description: "Error while replicating an IAM item",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
|
ErrSiteReplicationConfigMissing: {
|
||||||
|
Code: "XMinioSiteReplicationConfigMissingError",
|
||||||
|
Description: "Site not found in site replication configuration",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrMaximumExpires: {
|
ErrMaximumExpires: {
|
||||||
Code: "AuthorizationQueryParametersError",
|
Code: "AuthorizationQueryParametersError",
|
||||||
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
||||||
@@ -1825,6 +1882,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "User is not allowed to be same as admin access key",
|
Description: "User is not allowed to be same as admin access key",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
|
ErrAdminResourceInvalidArgument: {
|
||||||
|
Code: "XMinioInvalidResource",
|
||||||
|
Description: "Policy, user or group names are not allowed to begin or end with space characters",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminAccountNotEligible: {
|
ErrAdminAccountNotEligible: {
|
||||||
Code: "XMinioInvalidIAMCredentials",
|
Code: "XMinioInvalidIAMCredentials",
|
||||||
Description: "The administrator key is not eligible for this operation",
|
Description: "The administrator key is not eligible for this operation",
|
||||||
@@ -1845,6 +1907,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid according to Policy: Policy Condition failed",
|
Description: "Invalid according to Policy: Policy Condition failed",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
|
ErrInvalidChecksum: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Invalid checksum provided.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1875,6 +1942,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminNoSuchGroup
|
apiErr = ErrAdminNoSuchGroup
|
||||||
case errGroupNotEmpty:
|
case errGroupNotEmpty:
|
||||||
apiErr = ErrAdminGroupNotEmpty
|
apiErr = ErrAdminGroupNotEmpty
|
||||||
|
case errNoSuchJob:
|
||||||
|
apiErr = ErrAdminNoSuchJob
|
||||||
case errNoSuchPolicy:
|
case errNoSuchPolicy:
|
||||||
apiErr = ErrAdminNoSuchPolicy
|
apiErr = ErrAdminNoSuchPolicy
|
||||||
case errSignatureMismatch:
|
case errSignatureMismatch:
|
||||||
@@ -1898,6 +1967,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrInvalidEncryptionParameters
|
apiErr = ErrInvalidEncryptionParameters
|
||||||
case crypto.ErrInvalidEncryptionMethod:
|
case crypto.ErrInvalidEncryptionMethod:
|
||||||
apiErr = ErrInvalidEncryptionMethod
|
apiErr = ErrInvalidEncryptionMethod
|
||||||
|
case crypto.ErrInvalidEncryptionKeyID:
|
||||||
|
apiErr = ErrInvalidEncryptionKeyID
|
||||||
case crypto.ErrInvalidCustomerAlgorithm:
|
case crypto.ErrInvalidCustomerAlgorithm:
|
||||||
apiErr = ErrInvalidSSECustomerAlgorithm
|
apiErr = ErrInvalidSSECustomerAlgorithm
|
||||||
case crypto.ErrMissingCustomerKey:
|
case crypto.ErrMissingCustomerKey:
|
||||||
@@ -2015,6 +2086,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrSignatureDoesNotMatch
|
apiErr = ErrSignatureDoesNotMatch
|
||||||
case hash.SHA256Mismatch:
|
case hash.SHA256Mismatch:
|
||||||
apiErr = ErrContentSHA256Mismatch
|
apiErr = ErrContentSHA256Mismatch
|
||||||
|
case hash.ChecksumMismatch:
|
||||||
|
apiErr = ErrContentChecksumMismatch
|
||||||
case ObjectTooLarge:
|
case ObjectTooLarge:
|
||||||
apiErr = ErrEntityTooLarge
|
apiErr = ErrEntityTooLarge
|
||||||
case ObjectTooSmall:
|
case ObjectTooSmall:
|
||||||
@@ -2043,7 +2116,7 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrRemoteDestinationNotFoundError
|
apiErr = ErrRemoteDestinationNotFoundError
|
||||||
case BucketRemoteTargetNotFound:
|
case BucketRemoteTargetNotFound:
|
||||||
apiErr = ErrRemoteTargetNotFoundError
|
apiErr = ErrRemoteTargetNotFoundError
|
||||||
case BucketRemoteConnectionErr:
|
case RemoteTargetConnectionErr:
|
||||||
apiErr = ErrReplicationRemoteConnectionError
|
apiErr = ErrReplicationRemoteConnectionError
|
||||||
case BucketRemoteAlreadyExists:
|
case BucketRemoteAlreadyExists:
|
||||||
apiErr = ErrBucketRemoteAlreadyExists
|
apiErr = ErrBucketRemoteAlreadyExists
|
||||||
@@ -2168,6 +2241,12 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
// their internal error types. This code is only
|
// their internal error types. This code is only
|
||||||
// useful with gateway implementations.
|
// useful with gateway implementations.
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
|
case batchReplicationJobError:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: e.Code,
|
||||||
|
Description: e.Description,
|
||||||
|
HTTPStatusCode: e.HTTPStatusCode,
|
||||||
|
}
|
||||||
case InvalidArgument:
|
case InvalidArgument:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -2220,7 +2299,7 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
case crypto.Error:
|
case crypto.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinIOEncryptionError",
|
Code: "XMinioEncryptionError",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-4
@@ -20,7 +20,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -30,6 +29,8 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns a hexadecimal representation of time at the
|
// Returns a hexadecimal representation of time at the
|
||||||
@@ -64,9 +65,13 @@ func setCommonHeaders(w http.ResponseWriter) {
|
|||||||
// Encodes the response headers into XML format.
|
// Encodes the response headers into XML format.
|
||||||
func encodeResponse(response interface{}) []byte {
|
func encodeResponse(response interface{}) []byte {
|
||||||
var bytesBuffer bytes.Buffer
|
var bytesBuffer bytes.Buffer
|
||||||
bytesBuffer.WriteString(xml.Header)
|
bytesBuffer.WriteString(xxml.Header)
|
||||||
e := xml.NewEncoder(&bytesBuffer)
|
buf, err := xxml.Marshal(response)
|
||||||
e.Encode(response)
|
if err != nil {
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bytesBuffer.Write(buf)
|
||||||
return bytesBuffer.Bytes()
|
return bytesBuffer.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,5 +204,12 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
|
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := objInfo.UserDefined[ReservedMetadataPrefix+"compression"]; ok {
|
||||||
|
if i := strings.LastIndexByte(v, '/'); i >= 0 {
|
||||||
|
v = v[i+1:]
|
||||||
|
}
|
||||||
|
w.Header()[xhttp.MinIOCompressed] = []string{v}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-31
@@ -31,8 +31,10 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -162,6 +164,12 @@ type Part struct {
|
|||||||
LastModified string
|
LastModified string
|
||||||
ETag string
|
ETag string
|
||||||
Size int64
|
Size int64
|
||||||
|
|
||||||
|
// Checksum values
|
||||||
|
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||||
|
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||||
|
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||||
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPartsResponse - format for list parts response.
|
// ListPartsResponse - format for list parts response.
|
||||||
@@ -183,6 +191,7 @@ type ListPartsResponse struct {
|
|||||||
MaxParts int
|
MaxParts int
|
||||||
IsTruncated bool
|
IsTruncated bool
|
||||||
|
|
||||||
|
ChecksumAlgorithm string
|
||||||
// List of parts.
|
// List of parts.
|
||||||
Parts []Part `xml:"Part"`
|
Parts []Part `xml:"Part"`
|
||||||
}
|
}
|
||||||
@@ -252,45 +261,88 @@ type ObjectVersion struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML - marshal ObjectVersion
|
// MarshalXML - marshal ObjectVersion
|
||||||
func (o ObjectVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (o ObjectVersion) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||||
if o.isDeleteMarker {
|
if o.isDeleteMarker {
|
||||||
start.Name.Local = "DeleteMarker"
|
start.Name.Local = "DeleteMarker"
|
||||||
} else {
|
} else {
|
||||||
start.Name.Local = "Version"
|
start.Name.Local = "Version"
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectVersionWrapper ObjectVersion
|
type objectVersionWrapper ObjectVersion
|
||||||
return e.EncodeElement(objectVersionWrapper(o), start)
|
return e.EncodeElement(objectVersionWrapper(o), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringMap is a map[string]string
|
// DeleteMarkerVersion container for delete marker metadata
|
||||||
type StringMap map[string]string
|
type DeleteMarkerVersion struct {
|
||||||
|
Key string
|
||||||
|
LastModified string // time string of format "2006-01-02T15:04:05.000Z"
|
||||||
|
|
||||||
|
// Owner of the object.
|
||||||
|
Owner Owner
|
||||||
|
|
||||||
|
IsLatest bool
|
||||||
|
VersionID string `xml:"VersionId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata metadata items implemented to ensure XML marshaling works.
|
||||||
|
type Metadata struct {
|
||||||
|
Items []struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set add items, duplicate items get replaced.
|
||||||
|
func (s *Metadata) Set(k, v string) {
|
||||||
|
for i, item := range s.Items {
|
||||||
|
if item.Key == k {
|
||||||
|
s.Items[i] = struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}{
|
||||||
|
Key: k,
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Items = append(s.Items, struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}{
|
||||||
|
Key: k,
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type xmlKeyEntry struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalXML - StringMap marshals into XML.
|
// MarshalXML - StringMap marshals into XML.
|
||||||
func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
tokens := []xml.Token{start}
|
if s == nil {
|
||||||
|
return nil
|
||||||
for key, value := range s {
|
|
||||||
t := xml.StartElement{}
|
|
||||||
t.Name = xml.Name{
|
|
||||||
Space: "",
|
|
||||||
Local: key,
|
|
||||||
}
|
|
||||||
tokens = append(tokens, t, xml.CharData(value), xml.EndElement{Name: t.Name})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, xml.EndElement{
|
if len(s.Items) == 0 {
|
||||||
Name: start.Name,
|
return nil
|
||||||
})
|
}
|
||||||
|
|
||||||
for _, t := range tokens {
|
if err := e.EncodeToken(start); err != nil {
|
||||||
if err := e.EncodeToken(t); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range s.Items {
|
||||||
|
if err := e.Encode(xmlKeyEntry{
|
||||||
|
XMLName: xml.Name{Local: item.Key},
|
||||||
|
Value: item.Value,
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush to ensure tokens are written
|
return e.EncodeToken(start.End())
|
||||||
return e.Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object container for object metadata
|
// Object container for object metadata
|
||||||
@@ -307,7 +359,7 @@ type Object struct {
|
|||||||
StorageClass string
|
StorageClass string
|
||||||
|
|
||||||
// UserMetadata user-defined metadata
|
// UserMetadata user-defined metadata
|
||||||
UserMetadata StringMap `xml:"UserMetadata,omitempty"`
|
UserMetadata *Metadata `xml:"UserMetadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
|
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
|
||||||
@@ -350,6 +402,11 @@ type CompleteMultipartUploadResponse struct {
|
|||||||
Bucket string
|
Bucket string
|
||||||
Key string
|
Key string
|
||||||
ETag string
|
ETag string
|
||||||
|
|
||||||
|
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||||
|
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||||
|
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||||
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteError structure.
|
// DeleteError structure.
|
||||||
@@ -438,6 +495,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
|
|
||||||
owner := Owner{
|
owner := Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
DisplayName: "minio",
|
DisplayName: "minio",
|
||||||
@@ -445,10 +503,10 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
data := ListVersionsResponse{}
|
data := ListVersionsResponse{}
|
||||||
|
|
||||||
for _, object := range resp.Objects {
|
for _, object := range resp.Objects {
|
||||||
content := ObjectVersion{}
|
|
||||||
if object.Name == "" {
|
if object.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
content := ObjectVersion{}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
@@ -569,14 +627,14 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
}
|
}
|
||||||
content.Owner = owner
|
content.Owner = owner
|
||||||
if metadata {
|
if metadata {
|
||||||
content.UserMetadata = make(StringMap)
|
content.UserMetadata = &Metadata{}
|
||||||
switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
|
switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
|
||||||
case crypto.S3:
|
case crypto.S3:
|
||||||
content.UserMetadata[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||||
case crypto.S3KMS:
|
case crypto.S3KMS:
|
||||||
content.UserMetadata[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||||
case crypto.SSEC:
|
case crypto.SSEC:
|
||||||
content.UserMetadata[xhttp.AmzServerSideEncryptionCustomerAlgorithm] = xhttp.AmzEncryptionAES
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) {
|
for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) {
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||||
@@ -588,7 +646,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
content.UserMetadata[k] = v
|
content.UserMetadata.Set(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contents = append(contents, content)
|
contents = append(contents, content)
|
||||||
@@ -642,14 +700,20 @@ func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) Initi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
|
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
|
||||||
func generateCompleteMultpartUploadResponse(bucket, key, location, etag string) CompleteMultipartUploadResponse {
|
func generateCompleteMultpartUploadResponse(bucket, key, location string, oi ObjectInfo) CompleteMultipartUploadResponse {
|
||||||
return CompleteMultipartUploadResponse{
|
cs := oi.decryptChecksums()
|
||||||
|
c := CompleteMultipartUploadResponse{
|
||||||
Location: location,
|
Location: location,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
// AWS S3 quotes the ETag in XML, make sure we are compatible here.
|
// AWS S3 quotes the ETag in XML, make sure we are compatible here.
|
||||||
ETag: "\"" + etag + "\"",
|
ETag: "\"" + oi.ETag + "\"",
|
||||||
|
ChecksumSHA1: cs[hash.ChecksumSHA1.String()],
|
||||||
|
ChecksumSHA256: cs[hash.ChecksumSHA256.String()],
|
||||||
|
ChecksumCRC32: cs[hash.ChecksumCRC32.String()],
|
||||||
|
ChecksumCRC32C: cs[hash.ChecksumCRC32C.String()],
|
||||||
}
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// generates ListPartsResponse from ListPartsInfo.
|
// generates ListPartsResponse from ListPartsInfo.
|
||||||
@@ -674,6 +738,7 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis
|
|||||||
listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
|
listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
|
||||||
listPartsResponse.IsTruncated = partsInfo.IsTruncated
|
listPartsResponse.IsTruncated = partsInfo.IsTruncated
|
||||||
listPartsResponse.NextPartNumberMarker = partsInfo.NextPartNumberMarker
|
listPartsResponse.NextPartNumberMarker = partsInfo.NextPartNumberMarker
|
||||||
|
listPartsResponse.ChecksumAlgorithm = partsInfo.ChecksumAlgorithm
|
||||||
|
|
||||||
listPartsResponse.Parts = make([]Part, len(partsInfo.Parts))
|
listPartsResponse.Parts = make([]Part, len(partsInfo.Parts))
|
||||||
for index, part := range partsInfo.Parts {
|
for index, part := range partsInfo.Parts {
|
||||||
@@ -682,6 +747,10 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis
|
|||||||
newPart.ETag = "\"" + part.ETag + "\""
|
newPart.ETag = "\"" + part.ETag + "\""
|
||||||
newPart.Size = part.Size
|
newPart.Size = part.Size
|
||||||
newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat)
|
newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat)
|
||||||
|
newPart.ChecksumCRC32 = part.ChecksumCRC32
|
||||||
|
newPart.ChecksumCRC32C = part.ChecksumCRC32C
|
||||||
|
newPart.ChecksumSHA1 = part.ChecksumSHA1
|
||||||
|
newPart.ChecksumSHA256 = part.ChecksumSHA256
|
||||||
listPartsResponse.Parts[index] = newPart
|
listPartsResponse.Parts[index] = newPart
|
||||||
}
|
}
|
||||||
return listPartsResponse
|
return listPartsResponse
|
||||||
@@ -728,6 +797,14 @@ func generateMultiDeleteResponse(quiet bool, deletedObjects []DeletedObject, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
|
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
|
||||||
|
if statusCode == 0 {
|
||||||
|
statusCode = 200
|
||||||
|
}
|
||||||
|
// Similar check to http.checkWriteHeaderCode
|
||||||
|
if statusCode < 100 || statusCode > 999 {
|
||||||
|
logger.Error(fmt.Sprintf("invalid WriteHeader code %v", statusCode))
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
if mType != mimeNone {
|
if mType != mimeNone {
|
||||||
w.Header().Set(xhttp.ContentType, string(mType))
|
w.Header().Set(xhttp.ContentType, string(mType))
|
||||||
@@ -791,6 +868,12 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Similar check to http.checkWriteHeaderCode
|
||||||
|
if err.HTTPStatusCode < 100 || err.HTTPStatusCode > 999 {
|
||||||
|
logger.Error(fmt.Sprintf("invalid WriteHeader code %v from %v", err.HTTPStatusCode, err.Code))
|
||||||
|
err.HTTPStatusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
||||||
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
||||||
|
|||||||
+2
-2
@@ -342,7 +342,7 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
collectAPIStats("getbucketnotification", maxClients(gz(httpTraceAll(api.GetBucketNotificationHandler))))).Queries("notification", "")
|
collectAPIStats("getbucketnotification", maxClients(gz(httpTraceAll(api.GetBucketNotificationHandler))))).Queries("notification", "")
|
||||||
// ListenNotification
|
// ListenNotification
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listennotification", maxClients(gz(httpTraceAll(api.ListenNotificationHandler))))).Queries("events", "{events:.*}")
|
collectAPIStats("listennotification", gz(httpTraceAll(api.ListenNotificationHandler)))).Queries("events", "{events:.*}")
|
||||||
// ResetBucketReplicationStatus - MinIO extension API
|
// ResetBucketReplicationStatus - MinIO extension API
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("resetbucketreplicationstatus", maxClients(gz(httpTraceAll(api.ResetBucketReplicationStatusHandler))))).Queries("replication-reset-status", "")
|
collectAPIStats("resetbucketreplicationstatus", maxClients(gz(httpTraceAll(api.ResetBucketReplicationStatusHandler))))).Queries("replication-reset-status", "")
|
||||||
@@ -474,7 +474,7 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
|
|
||||||
// ListenNotification
|
// ListenNotification
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
||||||
collectAPIStats("listennotification", maxClients(gz(httpTraceAll(api.ListenNotificationHandler))))).Queries("events", "{events:.*}")
|
collectAPIStats("listennotification", gz(httpTraceAll(api.ListenNotificationHandler)))).Queries("events", "{events:.*}")
|
||||||
|
|
||||||
// ListBuckets
|
// ListBuckets
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
||||||
|
|||||||
+2
-2
@@ -35,8 +35,8 @@ func shouldEscape(c byte) bool {
|
|||||||
|
|
||||||
// s3URLEncode is based on Golang's url.QueryEscape() code,
|
// s3URLEncode is based on Golang's url.QueryEscape() code,
|
||||||
// while considering some S3 exceptions:
|
// while considering some S3 exceptions:
|
||||||
// - Avoid encoding '/' and '*'
|
// - Avoid encoding '/' and '*'
|
||||||
// - Force encoding of '~'
|
// - Force encoding of '~'
|
||||||
func s3URLEncode(s string) string {
|
func s3URLEncode(s string) string {
|
||||||
spaceCount, hexCount := 0, 0
|
spaceCount, hexCount := 0, 0
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
|
|||||||
+198
-187
File diff suppressed because one or more lines are too long
+116
-54
@@ -25,7 +25,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -85,6 +84,8 @@ func isRequestSignStreamingV4(r *http.Request) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authorization type.
|
// Authorization type.
|
||||||
|
//
|
||||||
|
//go:generate stringer -type=authType -trimprefix=authType $GOFILE
|
||||||
type authType int
|
type authType int
|
||||||
|
|
||||||
// List of all supported auth types.
|
// List of all supported auth types.
|
||||||
@@ -217,8 +218,8 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If OPA is set, return without any further checks.
|
// If AuthZPlugin is set, return without any further checks.
|
||||||
if globalPolicyOPA != nil {
|
if newGlobalAuthZPluginFn() != nil {
|
||||||
return claims.Map(), nil
|
return claims.Map(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +236,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
logger.LogIf(GlobalContext, err, logger.Application)
|
logger.LogIf(GlobalContext, err, logger.Application)
|
||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
|
claims.MapClaims[sessionPolicyNameExtracted] = string(spBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return claims.Map(), nil
|
return claims.Map(), nil
|
||||||
@@ -288,28 +289,33 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check request auth type verifies the incoming http request
|
// Check request auth type verifies the incoming http request
|
||||||
// - validates the request signature
|
// - validates the request signature
|
||||||
// - validates the policy action if anonymous tests bucket policies if any,
|
// - validates the policy action if anonymous tests bucket policies if any,
|
||||||
// for authenticated requests validates IAM policies.
|
// for authenticated requests validates IAM policies.
|
||||||
|
//
|
||||||
// returns APIErrorCode if any to be replied to the client.
|
// returns APIErrorCode if any to be replied to the client.
|
||||||
func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) {
|
func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) {
|
||||||
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action, bucketName, objectName)
|
logger.GetReqInfo(ctx).BucketName = bucketName
|
||||||
|
logger.GetReqInfo(ctx).ObjectName = objectName
|
||||||
|
|
||||||
|
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action)
|
||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check request auth type verifies the incoming http request
|
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
// - validates the request signature
|
if logger.GetReqInfo(ctx) == nil {
|
||||||
// - validates the policy action if anonymous tests bucket policies if any,
|
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
||||||
// for authenticated requests validates IAM policies.
|
return ErrAccessDenied
|
||||||
// returns APIErrorCode if any to be replied to the client.
|
}
|
||||||
// Additionally returns the accessKey used in the request, and if this request is by an admin.
|
|
||||||
func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
|
var cred auth.Credentials
|
||||||
|
var owner bool
|
||||||
switch getRequestAuthType(r) {
|
switch getRequestAuthType(r) {
|
||||||
case authTypeUnknown, authTypeStreamingSigned:
|
case authTypeUnknown, authTypeStreamingSigned:
|
||||||
return cred, owner, ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
case authTypePresignedV2, authTypeSignedV2:
|
case authTypePresignedV2, authTypeSignedV2:
|
||||||
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
|
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeSigned, authTypePresigned:
|
case authTypeSigned, authTypePresigned:
|
||||||
@@ -319,52 +325,67 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
|||||||
region = ""
|
region = ""
|
||||||
}
|
}
|
||||||
if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone {
|
if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocationConstraint is valid only for CreateBucketAction.
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
var locationConstraint string
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
|
|
||||||
|
// region is valid only for CreateBucketAction.
|
||||||
|
var region string
|
||||||
if action == policy.CreateBucketAction {
|
if action == policy.CreateBucketAction {
|
||||||
// To extract region from XML in request body, get copy of request body.
|
// To extract region from XML in request body, get copy of request body.
|
||||||
payload, err := ioutil.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
return cred, owner, ErrMalformedXML
|
return ErrMalformedXML
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate payload to extract location constraint.
|
// Populate payload to extract location constraint.
|
||||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
r.Body = io.NopCloser(bytes.NewReader(payload))
|
||||||
|
region, s3Err = parseLocationConstraint(r)
|
||||||
var s3Error APIErrorCode
|
if s3Err != ErrNone {
|
||||||
locationConstraint, s3Error = parseLocationConstraint(r)
|
return s3Err
|
||||||
if s3Error != ErrNone {
|
|
||||||
return cred, owner, s3Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate payload again to handle it in HTTP handler.
|
// Populate payload again to handle it in HTTP handler.
|
||||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
r.Body = io.NopCloser(bytes.NewReader(payload))
|
||||||
}
|
|
||||||
if cred.AccessKey != "" {
|
|
||||||
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.GetReqInfo(ctx).Region = region
|
||||||
|
|
||||||
|
return s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
if reqInfo == nil {
|
||||||
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
cred := reqInfo.Cred
|
||||||
|
owner := reqInfo.Owner
|
||||||
|
region := reqInfo.Region
|
||||||
|
bucket := reqInfo.BucketName
|
||||||
|
object := reqInfo.ObjectName
|
||||||
|
|
||||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
// Anonymous checks are not meant for ListBuckets action
|
// Anonymous checks are not meant for ListAllBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: action,
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, locationConstraint, "", nil),
|
ConditionValues: getConditionValues(r, region, "", nil),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
@@ -373,31 +394,31 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
|||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, locationConstraint, "", nil),
|
ConditionValues: getConditionValues(r, region, "", nil),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cred, owner, ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: iampolicy.Action(action),
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
@@ -407,18 +428,41 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
|||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cred, owner, ErrAccessDenied
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check request auth type verifies the incoming http request
|
||||||
|
// - validates the request signature
|
||||||
|
// - validates the policy action if anonymous tests bucket policies if any,
|
||||||
|
// for authenticated requests validates IAM policies.
|
||||||
|
//
|
||||||
|
// returns APIErrorCode if any to be replied to the client.
|
||||||
|
// Additionally returns the accessKey used in the request, and if this request is by an admin.
|
||||||
|
func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
|
||||||
|
s3Err = authenticateRequest(ctx, r, action)
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
if reqInfo == nil {
|
||||||
|
return cred, owner, ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
cred = reqInfo.Cred
|
||||||
|
owner = reqInfo.Owner
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
return cred, owner, s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cred, owner, authorizeRequest(ctx, r, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request has valid AWS Signature Version '2'.
|
// Verify if request has valid AWS Signature Version '2'.
|
||||||
@@ -500,11 +544,18 @@ func isSupportedS3AuthType(aType authType) bool {
|
|||||||
func setAuthHandler(h http.Handler) http.Handler {
|
func setAuthHandler(h http.Handler) http.Handler {
|
||||||
// handler for validating incoming authorization headers.
|
// handler for validating incoming authorization headers.
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tc, ok := r.Context().Value(contextTraceReqKey).(*traceCtxt)
|
||||||
|
|
||||||
aType := getRequestAuthType(r)
|
aType := getRequestAuthType(r)
|
||||||
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
|
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
|
||||||
// Verify if date headers are set, if not reject the request
|
// Verify if date headers are set, if not reject the request
|
||||||
amzDate, errCode := parseAmzDateHeader(r)
|
amzDate, errCode := parseAmzDateHeader(r)
|
||||||
if errCode != ErrNone {
|
if errCode != ErrNone {
|
||||||
|
if ok {
|
||||||
|
tc.funcName = "handler.Auth"
|
||||||
|
tc.responseRecorder.LogErrBody = true
|
||||||
|
}
|
||||||
|
|
||||||
// All our internal APIs are sensitive towards Date
|
// All our internal APIs are sensitive towards Date
|
||||||
// header, for all requests where Date header is not
|
// header, for all requests where Date header is not
|
||||||
// present we will reject such clients.
|
// present we will reject such clients.
|
||||||
@@ -516,6 +567,11 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
// or in the future, reject request otherwise.
|
// or in the future, reject request otherwise.
|
||||||
curTime := UTCNow()
|
curTime := UTCNow()
|
||||||
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
|
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
|
||||||
|
if ok {
|
||||||
|
tc.funcName = "handler.Auth"
|
||||||
|
tc.responseRecorder.LogErrBody = true
|
||||||
|
}
|
||||||
|
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
|
||||||
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
||||||
return
|
return
|
||||||
@@ -525,6 +581,12 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
tc.funcName = "handler.Auth"
|
||||||
|
tc.responseRecorder.LogErrBody = true
|
||||||
|
}
|
||||||
|
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
|
||||||
atomic.AddUint64(&globalHTTPStats.rejectedRequestsAuth, 1)
|
atomic.AddUint64(&globalHTTPStats.rejectedRequestsAuth, 1)
|
||||||
})
|
})
|
||||||
@@ -604,22 +666,22 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
|
region := globalSite.Region
|
||||||
switch atype {
|
switch atype {
|
||||||
case authTypeUnknown:
|
case authTypeUnknown:
|
||||||
return ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
case authTypeSignedV2, authTypePresignedV2:
|
case authTypeSignedV2, authTypePresignedV2:
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
||||||
region := globalSite.Region
|
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.AccessKey != "" {
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
}
|
logger.GetReqInfo(ctx).Region = region
|
||||||
|
|
||||||
// Do not check for PutObjectRetentionAction permission,
|
// Do not check for PutObjectRetentionAction permission,
|
||||||
// if mode and retain until date are not set.
|
// if mode and retain until date are not set.
|
||||||
|
|||||||
+21
-14
@@ -21,7 +21,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -32,13 +31,19 @@ import (
|
|||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type nullReader struct{}
|
||||||
|
|
||||||
|
func (r *nullReader) Read(b []byte) (int, error) {
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Test get request auth type.
|
// Test get request auth type.
|
||||||
func TestGetRequestAuthType(t *testing.T) {
|
func TestGetRequestAuthType(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
req *http.Request
|
req *http.Request
|
||||||
authT authType
|
authT authType
|
||||||
}
|
}
|
||||||
nopCloser := ioutil.NopCloser(io.LimitReader(&nullReader{}, 1024))
|
nopCloser := io.NopCloser(io.LimitReader(&nullReader{}, 1024))
|
||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
// Test case - 1
|
// Test case - 1
|
||||||
// Check for generic signature v4 header.
|
// Check for generic signature v4 header.
|
||||||
@@ -341,7 +346,8 @@ func mustNewSignedEmptyMD5Request(method string, urlStr string, contentLength in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int64,
|
func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int64,
|
||||||
body io.ReadSeeker, t *testing.T) *http.Request {
|
body io.ReadSeeker, t *testing.T,
|
||||||
|
) *http.Request {
|
||||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||||
req.Header.Set("Content-Md5", "YWFhYWFhYWFhYWFhYWFhCg==")
|
req.Header.Set("Content-Md5", "YWFhYWFhYWFhYWFhYWFhCg==")
|
||||||
cred := globalActiveCred
|
cred := globalActiveCred
|
||||||
@@ -353,7 +359,10 @@ func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int6
|
|||||||
|
|
||||||
// Tests is requested authenticated function, tests replies for s3 errors.
|
// Tests is requested authenticated function, tests replies for s3 errors.
|
||||||
func TestIsReqAuthenticated(t *testing.T) {
|
func TestIsReqAuthenticated(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(GlobalContext)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -362,10 +371,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
@@ -399,7 +405,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)
|
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)
|
||||||
if s3Error != testCase.s3Error {
|
if s3Error != testCase.s3Error {
|
||||||
if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
||||||
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,7 +413,10 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckAdminRequestAuthType(t *testing.T) {
|
func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -433,7 +442,6 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
@@ -445,7 +453,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -455,8 +463,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by "stringer -type=authType -trimprefix=authType auth-handler.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[authTypeUnknown-0]
|
||||||
|
_ = x[authTypeAnonymous-1]
|
||||||
|
_ = x[authTypePresigned-2]
|
||||||
|
_ = x[authTypePresignedV2-3]
|
||||||
|
_ = x[authTypePostPolicy-4]
|
||||||
|
_ = x[authTypeStreamingSigned-5]
|
||||||
|
_ = x[authTypeSigned-6]
|
||||||
|
_ = x[authTypeSignedV2-7]
|
||||||
|
_ = x[authTypeJWT-8]
|
||||||
|
_ = x[authTypeSTS-9]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTS"
|
||||||
|
|
||||||
|
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81}
|
||||||
|
|
||||||
|
func (i authType) String() string {
|
||||||
|
if i < 0 || i >= authType(len(_authType_index)-1) {
|
||||||
|
return "authType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _authType_name[_authType_index[i]:_authType_index[i+1]]
|
||||||
|
}
|
||||||
@@ -22,12 +22,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio/internal/pubsub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
// path: '/' => Heal disk formats along with metadata
|
//
|
||||||
// path: 'bucket/' or '/bucket/' => Heal bucket
|
// path: '/' => Heal disk formats along with metadata
|
||||||
// path: 'bucket/object' => Heal object
|
// path: 'bucket/' or '/bucket/' => Heal bucket
|
||||||
|
// path: 'bucket/object' => Heal object
|
||||||
type healTask struct {
|
type healTask struct {
|
||||||
bucket string
|
bucket string
|
||||||
object string
|
object string
|
||||||
@@ -49,10 +51,10 @@ type healRoutine struct {
|
|||||||
workers int
|
workers int
|
||||||
}
|
}
|
||||||
|
|
||||||
func systemIO() int {
|
func activeListeners() int {
|
||||||
// Bucket notification and http trace are not costly, it is okay to ignore them
|
// Bucket notification and http trace are not costly, it is okay to ignore them
|
||||||
// while counting the number of concurrent connections
|
// while counting the number of concurrent connections
|
||||||
return int(globalHTTPListen.NumSubscribers()) + int(globalTrace.NumSubscribers())
|
return int(globalHTTPListen.NumSubscribers(pubsub.MaskAll)) + int(globalTrace.NumSubscribers(pubsub.MaskAll))
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForLowHTTPReq() {
|
func waitForLowHTTPReq() {
|
||||||
@@ -61,7 +63,7 @@ func waitForLowHTTPReq() {
|
|||||||
currentIO = httpServer.GetRequestCount
|
currentIO = httpServer.GetRequestCount
|
||||||
}
|
}
|
||||||
|
|
||||||
globalHealConfig.Wait(currentIO, systemIO)
|
globalHealConfig.Wait(currentIO, activeListeners)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
||||||
|
|||||||
+138
-158
@@ -26,15 +26,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/color"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -90,14 +87,14 @@ type healingTracker struct {
|
|||||||
// The disk ID will be validated against the loaded one.
|
// The disk ID will be validated against the loaded one.
|
||||||
func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker, error) {
|
func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker, error) {
|
||||||
if disk == nil {
|
if disk == nil {
|
||||||
return nil, errors.New("loadHealingTracker: nil disk given")
|
return nil, errors.New("loadHealingTracker: nil drive given")
|
||||||
}
|
}
|
||||||
diskID, err := disk.GetDiskID()
|
diskID, err := disk.GetDiskID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b, err := disk.ReadAll(ctx, minioMetaBucket,
|
b, err := disk.ReadAll(ctx, minioMetaBucket,
|
||||||
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename))
|
pathJoin(bucketMetaPrefix, healingTrackerFilename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -107,7 +104,7 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if h.ID != diskID && h.ID != "" {
|
if h.ID != diskID && h.ID != "" {
|
||||||
return nil, fmt.Errorf("loadHealingTracker: disk id mismatch expected %s, got %s", h.ID, diskID)
|
return nil, fmt.Errorf("loadHealingTracker: drive id mismatch expected %s, got %s", h.ID, diskID)
|
||||||
}
|
}
|
||||||
h.disk = disk
|
h.disk = disk
|
||||||
h.ID = diskID
|
h.ID = diskID
|
||||||
@@ -132,7 +129,7 @@ func newHealingTracker(disk StorageAPI) *healingTracker {
|
|||||||
// If the tracker has been deleted an error is returned.
|
// If the tracker has been deleted an error is returned.
|
||||||
func (h *healingTracker) update(ctx context.Context) error {
|
func (h *healingTracker) update(ctx context.Context) error {
|
||||||
if h.disk.Healing() == nil {
|
if h.disk.Healing() == nil {
|
||||||
return fmt.Errorf("healingTracker: disk %q is not marked as healing", h.ID)
|
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
|
||||||
}
|
}
|
||||||
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
||||||
h.ID, _ = h.disk.GetDiskID()
|
h.ID, _ = h.disk.GetDiskID()
|
||||||
@@ -158,15 +155,19 @@ func (h *healingTracker) save(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
globalBackgroundHealState.updateHealStatus(h)
|
globalBackgroundHealState.updateHealStatus(h)
|
||||||
return h.disk.WriteAll(ctx, minioMetaBucket,
|
return h.disk.WriteAll(ctx, minioMetaBucket,
|
||||||
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename),
|
pathJoin(bucketMetaPrefix, healingTrackerFilename),
|
||||||
htrackerBytes)
|
htrackerBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the tracker on disk.
|
// delete the tracker on disk.
|
||||||
func (h *healingTracker) delete(ctx context.Context) error {
|
func (h *healingTracker) delete(ctx context.Context) error {
|
||||||
return h.disk.Delete(ctx, minioMetaBucket,
|
return h.disk.Delete(ctx, minioMetaBucket,
|
||||||
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename),
|
pathJoin(bucketMetaPrefix, healingTrackerFilename),
|
||||||
false)
|
DeleteOptions{
|
||||||
|
Recursive: false,
|
||||||
|
Force: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healingTracker) isHealed(bucket string) bool {
|
func (h *healingTracker) isHealed(bucket string) bool {
|
||||||
@@ -258,26 +259,9 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
||||||
|
|
||||||
bgSeq := mustGetHealSequence(ctx)
|
|
||||||
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
|
|
||||||
if drivesToHeal := globalBackgroundHealState.healDriveCount(); drivesToHeal > 0 {
|
go monitorLocalDisksAndHeal(ctx, z)
|
||||||
logger.Info(fmt.Sprintf("Found drives to heal %d, waiting until %s to heal the content - use 'mc admin heal alias/ --verbose' to check the status",
|
|
||||||
drivesToHeal, defaultMonitorNewDiskInterval))
|
|
||||||
|
|
||||||
// Heal any disk format and metadata early, if possible.
|
|
||||||
// Start with format healing
|
|
||||||
if err := bgSeq.healDiskFormat(); err != nil {
|
|
||||||
if newObjectLayerFn() != nil {
|
|
||||||
// log only in situations, when object layer
|
|
||||||
// has fully initialized.
|
|
||||||
logger.LogIf(bgSeq.ctx, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go monitorLocalDisksAndHeal(ctx, z, bgSeq)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||||
@@ -299,10 +283,111 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
|||||||
return disksToHeal
|
return disksToHeal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||||
|
|
||||||
|
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
||||||
|
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
|
||||||
|
|
||||||
|
disk, format, err := connectEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
||||||
|
if poolIdx < 0 {
|
||||||
|
return fmt.Errorf("unexpected pool index (%d) found in %s", poolIdx, disk.Endpoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the set index where the current endpoint belongs
|
||||||
|
z.serverPools[poolIdx].erasureDisksMu.RLock()
|
||||||
|
setIdx, _, err := findDiskIndex(z.serverPools[poolIdx].format, format)
|
||||||
|
z.serverPools[poolIdx].erasureDisksMu.RUnlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if setIdx < 0 {
|
||||||
|
return fmt.Errorf("unexpected set index (%d) found in %s", setIdx, disk.Endpoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent parallel erasure set healing
|
||||||
|
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%s/%d/%d", endpoint, poolIdx, setIdx))
|
||||||
|
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer locker.Unlock(lkctx.Cancel)
|
||||||
|
|
||||||
|
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
||||||
|
// Buckets data are dispersed in multiple zones/sets, make
|
||||||
|
// sure to heal all bucket metadata configuration.
|
||||||
|
buckets = append(buckets, BucketInfo{
|
||||||
|
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
||||||
|
}, BucketInfo{
|
||||||
|
Name: pathJoin(minioMetaBucket, bucketMetaPrefix),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Heal latest buckets first.
|
||||||
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
a, b := strings.HasPrefix(buckets[i].Name, minioMetaBucket), strings.HasPrefix(buckets[j].Name, minioMetaBucket)
|
||||||
|
if a != b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
|
})
|
||||||
|
|
||||||
|
if serverDebugLog {
|
||||||
|
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load healing tracker in this disk
|
||||||
|
tracker, err := loadHealingTracker(ctx, disk)
|
||||||
|
if err != nil {
|
||||||
|
// So someone changed the drives underneath, healing tracker missing.
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', drive was swapped again on %s pool: %w",
|
||||||
|
disk, humanize.Ordinal(poolIdx+1), err))
|
||||||
|
tracker = newHealingTracker(disk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load bucket totals
|
||||||
|
cache := dataUsageCache{}
|
||||||
|
if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil {
|
||||||
|
dataUsageInfo := cache.dui(dataUsageRoot, nil)
|
||||||
|
tracker.ObjectsTotalCount = dataUsageInfo.ObjectsTotalCount
|
||||||
|
tracker.ObjectsTotalSize = dataUsageInfo.ObjectsTotalSize
|
||||||
|
}
|
||||||
|
|
||||||
|
tracker.PoolIndex, tracker.SetIndex, tracker.DiskIndex = disk.GetDiskLoc()
|
||||||
|
tracker.setQueuedBuckets(buckets)
|
||||||
|
if err := tracker.save(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start or resume healing of this erasure set
|
||||||
|
if err = z.serverPools[poolIdx].sets[setIdx].healErasureSet(ctx, tracker.QueuedBuckets, tracker); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tracker.ItemsFailed > 0 {
|
||||||
|
logger.Info("Healing drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
||||||
|
} else {
|
||||||
|
logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverDebugLog {
|
||||||
|
tracker.printTo(os.Stdout)
|
||||||
|
logger.Info("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogIf(ctx, tracker.delete(ctx))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// monitorLocalDisksAndHeal - ensures that detected new disks are healed
|
// monitorLocalDisksAndHeal - ensures that detected new disks are healed
|
||||||
// 1. Only the concerned erasure set will be listed and healed
|
// 1. Only the concerned erasure set will be listed and healed
|
||||||
// 2. Only the node hosting the disk is responsible to perform the heal
|
// 2. Only the node hosting the disk is responsible to perform the heal
|
||||||
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq *healSequence) {
|
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
|
||||||
// Perform automatic disk healing when a disk is replaced locally.
|
// Perform automatic disk healing when a disk is replaced locally.
|
||||||
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
|
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
|
||||||
defer diskCheckTimer.Stop()
|
defer diskCheckTimer.Stop()
|
||||||
@@ -312,142 +397,37 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-diskCheckTimer.C:
|
case <-diskCheckTimer.C:
|
||||||
// Reset to next interval.
|
|
||||||
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
|
||||||
|
|
||||||
var erasureSetInPoolDisksToHeal []map[int][]StorageAPI
|
|
||||||
|
|
||||||
healDisks := globalBackgroundHealState.getHealLocalDiskEndpoints()
|
healDisks := globalBackgroundHealState.getHealLocalDiskEndpoints()
|
||||||
if len(healDisks) > 0 {
|
if len(healDisks) == 0 {
|
||||||
// Reformat disks
|
// Reset for next interval.
|
||||||
bgSeq.queueHealTask(healSource{bucket: SlashSeparator}, madmin.HealItemMetadata)
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
|
continue
|
||||||
// Ensure that reformatting disks is finished
|
|
||||||
bgSeq.queueHealTask(healSource{bucket: nopHeal}, madmin.HealItemMetadata)
|
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("Found drives to heal %d, proceeding to heal - 'mc admin heal alias/ --verbose' to check the status.",
|
|
||||||
len(healDisks)))
|
|
||||||
|
|
||||||
erasureSetInPoolDisksToHeal = make([]map[int][]StorageAPI, len(z.serverPools))
|
|
||||||
for i := range z.serverPools {
|
|
||||||
erasureSetInPoolDisksToHeal[i] = map[int][]StorageAPI{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverDebugLog && len(healDisks) > 0 {
|
// Reformat disks immediately
|
||||||
console.Debugf(color.Green("healDisk:")+" disk check timer fired, attempting to heal %d drives\n", len(healDisks))
|
_, err := z.HealFormat(context.Background(), false)
|
||||||
|
if err != nil && !errors.Is(err, errNoHealRequired) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
// Reset for next interval.
|
||||||
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// heal only if new disks found.
|
for _, disk := range healDisks {
|
||||||
for _, endpoint := range healDisks {
|
go func(disk Endpoint) {
|
||||||
disk, format, err := connectEndpoint(endpoint)
|
globalBackgroundHealState.markDiskForHealing(disk)
|
||||||
if err != nil {
|
err := healFreshDisk(ctx, z, disk)
|
||||||
printEndpointError(endpoint, err, true)
|
if err != nil {
|
||||||
continue
|
printEndpointError(disk, err, false)
|
||||||
}
|
return
|
||||||
|
|
||||||
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
|
||||||
if poolIdx < 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the set index where the current endpoint belongs
|
|
||||||
z.serverPools[poolIdx].erasureDisksMu.RLock()
|
|
||||||
// Protect reading reference format.
|
|
||||||
setIndex, _, err := findDiskIndex(z.serverPools[poolIdx].format, format)
|
|
||||||
z.serverPools[poolIdx].erasureDisksMu.RUnlock()
|
|
||||||
if err != nil {
|
|
||||||
printEndpointError(endpoint, err, false)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
erasureSetInPoolDisksToHeal[poolIdx][setIndex] = append(erasureSetInPoolDisksToHeal[poolIdx][setIndex], disk)
|
|
||||||
}
|
|
||||||
|
|
||||||
buckets, _ := z.ListBuckets(ctx)
|
|
||||||
|
|
||||||
// Buckets data are dispersed in multiple zones/sets, make
|
|
||||||
// sure to heal all bucket metadata configuration.
|
|
||||||
buckets = append(buckets, BucketInfo{
|
|
||||||
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
|
||||||
}, BucketInfo{
|
|
||||||
Name: pathJoin(minioMetaBucket, bucketMetaPrefix),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Heal latest buckets first.
|
|
||||||
sort.Slice(buckets, func(i, j int) bool {
|
|
||||||
a, b := strings.HasPrefix(buckets[i].Name, minioMetaBucket), strings.HasPrefix(buckets[j].Name, minioMetaBucket)
|
|
||||||
if a != b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return buckets[i].Created.After(buckets[j].Created)
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO(klauspost): This will block until all heals are done,
|
|
||||||
// in the future this should be able to start healing other sets at once.
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i, setMap := range erasureSetInPoolDisksToHeal {
|
|
||||||
i := i
|
|
||||||
for setIndex, disks := range setMap {
|
|
||||||
if len(disks) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
// Only upon success pop the healed disk.
|
||||||
go func(setIndex int, disks []StorageAPI) {
|
globalBackgroundHealState.popHealLocalDisks(disk)
|
||||||
defer wg.Done()
|
}(disk)
|
||||||
for _, disk := range disks {
|
|
||||||
if serverDebugLog {
|
|
||||||
logger.Info("Healing disk '%v' on %s pool", disk, humanize.Ordinal(i+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// So someone changed the drives underneath, healing tracker missing.
|
|
||||||
tracker, err := loadHealingTracker(ctx, disk)
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', disk was swapped again on %s pool: %w",
|
|
||||||
disk, humanize.Ordinal(i+1), err))
|
|
||||||
tracker = newHealingTracker(disk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load bucket totals
|
|
||||||
cache := dataUsageCache{}
|
|
||||||
if err := cache.load(ctx, z.serverPools[i].sets[setIndex], dataUsageCacheName); err == nil {
|
|
||||||
dataUsageInfo := cache.dui(dataUsageRoot, nil)
|
|
||||||
tracker.ObjectsTotalCount = dataUsageInfo.ObjectsTotalCount
|
|
||||||
tracker.ObjectsTotalSize = dataUsageInfo.ObjectsTotalSize
|
|
||||||
}
|
|
||||||
|
|
||||||
tracker.PoolIndex, tracker.SetIndex, tracker.DiskIndex = disk.GetDiskLoc()
|
|
||||||
tracker.setQueuedBuckets(buckets)
|
|
||||||
if err := tracker.save(ctx); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
// Unable to write healing tracker, permission denied or some
|
|
||||||
// other unexpected error occurred. Proceed to look for new
|
|
||||||
// disks to be healed again, we cannot proceed further.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = z.serverPools[i].sets[setIndex].healErasureSet(ctx, tracker.QueuedBuckets, tracker)
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverDebugLog {
|
|
||||||
logger.Info("Healing disk '%s' on %s pool, %s set complete", disk,
|
|
||||||
humanize.Ordinal(i+1), humanize.Ordinal(setIndex+1))
|
|
||||||
logger.Info("Summary:\n")
|
|
||||||
tracker.printTo(os.Stdout)
|
|
||||||
logger.Info("\n")
|
|
||||||
}
|
|
||||||
logger.LogIf(ctx, tracker.delete(ctx))
|
|
||||||
|
|
||||||
// Only upon success pop the healed disk.
|
|
||||||
globalBackgroundHealState.popHealLocalDisks(disk.Endpoint())
|
|
||||||
}
|
|
||||||
}(setIndex, disks)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
|
// Reset for next interval.
|
||||||
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by "stringer -type=batchReplicationMetric -trimprefix=batchReplicationMetric batch-handlers.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[batchReplicationMetricObject-0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _batchReplicationMetric_name = "Object"
|
||||||
|
|
||||||
|
var _batchReplicationMetric_index = [...]uint8{0, 6}
|
||||||
|
|
||||||
|
func (i batchReplicationMetric) String() string {
|
||||||
|
if i >= batchReplicationMetric(len(_batchReplicationMetric_index)-1) {
|
||||||
|
return "batchReplicationMetric(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _batchReplicationMetric_name[_batchReplicationMetric_index[i]:_batchReplicationMetric_index[i+1]]
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Benchmark utility functions for ObjectLayer.PutObject().
|
// Benchmark utility functions for ObjectLayer.PutObject().
|
||||||
@@ -35,7 +35,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// obtains random bucket name.
|
// obtains random bucket name.
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err = obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{})
|
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
|
|||||||
object := getRandomObjectName()
|
object := getRandomObjectName()
|
||||||
|
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err = obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{})
|
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -85,12 +85,12 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
|
|||||||
|
|
||||||
// PutObjectPart returns etag of the object inserted.
|
// PutObjectPart returns etag of the object inserted.
|
||||||
// etag variable is assigned with that value.
|
// etag variable is assigned with that value.
|
||||||
var etag, uploadID string
|
var etag string
|
||||||
// get text data generated for number of bytes equal to object size.
|
// get text data generated for number of bytes equal to object size.
|
||||||
textData := generateBytesData(objSize)
|
textData := generateBytesData(objSize)
|
||||||
// generate md5sum for the generated data.
|
// generate md5sum for the generated data.
|
||||||
// md5sum of the data to written is required as input for NewMultipartUpload.
|
// md5sum of the data to written is required as input for NewMultipartUpload.
|
||||||
uploadID, err = obj.NewMultipartUpload(context.Background(), bucket, object, ObjectOptions{})
|
res, err := obj.NewMultipartUpload(context.Background(), bucket, object, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
|
|||||||
}
|
}
|
||||||
md5hex := getMD5Hash(textPartData)
|
md5hex := getMD5Hash(textPartData)
|
||||||
var partInfo PartInfo
|
var partInfo PartInfo
|
||||||
partInfo, err = obj.PutObjectPart(context.Background(), bucket, object, uploadID, j,
|
partInfo, err = obj.PutObjectPart(context.Background(), bucket, object, res.UploadID, j,
|
||||||
mustGetPutObjReader(b, bytes.NewReader(textPartData), int64(len(textPartData)), md5hex, sha256hex), ObjectOptions{})
|
mustGetPutObjReader(b, bytes.NewReader(textPartData), int64(len(textPartData)), md5hex, sha256hex), ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
@@ -196,7 +196,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// obtains random bucket name.
|
// obtains random bucket name.
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err := obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{})
|
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-4
@@ -24,6 +24,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
@@ -146,6 +147,16 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
// Can never happen unless there are programmer bugs
|
// Can never happen unless there are programmer bugs
|
||||||
return 0, errUnexpected
|
return 0, errUnexpected
|
||||||
}
|
}
|
||||||
|
ignoredErrs := []error{
|
||||||
|
errDiskNotFound,
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(b.volume, minioMetaBucket) {
|
||||||
|
ignoredErrs = append(ignoredErrs,
|
||||||
|
errFileNotFound,
|
||||||
|
errVolumeNotFound,
|
||||||
|
errFileVersionNotFound,
|
||||||
|
)
|
||||||
|
}
|
||||||
if b.rc == nil {
|
if b.rc == nil {
|
||||||
// For the first ReadAt() call we need to open the stream for reading.
|
// For the first ReadAt() call we need to open the stream for reading.
|
||||||
b.currOffset = offset
|
b.currOffset = offset
|
||||||
@@ -153,9 +164,11 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
||||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext,
|
if !IsErr(err, ignoredErrs...) {
|
||||||
fmt.Errorf("Error(%w) reading erasure shards at (%s: %s/%s), will attempt to reconstruct if we have quorum",
|
logger.LogIf(GlobalContext,
|
||||||
err, b.disk, b.volume, b.filePath))
|
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
||||||
|
b.disk, b.volume, b.filePath, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||||
@@ -180,7 +193,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
b.h.Write(buf)
|
b.h.Write(buf)
|
||||||
|
|
||||||
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
||||||
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
|
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
|
||||||
return 0, errFileCorrupt
|
return 0, errFileCorrupt
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -38,12 +38,12 @@ type wholeBitrotWriter struct {
|
|||||||
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
|
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
|
||||||
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
|
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s returned %w", b.disk, err))
|
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
_, err = b.Hash.Write(p)
|
_, err = b.Hash.Write(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s returned %w", b.disk, err))
|
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
@@ -72,12 +72,12 @@ func (b *wholeBitrotReader) ReadAt(buf []byte, offset int64) (n int, err error)
|
|||||||
if b.buf == nil {
|
if b.buf == nil {
|
||||||
b.buf = make([]byte, b.tillOffset-offset)
|
b.buf = make([]byte, b.tillOffset-offset)
|
||||||
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
|
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
|
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(b.buf) < len(buf) {
|
if len(b.buf) < len(buf) {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
|
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
|
||||||
return 0, errLessData
|
return 0, errLessData
|
||||||
}
|
}
|
||||||
n = copy(buf, b.buf)
|
n = copy(buf, b.buf)
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -27,6 +26,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/minio/highwayhash"
|
"github.com/minio/highwayhash"
|
||||||
|
"github.com/minio/minio/internal/hash/sha256"
|
||||||
"golang.org/x/crypto/blake2b"
|
"golang.org/x/crypto/blake2b"
|
||||||
|
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
|
|||||||
+7
-8
@@ -20,17 +20,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||||
tmpDir, err := ioutil.TempDir("", "")
|
tmpDir := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
volume := "testvol"
|
volume := "testvol"
|
||||||
filePath := "testfile"
|
filePath := "testfile"
|
||||||
@@ -60,7 +54,9 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
writer.(io.Closer).Close()
|
if bw, ok := writer.(io.Closer); ok {
|
||||||
|
bw.Close()
|
||||||
|
}
|
||||||
|
|
||||||
reader := newBitrotReader(disk, nil, volume, filePath, 35, bitrotAlgo, bitrotWriterSum(writer), 10)
|
reader := newBitrotReader(disk, nil, volume, filePath, 35, bitrotAlgo, bitrotWriterSum(writer), 10)
|
||||||
b := make([]byte, 10)
|
b := make([]byte, 10)
|
||||||
@@ -76,6 +72,9 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
|||||||
if _, err = reader.ReadAt(b[:5], 30); err != nil {
|
if _, err = reader.ReadAt(b[:5], 30); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if br, ok := reader.(io.Closer); ok {
|
||||||
|
br.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllBitrotAlgorithms(t *testing.T) {
|
func TestAllBitrotAlgorithms(t *testing.T) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
kmsKey := encConfig.KeyID()
|
kmsKey := encConfig.KeyID()
|
||||||
if kmsKey != "" {
|
if kmsKey != "" {
|
||||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||||
_, err := GlobalKMS.GenerateKey(kmsKey, kmsContext)
|
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errKMSKeyNotFound), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, errKMSKeyNotFound), r.URL)
|
||||||
@@ -108,7 +108,8 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store the bucket encryption configuration in the object layer
|
// Store the bucket encryption configuration in the object layer
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, configData); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, configData)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -122,6 +123,7 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
SSEConfig: &cfgStr,
|
SSEConfig: &cfgStr,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -153,12 +155,12 @@ func (api objectAPIHandlers) GetBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
|
|
||||||
// Check if bucket exists
|
// Check if bucket exists
|
||||||
var err error
|
var err error
|
||||||
if _, err = objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err = objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -196,13 +198,14 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
|||||||
|
|
||||||
// Check if bucket exists
|
// Check if bucket exists
|
||||||
var err error
|
var err error
|
||||||
if _, err = objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err = objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete bucket encryption config from object layer
|
// Delete bucket encryption config from object layer
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, nil); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketSSEConfig)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -212,6 +215,7 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
|||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
SSEConfig: nil,
|
SSEConfig: nil,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ func (sys *BucketSSEConfigSys) Get(bucket string) (*sse.BucketSSEConfig, error)
|
|||||||
return nil, BucketSSEConfigNotFound{Bucket: bucket}
|
return nil, BucketSSEConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
return globalBucketMetadataSys.GetSSEConfig(bucket)
|
sseCfg, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
||||||
|
return sseCfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateBucketSSEConfig parses bucket encryption configuration and validates if it is supported by MinIO.
|
// validateBucketSSEConfig parses bucket encryption configuration and validates if it is supported by MinIO.
|
||||||
|
|||||||
+131
-449
@@ -21,7 +21,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -33,7 +32,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -69,11 +67,16 @@ const (
|
|||||||
// - Check if a bucket has an entry in etcd backend
|
// - Check if a bucket has an entry in etcd backend
|
||||||
// -- If no, make an entry
|
// -- If no, make an entry
|
||||||
// -- If yes, check if the entry matches local IP check if we
|
// -- If yes, check if the entry matches local IP check if we
|
||||||
// need to update the entry then proceed to update
|
//
|
||||||
|
// need to update the entry then proceed to update
|
||||||
|
//
|
||||||
// -- If yes, check if the IP of entry matches local IP.
|
// -- If yes, check if the IP of entry matches local IP.
|
||||||
// This means entry is for this instance.
|
//
|
||||||
|
// This means entry is for this instance.
|
||||||
|
//
|
||||||
// -- If IP of the entry doesn't match, this means entry is
|
// -- If IP of the entry doesn't match, this means entry is
|
||||||
// for another instance. Log an error to console.
|
//
|
||||||
|
// for another instance. Log an error to console.
|
||||||
func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
||||||
if len(buckets) == 0 {
|
if len(buckets) == 0 {
|
||||||
return
|
return
|
||||||
@@ -203,7 +206,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
getBucketInfo := objectAPI.GetBucketInfo
|
getBucketInfo := objectAPI.GetBucketInfo
|
||||||
|
|
||||||
if _, err := getBucketInfo(ctx, bucket); err != nil {
|
if _, err := getBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -229,7 +232,6 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
|||||||
// using the Initiate Multipart Upload request, but has not yet been
|
// using the Initiate Multipart Upload request, but has not yet been
|
||||||
// completed or aborted. This operation returns at most 1,000 multipart
|
// completed or aborted. This operation returns at most 1,000 multipart
|
||||||
// uploads in the response.
|
// uploads in the response.
|
||||||
//
|
|
||||||
func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListMultipartUploads")
|
ctx := newContext(r, w, "ListMultipartUploads")
|
||||||
|
|
||||||
@@ -298,7 +300,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
listBuckets := objectAPI.ListBuckets
|
listBuckets := objectAPI.ListBuckets
|
||||||
|
|
||||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction, "", "")
|
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction)
|
||||||
if s3Error != ErrNone && s3Error != ErrAccessDenied {
|
if s3Error != ErrNone && s3Error != ErrAccessDenied {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
@@ -334,7 +336,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
} else {
|
} else {
|
||||||
// Invoke the list buckets.
|
// Invoke the list buckets.
|
||||||
var err error
|
var err error
|
||||||
bucketsInfo, err = listBuckets(ctx)
|
bucketsInfo, err = listBuckets(ctx, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -364,6 +366,18 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
}) {
|
}) {
|
||||||
bucketsInfo[n] = bucketInfo
|
bucketsInfo[n] = bucketInfo
|
||||||
n++
|
n++
|
||||||
|
} else if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: iampolicy.GetBucketLocationAction,
|
||||||
|
BucketName: bucketInfo.Name,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||||
|
IsOwner: owner,
|
||||||
|
ObjectName: "",
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
bucketsInfo[n] = bucketInfo
|
||||||
|
n++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketsInfo = bucketsInfo[:n]
|
bucketsInfo = bucketsInfo[:n]
|
||||||
@@ -437,7 +451,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, "")
|
checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, "")
|
||||||
|
|
||||||
// Before proceeding validate if bucket exists.
|
// Before proceeding validate if bucket exists.
|
||||||
_, err := objectAPI.GetBucketInfo(ctx, bucket)
|
_, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -471,9 +485,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
hasLockEnabled = true
|
hasLockEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
versioned := globalBucketVersioningSys.Enabled(bucket)
|
|
||||||
suspended := globalBucketVersioningSys.Suspended(bucket)
|
|
||||||
|
|
||||||
type deleteResult struct {
|
type deleteResult struct {
|
||||||
delInfo DeletedObject
|
delInfo DeletedObject
|
||||||
errInfo DeleteError
|
errInfo DeleteError
|
||||||
@@ -481,8 +492,8 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
|
|
||||||
deleteResults := make([]deleteResult, len(deleteObjectsReq.Objects))
|
deleteResults := make([]deleteResult, len(deleteObjectsReq.Objects))
|
||||||
|
|
||||||
|
vc, _ := globalBucketVersioningSys.Get(bucket)
|
||||||
oss := make([]*objSweeper, len(deleteObjectsReq.Objects))
|
oss := make([]*objSweeper, len(deleteObjectsReq.Objects))
|
||||||
|
|
||||||
for index, object := range deleteObjectsReq.Objects {
|
for index, object := range deleteObjectsReq.Objects {
|
||||||
if apiErrCode := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName); apiErrCode != ErrNone {
|
if apiErrCode := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName); apiErrCode != ErrNone {
|
||||||
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
|
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
|
||||||
@@ -514,8 +525,8 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
VersionID: object.VersionID,
|
VersionID: object.VersionID,
|
||||||
Versioned: versioned,
|
Versioned: vc.PrefixEnabled(object.ObjectName),
|
||||||
VersionSuspended: suspended,
|
VersionSuspended: vc.Suspended(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if replicateDeletes || object.VersionID != "" && hasLockEnabled || !globalTierConfigMgr.Empty() {
|
if replicateDeletes || object.VersionID != "" && hasLockEnabled || !globalTierConfigMgr.Empty() {
|
||||||
@@ -526,7 +537,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !globalTierConfigMgr.Empty() {
|
if !globalTierConfigMgr.Empty() {
|
||||||
oss[index] = newObjSweeper(bucket, object.ObjectName).WithVersion(opts.VersionID).WithVersioning(versioned, suspended)
|
oss[index] = newObjSweeper(bucket, object.ObjectName).WithVersion(opts.VersionID).WithVersioning(opts.Versioned, opts.VersionSuspended)
|
||||||
oss[index].SetTransitionState(goi.TransitionedObject)
|
oss[index].SetTransitionState(goi.TransitionedObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,8 +592,8 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
|
|
||||||
deleteList := toNames(objectsToDelete)
|
deleteList := toNames(objectsToDelete)
|
||||||
dObjects, errs := deleteObjectsFn(ctx, bucket, deleteList, ObjectOptions{
|
dObjects, errs := deleteObjectsFn(ctx, bucket, deleteList, ObjectOptions{
|
||||||
Versioned: versioned,
|
PrefixEnabledFn: vc.PrefixEnabled,
|
||||||
VersionSuspended: suspended,
|
VersionSuspended: vc.Suspended(),
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
@@ -638,22 +649,13 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if replicateDeletes {
|
if replicateDeletes && (dobj.DeleteMarkerReplicationStatus() == replication.Pending || dobj.VersionPurgeStatus() == Pending) {
|
||||||
if dobj.DeleteMarkerReplicationStatus() == replication.Pending || dobj.VersionPurgeStatus() == Pending {
|
dv := DeletedObjectReplicationInfo{
|
||||||
dv := DeletedObjectReplicationInfo{
|
DeletedObject: dobj,
|
||||||
DeletedObject: dobj,
|
Bucket: bucket,
|
||||||
Bucket: bucket,
|
EventType: ReplicateIncomingDelete,
|
||||||
}
|
|
||||||
scheduleReplicationDelete(ctx, dv, objectAPI)
|
|
||||||
}
|
}
|
||||||
}
|
scheduleReplicationDelete(ctx, dv, objectAPI)
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify deleted event for objects.
|
|
||||||
for _, dobj := range deletedObjects {
|
|
||||||
if dobj.ObjectName == "" {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eventName := event.ObjectRemovedDelete
|
eventName := event.ObjectRemovedDelete
|
||||||
@@ -706,20 +708,53 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
objectLockEnabled := false
|
objectLockEnabled := false
|
||||||
if vs, found := r.Header[http.CanonicalHeaderKey("x-amz-bucket-object-lock-enabled")]; found {
|
if vs := r.Header.Get(xhttp.AmzObjectLockEnabled); len(vs) > 0 {
|
||||||
v := strings.ToLower(strings.Join(vs, ""))
|
v := strings.ToLower(vs)
|
||||||
if v != "true" && v != "false" {
|
switch v {
|
||||||
|
case "true", "false":
|
||||||
|
objectLockEnabled = v == "true"
|
||||||
|
default:
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objectLockEnabled = v == "true"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.CreateBucketAction, bucket, ""); s3Error != ErrNone {
|
forceCreate := false
|
||||||
|
if vs := r.Header.Get(xhttp.MinIOForceCreate); len(vs) > 0 {
|
||||||
|
v := strings.ToLower(vs)
|
||||||
|
switch v {
|
||||||
|
case "true", "false":
|
||||||
|
forceCreate = v == "true"
|
||||||
|
default:
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.CreateBucketAction)
|
||||||
|
if s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if objectLockEnabled {
|
||||||
|
// Creating a bucket with locking requires the user having more permissions
|
||||||
|
for _, action := range []iampolicy.Action{iampolicy.PutBucketObjectLockConfigurationAction, iampolicy.PutBucketVersioningAction} {
|
||||||
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: action,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||||
|
BucketName: bucket,
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse incoming location constraint.
|
// Parse incoming location constraint.
|
||||||
location, s3Error := parseLocationConstraint(r)
|
location, s3Error := parseLocationConstraint(r)
|
||||||
if s3Error != ErrNone {
|
if s3Error != ErrNone {
|
||||||
@@ -734,9 +769,18 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := BucketOptions{
|
// check if client is attempting to create more buckets than allowed maximum.
|
||||||
|
if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets {
|
||||||
|
apiErr := errorCodes.ToAPIErr(ErrTooManyBuckets)
|
||||||
|
apiErr.Description = fmt.Sprintf("You have attempted to create %d buckets than allowed %d", currBuckets+1, maxBuckets)
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := MakeBucketOptions{
|
||||||
Location: location,
|
Location: location,
|
||||||
LockEnabled: objectLockEnabled,
|
LockEnabled: objectLockEnabled,
|
||||||
|
ForceCreate: forceCreate,
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalDNSConfig != nil {
|
if globalDNSConfig != nil {
|
||||||
@@ -752,7 +796,11 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = globalDNSConfig.Put(bucket); err != nil {
|
if err = globalDNSConfig.Put(bucket); err != nil {
|
||||||
objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{Force: false, NoRecreate: true})
|
objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
|
||||||
|
Force: false,
|
||||||
|
NoRecreate: true,
|
||||||
|
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
||||||
|
})
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -794,19 +842,14 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Proceed to creating a bucket.
|
// Proceed to creating a bucket.
|
||||||
err := objectAPI.MakeBucketWithLocation(ctx, bucket, opts)
|
if err := objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil {
|
||||||
if _, ok := err.(BucketExists); ok {
|
if _, ok := err.(BucketExists); ok {
|
||||||
// Though bucket exists locally, we send the site-replication
|
// Though bucket exists locally, we send the site-replication
|
||||||
// hook to ensure all sites have this bucket. If the hook
|
// hook to ensure all sites have this bucket. If the hook
|
||||||
// succeeds, the client will still receive a bucket exists
|
// succeeds, the client will still receive a bucket exists
|
||||||
// message.
|
// message.
|
||||||
err2 := globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts)
|
globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts)
|
||||||
if err2 != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -815,8 +858,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket)
|
globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket)
|
||||||
|
|
||||||
// Call site replication hook
|
// Call site replication hook
|
||||||
err = globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts)
|
if err := globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts); err != nil {
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -858,7 +900,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok {
|
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1034,7 +1076,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if _, ok := crypto.IsRequested(formValues); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
if crypto.Requested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||||
if crypto.SSECopy.IsRequested(r.Header) {
|
if crypto.SSECopy.IsRequested(r.Header) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1060,7 +1102,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader, objectEncryptionKey, err = newEncryptReader(hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1109,9 +1151,12 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
Host: handlers.GetSourceIP(r),
|
Host: handlers.GetSourceIP(r),
|
||||||
})
|
})
|
||||||
|
|
||||||
if successRedirect != "" {
|
if redirectURL != nil { // success_action_redirect is valid and set.
|
||||||
// Replace raw query params..
|
v := redirectURL.Query()
|
||||||
redirectURL.RawQuery = getRedirectPostRawQuery(objInfo)
|
v.Add("bucket", objInfo.Bucket)
|
||||||
|
v.Add("key", objInfo.Name)
|
||||||
|
v.Add("etag", "\""+objInfo.ETag+"\"")
|
||||||
|
redirectURL.RawQuery = v.Encode()
|
||||||
writeRedirectSeeOther(w, redirectURL.String())
|
writeRedirectSeeOther(w, redirectURL.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1155,7 +1200,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1263,7 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
getBucketInfo := objectAPI.GetBucketInfo
|
getBucketInfo := objectAPI.GetBucketInfo
|
||||||
|
|
||||||
if _, err := getBucketInfo(ctx, bucket); err != nil {
|
if _, err := getBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1295,7 +1340,10 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
deleteBucket := objectAPI.DeleteBucket
|
deleteBucket := objectAPI.DeleteBucket
|
||||||
|
|
||||||
// Attempt to delete bucket.
|
// Attempt to delete bucket.
|
||||||
if err := deleteBucket(ctx, bucket, DeleteBucketOptions{Force: forceDelete}); err != nil {
|
if err := deleteBucket(ctx, bucket, DeleteBucketOptions{
|
||||||
|
Force: forceDelete,
|
||||||
|
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
||||||
|
}); err != nil {
|
||||||
apiErr := toAPIError(ctx, err)
|
apiErr := toAPIError(ctx, err)
|
||||||
if _, ok := err.(BucketNotEmpty); ok {
|
if _, ok := err.(BucketNotEmpty); ok {
|
||||||
if globalBucketVersioningSys.Enabled(bucket) || globalBucketVersioningSys.Suspended(bucket) {
|
if globalBucketVersioningSys.Enabled(bucket) || globalBucketVersioningSys.Suspended(bucket) {
|
||||||
@@ -1350,7 +1398,7 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !globalIsErasure {
|
if globalIsGateway {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1374,12 +1422,13 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
||||||
if _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
|
if _, _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, configData); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, configData)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1393,6 +1442,7 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
ObjectLockConfig: &cfgStr,
|
ObjectLockConfig: &cfgStr,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1427,7 +1477,7 @@ func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1460,7 +1510,7 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1484,7 +1534,8 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, configData); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, configData)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1495,9 +1546,10 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
// errors.
|
// errors.
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Tags: &cfgStr,
|
Tags: &cfgStr,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1529,7 +1581,7 @@ func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1566,14 +1618,16 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, nil); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTaggingConfig)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1582,375 +1636,3 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
|||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
|
||||||
// ----------
|
|
||||||
// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html
|
|
||||||
func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "PutBucketReplicationConfig")
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
objectAPI := api.ObjectAPI()
|
|
||||||
if objectAPI == nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !globalIsErasure {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check if bucket exists.
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if globalSiteReplicationSys.isEnabled() {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
replicationConfig, err := replication.ParseConfig(io.LimitReader(r.Body, r.ContentLength))
|
|
||||||
if err != nil {
|
|
||||||
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
|
||||||
apiErr.Description = err.Error()
|
|
||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig)
|
|
||||||
if apiErr != noError {
|
|
||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Validate the received bucket replication config
|
|
||||||
if err = replicationConfig.Validate(bucket, sameTarget); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configData, err := xml.Marshal(replicationConfig)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, configData); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBucketReplicationConfigHandler - GET Bucket replication configuration.
|
|
||||||
// ----------
|
|
||||||
// Gets the replication configuration for a bucket.
|
|
||||||
func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "GetBucketReplicationConfig")
|
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
|
||||||
if objectAPI == nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if user has permissions to perform this operation
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check if bucket exists.
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configData, err := xml.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
|
||||||
writeSuccessResponseXML(w, configData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBucketReplicationConfigHandler - DELETE Bucket replication config.
|
|
||||||
// ----------
|
|
||||||
func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "DeleteBucketReplicationConfig")
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
|
||||||
if objectAPI == nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check if bucket exists.
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if globalSiteReplicationSys.isEnabled() {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, nil); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBucketReplicationMetricsHandler - GET Bucket replication metrics.
|
|
||||||
// ----------
|
|
||||||
// Gets the replication metrics for a bucket.
|
|
||||||
func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "GetBucketReplicationMetrics")
|
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
|
||||||
if objectAPI == nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if user has permissions to perform this operation
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if bucket exists.
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var usageInfo BucketUsageInfo
|
|
||||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
|
||||||
if err == nil && !dataUsageInfo.LastUpdate.IsZero() {
|
|
||||||
usageInfo = dataUsageInfo.BucketsUsage[bucket]
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
if err = enc.Encode(getLatestReplicationStats(bucket, usageInfo)); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetBucketReplicationStartHandler - starts a replication reset for all objects in a bucket which
|
|
||||||
// qualify for replication and re-sync the object(s) to target, provided ExistingObjectReplication is
|
|
||||||
// enabled for the qualifying rule. This API is a MinIO only extension provided for situations where
|
|
||||||
// remote target is entirely lost,and previously replicated objects need to be re-synced. If resync is
|
|
||||||
// already in progress it returns an error
|
|
||||||
func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "ResetBucketReplicationStart")
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
durationStr := r.URL.Query().Get("older-than")
|
|
||||||
arn := r.URL.Query().Get("arn")
|
|
||||||
resetID := r.URL.Query().Get("reset-id")
|
|
||||||
if resetID == "" {
|
|
||||||
resetID = mustGetUUID()
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
days time.Duration
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if durationStr != "" {
|
|
||||||
days, err = time.ParseDuration(durationStr)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, InvalidArgument{
|
|
||||||
Bucket: bucket,
|
|
||||||
Err: fmt.Errorf("invalid query parameter older-than %s for %s : %w", durationStr, bucket, err),
|
|
||||||
}), r.URL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetBeforeDate := UTCNow().AddDate(0, 0, -1*int(days/24))
|
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
|
||||||
if objectAPI == nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if bucket exists.
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !config.HasActiveRules("", true) {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoMatchingRuleError), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tgtArns := config.FilterTargetArns(
|
|
||||||
replication.ObjectOpts{
|
|
||||||
OpType: replication.ResyncReplicationType,
|
|
||||||
TargetArn: arn,
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(tgtArns) == 0 {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
|
||||||
Bucket: bucket,
|
|
||||||
Err: fmt.Errorf("Remote target ARN %s missing or ineligible for replication resync", arn),
|
|
||||||
}), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tgtArns) > 1 && arn == "" {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
|
||||||
Bucket: bucket,
|
|
||||||
Err: fmt.Errorf("ARN should be specified for replication reset"),
|
|
||||||
}), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var rinfo ResyncTargetsInfo
|
|
||||||
target := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, tgtArns[0])
|
|
||||||
target.ResetBeforeDate = UTCNow().AddDate(0, 0, -1*int(days/24))
|
|
||||||
target.ResetID = resetID
|
|
||||||
rinfo.Targets = append(rinfo.Targets, ResyncTarget{Arn: tgtArns[0], ResetID: target.ResetID})
|
|
||||||
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, true); err != nil {
|
|
||||||
switch err.(type) {
|
|
||||||
case BucketRemoteConnectionErr:
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL)
|
|
||||||
default:
|
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := startReplicationResync(ctx, bucket, arn, resetID, resetBeforeDate, objectAPI); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
|
||||||
Bucket: bucket,
|
|
||||||
Err: err,
|
|
||||||
}), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(rinfo)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Write success response.
|
|
||||||
writeSuccessResponseJSON(w, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetBucketReplicationStatusHandler - returns the status of replication reset.
|
|
||||||
// This API is a MinIO only extension
|
|
||||||
func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "ResetBucketReplicationStatus")
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
arn := r.URL.Query().Get("arn")
|
|
||||||
var err error
|
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
|
||||||
if objectAPI == nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if bucket exists.
|
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
globalReplicationPool.resyncState.RLock()
|
|
||||||
brs, ok := globalReplicationPool.resyncState.statusMap[bucket]
|
|
||||||
if !ok {
|
|
||||||
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
|
||||||
Bucket: bucket,
|
|
||||||
Err: fmt.Errorf("No replication resync status available for %s", arn),
|
|
||||||
}), r.URL)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var rinfo ResyncTargetsInfo
|
|
||||||
for tarn, st := range brs.TargetsMap {
|
|
||||||
if arn != "" && tarn != arn {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rinfo.Targets = append(rinfo.Targets, ResyncTarget{
|
|
||||||
Arn: tarn,
|
|
||||||
ResetID: st.ResyncID,
|
|
||||||
StartTime: st.StartTime,
|
|
||||||
EndTime: st.EndTime,
|
|
||||||
ResyncStatus: st.ResyncStatus.String(),
|
|
||||||
ReplicatedSize: st.ReplicatedSize,
|
|
||||||
ReplicatedCount: st.ReplicatedCount,
|
|
||||||
FailedSize: st.FailedSize,
|
|
||||||
FailedCount: st.FailedCount,
|
|
||||||
Bucket: st.Bucket,
|
|
||||||
Object: st.Object,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
globalReplicationPool.resyncState.RUnlock()
|
|
||||||
data, err := json.Marshal(rinfo)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Write success response.
|
|
||||||
writeSuccessResponseJSON(w, data)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -36,7 +36,8 @@ func TestRemoveBucketHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
credentials auth.Credentials, t *testing.T) {
|
credentials auth.Credentials, t *testing.T,
|
||||||
|
) {
|
||||||
_, err := obj.PutObject(GlobalContext, bucketName, "test-object", mustGetPutObjReader(t, bytes.NewReader([]byte{}), int64(0), "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), ObjectOptions{})
|
_, err := obj.PutObject(GlobalContext, bucketName, "test-object", mustGetPutObjReader(t, bytes.NewReader([]byte{}), int64(0), "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), ObjectOptions{})
|
||||||
// if object upload fails stop the test.
|
// if object upload fails stop the test.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -884,7 +885,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// read the response body.
|
// read the response body.
|
||||||
actualContent, err = ioutil.ReadAll(rec.Body)
|
actualContent, err = io.ReadAll(rec.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,12 +62,12 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(r.Body, r.ContentLength))
|
bucketLifecycle, err := lifecycle.ParseLifecycleConfigWithID(io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -91,7 +91,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, configData); err != nil {
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, configData); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,12 +163,12 @@ func (api objectAPIHandlers) DeleteBucketLifecycleHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, nil); err != nil {
|
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketLifecycleConfig); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ func TestBucketLifecycleWrongCredentials(t *testing.T) {
|
|||||||
|
|
||||||
// Test for authentication
|
// Test for authentication
|
||||||
func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
credentials auth.Credentials, t *testing.T) {
|
credentials auth.Credentials, t *testing.T,
|
||||||
|
) {
|
||||||
// test cases with sample input and expected output.
|
// test cases with sample input and expected output.
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
method string
|
method string
|
||||||
|
|||||||
+46
-14
@@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/amztime"
|
||||||
sse "github.com/minio/minio/internal/bucket/encryption"
|
sse "github.com/minio/minio/internal/bucket/encryption"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
@@ -247,11 +248,11 @@ func (t *transitionState) addLastDayStats(tier string, ts tierStats) {
|
|||||||
t.lastDayStats[tier].addStats(ts)
|
t.lastDayStats[tier].addStats(ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transitionState) getDailyAllTierStats() dailyAllTierStats {
|
func (t *transitionState) getDailyAllTierStats() DailyAllTierStats {
|
||||||
t.lastDayMu.RLock()
|
t.lastDayMu.RLock()
|
||||||
defer t.lastDayMu.RUnlock()
|
defer t.lastDayMu.RUnlock()
|
||||||
|
|
||||||
res := make(dailyAllTierStats, len(t.lastDayStats))
|
res := make(DailyAllTierStats, len(t.lastDayStats))
|
||||||
for tier, st := range t.lastDayStats {
|
for tier, st := range t.lastDayStats {
|
||||||
res[tier] = st.clone()
|
res[tier] = st.clone()
|
||||||
}
|
}
|
||||||
@@ -330,7 +331,7 @@ const (
|
|||||||
// 2. when a transitioned object expires (based on an ILM rule).
|
// 2. when a transitioned object expires (based on an ILM rule).
|
||||||
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, action expireAction) error {
|
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, action expireAction) error {
|
||||||
var opts ObjectOptions
|
var opts ObjectOptions
|
||||||
opts.Versioned = globalBucketVersioningSys.Enabled(oi.Bucket)
|
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name)
|
||||||
opts.VersionID = lcOpts.VersionID
|
opts.VersionID = lcOpts.VersionID
|
||||||
opts.Expiration = ExpirationOptions{Expire: true}
|
opts.Expiration = ExpirationOptions{Expire: true}
|
||||||
switch action {
|
switch action {
|
||||||
@@ -415,13 +416,43 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
|
|||||||
ETag: oi.ETag,
|
ETag: oi.ETag,
|
||||||
},
|
},
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
Versioned: globalBucketVersioningSys.Enabled(oi.Bucket),
|
Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
|
||||||
VersionSuspended: globalBucketVersioningSys.Suspended(oi.Bucket),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||||
MTime: oi.ModTime,
|
MTime: oi.ModTime,
|
||||||
}
|
}
|
||||||
return tier, objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
return tier, objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type auditTierOp struct {
|
||||||
|
Tier string `json:"tier"`
|
||||||
|
TimeToResponseNS int64 `json:"timeToResponseNS"`
|
||||||
|
OutputBytes int64 `json:"tx,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func auditTierActions(ctx context.Context, tier string, bytes int64) func(err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
return func(err error) {
|
||||||
|
// Record only when audit targets configured.
|
||||||
|
if len(logger.AuditTargets()) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
op := auditTierOp{
|
||||||
|
Tier: tier,
|
||||||
|
OutputBytes: bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
op.TimeToResponseNS = time.Since(startTime).Nanoseconds()
|
||||||
|
} else {
|
||||||
|
op.Error = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.GetReqInfo(ctx).AppendTags("tierStats", op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getTransitionedObjectReader returns a reader from the transitioned tier.
|
// getTransitionedObjectReader returns a reader from the transitioned tier.
|
||||||
func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
||||||
tgtClient, err := globalTierConfigMgr.getDriver(oi.TransitionedObject.Tier)
|
tgtClient, err := globalTierConfigMgr.getDriver(oi.TransitionedObject.Tier)
|
||||||
@@ -441,12 +472,13 @@ func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs
|
|||||||
gopts.length = length
|
gopts.length = length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timeTierAction := auditTierActions(ctx, oi.TransitionedObject.Tier, length)
|
||||||
reader, err := tgtClient.Get(ctx, oi.TransitionedObject.Name, remoteVersionID(oi.TransitionedObject.VersionID), gopts)
|
reader, err := tgtClient.Get(ctx, oi.TransitionedObject.Name, remoteVersionID(oi.TransitionedObject.VersionID), gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
closer := func() {
|
closer := func() {
|
||||||
reader.Close()
|
timeTierAction(reader.Close())
|
||||||
}
|
}
|
||||||
return fn(reader, h, closer)
|
return fn(reader, h, closer)
|
||||||
}
|
}
|
||||||
@@ -560,7 +592,7 @@ func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer)
|
|||||||
}
|
}
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if !r.OutputLocation.IsEmpty() {
|
if !r.OutputLocation.IsEmpty() {
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, r.OutputLocation.S3.BucketName); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, r.OutputLocation.S3.BucketName, BucketOptions{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if r.OutputLocation.S3.Prefix == "" {
|
if r.OutputLocation.S3.Prefix == "" {
|
||||||
@@ -575,8 +607,8 @@ func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer)
|
|||||||
|
|
||||||
// postRestoreOpts returns ObjectOptions with version-id from the POST restore object request for a given bucket and object.
|
// postRestoreOpts returns ObjectOptions with version-id from the POST restore object request for a given bucket and object.
|
||||||
func postRestoreOpts(ctx context.Context, r *http.Request, bucket, object string) (opts ObjectOptions, err error) {
|
func postRestoreOpts(ctx context.Context, r *http.Request, bucket, object string) (opts ObjectOptions, err error) {
|
||||||
versioned := globalBucketVersioningSys.Enabled(bucket)
|
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
||||||
versionSuspended := globalBucketVersioningSys.Suspended(bucket)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
vid := strings.TrimSpace(r.Form.Get(xhttp.VersionID))
|
vid := strings.TrimSpace(r.Form.Get(xhttp.VersionID))
|
||||||
if vid != "" && vid != nullVersionID {
|
if vid != "" && vid != nullVersionID {
|
||||||
_, err := uuid.Parse(vid)
|
_, err := uuid.Parse(vid)
|
||||||
@@ -627,8 +659,8 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
|||||||
meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
||||||
}
|
}
|
||||||
return ObjectOptions{
|
return ObjectOptions{
|
||||||
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object),
|
||||||
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object),
|
||||||
UserDefined: meta,
|
UserDefined: meta,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -640,8 +672,8 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ObjectOptions{
|
return ObjectOptions{
|
||||||
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object),
|
||||||
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object),
|
||||||
UserDefined: meta,
|
UserDefined: meta,
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: objInfo.VersionID,
|
||||||
MTime: objInfo.ModTime,
|
MTime: objInfo.ModTime,
|
||||||
@@ -755,7 +787,7 @@ func parseRestoreObjStatus(restoreHdr string) (restoreObjStatus, error) {
|
|||||||
if strings.TrimSpace(expiryTokens[0]) != "expiry-date" {
|
if strings.TrimSpace(expiryTokens[0]) != "expiry-date" {
|
||||||
return restoreObjStatus{}, errRestoreHDRMalformed
|
return restoreObjStatus{}, errRestoreHDRMalformed
|
||||||
}
|
}
|
||||||
expiry, err := time.Parse(http.TimeFormat, strings.Trim(expiryTokens[1], `"`))
|
expiry, err := amztime.ParseHeader(strings.Trim(expiryTokens[1], `"`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return restoreObjStatus{}, errRestoreHDRMalformed
|
return restoreObjStatus{}, errRestoreHDRMalformed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ func TestObjectIsRemote(t *testing.T) {
|
|||||||
if got := fi.IsRemote(); got != tc.remote {
|
if got := fi.IsRemote(); got != tc.remote {
|
||||||
t.Fatalf("Test %d.a: expected %v got %v", i+1, tc.remote, got)
|
t.Fatalf("Test %d.a: expected %v got %v", i+1, tc.remote, got)
|
||||||
}
|
}
|
||||||
oi := fi.ToObjectInfo("bucket", "object")
|
oi := fi.ToObjectInfo("bucket", "object", false)
|
||||||
if got := oi.IsRemote(); got != tc.remote {
|
if got := oi.IsRemote(); got != tc.remote {
|
||||||
t.Fatalf("Test %d.b: expected %v got %v", i+1, tc.remote, got)
|
t.Fatalf("Test %d.b: expected %v got %v", i+1, tc.remote, got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/internal/kms"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
@@ -33,9 +32,9 @@ import (
|
|||||||
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
||||||
// if one of the args do not meet the required conditions.
|
// if one of the args do not meet the required conditions.
|
||||||
// Special conditions required by MinIO server are as below
|
// Special conditions required by MinIO server are as below
|
||||||
// - delimiter if set should be equal to '/', otherwise the request is rejected.
|
// - delimiter if set should be equal to '/', otherwise the request is rejected.
|
||||||
// - marker if set should have a common prefix with 'prefix' param, otherwise
|
// - marker if set should have a common prefix with 'prefix' param, otherwise
|
||||||
// the request is rejected.
|
// the request is rejected.
|
||||||
func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int) APIErrorCode {
|
func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int) APIErrorCode {
|
||||||
// Max keys cannot be negative.
|
// Max keys cannot be negative.
|
||||||
if maxKeys < 0 {
|
if maxKeys < 0 {
|
||||||
@@ -100,7 +99,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = DecryptETags(ctx, GlobalKMS, listObjectVersionsInfo.Objects, kms.BatchSize()); err != nil {
|
if err = DecryptETags(ctx, GlobalKMS, listObjectVersionsInfo.Objects); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -165,7 +164,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = DecryptETags(ctx, GlobalKMS, listObjectsV2Info.Objects, kms.BatchSize()); err != nil {
|
if err = DecryptETags(ctx, GlobalKMS, listObjectsV2Info.Objects); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -243,7 +242,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = DecryptETags(ctx, GlobalKMS, listObjectsV2Info.Objects, kms.BatchSize()); err != nil {
|
if err = DecryptETags(ctx, GlobalKMS, listObjectsV2Info.Objects); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -299,7 +298,6 @@ func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http
|
|||||||
// This implementation of the GET operation returns some or all (up to 1000)
|
// This implementation of the GET operation returns some or all (up to 1000)
|
||||||
// of the objects in a bucket. You can use the request parameters as selection
|
// of the objects in a bucket. You can use the request parameters as selection
|
||||||
// criteria to return a subset of the objects in a bucket.
|
// criteria to return a subset of the objects in a bucket.
|
||||||
//
|
|
||||||
func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListObjectsV1")
|
ctx := newContext(r, w, "ListObjectsV1")
|
||||||
|
|
||||||
@@ -343,7 +341,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = DecryptETags(ctx, GlobalKMS, listObjectsInfo.Objects, kms.BatchSize()); err != nil {
|
if err = DecryptETags(ctx, GlobalKMS, listObjectsInfo.Objects); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+96
-74
@@ -23,6 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
@@ -44,6 +45,14 @@ type BucketMetadataSys struct {
|
|||||||
metadataMap map[string]BucketMetadata
|
metadataMap map[string]BucketMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count returns number of bucket metadata map entries.
|
||||||
|
func (sys *BucketMetadataSys) Count() int {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
|
||||||
|
return len(sys.metadataMap)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove bucket metadata from memory.
|
// Remove bucket metadata from memory.
|
||||||
func (sys *BucketMetadataSys) Remove(bucket string) {
|
func (sys *BucketMetadataSys) Remove(bucket string) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
@@ -72,90 +81,98 @@ func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update update bucket metadata for the specified config file.
|
func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string, configFile string, configData []byte, parse bool) (updatedAt time.Time, err error) {
|
||||||
// The configData data should not be modified after being sent here.
|
|
||||||
func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) error {
|
|
||||||
objAPI := newObjectLayerFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return errServerNotInitialized
|
return updatedAt, errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIsGateway && globalGatewayName != NASBackendGateway {
|
if globalIsGateway && globalGatewayName != NASBackendGateway {
|
||||||
if configFile == bucketPolicyConfig {
|
if configFile == bucketPolicyConfig {
|
||||||
if configData == nil {
|
if configData == nil {
|
||||||
return objAPI.DeleteBucketPolicy(ctx, bucket)
|
return updatedAt, objAPI.DeleteBucketPolicy(ctx, bucket)
|
||||||
}
|
}
|
||||||
config, err := policy.ParseConfig(bytes.NewReader(configData), bucket)
|
config, err := policy.ParseConfig(bytes.NewReader(configData), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return updatedAt, err
|
||||||
}
|
}
|
||||||
return objAPI.SetBucketPolicy(ctx, bucket, config)
|
return updatedAt, objAPI.SetBucketPolicy(ctx, bucket, config)
|
||||||
}
|
}
|
||||||
return NotImplemented{}
|
return updatedAt, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket == minioMetaBucket {
|
if bucket == minioMetaBucket {
|
||||||
return errInvalidArgument
|
return updatedAt, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, bucket)
|
meta, err := loadBucketMetadataParse(ctx, objAPI, bucket, parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
||||||
// Only single drive mode needs this fallback.
|
// Only single drive mode needs this fallback.
|
||||||
meta = newBucketMetadata(bucket)
|
meta = newBucketMetadata(bucket)
|
||||||
} else {
|
} else {
|
||||||
return err
|
return updatedAt, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updatedAt = UTCNow()
|
||||||
switch configFile {
|
switch configFile {
|
||||||
case bucketPolicyConfig:
|
case bucketPolicyConfig:
|
||||||
meta.PolicyConfigJSON = configData
|
meta.PolicyConfigJSON = configData
|
||||||
|
meta.PolicyConfigUpdatedAt = updatedAt
|
||||||
case bucketNotificationConfig:
|
case bucketNotificationConfig:
|
||||||
meta.NotificationConfigXML = configData
|
meta.NotificationConfigXML = configData
|
||||||
case bucketLifecycleConfig:
|
case bucketLifecycleConfig:
|
||||||
meta.LifecycleConfigXML = configData
|
meta.LifecycleConfigXML = configData
|
||||||
case bucketSSEConfig:
|
case bucketSSEConfig:
|
||||||
meta.EncryptionConfigXML = configData
|
meta.EncryptionConfigXML = configData
|
||||||
|
meta.EncryptionConfigUpdatedAt = updatedAt
|
||||||
case bucketTaggingConfig:
|
case bucketTaggingConfig:
|
||||||
meta.TaggingConfigXML = configData
|
meta.TaggingConfigXML = configData
|
||||||
|
meta.TaggingConfigUpdatedAt = updatedAt
|
||||||
case bucketQuotaConfigFile:
|
case bucketQuotaConfigFile:
|
||||||
meta.QuotaConfigJSON = configData
|
meta.QuotaConfigJSON = configData
|
||||||
|
meta.QuotaConfigUpdatedAt = updatedAt
|
||||||
case objectLockConfig:
|
case objectLockConfig:
|
||||||
if !globalIsErasure && !globalIsDistErasure {
|
|
||||||
return NotImplemented{}
|
|
||||||
}
|
|
||||||
meta.ObjectLockConfigXML = configData
|
meta.ObjectLockConfigXML = configData
|
||||||
|
meta.ObjectLockConfigUpdatedAt = updatedAt
|
||||||
case bucketVersioningConfig:
|
case bucketVersioningConfig:
|
||||||
if !globalIsErasure && !globalIsDistErasure {
|
|
||||||
return NotImplemented{}
|
|
||||||
}
|
|
||||||
meta.VersioningConfigXML = configData
|
meta.VersioningConfigXML = configData
|
||||||
|
meta.VersioningConfigUpdatedAt = updatedAt
|
||||||
case bucketReplicationConfig:
|
case bucketReplicationConfig:
|
||||||
if !globalIsErasure && !globalIsDistErasure {
|
|
||||||
return NotImplemented{}
|
|
||||||
}
|
|
||||||
meta.ReplicationConfigXML = configData
|
meta.ReplicationConfigXML = configData
|
||||||
|
meta.ReplicationConfigUpdatedAt = updatedAt
|
||||||
case bucketTargetsFile:
|
case bucketTargetsFile:
|
||||||
meta.BucketTargetsConfigJSON, meta.BucketTargetsConfigMetaJSON, err = encryptBucketMetadata(meta.Name, configData, kms.Context{
|
meta.BucketTargetsConfigJSON, meta.BucketTargetsConfigMetaJSON, err = encryptBucketMetadata(ctx, meta.Name, configData, kms.Context{
|
||||||
bucket: meta.Name,
|
bucket: meta.Name,
|
||||||
bucketTargetsFile: bucketTargetsFile,
|
bucketTargetsFile: bucketTargetsFile,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error encrypting bucket target metadata %w", err)
|
return updatedAt, fmt.Errorf("Error encrypting bucket target metadata %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
return updatedAt, fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := meta.Save(ctx, objAPI); err != nil {
|
if err := meta.Save(ctx, objAPI); err != nil {
|
||||||
return err
|
return updatedAt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.Set(bucket, meta)
|
sys.Set(bucket, meta)
|
||||||
globalNotificationSys.LoadBucketMetadata(bgContext(ctx), bucket) // Do not use caller context here
|
globalNotificationSys.LoadBucketMetadata(bgContext(ctx), bucket) // Do not use caller context here
|
||||||
|
|
||||||
return nil
|
return updatedAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete the bucket metadata for the specified bucket.
|
||||||
|
// must be used by all callers instead of using Update() with nil configData.
|
||||||
|
func (sys *BucketMetadataSys) Delete(ctx context.Context, bucket string, configFile string) (updatedAt time.Time, err error) {
|
||||||
|
return sys.updateAndParse(ctx, bucket, configFile, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update update bucket metadata for the specified bucket.
|
||||||
|
// The configData data should not be modified after being sent here.
|
||||||
|
func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) (updatedAt time.Time, err error) {
|
||||||
|
return sys.updateAndParse(ctx, bucket, configFile, configData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get metadata for a bucket.
|
// Get metadata for a bucket.
|
||||||
@@ -186,44 +203,47 @@ func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
|||||||
|
|
||||||
// GetVersioningConfig returns configured versioning config
|
// GetVersioningConfig returns configured versioning config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, error) {
|
func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if errors.Is(err, errConfigNotFound) {
|
||||||
|
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, meta.Created, nil
|
||||||
|
}
|
||||||
|
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, time.Time{}, err
|
||||||
}
|
}
|
||||||
return meta.versioningConfig, nil
|
return meta.versioningConfig, meta.VersioningConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTaggingConfig returns configured tagging config
|
// GetTaggingConfig returns configured tagging config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, error) {
|
func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketTaggingNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
if meta.taggingConfig == nil {
|
if meta.taggingConfig == nil {
|
||||||
return nil, BucketTaggingNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return meta.taggingConfig, nil
|
return meta.taggingConfig, meta.TaggingConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectLockConfig returns configured object lock config
|
// GetObjectLockConfig returns configured object lock config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, error) {
|
func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketObjectLockConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
if meta.objectLockConfig == nil {
|
if meta.objectLockConfig == nil {
|
||||||
return nil, BucketObjectLockConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return meta.objectLockConfig, nil
|
return meta.objectLockConfig, meta.ObjectLockConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLifecycleConfig returns configured lifecycle config
|
// GetLifecycleConfig returns configured lifecycle config
|
||||||
@@ -283,69 +303,82 @@ func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Confi
|
|||||||
|
|
||||||
// GetSSEConfig returns configured SSE config
|
// GetSSEConfig returns configured SSE config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, error) {
|
func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketSSEConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
if meta.sseConfig == nil {
|
if meta.sseConfig == nil {
|
||||||
return nil, BucketSSEConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return meta.sseConfig, nil
|
return meta.sseConfig, meta.EncryptionConfigUpdatedAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAt returns the time of creation of bucket
|
||||||
|
func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
|
||||||
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
return meta.Created.UTC(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPolicyConfig returns configured bucket policy
|
// GetPolicyConfig returns configured bucket policy
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, error) {
|
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
objAPI := newObjectLayerFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, time.Time{}, errServerNotInitialized
|
||||||
}
|
}
|
||||||
return objAPI.GetBucketPolicy(GlobalContext, bucket)
|
p, err := objAPI.GetBucketPolicy(GlobalContext, bucket)
|
||||||
|
return p, UTCNow(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketPolicyNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
if meta.policyConfig == nil {
|
if meta.policyConfig == nil {
|
||||||
return nil, BucketPolicyNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return meta.policyConfig, nil
|
return meta.policyConfig, meta.PolicyConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQuotaConfig returns configured bucket quota
|
// GetQuotaConfig returns configured bucket quota
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, error) {
|
func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(ctx, bucket)
|
meta, err := sys.GetConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if errors.Is(err, errConfigNotFound) {
|
||||||
|
return nil, time.Time{}, BucketQuotaConfigNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
return meta.quotaConfig, nil
|
return meta.quotaConfig, meta.QuotaConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReplicationConfig returns configured bucket replication config
|
// GetReplicationConfig returns configured bucket replication config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, error) {
|
func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(ctx, bucket)
|
meta, err := sys.GetConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketReplicationConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if meta.replicationConfig == nil {
|
if meta.replicationConfig == nil {
|
||||||
return nil, BucketReplicationConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return meta.replicationConfig, nil
|
return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketTargetsConfig returns configured bucket targets for this bucket
|
// GetBucketTargetsConfig returns configured bucket targets for this bucket
|
||||||
@@ -353,6 +386,9 @@ func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket s
|
|||||||
func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
|
func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errConfigNotFound) {
|
||||||
|
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if meta.bucketTargetConfig == nil {
|
if meta.bucketTargetConfig == nil {
|
||||||
@@ -361,20 +397,6 @@ func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.Buc
|
|||||||
return meta.bucketTargetConfig, nil
|
return meta.bucketTargetConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketTarget returns the target for the bucket and arn.
|
|
||||||
func (sys *BucketMetadataSys) GetBucketTarget(bucket string, arn string) (madmin.BucketTarget, error) {
|
|
||||||
targets, err := sys.GetBucketTargetsConfig(bucket)
|
|
||||||
if err != nil {
|
|
||||||
return madmin.BucketTarget{}, err
|
|
||||||
}
|
|
||||||
for _, t := range targets.Targets {
|
|
||||||
if t.Arn == arn {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return madmin.BucketTarget{}, errConfigNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfig returns a specific configuration from the bucket metadata.
|
// GetConfig returns a specific configuration from the bucket metadata.
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (BucketMetadata, error) {
|
func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (BucketMetadata, error) {
|
||||||
@@ -448,7 +470,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
sys.metadataMap[buckets[index].Name] = meta
|
sys.metadataMap[buckets[index].Name] = meta
|
||||||
sys.Unlock()
|
sys.Unlock()
|
||||||
|
|
||||||
globalNotificationSys.set(buckets[index], meta) // set notification targets
|
globalEventNotifier.set(buckets[index], meta) // set notification targets
|
||||||
|
|
||||||
globalBucketTargetSys.set(buckets[index], meta) // set remote replication targets
|
globalBucketTargetSys.set(buckets[index], meta) // set remote replication targets
|
||||||
|
|
||||||
|
|||||||
+92
-19
@@ -81,6 +81,13 @@ type BucketMetadata struct {
|
|||||||
ReplicationConfigXML []byte
|
ReplicationConfigXML []byte
|
||||||
BucketTargetsConfigJSON []byte
|
BucketTargetsConfigJSON []byte
|
||||||
BucketTargetsConfigMetaJSON []byte
|
BucketTargetsConfigMetaJSON []byte
|
||||||
|
PolicyConfigUpdatedAt time.Time
|
||||||
|
ObjectLockConfigUpdatedAt time.Time
|
||||||
|
EncryptionConfigUpdatedAt time.Time
|
||||||
|
TaggingConfigUpdatedAt time.Time
|
||||||
|
QuotaConfigUpdatedAt time.Time
|
||||||
|
ReplicationConfigUpdatedAt time.Time
|
||||||
|
VersioningConfigUpdatedAt time.Time
|
||||||
|
|
||||||
// Unexported fields. Must be updated atomically.
|
// Unexported fields. Must be updated atomically.
|
||||||
policyConfig *policy.Policy
|
policyConfig *policy.Policy
|
||||||
@@ -99,8 +106,7 @@ type BucketMetadata struct {
|
|||||||
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
||||||
func newBucketMetadata(name string) BucketMetadata {
|
func newBucketMetadata(name string) BucketMetadata {
|
||||||
return BucketMetadata{
|
return BucketMetadata{
|
||||||
Name: name,
|
Name: name,
|
||||||
Created: UTCNow(),
|
|
||||||
notificationConfig: &event.Config{
|
notificationConfig: &event.Config{
|
||||||
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||||||
},
|
},
|
||||||
@@ -113,6 +119,17 @@ func newBucketMetadata(name string) BucketMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCreatedAt preserves the CreatedAt time for bucket across sites in site replication. It defaults to
|
||||||
|
// creation time of bucket on this cluster in all other cases.
|
||||||
|
func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
|
||||||
|
if b.Created.IsZero() {
|
||||||
|
b.Created = UTCNow()
|
||||||
|
}
|
||||||
|
if !createdAt.IsZero() {
|
||||||
|
b.Created = createdAt.UTC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load - loads the metadata of bucket by name from ObjectLayer api.
|
// Load - loads the metadata of bucket by name from ObjectLayer api.
|
||||||
// If an error is returned the returned metadata will be default initialized.
|
// If an error is returned the returned metadata will be default initialized.
|
||||||
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
|
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
|
||||||
@@ -145,20 +162,45 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadBucketMetadata loads and migrates to bucket metadata.
|
func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
|
||||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
|
||||||
b := newBucketMetadata(bucket)
|
b := newBucketMetadata(bucket)
|
||||||
err := b.Load(ctx, objectAPI, b.Name)
|
err := b.Load(ctx, objectAPI, b.Name)
|
||||||
if err != nil && !errors.Is(err, errConfigNotFound) {
|
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
b.defaultTimestamps()
|
||||||
|
}
|
||||||
|
|
||||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
|
||||||
if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil {
|
if err != nil {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(configs) == 0 {
|
||||||
|
if parse {
|
||||||
|
// nothing to update, parse and proceed.
|
||||||
|
err = b.parseAllConfigs(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||||
|
err = b.convertLegacyConfigs(ctx, objectAPI, configs)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
// migrate unencrypted remote targets
|
// migrate unencrypted remote targets
|
||||||
return b, b.migrateTargetConfig(ctx, objectAPI)
|
if err := b.migrateTargetConfig(ctx, objectAPI); err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBucketMetadata loads and migrates to bucket metadata.
|
||||||
|
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
||||||
|
return loadBucketMetadataParse(ctx, objectAPI, bucket, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAllConfigs will parse all configs and populate the private fields.
|
// parseAllConfigs will parse all configs and populate the private fields.
|
||||||
@@ -253,7 +295,7 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) error {
|
func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) (map[string][]byte, error) {
|
||||||
legacyConfigs := []string{
|
legacyConfigs := []string{
|
||||||
legacyBucketObjectLockEnabledConfigFile,
|
legacyBucketObjectLockEnabledConfigFile,
|
||||||
bucketPolicyConfig,
|
bucketPolicyConfig,
|
||||||
@@ -267,7 +309,7 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
objectLockConfig,
|
objectLockConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
configs := make(map[string][]byte)
|
configs := make(map[string][]byte, len(legacyConfigs))
|
||||||
|
|
||||||
// Handle migration from lockEnabled to newer format.
|
// Handle migration from lockEnabled to newer format.
|
||||||
if b.LockEnabled {
|
if b.LockEnabled {
|
||||||
@@ -292,16 +334,15 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
configs[legacyFile] = configData
|
configs[legacyFile] = configData
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(configs) == 0 {
|
return configs, nil
|
||||||
// nothing to update, return right away.
|
}
|
||||||
return b.parseAllConfigs(ctx, objectAPI)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer, configs map[string][]byte) error {
|
||||||
for legacyFile, configData := range configs {
|
for legacyFile, configData := range configs {
|
||||||
switch legacyFile {
|
switch legacyFile {
|
||||||
case legacyBucketObjectLockEnabledConfigFile:
|
case legacyBucketObjectLockEnabledConfigFile:
|
||||||
@@ -332,6 +373,7 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
b.BucketTargetsConfigJSON = configData
|
b.BucketTargetsConfigJSON = configData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.defaultTimestamps()
|
||||||
|
|
||||||
if err := b.Save(ctx, objectAPI); err != nil {
|
if err := b.Save(ctx, objectAPI); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -347,6 +389,37 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default timestamps to metadata Created timestamp if unset.
|
||||||
|
func (b *BucketMetadata) defaultTimestamps() {
|
||||||
|
if b.PolicyConfigUpdatedAt.IsZero() {
|
||||||
|
b.PolicyConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.EncryptionConfigUpdatedAt.IsZero() {
|
||||||
|
b.EncryptionConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.TaggingConfigUpdatedAt.IsZero() {
|
||||||
|
b.TaggingConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.ObjectLockConfigUpdatedAt.IsZero() {
|
||||||
|
b.ObjectLockConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.QuotaConfigUpdatedAt.IsZero() {
|
||||||
|
b.QuotaConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.ReplicationConfigUpdatedAt.IsZero() {
|
||||||
|
b.ReplicationConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.VersioningConfigUpdatedAt.IsZero() {
|
||||||
|
b.VersioningConfigUpdatedAt = b.Created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save config to supplied ObjectLayer api.
|
// Save config to supplied ObjectLayer api.
|
||||||
func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
|
func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
|
||||||
if err := b.parseAllConfigs(ctx, api); err != nil {
|
if err := b.parseAllConfigs(ctx, api); err != nil {
|
||||||
@@ -394,7 +467,7 @@ func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI Obje
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
encBytes, metaBytes, err := encryptBucketMetadata(b.Name, b.BucketTargetsConfigJSON, kms.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile})
|
encBytes, metaBytes, err := encryptBucketMetadata(ctx, b.Name, b.BucketTargetsConfigJSON, kms.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -405,14 +478,14 @@ func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI Obje
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encrypt bucket metadata if kms is configured.
|
// encrypt bucket metadata if kms is configured.
|
||||||
func encryptBucketMetadata(bucket string, input []byte, kmsContext kms.Context) (output, metabytes []byte, err error) {
|
func encryptBucketMetadata(ctx context.Context, bucket string, input []byte, kmsContext kms.Context) (output, metabytes []byte, err error) {
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
output = input
|
output = input
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
key, err := GlobalKMS.GenerateKey("", kmsContext)
|
key, err := GlobalKMS.GenerateKey(ctx, "", kmsContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -421,7 +494,7 @@ func encryptBucketMetadata(bucket string, input []byte, kmsContext kms.Context)
|
|||||||
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
||||||
sealedKey := objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
|
sealedKey := objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
|
||||||
crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey)
|
crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey)
|
||||||
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()})
|
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, metabytes, err
|
return output, metabytes, err
|
||||||
}
|
}
|
||||||
@@ -451,6 +524,6 @@ func decryptBucketMetadata(input []byte, bucket string, meta map[string]string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
outbuf := bytes.NewBuffer(nil)
|
outbuf := bytes.NewBuffer(nil)
|
||||||
_, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()})
|
_, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()})
|
||||||
return outbuf.Bytes(), err
|
return outbuf.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|||||||
+180
-5
@@ -108,6 +108,48 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "PolicyConfigUpdatedAt":
|
||||||
|
z.PolicyConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PolicyConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ObjectLockConfigUpdatedAt":
|
||||||
|
z.ObjectLockConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectLockConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "EncryptionConfigUpdatedAt":
|
||||||
|
z.EncryptionConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "EncryptionConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "TaggingConfigUpdatedAt":
|
||||||
|
z.TaggingConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "TaggingConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "QuotaConfigUpdatedAt":
|
||||||
|
z.QuotaConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QuotaConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ReplicationConfigUpdatedAt":
|
||||||
|
z.ReplicationConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "VersioningConfigUpdatedAt":
|
||||||
|
z.VersioningConfigUpdatedAt, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -121,9 +163,9 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 14
|
// map header, size 21
|
||||||
// write "Name"
|
// write "Name"
|
||||||
err = en.Append(0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
err = en.Append(0xde, 0x0, 0x15, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,15 +304,85 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "PolicyConfigUpdatedAt"
|
||||||
|
err = en.Append(0xb5, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.PolicyConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PolicyConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ObjectLockConfigUpdatedAt"
|
||||||
|
err = en.Append(0xb9, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.ObjectLockConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectLockConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "EncryptionConfigUpdatedAt"
|
||||||
|
err = en.Append(0xb9, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.EncryptionConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "EncryptionConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "TaggingConfigUpdatedAt"
|
||||||
|
err = en.Append(0xb6, 0x54, 0x61, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.TaggingConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "TaggingConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "QuotaConfigUpdatedAt"
|
||||||
|
err = en.Append(0xb4, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.QuotaConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QuotaConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ReplicationConfigUpdatedAt"
|
||||||
|
err = en.Append(0xba, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.ReplicationConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "VersioningConfigUpdatedAt"
|
||||||
|
err = en.Append(0xb9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.VersioningConfigUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 14
|
// map header, size 21
|
||||||
// string "Name"
|
// string "Name"
|
||||||
o = append(o, 0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
o = append(o, 0xde, 0x0, 0x15, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
o = msgp.AppendString(o, z.Name)
|
o = msgp.AppendString(o, z.Name)
|
||||||
// string "Created"
|
// string "Created"
|
||||||
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
|
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
|
||||||
@@ -311,6 +423,27 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "BucketTargetsConfigMetaJSON"
|
// string "BucketTargetsConfigMetaJSON"
|
||||||
o = append(o, 0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e)
|
o = append(o, 0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e)
|
||||||
o = msgp.AppendBytes(o, z.BucketTargetsConfigMetaJSON)
|
o = msgp.AppendBytes(o, z.BucketTargetsConfigMetaJSON)
|
||||||
|
// string "PolicyConfigUpdatedAt"
|
||||||
|
o = append(o, 0xb5, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.PolicyConfigUpdatedAt)
|
||||||
|
// string "ObjectLockConfigUpdatedAt"
|
||||||
|
o = append(o, 0xb9, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.ObjectLockConfigUpdatedAt)
|
||||||
|
// string "EncryptionConfigUpdatedAt"
|
||||||
|
o = append(o, 0xb9, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.EncryptionConfigUpdatedAt)
|
||||||
|
// string "TaggingConfigUpdatedAt"
|
||||||
|
o = append(o, 0xb6, 0x54, 0x61, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.TaggingConfigUpdatedAt)
|
||||||
|
// string "QuotaConfigUpdatedAt"
|
||||||
|
o = append(o, 0xb4, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.QuotaConfigUpdatedAt)
|
||||||
|
// string "ReplicationConfigUpdatedAt"
|
||||||
|
o = append(o, 0xba, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.ReplicationConfigUpdatedAt)
|
||||||
|
// string "VersioningConfigUpdatedAt"
|
||||||
|
o = append(o, 0xb9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.VersioningConfigUpdatedAt)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,6 +549,48 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "PolicyConfigUpdatedAt":
|
||||||
|
z.PolicyConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PolicyConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ObjectLockConfigUpdatedAt":
|
||||||
|
z.ObjectLockConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectLockConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "EncryptionConfigUpdatedAt":
|
||||||
|
z.EncryptionConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "EncryptionConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "TaggingConfigUpdatedAt":
|
||||||
|
z.TaggingConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "TaggingConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "QuotaConfigUpdatedAt":
|
||||||
|
z.QuotaConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QuotaConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ReplicationConfigUpdatedAt":
|
||||||
|
z.ReplicationConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "VersioningConfigUpdatedAt":
|
||||||
|
z.VersioningConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -430,6 +605,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *BucketMetadata) Msgsize() (s int) {
|
func (z *BucketMetadata) Msgsize() (s int) {
|
||||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON)
|
s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := objAPI.GetBucketInfo(ctx, bucketName)
|
_, err := objAPI.GetBucketInfo(ctx, bucketName, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -72,7 +72,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.SetRegion(globalSite.Region)
|
config.SetRegion(globalSite.Region)
|
||||||
if err = config.Validate(globalSite.Region, globalNotificationSys.targetList); err != nil {
|
if err = config.Validate(globalSite.Region, globalEventNotifier.targetList); err != nil {
|
||||||
arnErr, ok := err.(*event.ErrARNNotFound)
|
arnErr, ok := err.(*event.ErrARNNotFound)
|
||||||
if ok {
|
if ok {
|
||||||
for i, queue := range config.QueueList {
|
for i, queue := range config.QueueList {
|
||||||
@@ -132,7 +132,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := objectAPI.GetBucketInfo(ctx, bucketName)
|
_, err := objectAPI.GetBucketInfo(ctx, bucketName, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -144,7 +144,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region, globalNotificationSys.targetList)
|
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region, globalEventNotifier.targetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
||||||
if event.IsEventError(err) {
|
if event.IsEventError(err) {
|
||||||
@@ -160,13 +160,13 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucketName, bucketNotificationConfig, configData); err != nil {
|
if _, err = globalBucketMetadataSys.Update(ctx, bucketName, bucketNotificationConfig, configData); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rulesMap := config.ToRulesMap()
|
rulesMap := config.ToRulesMap()
|
||||||
globalNotificationSys.AddRulesMap(bucketName, rulesMap)
|
globalEventNotifier.AddRulesMap(bucketName, rulesMap)
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -44,13 +45,16 @@ func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention,
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName)
|
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BucketObjectLockConfigNotFound); ok {
|
if errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucketName}) {
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, errInvalidArgument) {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
logger.CriticalIf(context.Background(), err)
|
||||||
return r, err
|
return r, err
|
||||||
|
|
||||||
}
|
}
|
||||||
return config.ToRetention(), nil
|
return config.ToRetention(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
@@ -61,7 +60,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
bucketPolicyBytes, err := io.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -103,16 +102,18 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, configData); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, configData)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Policy: bucketPolicyBytes,
|
Policy: bucketPolicyBytes,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -143,20 +144,22 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, nil); err != nil {
|
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketPolicyConfig)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -187,7 +190,7 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -113,7 +112,8 @@ func TestCreateBucket(t *testing.T) {
|
|||||||
|
|
||||||
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
|
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
|
||||||
func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
credentials auth.Credentials, t *testing.T) {
|
credentials auth.Credentials, t *testing.T,
|
||||||
|
) {
|
||||||
bucketName1 := fmt.Sprintf("%s-1", bucketName)
|
bucketName1 := fmt.Sprintf("%s-1", bucketName)
|
||||||
|
|
||||||
const n = 100
|
const n = 100
|
||||||
@@ -127,7 +127,7 @@ func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRoute
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
// Sync start.
|
// Sync start.
|
||||||
<-start
|
<-start
|
||||||
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, BucketOptions{}); err != nil {
|
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
t.Logf("unexpected error: %T: %v", err, err)
|
t.Logf("unexpected error: %T: %v", err, err)
|
||||||
return
|
return
|
||||||
@@ -162,7 +162,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
credentials auth.Credentials, t *testing.T,
|
credentials auth.Credentials, t *testing.T,
|
||||||
) {
|
) {
|
||||||
bucketName1 := fmt.Sprintf("%s-1", bucketName)
|
bucketName1 := fmt.Sprintf("%s-1", bucketName)
|
||||||
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, BucketOptions{}); err != nil {
|
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +378,8 @@ func TestGetBucketPolicyHandler(t *testing.T) {
|
|||||||
|
|
||||||
// testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket.
|
// testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket.
|
||||||
func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
credentials auth.Credentials, t *testing.T) {
|
credentials auth.Credentials, t *testing.T,
|
||||||
|
) {
|
||||||
// template for constructing HTTP request body for PUT bucket policy.
|
// template for constructing HTTP request body for PUT bucket policy.
|
||||||
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"]},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"]}]}`
|
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"]},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"]}]}`
|
||||||
|
|
||||||
@@ -485,7 +486,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
|
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
|
||||||
}
|
}
|
||||||
// read the response body.
|
// read the response body.
|
||||||
bucketPolicyReadBuf, err := ioutil.ReadAll(recV4.Body)
|
bucketPolicyReadBuf, err := io.ReadAll(recV4.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||||
}
|
}
|
||||||
@@ -523,7 +524,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
|
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
|
||||||
}
|
}
|
||||||
// read the response body.
|
// read the response body.
|
||||||
bucketPolicyReadBuf, err = ioutil.ReadAll(recV2.Body)
|
bucketPolicyReadBuf, err = io.ReadAll(recV2.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-1
@@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -38,7 +39,8 @@ type PolicySys struct{}
|
|||||||
|
|
||||||
// Get returns stored bucket policy
|
// Get returns stored bucket policy
|
||||||
func (sys *PolicySys) Get(bucket string) (*policy.Policy, error) {
|
func (sys *PolicySys) Get(bucket string) (*policy.Policy, error) {
|
||||||
return globalBucketMetadataSys.GetPolicyConfig(bucket)
|
policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket)
|
||||||
|
return policy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
@@ -124,6 +126,20 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
|||||||
|
|
||||||
cloneHeader := r.Header.Clone()
|
cloneHeader := r.Header.Clone()
|
||||||
|
|
||||||
|
if userTags := cloneHeader.Get(xhttp.AmzObjectTagging); userTags != "" {
|
||||||
|
tag, _ := tags.ParseObjectTags(userTags)
|
||||||
|
if tag != nil {
|
||||||
|
tagMap := tag.ToMap()
|
||||||
|
keys := make([]string, 0, len(tagMap))
|
||||||
|
for k, v := range tagMap {
|
||||||
|
args[pathJoin("ExistingObjectTag", k)] = []string{v}
|
||||||
|
args[pathJoin("RequestObjectTag", k)] = []string{v}
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
args["RequestObjectTagKeys"] = keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, objLock := range []string{
|
for _, objLock := range []string{
|
||||||
xhttp.AmzObjectLockMode,
|
xhttp.AmzObjectLockMode,
|
||||||
xhttp.AmzObjectLockLegalHold,
|
xhttp.AmzObjectLockLegalHold,
|
||||||
@@ -136,6 +152,9 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
|||||||
}
|
}
|
||||||
|
|
||||||
for key, values := range cloneHeader {
|
for key, values := range cloneHeader {
|
||||||
|
if strings.EqualFold(key, xhttp.AmzObjectTagging) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if existingValues, found := args[key]; found {
|
if existingValues, found := args[key]; found {
|
||||||
args[key] = append(existingValues, values...)
|
args[key] = append(existingValues, values...)
|
||||||
} else {
|
} else {
|
||||||
@@ -168,6 +187,8 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JWT specific values
|
// JWT specific values
|
||||||
|
//
|
||||||
|
// Add all string claims
|
||||||
for k, v := range claims {
|
for k, v := range claims {
|
||||||
vStr, ok := v.(string)
|
vStr, ok := v.(string)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -182,6 +203,21 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Add groups claim which could be a list. This will ensure that the claim
|
||||||
|
// `jwt:groups` works.
|
||||||
|
if grpsVal, ok := claims["groups"]; ok {
|
||||||
|
if grpsIs, ok := grpsVal.([]interface{}); ok {
|
||||||
|
grps := []string{}
|
||||||
|
for _, gI := range grpsIs {
|
||||||
|
if g, ok := gI.(string); ok {
|
||||||
|
grps = append(grps, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(grps) > 0 {
|
||||||
|
args["groups"] = grps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -42,8 +42,8 @@ func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.
|
|||||||
}
|
}
|
||||||
return &madmin.BucketQuota{}, nil
|
return &madmin.BucketQuota{}, nil
|
||||||
}
|
}
|
||||||
|
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
||||||
return globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
return qCfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
||||||
|
|||||||
@@ -0,0 +1,410 @@
|
|||||||
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/bucket/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
||||||
|
// ----------
|
||||||
|
// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html
|
||||||
|
func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "PutBucketReplicationConfig")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if globalIsGateway {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replicationConfig, err := replication.ParseConfig(io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
||||||
|
apiErr.Description = err.Error()
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, true)
|
||||||
|
if apiErr != noError {
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Validate the received bucket replication config
|
||||||
|
if err = replicationConfig.Validate(bucket, sameTarget); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(replicationConfig)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, configData); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketReplicationConfigHandler - GET Bucket replication configuration.
|
||||||
|
// ----------
|
||||||
|
// Gets the replication configuration for a bucket.
|
||||||
|
func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetBucketReplicationConfig")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if user has permissions to perform this operation
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configData, err := xml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseXML(w, configData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucketReplicationConfigHandler - DELETE Bucket replication config.
|
||||||
|
// ----------
|
||||||
|
func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "DeleteBucketReplicationConfig")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if globalSiteReplicationSys.isEnabled() {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketReplicationConfig); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketReplicationMetricsHandler - GET Bucket replication metrics.
|
||||||
|
// ----------
|
||||||
|
// Gets the replication metrics for a bucket.
|
||||||
|
func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetBucketReplicationMetrics")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if user has permissions to perform this operation
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var usageInfo BucketUsageInfo
|
||||||
|
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
|
if err == nil && !dataUsageInfo.LastUpdate.IsZero() {
|
||||||
|
usageInfo = dataUsageInfo.BucketsUsage[bucket]
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||||
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
if err = enc.Encode(globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBucketReplicationStartHandler - starts a replication reset for all objects in a bucket which
|
||||||
|
// qualify for replication and re-sync the object(s) to target, provided ExistingObjectReplication is
|
||||||
|
// enabled for the qualifying rule. This API is a MinIO only extension provided for situations where
|
||||||
|
// remote target is entirely lost,and previously replicated objects need to be re-synced. If resync is
|
||||||
|
// already in progress it returns an error
|
||||||
|
func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ResetBucketReplicationStart")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
durationStr := r.URL.Query().Get("older-than")
|
||||||
|
arn := r.URL.Query().Get("arn")
|
||||||
|
resetID := r.URL.Query().Get("reset-id")
|
||||||
|
if resetID == "" {
|
||||||
|
resetID = mustGetUUID()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
days time.Duration
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if durationStr != "" {
|
||||||
|
days, err = time.ParseDuration(durationStr)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Err: fmt.Errorf("invalid query parameter older-than %s for %s : %w", durationStr, bucket, err),
|
||||||
|
}), r.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetBeforeDate := UTCNow().AddDate(0, 0, -1*int(days/24))
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !config.HasExistingObjectReplication(arn) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoExistingObjects), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tgtArns := config.FilterTargetArns(
|
||||||
|
replication.ObjectOpts{
|
||||||
|
OpType: replication.ResyncReplicationType,
|
||||||
|
TargetArn: arn,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(tgtArns) == 0 {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Err: fmt.Errorf("Remote target ARN %s missing or ineligible for replication resync", arn),
|
||||||
|
}), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tgtArns) > 1 && arn == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Err: fmt.Errorf("ARN should be specified for replication reset"),
|
||||||
|
}), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rinfo ResyncTargetsInfo
|
||||||
|
target := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, tgtArns[0])
|
||||||
|
target.ResetBeforeDate = UTCNow().AddDate(0, 0, -1*int(days/24))
|
||||||
|
target.ResetID = resetID
|
||||||
|
rinfo.Targets = append(rinfo.Targets, ResyncTarget{Arn: tgtArns[0], ResetID: target.ResetID})
|
||||||
|
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, true); err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case RemoteTargetConnectionErr:
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL)
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := startReplicationResync(ctx, bucket, arn, resetID, resetBeforeDate, objectAPI); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Err: err,
|
||||||
|
}), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(rinfo)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBucketReplicationStatusHandler - returns the status of replication reset.
|
||||||
|
// This API is a MinIO only extension
|
||||||
|
func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ResetBucketReplicationStatus")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
arn := r.URL.Query().Get("arn")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
globalReplicationPool.resyncState.RLock()
|
||||||
|
brs, ok := globalReplicationPool.resyncState.statusMap[bucket]
|
||||||
|
globalReplicationPool.resyncState.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Err: fmt.Errorf("No replication resync status available for %s", arn),
|
||||||
|
}), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rinfo ResyncTargetsInfo
|
||||||
|
for tarn, st := range brs.TargetsMap {
|
||||||
|
if arn != "" && tarn != arn {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rinfo.Targets = append(rinfo.Targets, ResyncTarget{
|
||||||
|
Arn: tarn,
|
||||||
|
ResetID: st.ResyncID,
|
||||||
|
StartTime: st.StartTime,
|
||||||
|
EndTime: st.EndTime,
|
||||||
|
ResyncStatus: st.ResyncStatus.String(),
|
||||||
|
ReplicatedSize: st.ReplicatedSize,
|
||||||
|
ReplicatedCount: st.ReplicatedCount,
|
||||||
|
FailedSize: st.FailedSize,
|
||||||
|
FailedCount: st.FailedCount,
|
||||||
|
Bucket: st.Bucket,
|
||||||
|
Object: st.Object,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(rinfo)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
}
|
||||||
+180
-15
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -36,10 +37,12 @@ func (b *BucketReplicationStats) hasReplicationUsage() bool {
|
|||||||
|
|
||||||
// ReplicationStats holds the global in-memory replication stats
|
// ReplicationStats holds the global in-memory replication stats
|
||||||
type ReplicationStats struct {
|
type ReplicationStats struct {
|
||||||
Cache map[string]*BucketReplicationStats
|
Cache map[string]*BucketReplicationStats
|
||||||
UsageCache map[string]*BucketReplicationStats
|
UsageCache map[string]*BucketReplicationStats
|
||||||
sync.RWMutex
|
mostRecentStats BucketStatsMap
|
||||||
ulock sync.RWMutex
|
sync.RWMutex // mutex for Cache
|
||||||
|
ulock sync.RWMutex // mutex for UsageCache
|
||||||
|
dlock sync.RWMutex // mutex for mostRecentStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes in-memory replication statistics for a bucket.
|
// Delete deletes in-memory replication statistics for a bucket.
|
||||||
@@ -92,14 +95,23 @@ func (r *ReplicationStats) Update(bucket string, arn string, n int64, duration t
|
|||||||
bs.Stats[arn] = b
|
bs.Stats[arn] = b
|
||||||
}
|
}
|
||||||
switch status {
|
switch status {
|
||||||
|
case replication.Pending:
|
||||||
|
if opType.IsDataReplication() && prevStatus != status {
|
||||||
|
b.PendingSize += n
|
||||||
|
b.PendingCount++
|
||||||
|
}
|
||||||
case replication.Completed:
|
case replication.Completed:
|
||||||
switch prevStatus { // adjust counters based on previous state
|
switch prevStatus { // adjust counters based on previous state
|
||||||
|
case replication.Pending:
|
||||||
|
b.PendingCount--
|
||||||
case replication.Failed:
|
case replication.Failed:
|
||||||
b.FailedCount--
|
b.FailedCount--
|
||||||
}
|
}
|
||||||
if opType == replication.ObjectReplicationType {
|
if opType.IsDataReplication() {
|
||||||
b.ReplicatedSize += n
|
b.ReplicatedSize += n
|
||||||
switch prevStatus {
|
switch prevStatus {
|
||||||
|
case replication.Pending:
|
||||||
|
b.PendingSize -= n
|
||||||
case replication.Failed:
|
case replication.Failed:
|
||||||
b.FailedSize -= n
|
b.FailedSize -= n
|
||||||
}
|
}
|
||||||
@@ -108,10 +120,12 @@ func (r *ReplicationStats) Update(bucket string, arn string, n int64, duration t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case replication.Failed:
|
case replication.Failed:
|
||||||
if opType == replication.ObjectReplicationType {
|
if opType.IsDataReplication() {
|
||||||
if prevStatus == replication.Pending {
|
if prevStatus == replication.Pending {
|
||||||
b.FailedSize += n
|
b.FailedSize += n
|
||||||
b.FailedCount++
|
b.FailedCount++
|
||||||
|
b.PendingSize -= n
|
||||||
|
b.PendingCount--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case replication.Replica:
|
case replication.Replica:
|
||||||
@@ -135,6 +149,23 @@ func (r *ReplicationStats) GetInitialUsage(bucket string) BucketReplicationStats
|
|||||||
return st.Clone()
|
return st.Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAll returns replication metrics for all buckets at once.
|
||||||
|
func (r *ReplicationStats) GetAll() map[string]BucketReplicationStats {
|
||||||
|
if r == nil {
|
||||||
|
return map[string]BucketReplicationStats{}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
|
||||||
|
bucketReplicationStats := make(map[string]BucketReplicationStats, len(r.Cache))
|
||||||
|
for k, v := range r.Cache {
|
||||||
|
bucketReplicationStats[k] = v.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucketReplicationStats
|
||||||
|
}
|
||||||
|
|
||||||
// Get replication metrics for a bucket from this node since this node came up.
|
// Get replication metrics for a bucket from this node since this node came up.
|
||||||
func (r *ReplicationStats) Get(bucket string) BucketReplicationStats {
|
func (r *ReplicationStats) Get(bucket string) BucketReplicationStats {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
@@ -159,9 +190,20 @@ func NewReplicationStats(ctx context.Context, objectAPI ObjectLayer) *Replicatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load replication metrics at cluster start from initial data usage
|
// load replication metrics at cluster start from latest replication stats saved in .minio.sys/buckets/replication/node-name.stats
|
||||||
|
// fallback to replication stats in data usage to be backward compatible
|
||||||
func (r *ReplicationStats) loadInitialReplicationMetrics(ctx context.Context) {
|
func (r *ReplicationStats) loadInitialReplicationMetrics(ctx context.Context) {
|
||||||
rTimer := time.NewTimer(time.Minute * 1)
|
m := make(map[string]*BucketReplicationStats)
|
||||||
|
if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil {
|
||||||
|
for b, st := range stats {
|
||||||
|
m[b] = &st
|
||||||
|
}
|
||||||
|
r.ulock.Lock()
|
||||||
|
r.UsageCache = m
|
||||||
|
r.ulock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rTimer := time.NewTimer(time.Second * 5)
|
||||||
defer rTimer.Stop()
|
defer rTimer.Stop()
|
||||||
var (
|
var (
|
||||||
dui DataUsageInfo
|
dui DataUsageInfo
|
||||||
@@ -174,17 +216,13 @@ outer:
|
|||||||
return
|
return
|
||||||
case <-rTimer.C:
|
case <-rTimer.C:
|
||||||
dui, err = loadDataUsageFromBackend(GlobalContext, newObjectLayerFn())
|
dui, err = loadDataUsageFromBackend(GlobalContext, newObjectLayerFn())
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If LastUpdate is set, data usage is available.
|
// If LastUpdate is set, data usage is available.
|
||||||
if !dui.LastUpdate.IsZero() {
|
if err == nil {
|
||||||
break outer
|
break outer
|
||||||
}
|
}
|
||||||
|
rTimer.Reset(time.Second * 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m := make(map[string]*BucketReplicationStats)
|
|
||||||
for bucket, usage := range dui.BucketsUsage {
|
for bucket, usage := range dui.BucketsUsage {
|
||||||
b := &BucketReplicationStats{
|
b := &BucketReplicationStats{
|
||||||
Stats: make(map[string]*BucketReplicationStat, len(usage.ReplicationInfo)),
|
Stats: make(map[string]*BucketReplicationStat, len(usage.ReplicationInfo)),
|
||||||
@@ -203,6 +241,133 @@ outer:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.ulock.Lock()
|
r.ulock.Lock()
|
||||||
defer r.ulock.Unlock()
|
|
||||||
r.UsageCache = m
|
r.UsageCache = m
|
||||||
|
r.ulock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) getAllCachedLatest() BucketStatsMap {
|
||||||
|
if r == nil {
|
||||||
|
return BucketStatsMap{}
|
||||||
|
}
|
||||||
|
r.dlock.RLock()
|
||||||
|
defer r.dlock.RUnlock()
|
||||||
|
return r.mostRecentStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketReplicationStats) {
|
||||||
|
peerBucketStatsList := globalNotificationSys.GetClusterAllBucketStats(GlobalContext)
|
||||||
|
bucketsReplicationStats = make(map[string]BucketReplicationStats, len(bucketsUsage))
|
||||||
|
|
||||||
|
for bucket, u := range bucketsUsage {
|
||||||
|
bucketStats := make([]BucketStats, len(peerBucketStatsList))
|
||||||
|
for i, peerBucketStats := range peerBucketStatsList {
|
||||||
|
bucketStat, ok := peerBucketStats.Stats[bucket]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bucketStats[i] = bucketStat
|
||||||
|
}
|
||||||
|
bucketsReplicationStats[bucket] = r.calculateBucketReplicationStats(bucket, u, bucketStats)
|
||||||
|
}
|
||||||
|
return bucketsReplicationStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u BucketUsageInfo, bucketStats []BucketStats) (s BucketReplicationStats) {
|
||||||
|
if r == nil {
|
||||||
|
s = BucketReplicationStats{
|
||||||
|
Stats: make(map[string]*BucketReplicationStat),
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate cluster bucket stats
|
||||||
|
stats := make(map[string]*BucketReplicationStat)
|
||||||
|
var totReplicaSize int64
|
||||||
|
for _, bucketStat := range bucketStats {
|
||||||
|
totReplicaSize += bucketStat.ReplicationStats.ReplicaSize
|
||||||
|
for arn, stat := range bucketStat.ReplicationStats.Stats {
|
||||||
|
oldst := stats[arn]
|
||||||
|
if oldst == nil {
|
||||||
|
oldst = &BucketReplicationStat{}
|
||||||
|
}
|
||||||
|
stats[arn] = &BucketReplicationStat{
|
||||||
|
FailedCount: stat.FailedCount + oldst.FailedCount,
|
||||||
|
FailedSize: stat.FailedSize + oldst.FailedSize,
|
||||||
|
ReplicatedSize: stat.ReplicatedSize + oldst.ReplicatedSize,
|
||||||
|
Latency: stat.Latency.merge(oldst.Latency),
|
||||||
|
PendingCount: stat.PendingCount + oldst.PendingCount,
|
||||||
|
PendingSize: stat.PendingSize + oldst.PendingSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add initial usage stat to cluster stats
|
||||||
|
usageStat := globalReplicationStats.GetInitialUsage(bucket)
|
||||||
|
|
||||||
|
totReplicaSize += usageStat.ReplicaSize
|
||||||
|
for arn, stat := range usageStat.Stats {
|
||||||
|
st, ok := stats[arn]
|
||||||
|
if !ok {
|
||||||
|
st = &BucketReplicationStat{}
|
||||||
|
stats[arn] = st
|
||||||
|
}
|
||||||
|
st.ReplicatedSize += stat.ReplicatedSize
|
||||||
|
st.FailedSize += stat.FailedSize
|
||||||
|
st.FailedCount += stat.FailedCount
|
||||||
|
st.PendingSize += stat.PendingSize
|
||||||
|
st.PendingCount += stat.PendingCount
|
||||||
|
}
|
||||||
|
|
||||||
|
s = BucketReplicationStats{
|
||||||
|
Stats: make(map[string]*BucketReplicationStat, len(stats)),
|
||||||
|
}
|
||||||
|
var latestTotReplicatedSize int64
|
||||||
|
for _, st := range u.ReplicationInfo {
|
||||||
|
latestTotReplicatedSize += int64(st.ReplicatedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize computed real time stats with latest usage stat
|
||||||
|
for arn, tgtstat := range stats {
|
||||||
|
st := BucketReplicationStat{}
|
||||||
|
bu, ok := u.ReplicationInfo[arn]
|
||||||
|
if !ok {
|
||||||
|
bu = BucketTargetUsageInfo{}
|
||||||
|
}
|
||||||
|
// use in memory replication stats if it is ahead of usage info.
|
||||||
|
st.ReplicatedSize = int64(bu.ReplicatedSize)
|
||||||
|
if tgtstat.ReplicatedSize >= int64(bu.ReplicatedSize) {
|
||||||
|
st.ReplicatedSize = tgtstat.ReplicatedSize
|
||||||
|
}
|
||||||
|
s.ReplicatedSize += st.ReplicatedSize
|
||||||
|
// Reset FailedSize and FailedCount to 0 for negative overflows which can
|
||||||
|
// happen since data usage picture can lag behind actual usage state at the time of cluster start
|
||||||
|
st.FailedSize = int64(math.Max(float64(tgtstat.FailedSize), 0))
|
||||||
|
st.FailedCount = int64(math.Max(float64(tgtstat.FailedCount), 0))
|
||||||
|
st.PendingSize = int64(math.Max(float64(tgtstat.PendingSize), 0))
|
||||||
|
st.PendingCount = int64(math.Max(float64(tgtstat.PendingCount), 0))
|
||||||
|
st.Latency = tgtstat.Latency
|
||||||
|
|
||||||
|
s.Stats[arn] = &st
|
||||||
|
s.FailedSize += st.FailedSize
|
||||||
|
s.FailedCount += st.FailedCount
|
||||||
|
s.PendingCount += st.PendingCount
|
||||||
|
s.PendingSize += st.PendingSize
|
||||||
|
}
|
||||||
|
// normalize overall stats
|
||||||
|
s.ReplicaSize = int64(math.Max(float64(totReplicaSize), float64(u.ReplicaSize)))
|
||||||
|
s.ReplicatedSize = int64(math.Max(float64(s.ReplicatedSize), float64(latestTotReplicatedSize)))
|
||||||
|
r.dlock.Lock()
|
||||||
|
if len(r.mostRecentStats.Stats) == 0 {
|
||||||
|
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
|
||||||
|
}
|
||||||
|
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
|
||||||
|
r.mostRecentStats.Timestamp = UTCNow()
|
||||||
|
r.dlock.Unlock()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the most current of in-memory replication stats and data usage info from crawler.
|
||||||
|
func (r *ReplicationStats) getLatestReplicationStats(bucket string, u BucketUsageInfo) (s BucketReplicationStats) {
|
||||||
|
bucketStats := globalNotificationSys.GetClusterBucketStats(GlobalContext, bucket)
|
||||||
|
return r.calculateBucketReplicationStats(bucket, u, bucketStats)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
@@ -503,28 +505,32 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
|
|||||||
}
|
}
|
||||||
var dsc ReplicateDecision
|
var dsc ReplicateDecision
|
||||||
var tgtStatuses map[string]replication.StatusType
|
var tgtStatuses map[string]replication.StatusType
|
||||||
|
var purgeStatuses map[string]VersionPurgeStatusType
|
||||||
|
|
||||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||||
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
|
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
|
||||||
ObjectV: ObjectV{
|
ObjectV: ObjectV{
|
||||||
ObjectName: oi.Name,
|
ObjectName: oi.Name,
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
},
|
},
|
||||||
}, oi, ObjectOptions{}, nil)
|
}, oi, ObjectOptions{VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name)}, nil)
|
||||||
} else {
|
} else {
|
||||||
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(ObjectInfo{
|
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(ObjectInfo{
|
||||||
UserDefined: oi.UserDefined,
|
UserDefined: oi.UserDefined,
|
||||||
}, replication.HealReplicationType, ObjectOptions{}))
|
}, replication.HealReplicationType, ObjectOptions{}))
|
||||||
}
|
}
|
||||||
tgtStatuses = replicationStatusesMap(oi.ReplicationStatusInternal)
|
tgtStatuses = replicationStatusesMap(oi.ReplicationStatusInternal)
|
||||||
|
purgeStatuses = versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
|
||||||
existingObjResync := rcfg.Resync(GlobalContext, oi, &dsc, tgtStatuses)
|
existingObjResync := rcfg.Resync(GlobalContext, oi, &dsc, tgtStatuses)
|
||||||
|
tm, _ := time.Parse(time.RFC3339Nano, oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
|
||||||
return ReplicateObjectInfo{
|
return ReplicateObjectInfo{
|
||||||
ObjectInfo: oi,
|
ObjectInfo: oi,
|
||||||
OpType: replication.HealReplicationType,
|
OpType: replication.HealReplicationType,
|
||||||
Dsc: dsc,
|
Dsc: dsc,
|
||||||
ExistingObjResync: existingObjResync,
|
ExistingObjResync: existingObjResync,
|
||||||
TargetStatuses: tgtStatuses,
|
TargetStatuses: tgtStatuses,
|
||||||
|
TargetPurgeStatuses: purgeStatuses,
|
||||||
|
ReplicationTimestamp: tm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,3 +730,51 @@ func parseSizeFromContentRange(h http.Header) (sz int64, err error) {
|
|||||||
}
|
}
|
||||||
return int64(usz), nil
|
return int64(usz), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractReplicateDiffOpts(q url.Values) (opts madmin.ReplDiffOpts) {
|
||||||
|
opts.Verbose = q.Get("verbose") == "true"
|
||||||
|
opts.ARN = q.Get("arn")
|
||||||
|
opts.Prefix = q.Get("prefix")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
replicationMRFDir = bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "mrf"
|
||||||
|
mrfMetaFormat = 1
|
||||||
|
mrfMetaVersionV1 = 1
|
||||||
|
mrfMetaVersion = mrfMetaVersionV1
|
||||||
|
)
|
||||||
|
|
||||||
|
// MRFReplicateEntry mrf entry to save to disk
|
||||||
|
type MRFReplicateEntry struct {
|
||||||
|
Bucket string `json:"bucket" msg:"b"`
|
||||||
|
Object string `json:"object" msg:"o"`
|
||||||
|
versionID string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MRFReplicateEntries has the map of MRF entries to save to disk
|
||||||
|
type MRFReplicateEntries struct {
|
||||||
|
Entries map[string]MRFReplicateEntry `json:"entries" msg:"e"`
|
||||||
|
Version int `json:"version" msg:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMRFEntry returns the relevant info needed by MRF
|
||||||
|
func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
|
||||||
|
return MRFReplicateEntry{
|
||||||
|
Bucket: ri.Bucket,
|
||||||
|
Object: ri.Name,
|
||||||
|
versionID: ri.VersionID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReplicationStatsPath(nodeName string) string {
|
||||||
|
nodeStr := strings.ReplaceAll(nodeName, ":", "_")
|
||||||
|
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + nodeStr + ".stats"
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
replStatsMetaFormat = 1
|
||||||
|
replStatsVersionV1 = 1
|
||||||
|
replStatsVersion = replStatsVersionV1
|
||||||
|
replStatsSaveInterval = time.Minute * 5
|
||||||
|
)
|
||||||
|
|||||||
@@ -260,6 +260,413 @@ func (z *BucketReplicationResyncStatus) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *MRFReplicateEntries) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "e":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Entries == nil {
|
||||||
|
z.Entries = make(map[string]MRFReplicateEntry, zb0002)
|
||||||
|
} else if len(z.Entries) > 0 {
|
||||||
|
for key := range z.Entries {
|
||||||
|
delete(z.Entries, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0002 > 0 {
|
||||||
|
zb0002--
|
||||||
|
var za0001 string
|
||||||
|
var za0002 MRFReplicateEntry
|
||||||
|
za0001, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0003 > 0 {
|
||||||
|
zb0003--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "b":
|
||||||
|
za0002.Bucket, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "o":
|
||||||
|
za0002.Object, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.Entries[za0001] = za0002
|
||||||
|
}
|
||||||
|
case "v":
|
||||||
|
z.Version, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *MRFReplicateEntries) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "e"
|
||||||
|
err = en.Append(0x82, 0xa1, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteMapHeader(uint32(len(z.Entries)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001, za0002 := range z.Entries {
|
||||||
|
err = en.WriteString(za0001)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// map header, size 2
|
||||||
|
// write "b"
|
||||||
|
err = en.Append(0x82, 0xa1, 0x62)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(za0002.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "o"
|
||||||
|
err = en.Append(0xa1, 0x6f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(za0002.Object)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "v"
|
||||||
|
err = en.Append(0xa1, 0x76)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Version)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *MRFReplicateEntries) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "e"
|
||||||
|
o = append(o, 0x82, 0xa1, 0x65)
|
||||||
|
o = msgp.AppendMapHeader(o, uint32(len(z.Entries)))
|
||||||
|
for za0001, za0002 := range z.Entries {
|
||||||
|
o = msgp.AppendString(o, za0001)
|
||||||
|
// map header, size 2
|
||||||
|
// string "b"
|
||||||
|
o = append(o, 0x82, 0xa1, 0x62)
|
||||||
|
o = msgp.AppendString(o, za0002.Bucket)
|
||||||
|
// string "o"
|
||||||
|
o = append(o, 0xa1, 0x6f)
|
||||||
|
o = msgp.AppendString(o, za0002.Object)
|
||||||
|
}
|
||||||
|
// string "v"
|
||||||
|
o = append(o, 0xa1, 0x76)
|
||||||
|
o = msgp.AppendInt(o, z.Version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *MRFReplicateEntries) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "e":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Entries == nil {
|
||||||
|
z.Entries = make(map[string]MRFReplicateEntry, zb0002)
|
||||||
|
} else if len(z.Entries) > 0 {
|
||||||
|
for key := range z.Entries {
|
||||||
|
delete(z.Entries, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0002 > 0 {
|
||||||
|
var za0001 string
|
||||||
|
var za0002 MRFReplicateEntry
|
||||||
|
zb0002--
|
||||||
|
za0001, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0003 > 0 {
|
||||||
|
zb0003--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "b":
|
||||||
|
za0002.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "o":
|
||||||
|
za0002.Object, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.Entries[za0001] = za0002
|
||||||
|
}
|
||||||
|
case "v":
|
||||||
|
z.Version, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *MRFReplicateEntries) Msgsize() (s int) {
|
||||||
|
s = 1 + 2 + msgp.MapHeaderSize
|
||||||
|
if z.Entries != nil {
|
||||||
|
for za0001, za0002 := range z.Entries {
|
||||||
|
_ = za0002
|
||||||
|
s += msgp.StringPrefixSize + len(za0001) + 1 + 2 + msgp.StringPrefixSize + len(za0002.Bucket) + 2 + msgp.StringPrefixSize + len(za0002.Object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += 2 + msgp.IntSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *MRFReplicateEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "b":
|
||||||
|
z.Bucket, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "o":
|
||||||
|
z.Object, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z MRFReplicateEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "b"
|
||||||
|
err = en.Append(0x82, 0xa1, 0x62)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "o"
|
||||||
|
err = en.Append(0xa1, 0x6f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Object)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z MRFReplicateEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "b"
|
||||||
|
o = append(o, 0x82, 0xa1, 0x62)
|
||||||
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
|
// string "o"
|
||||||
|
o = append(o, 0xa1, 0x6f)
|
||||||
|
o = msgp.AppendString(o, z.Object)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *MRFReplicateEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "b":
|
||||||
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "o":
|
||||||
|
z.Object, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z MRFReplicateEntry) Msgsize() (s int) {
|
||||||
|
s = 1 + 2 + msgp.StringPrefixSize + len(z.Bucket) + 2 + msgp.StringPrefixSize + len(z.Object)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *ReplicateDecision) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *ReplicateDecision) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
var field []byte
|
var field []byte
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user