mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 10:43:15 +03:00
Compare commits
391 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d3639396 | |||
| 027e17468a | |||
| 45ea161f8d | |||
| 7b8a456f68 | |||
| b43906f6ee | |||
| 6a66f142d4 | |||
| 5982965839 | |||
| bfb92a27b7 | |||
| 8565cefe4e | |||
| 8cdf2106b0 | |||
| 35fafb837b | |||
| 274bbad5cb | |||
| 5c451d1690 | |||
| c987313431 | |||
| 2ecaab55a6 | |||
| 3e792ae2a2 | |||
| 6df6ac0f34 | |||
| 4cc500a041 | |||
| d8e28830cf | |||
| 3e16ec457a | |||
| e5d378931d | |||
| 6128304f6e | |||
| e63a10e505 | |||
| 5434088c51 | |||
| a773cf48d8 | |||
| 386dd56856 | |||
| f714840da7 | |||
| 7c9ef76f66 | |||
| cffdb01279 | |||
| 970ddb424b | |||
| b390a2a0b9 | |||
| cce5d7152a | |||
| 90158f1e33 | |||
| 26624552be | |||
| c606c76323 | |||
| d674263eb7 | |||
| e7d3b49a20 | |||
| 5df61ab96b | |||
| 3456b03b12 | |||
| f6fb27e8f0 | |||
| 8368ab76aa | |||
| 3e83643320 | |||
| 2eb52ca5f4 | |||
| 705e196b6c | |||
| 7b5223d83d | |||
| f164085227 | |||
| 31bf6f0c25 | |||
| 48191dd748 | |||
| c4f29d24da | |||
| db7890660e | |||
| 9adc33efbb | |||
| 8f65aba04b | |||
| 3a0082f0f1 | |||
| 14792cdbc6 | |||
| 4939987eb8 | |||
| 4bca62a0bd | |||
| 82e2be4239 | |||
| 4550ac6fff | |||
| 97856bfebf | |||
| a60a0e52bb | |||
| 83a67a1d21 | |||
| 12391ec4ba | |||
| e65ed2e44f | |||
| d90044b847 | |||
| d8c1f93de6 | |||
| 54d243cd98 | |||
| d74e4642e3 | |||
| a51488cbaa | |||
| 04848dfa1c | |||
| 78d18d8fc8 | |||
| dc819afa44 | |||
| 4a564336fe | |||
| 6b7ced80fe | |||
| f60bbdf86b | |||
| 8c79f87f02 | |||
| f3beb1236a | |||
| 934bed47fa | |||
| 038bcd9079 | |||
| 6d70f6a4ac | |||
| ce93b2681b | |||
| 8d036ed6d8 | |||
| 9c53cc1b83 | |||
| 3514e89eb3 | |||
| 6ff12f5f01 | |||
| ee2a436a5b | |||
| a896125490 | |||
| e083471ec4 | |||
| de9b64834e | |||
| 919441d9c4 | |||
| 80d31113e5 | |||
| 7e2b79984e | |||
| d54cf77356 | |||
| 951b6b203b | |||
| 44e23b7f4f | |||
| c22a387695 | |||
| 1ab4d6a6aa | |||
| 96c0ce1f0c | |||
| fe11e9047d | |||
| ce0e17b62b | |||
| 303be1866d | |||
| a6113b2315 | |||
| 3ca046b408 | |||
| 4ec45753e6 | |||
| e6ea5c2703 | |||
| 790833f3b2 | |||
| 02aecb2fc1 | |||
| 7cbca43eb1 | |||
| 2f564437ae | |||
| ae4ded7fd1 | |||
| bdd094bc39 | |||
| e6fa410778 | |||
| 350c5ff8f8 | |||
| f139a19238 | |||
| e90efd73a2 | |||
| 81c907b4bf | |||
| ab49471f33 | |||
| aabf053d2f | |||
| f839bb5a0a | |||
| 91130e884b | |||
| 2ff655a745 | |||
| 0422eda6a2 | |||
| 31e6f60847 | |||
| 7742238495 | |||
| 3ad41fe89d | |||
| f96ed3769f | |||
| a75fafdbe2 | |||
| a58b7874ef | |||
| 6990de9c94 | |||
| 75a8e81f8f | |||
| 519c0077a9 | |||
| 734d07a532 | |||
| df93102235 | |||
| 39f3d5493b | |||
| 14a7ae8586 | |||
| 692ff41ef7 | |||
| 86409fa93d | |||
| 7bc47a14cc | |||
| 4a31b31ca6 | |||
| 9263be8cca | |||
| 73e308079a | |||
| 08b24620c0 | |||
| 95675b0c9a | |||
| 251c1ef6da | |||
| 0fa430c1da | |||
| f60b6eb82e | |||
| 1ebf6f146a | |||
| 8f7fe0405e | |||
| 9a34fd5c4a | |||
| b9e3a8b5ac | |||
| f794fe79e3 | |||
| 0f9e125cf3 | |||
| d778d9493f | |||
| 70d2c2ccc9 | |||
| 9dea7020f0 | |||
| 990d074f7d | |||
| e413f05397 | |||
| d1b1fee080 | |||
| 9738d605e4 | |||
| 7ff8128f15 | |||
| 10099357b6 | |||
| 80b8ce89a4 | |||
| 0745736e28 | |||
| 0b766288ef | |||
| 598ca0569c | |||
| d295ce5708 | |||
| b5a3d79bce | |||
| 17a5ff51ff | |||
| 0784a0c33a | |||
| 3595cb1267 | |||
| 0bcb1b679d | |||
| a3017c724e | |||
| 07859ef48b | |||
| 267d7bf0a9 | |||
| be83dfc52a | |||
| ca88ca753c | |||
| f86d3538f6 | |||
| 1c3590078d | |||
| aa158228f9 | |||
| 8747834c69 | |||
| 2c1e37197b | |||
| 50c10a5087 | |||
| 9f4ad873bc | |||
| 4683a623dc | |||
| 06899210a7 | |||
| cbdab62c1e | |||
| 8df6112204 | |||
| 8e8ddf7233 | |||
| 311ab43d4c | |||
| 97692bc772 | |||
| 21016265e5 | |||
| 54120107ce | |||
| 9bf5990ea9 | |||
| 74f7cf24ae | |||
| fb28aa847b | |||
| 0724205f35 | |||
| b72cac4cf3 | |||
| 47d715f642 | |||
| bd77f29fc4 | |||
| d1e1205036 | |||
| 71753e21e0 | |||
| fde3299bf3 | |||
| 1a1f00fa15 | |||
| 4a1efabda4 | |||
| 3b88a646ec | |||
| 2294e53a0b | |||
| f0819cce75 | |||
| 1e11b4629f | |||
| 5c72a34fa8 | |||
| b9277c8030 | |||
| 8c76e1353e | |||
| ad382799b1 | |||
| 68de5a6f6a | |||
| 4ea31da889 | |||
| 0a796505c1 | |||
| 37749f4623 | |||
| 86e0d272f3 | |||
| 8527f22df1 | |||
| b456292295 | |||
| 03fdbc3ec2 | |||
| 4c773f7068 | |||
| d8e07f2c41 | |||
| 5412d730c1 | |||
| fe9f23e632 | |||
| 422898d9b3 | |||
| b686bb9c83 | |||
| 5e5cdc581d | |||
| 02cfa774be | |||
| 3a2f89b3c0 | |||
| 6135f072d2 | |||
| 7331659d3d | |||
| e63a44b734 | |||
| 6b14c4ab1e | |||
| 4bf90ca67f | |||
| e0655e24f2 | |||
| bfc36aed89 | |||
| be7f67268d | |||
| a982baff27 | |||
| 51222cc664 | |||
| f53c5a020e | |||
| 5b30bbda92 | |||
| 858e2a43df | |||
| df9894e275 | |||
| ca77ee1c0e | |||
| 592f2f23a3 | |||
| c49a80db41 | |||
| 46275c6547 | |||
| 0994ed9783 | |||
| eb95353cb1 | |||
| 029758cb20 | |||
| 649035677f | |||
| 646d6917ed | |||
| d9db7f3308 | |||
| 6a8c62f9fd | |||
| 4442382c16 | |||
| 00124c56d9 | |||
| 2c32c2149e | |||
| 734f258878 | |||
| 0e0c53bba4 | |||
| 5cc23ae052 | |||
| 6fd088f448 | |||
| d6d770c1b1 | |||
| b07df5cae1 | |||
| c107728676 | |||
| ba5215561f | |||
| 4eb45c9a0f | |||
| 187129a907 | |||
| 284a2b9021 | |||
| 0b53e30ecb | |||
| bd2131ba34 | |||
| 73a41a725a | |||
| 1aec168c84 | |||
| 21a549a83b | |||
| 8a16a1a1a9 | |||
| ad726b49b4 | |||
| db2241066b | |||
| f1cc16e788 | |||
| 3820a905e0 | |||
| 2042d4873c | |||
| f9be783f3e | |||
| 23773bb32b | |||
| 2fd7545b6c | |||
| 71b97fd3ac | |||
| 03991c5d41 | |||
| 9c042a503b | |||
| 614060764d | |||
| 4a678ad70f | |||
| a3ba8188d7 | |||
| 2760fc86af | |||
| abb14aeec1 | |||
| 8ceb2a93fd | |||
| c2f16ee846 | |||
| 071c004f8b | |||
| 6484453fc6 | |||
| a0d0645128 | |||
| 1738eb24b1 | |||
| 253194e491 | |||
| 736e58dd68 | |||
| 907a171edd | |||
| ed6d2a100f | |||
| effe131090 | |||
| 01498a3e34 | |||
| 18063bf25c | |||
| 57f0176759 | |||
| dbbed6f7f0 | |||
| 7fbfdceba3 | |||
| 9dda9fb903 | |||
| f1418a50f0 | |||
| 017954e7ea | |||
| 806625cbff | |||
| 045e30f2c1 | |||
| c6a9a94f94 | |||
| 8e7c00f3d4 | |||
| d1ed1da8c6 | |||
| 23e8390997 | |||
| 71403be912 | |||
| f28d02b7f2 | |||
| e0cb814f3f | |||
| 98a08e1644 | |||
| 3047121255 | |||
| 5a7f92481e | |||
| 0d45c38782 | |||
| 56d1b227cf | |||
| 061fa0635c | |||
| 6e138f955e | |||
| bea87a5a20 | |||
| 2b4eb87d77 | |||
| 799758e54f | |||
| 1f9abbee4d | |||
| fdf0ae9167 | |||
| 00eb6f6bc9 | |||
| 66174692a2 | |||
| 849fcf0127 | |||
| 209680e89f | |||
| e0c04a2da0 | |||
| 27d9bd04e5 | |||
| 511424a287 | |||
| bebcf4f004 | |||
| eafa775952 | |||
| 66b4a862e0 | |||
| 9603489dd3 | |||
| b302c8a5f4 | |||
| 4de88e87bb | |||
| b880796aef | |||
| 37a5d5d7a0 | |||
| 3cac262dd1 | |||
| e6ab4db6b8 | |||
| ca989eb0b3 | |||
| d778d034e7 | |||
| df08fd1f03 | |||
| ac82f416a4 | |||
| f7f9517b6a | |||
| 90cff10e2b | |||
| 81caf35926 | |||
| 5726cef3ca | |||
| 5fdf47b118 | |||
| 8b74a72b21 | |||
| eec69d6796 | |||
| 0537a21b79 | |||
| 4c54ed8748 | |||
| a4006e23a0 | |||
| b17dc81540 | |||
| d73c4f09f3 | |||
| 4c81201f95 | |||
| cd8d511d3d | |||
| 899a2fa1c7 | |||
| 17e17da00d | |||
| a5da9120f3 | |||
| aa12d75d75 | |||
| dd4a2d7419 | |||
| 6fcbdd5607 | |||
| 3831cc9e3b | |||
| 230fc0d186 | |||
| 7f9498f43f | |||
| 1cf322b7d4 | |||
| 3168e93730 | |||
| 0b1c824618 | |||
| c851e022b7 | |||
| 6f45e303f5 | |||
| 5ad032826a | |||
| 84bf4624a4 | |||
| dff37aa33d | |||
| d12831eb07 | |||
| 4a36cd7035 | |||
| 00555c747e | |||
| 03490c811b | |||
| 48d2c03250 | |||
| ed78854cea | |||
| e60834838f | |||
| d616d8a857 | |||
| 24cab7f9df | |||
| b2536476c9 |
@@ -1,51 +0,0 @@
|
|||||||
name: "Code scanning - action"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 19 * * 0'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
CodeQL-Build:
|
|
||||||
|
|
||||||
# CodeQL runs on ubuntu-latest and windows-latest
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
# We must fetch at least the immediate parents so that if this is
|
|
||||||
# a pull request then we can checkout the head.
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
with:
|
|
||||||
languages: go, javascript
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
||||||
@@ -26,7 +26,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
MINIO_CI_CD: 1
|
|
||||||
run: |
|
run: |
|
||||||
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
||||||
go test -v --timeout 50m ./...
|
go test -v --timeout 50m ./...
|
||||||
@@ -35,8 +34,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
MINIO_CI_CD: 1
|
|
||||||
run: |
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
sudo apt-get install devscripts shellcheck
|
sudo apt-get install devscripts shellcheck
|
||||||
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\///")
|
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-linux.amd64-${nancy_version} && chmod +x nancy
|
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-linux.amd64-${nancy_version} && chmod +x nancy
|
||||||
|
|||||||
@@ -23,5 +23,11 @@ issues:
|
|||||||
exclude:
|
exclude:
|
||||||
- should have a package comment
|
- should have a package comment
|
||||||
- error strings should not be capitalized or end with punctuation or a newline
|
- error strings should not be capitalized or end with punctuation or a newline
|
||||||
|
|
||||||
|
run:
|
||||||
|
skip-dirs:
|
||||||
|
- pkg/rpc
|
||||||
|
- pkg/argon2
|
||||||
|
|
||||||
service:
|
service:
|
||||||
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
|
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
CVE-2018-17075
|
|
||||||
CVE-2018-17848
|
|
||||||
CVE-2018-17846
|
|
||||||
CVE-2018-17847
|
|
||||||
CVE-2018-17143
|
|
||||||
CVE-2018-17142
|
|
||||||
+9
-6
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.14-alpine as builder
|
FROM golang:1.15-alpine as builder
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
@@ -11,22 +11,25 @@ RUN \
|
|||||||
git clone https://github.com/minio/minio && cd minio && \
|
git clone https://github.com/minio/minio && cd minio && \
|
||||||
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
||||||
|
|
||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
||||||
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
COPY --from=builder /go/bin/minio /usr/bin/minio
|
COPY --from=builder /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=builder /go/minio/CREDITS /third_party/
|
COPY --from=builder /go/minio/CREDITS /licenses/CREDITS
|
||||||
|
COPY --from=builder /go/minio/LICENSE /licenses/LICENSE
|
||||||
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
microdnf update --nodocs && \
|
||||||
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
|
microdnf clean all && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
|
||||||
FROM arm32v7/alpine:3.12
|
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
|
||||||
|
|
||||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
|
||||||
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-arm/minio -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-arm/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-arm/minio.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
|
||||||
FROM arm64v8/alpine:3.12
|
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
|
||||||
|
|
||||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
|
||||||
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-arm64/minio -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-arm64/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-arm64/minio.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
|
||||||
/usr/bin/verify-minio.sh
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
FROM golang:1.15-alpine as builder
|
||||||
|
|
||||||
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
ENV GO111MODULE on
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apk add --no-cache git && \
|
||||||
|
git clone https://github.com/minio/minio && cd minio && \
|
||||||
|
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
||||||
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
COPY --from=builder /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=builder /go/minio/CREDITS /licenses/CREDITS
|
||||||
|
COPY --from=builder /go/minio/LICENSE /licenses/LICENSE
|
||||||
|
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
microdnf update --nodocs && \
|
||||||
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
|
microdnf clean all
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
CMD ["minio", "server", "/data"]
|
||||||
+13
-9
@@ -1,22 +1,26 @@
|
|||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
COPY minio /usr/bin/
|
COPY minio /usr/bin/
|
||||||
COPY CREDITS /third_party/
|
COPY CREDITS /licenses/CREDITS
|
||||||
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE=off \
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
microdnf update --nodocs && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
chmod +x /usr/bin/minio && \
|
microdnf clean all && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh
|
chmod +x /usr/bin/minio && \
|
||||||
|
chmod +x /usr/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
FROM multiarch/qemu-user-static:x86_64-ppc64le as qemu
|
|
||||||
FROM ppc64le/alpine:3.12
|
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
|
||||||
|
|
||||||
COPY --from=qemu /usr/bin/qemu-ppc64le-static /usr/bin
|
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
|
||||||
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-ppc64le/minio -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-ppc64le/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-ppc64le/minio.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
|
||||||
/usr/bin/verify-minio.sh
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
+25
-13
@@ -1,26 +1,38 @@
|
|||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
LABEL name="MinIO" \
|
||||||
|
vendor="MinIO Inc <dev@min.io>" \
|
||||||
|
maintainer="MinIO Inc <dev@min.io>" \
|
||||||
|
version="RELEASE.2020-11-25T22-36-25Z" \
|
||||||
|
release="RELEASE.2020-11-25T22-36-25Z" \
|
||||||
|
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
||||||
|
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
||||||
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
||||||
|
|
||||||
|
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
COPY CREDITS /licenses/CREDITS
|
||||||
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
microdnf update --nodocs && \
|
||||||
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/bin/minio && \
|
microdnf install minisign --nodocs && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-amd64/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-amd64/minio.minisig -o /usr/bin/minio.minisig && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
microdnf clean all && \
|
||||||
chmod +x /usr/bin/minio && \
|
chmod +x /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
|
||||||
/usr/bin/verify-minio.sh
|
/usr/bin/verify-minio.sh
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
FROM multiarch/qemu-user-static:x86_64-s390x as qemu
|
|
||||||
FROM s390x/alpine:3.12
|
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
|
||||||
|
|
||||||
COPY --from=qemu /usr/bin/qemu-s390x-static /usr/bin
|
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
|
||||||
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-s390x/minio -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-s390x/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-s390x/minio.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/verify-minio.sh -o /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q https://raw.githubusercontent.com/minio/minio/master/dockerscripts/docker-entrypoint.sh -o /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
|
||||||
/usr/bin/verify-minio.sh
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -18,11 +18,17 @@ getdeps:
|
|||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
|
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
|
||||||
@which ruleguard 1>/dev/null || (echo "Installing ruleguard" && GO111MODULE=off go get github.com/quasilyte/go-ruleguard/...)
|
@which ruleguard 1>/dev/null || (echo "Installing ruleguard" && GO111MODULE=off go get github.com/quasilyte/go-ruleguard/...)
|
||||||
|
@which msgp 1>/dev/null || (echo "Installing msgp" && GO111MODULE=off go get github.com/tinylib/msgp)
|
||||||
|
@which stringer 1>/dev/null || (echo "Installing stringer" && GO111MODULE=off go get golang.org/x/tools/cmd/stringer)
|
||||||
|
|
||||||
crosscompile:
|
crosscompile:
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
|
|
||||||
verifiers: getdeps fmt lint ruleguard
|
verifiers: getdeps fmt lint ruleguard check-gen
|
||||||
|
|
||||||
|
check-gen:
|
||||||
|
@go generate ./... >/dev/null
|
||||||
|
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@@ -51,7 +57,7 @@ test-race: verifiers build
|
|||||||
# Verify minio binary
|
# Verify minio binary
|
||||||
verify:
|
verify:
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=1 go build -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 of disks with minio binary
|
# Verify healing of disks with minio binary
|
||||||
@@ -65,6 +71,10 @@ build: checks
|
|||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|
||||||
|
hotfix: LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
||||||
|
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#'))
|
||||||
|
hotfix: install
|
||||||
|
|
||||||
docker: checks
|
docker: checks
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ service minio start
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Install from Source
|
## Install from Source
|
||||||
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.14](https://golang.org/dl/#stable)
|
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.15](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GO111MODULE=on go get github.com/minio/minio
|
GO111MODULE=on go get github.com/minio/minio
|
||||||
@@ -150,7 +150,7 @@ service iptables restart
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Test using MinIO Browser
|
## Test using MinIO Browser
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully.
|
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure your server has started successfully.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
## Vulnerability Management Policy
|
||||||
|
|
||||||
|
This document formally describes the process of addressing and managing a
|
||||||
|
reported vulnerability that has been found in the MinIO server code base,
|
||||||
|
any directly connected ecosystem component or a direct / indirect dependency
|
||||||
|
of the code base.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
The vulnerability management policy described in this document covers the
|
||||||
|
process of investigating, assessing and resolving a vulnerability report
|
||||||
|
opened by a MinIO employee or an external third party.
|
||||||
|
|
||||||
|
Therefore, it lists pre-conditions and actions that should be performed to
|
||||||
|
resolve and fix a reported vulnerability.
|
||||||
|
|
||||||
|
### Vulnerability Management Process
|
||||||
|
|
||||||
|
The vulnerability management process requires that the vulnerability report
|
||||||
|
contains the following information:
|
||||||
|
|
||||||
|
- The project / component that contains the reported vulnerability.
|
||||||
|
- A description of the vulnerability. In particular, the type of the
|
||||||
|
reported vulnerability and how it might be exploited. Alternatively,
|
||||||
|
a well-established vulnerability identifier, e.g. CVE number, can be
|
||||||
|
used instead.
|
||||||
|
|
||||||
|
Based on the description mentioned above, a MinIO engineer or security team
|
||||||
|
member investigates:
|
||||||
|
|
||||||
|
- Whether the reported vulnerability exists.
|
||||||
|
- The conditions that are required such that the vulnerability can be exploited.
|
||||||
|
- The steps required to fix the vulnerability.
|
||||||
|
|
||||||
|
In general, if the vulnerability exists in one of the MinIO code bases
|
||||||
|
itself - not in a code dependency - then MinIO will, if possible, fix
|
||||||
|
the vulnerability or implement reasonable countermeasures such that the
|
||||||
|
vulnerability cannot be exploited anymore.
|
||||||
|
|
||||||
@@ -57,22 +57,6 @@ export class BrowserDropdown extends React.Component {
|
|||||||
const { fetchServerInfo } = this.props
|
const { fetchServerInfo } = this.props
|
||||||
fetchServerInfo()
|
fetchServerInfo()
|
||||||
}
|
}
|
||||||
fullScreen(e) {
|
|
||||||
e.preventDefault()
|
|
||||||
let el = document.documentElement
|
|
||||||
if (el.requestFullscreen) {
|
|
||||||
el.requestFullscreen()
|
|
||||||
}
|
|
||||||
if (el.mozRequestFullScreen) {
|
|
||||||
el.mozRequestFullScreen()
|
|
||||||
}
|
|
||||||
if (el.webkitRequestFullscreen) {
|
|
||||||
el.webkitRequestFullscreen()
|
|
||||||
}
|
|
||||||
if (el.msRequestFullscreen) {
|
|
||||||
el.msRequestFullscreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logout(e) {
|
logout(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
web.Logout()
|
web.Logout()
|
||||||
@@ -87,24 +71,30 @@ export class BrowserDropdown extends React.Component {
|
|||||||
<i className="fas fa-bars" />
|
<i className="fas fa-bars" />
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
<Dropdown.Menu className="dropdown-menu-right">
|
<Dropdown.Menu className="dropdown-menu-right">
|
||||||
|
<li>
|
||||||
|
<a href="" onClick={this.showChangePassword.bind(this)}>
|
||||||
|
Change Password <i className="fas fa-cog" />
|
||||||
|
</a>
|
||||||
|
{this.state.showChangePasswordModal && (
|
||||||
|
<ChangePasswordModal
|
||||||
|
serverInfo={serverInfo}
|
||||||
|
hideChangePassword={this.hideChangePassword.bind(this)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" href="https://docs.min.io/?ref=ob">
|
||||||
|
Documentation <i className="fas fa-book" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a target="_blank" href="https://github.com/minio/minio">
|
<a target="_blank" href="https://github.com/minio/minio">
|
||||||
GitHub <i className="fab fa-github" />
|
GitHub <i className="fab fa-github" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="" onClick={this.fullScreen}>
|
<a target="_blank" href="https://min.io/pricing?ref=ob">
|
||||||
Fullscreen <i className="fas fa-expand" />
|
Get Support <i className="fas fa-question-circle" />
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a target="_blank" href="https://docs.min.io/">
|
|
||||||
Documentation <i className="fas fa-book" />
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a target="_blank" href="https://slack.min.io">
|
|
||||||
Ask for help <i className="fas fa-question-circle" />
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -118,20 +108,9 @@ export class BrowserDropdown extends React.Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="" onClick={this.showChangePassword.bind(this)}>
|
|
||||||
Change Password <i className="fas fa-cog" />
|
|
||||||
</a>
|
|
||||||
{this.state.showChangePasswordModal && (
|
|
||||||
<ChangePasswordModal
|
|
||||||
serverInfo={serverInfo}
|
|
||||||
hideChangePassword={this.hideChangePassword.bind(this)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="" id="logout" onClick={this.logout}>
|
<a href="" id="logout" onClick={this.logout}>
|
||||||
Sign Out <i className="fas fa-sign-out-alt" />
|
Logout <i className="fas fa-sign-out-alt" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
import ObjectsSearch from "../objects/ObjectsSearch"
|
||||||
import Path from "../objects/Path"
|
import Path from "../objects/Path"
|
||||||
import StorageInfo from "./StorageInfo"
|
import StorageInfo from "./StorageInfo"
|
||||||
import BrowserDropdown from "./BrowserDropdown"
|
import BrowserDropdown from "./BrowserDropdown"
|
||||||
@@ -27,6 +28,7 @@ export const Header = () => {
|
|||||||
<header className="fe-header">
|
<header className="fe-header">
|
||||||
<Path />
|
<Path />
|
||||||
{loggedIn && <StorageInfo />}
|
{loggedIn && <StorageInfo />}
|
||||||
|
{loggedIn && <ObjectsSearch />}
|
||||||
<ul className="feh-actions">
|
<ul className="feh-actions">
|
||||||
{loggedIn ? (
|
{loggedIn ? (
|
||||||
<BrowserDropdown />
|
<BrowserDropdown />
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ const bucketsFilterSelector = state => state.buckets.filter
|
|||||||
export const getFilteredBuckets = createSelector(
|
export const getFilteredBuckets = createSelector(
|
||||||
bucketsSelector,
|
bucketsSelector,
|
||||||
bucketsFilterSelector,
|
bucketsFilterSelector,
|
||||||
(buckets, filter) => buckets.filter(bucket => bucket.indexOf(filter) > -1)
|
(buckets, filter) => buckets.filter(
|
||||||
|
bucket => bucket.toLowerCase().indexOf(filter.toLowerCase()) > -1)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getCurrentBucket = state => state.buckets.currentBucket
|
export const getCurrentBucket = state => state.buckets.currentBucket
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import React from "react"
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import InfiniteScroll from "react-infinite-scroller"
|
import InfiniteScroll from "react-infinite-scroller"
|
||||||
import ObjectsList from "./ObjectsList"
|
import ObjectsList from "./ObjectsList"
|
||||||
|
import { getFilteredObjects } from "./selectors"
|
||||||
|
|
||||||
export class ObjectsListContainer extends React.Component {
|
export class ObjectsListContainer extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -39,22 +40,29 @@ export class ObjectsListContainer extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.filter !== prevProps.filter) {
|
||||||
|
this.setState({
|
||||||
|
page: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
loadNextPage() {
|
loadNextPage() {
|
||||||
this.setState(state => {
|
this.setState(state => {
|
||||||
return { page: state.page + 1 }
|
return { page: state.page + 1 }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { objects, listLoading } = this.props
|
const { filteredObjects, listLoading } = this.props
|
||||||
|
|
||||||
const visibleObjects = objects.slice(0, this.state.page * 100)
|
const visibleObjects = filteredObjects.slice(0, this.state.page * 100)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
pageStart={0}
|
pageStart={0}
|
||||||
loadMore={this.loadNextPage}
|
loadMore={this.loadNextPage}
|
||||||
hasMore={objects.length > visibleObjects.length}
|
hasMore={filteredObjects.length > visibleObjects.length}
|
||||||
useWindow={true}
|
useWindow={true}
|
||||||
initialLoad={false}
|
initialLoad={false}
|
||||||
>
|
>
|
||||||
@@ -70,7 +78,8 @@ const mapStateToProps = state => {
|
|||||||
return {
|
return {
|
||||||
currentBucket: state.buckets.currentBucket,
|
currentBucket: state.buckets.currentBucket,
|
||||||
currentPrefix: state.objects.currentPrefix,
|
currentPrefix: state.objects.currentPrefix,
|
||||||
objects: state.objects.list,
|
filteredObjects: getFilteredObjects(state),
|
||||||
|
filter: state.objects.filter,
|
||||||
sortBy: state.objects.sortBy,
|
sortBy: state.objects.sortBy,
|
||||||
sortOrder: state.objects.sortOrder,
|
sortOrder: state.objects.sortOrder,
|
||||||
listLoading: state.objects.listLoading
|
listLoading: state.objects.listLoading
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import * as actionsObjects from "./actions"
|
||||||
|
|
||||||
|
export const ObjectsSearch = ({ onChange }) => (
|
||||||
|
<div
|
||||||
|
className="input-group ig-left ig-search-dark"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="ig-text"
|
||||||
|
type="input"
|
||||||
|
placeholder="Search Objects..."
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<i className="ig-helpers" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
onChange: filter =>
|
||||||
|
dispatch(actionsObjects.setFilter(filter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(undefined, mapDispatchToProps)(ObjectsSearch)
|
||||||
@@ -20,13 +20,13 @@ import { ObjectsListContainer } from "../ObjectsListContainer"
|
|||||||
|
|
||||||
describe("ObjectsList", () => {
|
describe("ObjectsList", () => {
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<ObjectsListContainer objects={[]} />)
|
shallow(<ObjectsListContainer filteredObjects={[]} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render ObjectsList with objects", () => {
|
it("should render ObjectsList with objects", () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsListContainer
|
<ObjectsListContainer
|
||||||
objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
|
filteredObjects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(wrapper.find("ObjectsList").length).toBe(1)
|
expect(wrapper.find("ObjectsList").length).toBe(1)
|
||||||
@@ -40,7 +40,7 @@ describe("ObjectsList", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsListContainer
|
<ObjectsListContainer
|
||||||
currentBucket="test1"
|
currentBucket="test1"
|
||||||
objects={[]}
|
filteredObjects={[]}
|
||||||
listLoading={true}
|
listLoading={true}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage (C) 2018 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { shallow } from "enzyme"
|
||||||
|
import { ObjectsSearch } from "../ObjectsSearch"
|
||||||
|
|
||||||
|
describe("ObjectsSearch", () => {
|
||||||
|
it("should render without crashing", () => {
|
||||||
|
shallow(<ObjectsSearch />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call onChange with search text", () => {
|
||||||
|
const onChange = jest.fn()
|
||||||
|
const wrapper = shallow(<ObjectsSearch onChange={onChange} />)
|
||||||
|
wrapper.find("input").simulate("change", { target: { value: "test" } })
|
||||||
|
expect(onChange).toHaveBeenCalledWith("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -23,6 +23,7 @@ describe("objects reducer", () => {
|
|||||||
const initialState = reducer(undefined, {})
|
const initialState = reducer(undefined, {})
|
||||||
expect(initialState).toEqual({
|
expect(initialState).toEqual({
|
||||||
list: [],
|
list: [],
|
||||||
|
filter: "",
|
||||||
listLoading: false,
|
listLoading: false,
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: SORT_ORDER_ASC,
|
sortOrder: SORT_ORDER_ASC,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { getServerInfo, hasServerPublicDomain } from '../browser/selectors'
|
|||||||
|
|
||||||
export const SET_LIST = "objects/SET_LIST"
|
export const SET_LIST = "objects/SET_LIST"
|
||||||
export const RESET_LIST = "objects/RESET_LIST"
|
export const RESET_LIST = "objects/RESET_LIST"
|
||||||
|
export const SET_FILTER = "objects/SET_FILTER"
|
||||||
export const APPEND_LIST = "objects/APPEND_LIST"
|
export const APPEND_LIST = "objects/APPEND_LIST"
|
||||||
export const REMOVE = "objects/REMOVE"
|
export const REMOVE = "objects/REMOVE"
|
||||||
export const SET_SORT_BY = "objects/SET_SORT_BY"
|
export const SET_SORT_BY = "objects/SET_SORT_BY"
|
||||||
@@ -57,6 +58,13 @@ export const resetList = () => ({
|
|||||||
type: RESET_LIST,
|
type: RESET_LIST,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const setFilter = filter => {
|
||||||
|
return {
|
||||||
|
type: SET_FILTER,
|
||||||
|
filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const setListLoading = (listLoading) => ({
|
export const setListLoading = (listLoading) => ({
|
||||||
type: SET_LIST_LOADING,
|
type: SET_LIST_LOADING,
|
||||||
listLoading,
|
listLoading,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const removeObject = (list, objectToRemove, lookup) => {
|
|||||||
export default (
|
export default (
|
||||||
state = {
|
state = {
|
||||||
list: [],
|
list: [],
|
||||||
|
filter: "",
|
||||||
listLoading: false,
|
listLoading: false,
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: SORT_ORDER_ASC,
|
sortOrder: SORT_ORDER_ASC,
|
||||||
@@ -53,6 +54,11 @@ export default (
|
|||||||
...state,
|
...state,
|
||||||
list: []
|
list: []
|
||||||
}
|
}
|
||||||
|
case actionsObjects.SET_FILTER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
filter: action.filter
|
||||||
|
}
|
||||||
case actionsObjects.SET_LIST_LOADING:
|
case actionsObjects.SET_LIST_LOADING:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -21,3 +21,13 @@ export const getCurrentPrefix = state => state.objects.currentPrefix
|
|||||||
export const getCheckedList = state => state.objects.checkedList
|
export const getCheckedList = state => state.objects.checkedList
|
||||||
|
|
||||||
export const getPrefixWritable = state => state.objects.prefixWritable
|
export const getPrefixWritable = state => state.objects.prefixWritable
|
||||||
|
|
||||||
|
const objectsSelector = state => state.objects.list
|
||||||
|
const objectsFilterSelector = state => state.objects.filter
|
||||||
|
|
||||||
|
export const getFilteredObjects = createSelector(
|
||||||
|
objectsSelector,
|
||||||
|
objectsFilterSelector,
|
||||||
|
(objects, filter) => objects.filter(
|
||||||
|
object => object.name.toLowerCase().startsWith(filter.toLowerCase()))
|
||||||
|
)
|
||||||
@@ -36,7 +36,7 @@ export class Dropzone extends React.Component {
|
|||||||
// Overwrite the default styling from react-dropzone; otherwise it
|
// Overwrite the default styling from react-dropzone; otherwise it
|
||||||
// won't handle child elements correctly.
|
// won't handle child elements correctly.
|
||||||
const style = {
|
const style = {
|
||||||
height: "100%",
|
flex: "1",
|
||||||
borderWidth: "0",
|
borderWidth: "0",
|
||||||
borderStyle: "dashed",
|
borderStyle: "dashed",
|
||||||
borderColor: "#fff"
|
borderColor: "#fff"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import storage from 'local-storage-fallback'
|
|||||||
|
|
||||||
class Web {
|
class Web {
|
||||||
constructor(endpoint) {
|
constructor(endpoint) {
|
||||||
const namespace = 'Web'
|
const namespace = 'web'
|
||||||
this.JSONrpc = new JSONrpc({
|
this.JSONrpc = new JSONrpc({
|
||||||
endpoint,
|
endpoint,
|
||||||
namespace
|
namespace
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
@media(max-width: @screen-sm-max) {
|
@media(max-width: @screen-sm-max) {
|
||||||
padding: 75px 0 80px;
|
padding: 75px 0 80px;
|
||||||
}
|
}
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
min-height:100vh;
|
min-height:100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,24 @@ select.form-control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ig-search-dark {
|
||||||
|
&:before {
|
||||||
|
font-family: @font-family-icon;
|
||||||
|
font-weight: 900;
|
||||||
|
content: '\f002';
|
||||||
|
font-size: 15px;
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
top: 8px;
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ig-text {
|
||||||
|
padding-left: 25px;
|
||||||
|
.placeholder(rgba(0, 0, 0, 0.5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ig-search {
|
.ig-search {
|
||||||
&:before {
|
&:before {
|
||||||
font-family: @font-family-icon;
|
font-family: @font-family-icon;
|
||||||
@@ -270,4 +288,4 @@ select.form-control {
|
|||||||
.set-expire-decrease {
|
.set-expire-decrease {
|
||||||
bottom: -27px;
|
bottom: -27px;
|
||||||
.rotate(-180deg);
|
.rotate(-180deg);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+231
-315
@@ -2271,7 +2271,9 @@
|
|||||||
"asap": {
|
"asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
@@ -2283,19 +2285,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"asn1.js": {
|
"asn1.js": {
|
||||||
"version": "4.10.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||||
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.0.0",
|
"bn.js": "^4.0.0",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"minimalistic-assert": "^1.0.0"
|
"minimalistic-assert": "^1.0.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3541,9 +3544,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"base64-js": {
|
"base64-js": {
|
||||||
"version": "1.3.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||||
},
|
},
|
||||||
"batch": {
|
"batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@@ -3574,13 +3577,12 @@
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
||||||
"integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA=="
|
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
|
||||||
},
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
"version": "1.19.0",
|
"version": "1.19.0",
|
||||||
@@ -3754,31 +3756,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserify-rsa": {
|
"browserify-rsa": {
|
||||||
"version": "4.0.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
|
||||||
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
"integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.1.0",
|
"bn.js": "^5.0.0",
|
||||||
"randombytes": "^2.0.1"
|
"randombytes": "^2.0.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"bn.js": {
|
|
||||||
"version": "4.11.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserify-sign": {
|
"browserify-sign": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
|
||||||
"integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==",
|
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^5.1.1",
|
"bn.js": "^5.1.1",
|
||||||
"browserify-rsa": "^4.0.1",
|
"browserify-rsa": "^4.0.1",
|
||||||
"create-hash": "^1.2.0",
|
"create-hash": "^1.2.0",
|
||||||
"create-hmac": "^1.1.7",
|
"create-hmac": "^1.1.7",
|
||||||
"elliptic": "^6.5.2",
|
"elliptic": "^6.5.3",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
"parse-asn1": "^5.1.5",
|
"parse-asn1": "^5.1.5",
|
||||||
"readable-stream": "^3.6.0",
|
"readable-stream": "^3.6.0",
|
||||||
@@ -4022,9 +4017,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
|
||||||
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
|
"integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "~3.1.1",
|
"anymatch": "~3.1.1",
|
||||||
@@ -4034,7 +4029,7 @@
|
|||||||
"is-binary-path": "~2.1.0",
|
"is-binary-path": "~2.1.0",
|
||||||
"is-glob": "~4.0.1",
|
"is-glob": "~4.0.1",
|
||||||
"normalize-path": "~3.0.0",
|
"normalize-path": "~3.0.0",
|
||||||
"readdirp": "~3.4.0"
|
"readdirp": "~3.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
@@ -4048,9 +4043,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"binary-extensions": {
|
"binary-extensions": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
|
||||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"braces": {
|
"braces": {
|
||||||
@@ -4123,9 +4118,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"readdirp": {
|
"readdirp": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||||
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
|
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@@ -4143,9 +4138,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chownr": {
|
"chownr": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||||
},
|
},
|
||||||
"chrome-trace-event": {
|
"chrome-trace-event": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -4713,18 +4708,18 @@
|
|||||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
},
|
},
|
||||||
"create-ecdh": {
|
"create-ecdh": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
|
||||||
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
|
"integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.1.0",
|
"bn.js": "^4.1.0",
|
||||||
"elliptic": "^6.0.0"
|
"elliptic": "^6.5.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4925,9 +4920,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cyclist": {
|
"cyclist": {
|
||||||
"version": "0.2.2",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
||||||
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
|
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
|
||||||
},
|
},
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
@@ -5242,9 +5237,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5420,9 +5415,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"duplexify": {
|
"duplexify": {
|
||||||
"version": "3.6.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||||
"integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==",
|
"integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "^1.0.0",
|
"end-of-stream": "^1.0.0",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
@@ -5471,9 +5466,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||||
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
|
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.4.0",
|
||||||
"brorand": "^1.0.1",
|
"brorand": "^1.0.1",
|
||||||
@@ -5485,9 +5480,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5509,14 +5504,6 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"encoding": {
|
|
||||||
"version": "0.1.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
|
||||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
|
||||||
"requires": {
|
|
||||||
"iconv-lite": "~0.4.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||||
@@ -5526,9 +5513,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enhanced-resolve": {
|
"enhanced-resolve": {
|
||||||
"version": "4.1.1",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
|
||||||
"integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
|
"integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"memory-fs": "^0.5.0",
|
"memory-fs": "^0.5.0",
|
||||||
@@ -5886,9 +5873,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
@@ -6162,11 +6149,18 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"esrecurse": {
|
"esrecurse": {
|
||||||
"version": "4.2.1",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||||
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
|
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"estraverse": "^4.1.0"
|
"estraverse": "^5.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"estraverse": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"estraverse": {
|
"estraverse": {
|
||||||
@@ -6193,9 +6187,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
|
||||||
"integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg=="
|
"integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg=="
|
||||||
},
|
},
|
||||||
"eventsource": {
|
"eventsource": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
@@ -6722,27 +6716,6 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fbjs": {
|
|
||||||
"version": "0.8.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
|
||||||
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
|
|
||||||
"requires": {
|
|
||||||
"core-js": "^1.0.0",
|
|
||||||
"isomorphic-fetch": "^2.1.1",
|
|
||||||
"loose-envify": "^1.0.0",
|
|
||||||
"object-assign": "^4.1.0",
|
|
||||||
"promise": "^7.1.1",
|
|
||||||
"setimmediate": "^1.0.5",
|
|
||||||
"ua-parser-js": "^0.7.9"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"core-js": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
|
||||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"figgy-pudding": {
|
"figgy-pudding": {
|
||||||
"version": "3.5.2",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
||||||
@@ -7013,12 +6986,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flush-write-stream": {
|
"flush-write-stream": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
|
||||||
"integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
|
"integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.3",
|
||||||
"readable-stream": "^2.0.4"
|
"readable-stream": "^2.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
@@ -8376,9 +8349,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tapable": {
|
"tapable": {
|
||||||
@@ -8651,14 +8624,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
|
||||||
"integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ="
|
"integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ="
|
||||||
},
|
},
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.4.23",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
|
||||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"icss-utils": {
|
"icss-utils": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
|
||||||
@@ -8677,9 +8642,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ieee754": {
|
"ieee754": {
|
||||||
"version": "1.1.13",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||||
},
|
},
|
||||||
"iferr": {
|
"iferr": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
@@ -8746,9 +8711,9 @@
|
|||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"internal-ip": {
|
"internal-ip": {
|
||||||
@@ -9013,7 +8978,8 @@
|
|||||||
"is-stream": {
|
"is-stream": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-subset": {
|
"is-subset": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@@ -9068,15 +9034,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||||
},
|
},
|
||||||
"isomorphic-fetch": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
|
||||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
|
||||||
"requires": {
|
|
||||||
"node-fetch": "^1.0.1",
|
|
||||||
"whatwg-fetch": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
@@ -10573,9 +10530,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.14",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash._baseisequal": {
|
"lodash._baseisequal": {
|
||||||
@@ -10682,6 +10639,14 @@
|
|||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||||
|
"requires": {
|
||||||
|
"yallist": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
||||||
@@ -10868,9 +10833,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10993,6 +10958,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mississippi": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
||||||
|
"requires": {
|
||||||
|
"concat-stream": "^1.5.0",
|
||||||
|
"duplexify": "^3.4.2",
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"flush-write-stream": "^1.0.0",
|
||||||
|
"from2": "^2.1.0",
|
||||||
|
"parallel-transform": "^1.1.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"pumpify": "^1.3.3",
|
||||||
|
"stream-each": "^1.1.0",
|
||||||
|
"through2": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mixin-deep": {
|
"mixin-deep": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||||
@@ -11165,19 +11147,10 @@
|
|||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
|
||||||
"version": "1.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz",
|
|
||||||
"integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=",
|
|
||||||
"requires": {
|
|
||||||
"encoding": "^0.1.11",
|
|
||||||
"is-stream": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.9.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node-int64": {
|
"node-int64": {
|
||||||
@@ -11768,11 +11741,11 @@
|
|||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||||
},
|
},
|
||||||
"parallel-transform": {
|
"parallel-transform": {
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
|
||||||
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
|
"integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cyclist": "~0.2.2",
|
"cyclist": "^1.0.1",
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"readable-stream": "^2.1.5"
|
"readable-stream": "^2.1.5"
|
||||||
}
|
}
|
||||||
@@ -11788,13 +11761,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parse-asn1": {
|
"parse-asn1": {
|
||||||
"version": "5.1.5",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
|
||||||
"integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
|
"integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"asn1.js": "^4.0.0",
|
"asn1.js": "^5.2.0",
|
||||||
"browserify-aes": "^1.0.0",
|
"browserify-aes": "^1.0.0",
|
||||||
"create-hash": "^1.1.0",
|
|
||||||
"evp_bytestokey": "^1.0.0",
|
"evp_bytestokey": "^1.0.0",
|
||||||
"pbkdf2": "^3.0.3",
|
"pbkdf2": "^3.0.3",
|
||||||
"safe-buffer": "^5.1.1"
|
"safe-buffer": "^5.1.1"
|
||||||
@@ -11913,9 +11885,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2": {
|
"pbkdf2": {
|
||||||
"version": "3.0.17",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
||||||
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
|
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"create-hash": "^1.1.2",
|
"create-hash": "^1.1.2",
|
||||||
"create-hmac": "^1.1.4",
|
"create-hmac": "^1.1.4",
|
||||||
@@ -12218,6 +12190,8 @@
|
|||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "~2.0.3"
|
"asap": "~2.0.3"
|
||||||
}
|
}
|
||||||
@@ -12238,13 +12212,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.6.0",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fbjs": "^0.8.16",
|
"loose-envify": "^1.4.0",
|
||||||
"loose-envify": "^1.3.1",
|
"object-assign": "^4.1.1",
|
||||||
"object-assign": "^4.1.1"
|
"react-is": "^16.8.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"requires": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types-exact": {
|
"prop-types-exact": {
|
||||||
@@ -12330,16 +12314,16 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
"version": "2.0.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "^1.1.0",
|
"end-of-stream": "^1.1.0",
|
||||||
"once": "^1.3.1"
|
"once": "^1.3.1"
|
||||||
@@ -12353,6 +12337,17 @@
|
|||||||
"duplexify": "^3.6.0",
|
"duplexify": "^3.6.0",
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"pump": "^2.0.0"
|
"pump": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pump": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||||
|
"requires": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
@@ -13492,9 +13487,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13958,12 +13953,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"selfsigned": {
|
"selfsigned": {
|
||||||
"version": "1.10.7",
|
"version": "1.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
|
||||||
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
|
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-forge": "0.9.0"
|
"node-forge": "^0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
@@ -14001,10 +13996,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
|
||||||
"integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==",
|
"integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"randombytes": "^2.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"serializerr": {
|
"serializerr": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@@ -14644,9 +14642,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stream-shift": {
|
"stream-shift": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
|
||||||
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
|
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
|
||||||
},
|
},
|
||||||
"strict-uri-encode": {
|
"strict-uri-encode": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -15356,26 +15354,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||||
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cacache": "^12.0.2",
|
"cacache": "^12.0.2",
|
||||||
"find-cache-dir": "^2.1.0",
|
"find-cache-dir": "^2.1.0",
|
||||||
"is-wsl": "^1.1.0",
|
"is-wsl": "^1.1.0",
|
||||||
"schema-utils": "^1.0.0",
|
"schema-utils": "^1.0.0",
|
||||||
"serialize-javascript": "^2.1.2",
|
"serialize-javascript": "^4.0.0",
|
||||||
"source-map": "^0.6.1",
|
"source-map": "^0.6.1",
|
||||||
"terser": "^4.1.2",
|
"terser": "^4.1.2",
|
||||||
"webpack-sources": "^1.4.0",
|
"webpack-sources": "^1.4.0",
|
||||||
"worker-farm": "^1.7.0"
|
"worker-farm": "^1.7.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": {
|
|
||||||
"version": "3.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
|
||||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
|
||||||
},
|
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
||||||
@@ -15443,14 +15436,6 @@
|
|||||||
"path-exists": "^3.0.0"
|
"path-exists": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lru-cache": {
|
|
||||||
"version": "5.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
||||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
|
||||||
"requires": {
|
|
||||||
"yallist": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
@@ -15460,23 +15445,6 @@
|
|||||||
"semver": "^5.6.0"
|
"semver": "^5.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mississippi": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
|
||||||
"requires": {
|
|
||||||
"concat-stream": "^1.5.0",
|
|
||||||
"duplexify": "^3.4.2",
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"flush-write-stream": "^1.0.0",
|
|
||||||
"from2": "^2.1.0",
|
|
||||||
"parallel-transform": "^1.1.0",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"pumpify": "^1.3.3",
|
|
||||||
"stream-each": "^1.1.0",
|
|
||||||
"through2": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-limit": {
|
"p-limit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
@@ -15511,24 +15479,13 @@
|
|||||||
"find-up": "^3.0.0"
|
"find-up": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
|
||||||
"requires": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "2.1.2",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
|
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||||
},
|
"requires": {
|
||||||
"source-list-map": {
|
"randombytes": "^2.1.0"
|
||||||
"version": "2.0.1",
|
}
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
|
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@@ -15542,15 +15499,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"webpack-sources": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
|
||||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
|
||||||
"requires": {
|
|
||||||
"source-list-map": "^2.0.0",
|
|
||||||
"source-map": "~0.6.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -15589,9 +15537,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"timers-browserify": {
|
"timers-browserify": {
|
||||||
"version": "2.0.11",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
|
||||||
"integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
|
"integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"setimmediate": "^1.0.4"
|
"setimmediate": "^1.0.4"
|
||||||
}
|
}
|
||||||
@@ -15790,11 +15738,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
|
||||||
"version": "0.7.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz",
|
|
||||||
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw=="
|
|
||||||
},
|
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.9.3",
|
"version": "3.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz",
|
||||||
@@ -16172,20 +16115,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchpack": {
|
"watchpack": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.5",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
|
||||||
"integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==",
|
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": "^3.4.0",
|
"chokidar": "^3.4.1",
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"neo-async": "^2.5.0",
|
"neo-async": "^2.5.0",
|
||||||
"watchpack-chokidar2": "^2.0.0"
|
"watchpack-chokidar2": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchpack-chokidar2": {
|
"watchpack-chokidar2": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz",
|
||||||
"integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==",
|
"integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": "^2.1.8"
|
"chokidar": "^2.1.8"
|
||||||
@@ -16447,9 +16390,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "4.43.0",
|
"version": "4.44.2",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz",
|
||||||
"integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==",
|
"integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@webassemblyjs/ast": "1.9.0",
|
"@webassemblyjs/ast": "1.9.0",
|
||||||
"@webassemblyjs/helper-module-context": "1.9.0",
|
"@webassemblyjs/helper-module-context": "1.9.0",
|
||||||
@@ -16459,7 +16402,7 @@
|
|||||||
"ajv": "^6.10.2",
|
"ajv": "^6.10.2",
|
||||||
"ajv-keywords": "^3.4.1",
|
"ajv-keywords": "^3.4.1",
|
||||||
"chrome-trace-event": "^1.0.2",
|
"chrome-trace-event": "^1.0.2",
|
||||||
"enhanced-resolve": "^4.1.0",
|
"enhanced-resolve": "^4.3.0",
|
||||||
"eslint-scope": "^4.0.3",
|
"eslint-scope": "^4.0.3",
|
||||||
"json-parse-better-errors": "^1.0.2",
|
"json-parse-better-errors": "^1.0.2",
|
||||||
"loader-runner": "^2.4.0",
|
"loader-runner": "^2.4.0",
|
||||||
@@ -16472,14 +16415,14 @@
|
|||||||
"schema-utils": "^1.0.0",
|
"schema-utils": "^1.0.0",
|
||||||
"tapable": "^1.1.3",
|
"tapable": "^1.1.3",
|
||||||
"terser-webpack-plugin": "^1.4.3",
|
"terser-webpack-plugin": "^1.4.3",
|
||||||
"watchpack": "^1.6.1",
|
"watchpack": "^1.7.4",
|
||||||
"webpack-sources": "^1.4.1"
|
"webpack-sources": "^1.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.2",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -16578,9 +16521,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fast-deep-equal": {
|
"fast-deep-equal": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
"is-accessor-descriptor": {
|
"is-accessor-descriptor": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
@@ -16661,29 +16604,10 @@
|
|||||||
"to-regex": "^3.0.2"
|
"to-regex": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"source-list-map": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
|
||||||
},
|
|
||||||
"tapable": {
|
"tapable": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
||||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
||||||
},
|
|
||||||
"webpack-sources": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
|
||||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
|
||||||
"requires": {
|
|
||||||
"source-list-map": "^2.0.0",
|
|
||||||
"source-map": "~0.6.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -17605,7 +17529,6 @@
|
|||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
||||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"source-list-map": "^2.0.0",
|
"source-list-map": "^2.0.0",
|
||||||
"source-map": "~0.6.1"
|
"source-map": "~0.6.1"
|
||||||
@@ -17614,14 +17537,12 @@
|
|||||||
"source-list-map": {
|
"source-list-map": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||||
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
|
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -17660,11 +17581,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"whatwg-fetch": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
|
||||||
},
|
|
||||||
"whatwg-mimetype": {
|
"whatwg-mimetype": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
||||||
@@ -17780,9 +17696,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
|||||||
+32
-32
File diff suppressed because one or more lines are too long
@@ -44,10 +44,21 @@ func releaseTag(version string) string {
|
|||||||
relPrefix = prefix
|
relPrefix = prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relSuffix := ""
|
||||||
|
if hotfix := os.Getenv("MINIO_HOTFIX"); hotfix != "" {
|
||||||
|
relSuffix = hotfix
|
||||||
|
}
|
||||||
|
|
||||||
relTag := strings.Replace(version, " ", "-", -1)
|
relTag := strings.Replace(version, " ", "-", -1)
|
||||||
relTag = strings.Replace(relTag, ":", "-", -1)
|
relTag = strings.Replace(relTag, ":", "-", -1)
|
||||||
relTag = strings.Replace(relTag, ",", "", -1)
|
relTag = strings.Replace(relTag, ",", "", -1)
|
||||||
return relPrefix + "." + relTag
|
relTag = relPrefix + "." + relTag
|
||||||
|
|
||||||
|
if relSuffix != "" {
|
||||||
|
relTag += "." + relSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return relTag
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitID returns the abbreviated commit-id hash of the last commit.
|
// commitID returns the abbreviated commit-id hash of the last commit.
|
||||||
@@ -68,5 +79,12 @@ func commitID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(genLDFlags(time.Now().UTC().Format(time.RFC3339)))
|
var version string
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
version = os.Args[1]
|
||||||
|
} else {
|
||||||
|
version = time.Now().UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(genLDFlags(version))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,28 +56,29 @@ function start_minio_erasure()
|
|||||||
|
|
||||||
function start_minio_erasure_sets()
|
function start_minio_erasure_sets()
|
||||||
{
|
{
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk-sets{1...32}" >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||||
|
"${MINIO[@]}" server > "$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||||
sleep 15
|
sleep 15
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_zone_erasure_sets()
|
function start_minio_pool_erasure_sets()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_zone_erasure_sets_ipv6()
|
function start_minio_pool_erasure_sets_ipv6()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
|
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
"${MINIO[@]}" server --address="[::1]:9000" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
@@ -86,10 +87,10 @@ function start_minio_dist_erasure()
|
|||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9000.log" 2>&1 &
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9001.log" 2>&1 &
|
for i in $(seq 0 3); do
|
||||||
"${MINIO[@]}" server --address=:9002 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9002.log" 2>&1 &
|
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
done
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,8 @@ function run_test_fs()
|
|||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_erasure_sets() {
|
function run_test_erasure_sets()
|
||||||
|
{
|
||||||
start_minio_erasure_sets
|
start_minio_erasure_sets
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
@@ -129,9 +131,9 @@ function run_test_erasure_sets() {
|
|||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_zone_erasure_sets()
|
function run_test_pool_erasure_sets()
|
||||||
{
|
{
|
||||||
start_minio_zone_erasure_sets
|
start_minio_pool_erasure_sets
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
@@ -142,20 +144,20 @@ function run_test_zone_erasure_sets()
|
|||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/zone-minio-900$i.log"
|
cat "$WORK_DIR/pool-minio-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/zone-minio-900$i.log"
|
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_zone_erasure_sets_ipv6()
|
function run_test_pool_erasure_sets_ipv6()
|
||||||
{
|
{
|
||||||
start_minio_zone_erasure_sets_ipv6
|
start_minio_pool_erasure_sets_ipv6
|
||||||
|
|
||||||
export SERVER_ENDPOINT="[::1]:9000"
|
export SERVER_ENDPOINT="[::1]:9000"
|
||||||
|
|
||||||
@@ -168,12 +170,12 @@ function run_test_zone_erasure_sets_ipv6()
|
|||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/zone-minio-ipv6-900$i.log"
|
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/zone-minio-ipv6-900$i.log"
|
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
@@ -220,7 +222,7 @@ function run_test_dist_erasure()
|
|||||||
|
|
||||||
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge()
|
||||||
@@ -293,14 +295,14 @@ function main()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Eraure expanded setup"
|
echo "Testing in Distributed Eraure expanded setup"
|
||||||
if ! run_test_zone_erasure_sets; then
|
if ! run_test_pool_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||||
if ! run_test_zone_erasure_sets_ipv6; then
|
if ! run_test_pool_erasure_sets_ipv6; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -29,44 +29,52 @@ MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
|||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_minio_3_node() {
|
||||||
declare -a minio_pids
|
|
||||||
declare -a ARGS
|
|
||||||
export MINIO_ACCESS_KEY=minio
|
export MINIO_ACCESS_KEY=minio
|
||||||
export MINIO_SECRET_KEY=minio123
|
export MINIO_SECRET_KEY=minio123
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
|
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
args=""
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
ARGS+=("http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/")
|
args="$args http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/"
|
||||||
done
|
done
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+1]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
"${MINIO[@]}" --address ":$[$start_port+1]" $args > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||||
minio_pids[0]=$!
|
disown $!
|
||||||
disown "${minio_pids[0]}"
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+2]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
"${MINIO[@]}" --address ":$[$start_port+2]" $args > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||||
minio_pids[1]=$!
|
disown $!
|
||||||
disown "${minio_pids[1]}"
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+3]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
"${MINIO[@]}" --address ":$[$start_port+3]" $args > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||||
minio_pids[2]=$!
|
disown $!
|
||||||
disown "${minio_pids[2]}"
|
|
||||||
|
|
||||||
sleep "$1"
|
sleep "$1"
|
||||||
for pid in "${minio_pids[@]}"; do
|
if [ "$(pgrep -c minio)" -ne 3 ]; then
|
||||||
if ! kill "$pid"; then
|
for i in $(seq 1 3); do
|
||||||
for i in $(seq 1 3); do
|
echo "server$i log:"
|
||||||
echo "server$i log:"
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
done
|
||||||
done
|
echo "FAILED"
|
||||||
echo "FAILED"
|
purge "$WORK_DIR"
|
||||||
purge "$WORK_DIR"
|
exit 1
|
||||||
exit 1
|
fi
|
||||||
fi
|
if ! pkill minio; then
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1;
|
||||||
|
if pgrep minio; then
|
||||||
# forcibly killing, to proceed further properly.
|
# forcibly killing, to proceed further properly.
|
||||||
kill -9 "$pid"
|
if ! pkill -9 minio; then
|
||||||
sleep 1 # wait 1sec per pid
|
echo "no minio process running anymore, proceed."
|
||||||
done
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -128,6 +127,12 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
update := r.URL.Query().Get("update") == "true"
|
||||||
|
|
||||||
|
if !globalIsErasure {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
@@ -135,10 +140,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !globalIsErasure {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
@@ -163,23 +164,23 @@ 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
|
||||||
}
|
}
|
||||||
host, port, err := net.SplitHostPort(target.Endpoint)
|
|
||||||
if err != nil {
|
sameTarget, _ := isLocalHost(target.URL().Hostname(), target.URL().Port(), globalMinioPort)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sameTarget, _ := isLocalHost(host, port, globalMinioPort)
|
|
||||||
if sameTarget && bucket == target.TargetBucket {
|
if sameTarget && bucket == target.TargetBucket {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
target.SourceBucket = bucket
|
target.SourceBucket = bucket
|
||||||
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
if !update {
|
||||||
|
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
||||||
|
}
|
||||||
if target.Arn == "" {
|
if target.Arn == "" {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target); err != nil {
|
|
||||||
|
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, update); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -187,14 +188,12 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
tgtBytes, err := json.Marshal(&targets)
|
tgtBytes, err := json.Marshal(&targets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@@ -218,7 +217,10 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
|
if !globalIsErasure {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -226,6 +228,11 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bucket != "" {
|
if bucket != "" {
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket); err != nil {
|
if _, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -250,16 +257,16 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|
||||||
|
if !globalIsErasure {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !globalIsErasure {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
|||||||
@@ -42,14 +42,14 @@ import (
|
|||||||
|
|
||||||
func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *http.Request) (auth.Credentials, ObjectLayer) {
|
func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *http.Request) (auth.Credentials, ObjectLayer) {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return auth.Credentials{}, nil
|
return auth.Credentials{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
|
cred, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return cred, nil
|
return cred, nil
|
||||||
@@ -130,8 +130,8 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||||
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,17 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply dynamic values.
|
||||||
|
if err := applyDynamicConfig(GlobalContext, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
globalNotificationSys.SignalService(serviceReloadDynamic)
|
||||||
|
|
||||||
|
// If all values were dynamic, tell the client.
|
||||||
|
if dynamic {
|
||||||
|
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||||
|
}
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +277,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
if _, err = cfg.ReadConfig(bytes.NewReader(kvBytes)); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -378,7 +389,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := newServerConfig()
|
cfg := newServerConfig()
|
||||||
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
if _, err = cfg.ReadConfig(bytes.NewReader(kvBytes)); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+106
-34
@@ -22,6 +22,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -35,14 +36,14 @@ func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
var adminAPIErr APIErrorCode
|
var adminAPIErr APIErrorCode
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
|
cred, adminAPIErr = checkAdminRequestAuth(ctx, r, action, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
@@ -129,13 +130,40 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetUserInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "GetUserInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetUserAdminAction)
|
vars := mux.Vars(r)
|
||||||
if objectAPI == nil {
|
name := vars["accessKey"]
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
name := vars["accessKey"]
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessKey := cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
accessKey = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitPerm := name == accessKey
|
||||||
|
if !implicitPerm {
|
||||||
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: accessKey,
|
||||||
|
Action: iampolicy.GetUserAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", accessKey, claims),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userInfo, err := globalIAMSys.GetUserInfo(name)
|
userInfo, err := globalIAMSys.GetUserInfo(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -304,7 +332,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
accessKey := vars["accessKey"]
|
accessKey := vars["accessKey"]
|
||||||
status := vars["status"]
|
status := vars["status"]
|
||||||
|
|
||||||
// Custom IAM policies not allowed for admin user.
|
// This API is not allowed to lookup accessKey user status
|
||||||
if accessKey == globalActiveCred.AccessKey {
|
if accessKey == globalActiveCred.AccessKey {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
return
|
return
|
||||||
@@ -330,20 +358,53 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.CreateUserAdminAction)
|
vars := mux.Vars(r)
|
||||||
if objectAPI == nil {
|
accessKey := path.Clean(vars["accessKey"])
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
accessKey := vars["accessKey"]
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Custom IAM policies not allowed for admin user.
|
// Not allowed to add a user with same access key as root credential
|
||||||
if accessKey == globalActiveCred.AccessKey {
|
if owner && accessKey == cred.AccessKey {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
|
||||||
|
// Incoming access key matches parent user then we should
|
||||||
|
// reject password change requests.
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitPerm := accessKey == cred.AccessKey
|
||||||
|
if !implicitPerm {
|
||||||
|
parentUser := cred.ParentUser
|
||||||
|
if parentUser == "" {
|
||||||
|
parentUser = cred.AccessKey
|
||||||
|
}
|
||||||
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: parentUser,
|
||||||
|
Action: iampolicy.CreateUserAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", parentUser, claims),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
// More than maxConfigSize bytes were available
|
// More than maxConfigSize bytes were available
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
@@ -386,8 +447,8 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
defer logger.AuditLog(w, r, "AddServiceAccount", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "AddServiceAccount", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -398,6 +459,12 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disallow creating service accounts by root user.
|
||||||
|
if owner {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -411,12 +478,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow creating service accounts by root user.
|
|
||||||
if owner {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parentUser := cred.AccessKey
|
parentUser := cred.AccessKey
|
||||||
if cred.ParentUser != "" {
|
if cred.ParentUser != "" {
|
||||||
parentUser = cred.ParentUser
|
parentUser = cred.ParentUser
|
||||||
@@ -465,8 +526,8 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
defer logger.AuditLog(w, r, "ListServiceAccounts", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "ListServiceAccounts", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -520,8 +581,8 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
defer logger.AuditLog(w, r, "DeleteServiceAccount", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "DeleteServiceAccount", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -572,15 +633,15 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUsageInfoHandler returns usage
|
// AccountInfoHandler returns usage
|
||||||
func (a adminAPIHandlers) AccountUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AccountUsageInfo")
|
ctx := newContext(r, w, "AccountInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "AccountUsageInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "AccountInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -645,8 +706,16 @@ func (a adminAPIHandlers) AccountUsageInfoHandler(w http.ResponseWriter, r *http
|
|||||||
accountName = cred.ParentUser
|
accountName = cred.ParentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
acctInfo := madmin.AccountUsageInfo{
|
policies, err := globalIAMSys.PolicyDBGet(accountName, false)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acctInfo := madmin.AccountInfo{
|
||||||
AccountName: accountName,
|
AccountName: accountName,
|
||||||
|
Policy: globalIAMSys.GetCombinedPolicy(policies...),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
@@ -722,7 +791,10 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(policy)
|
if err = json.NewEncoder(w).Encode(policy); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+301
-247
@@ -35,14 +35,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
"github.com/minio/minio/cmd/config/notify"
|
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/cmd/logger/message/log"
|
"github.com/minio/minio/cmd/logger/message/log"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
"github.com/minio/minio/pkg/bandwidth"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
@@ -54,16 +53,13 @@ const (
|
|||||||
maxEConfigJSONSize = 262272
|
maxEConfigJSONSize = 262272
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type-safe query params.
|
|
||||||
type mgmtQueryKey string
|
|
||||||
|
|
||||||
// Only valid query params for mgmt admin APIs.
|
// Only valid query params for mgmt admin APIs.
|
||||||
const (
|
const (
|
||||||
mgmtBucket mgmtQueryKey = "bucket"
|
mgmtBucket = "bucket"
|
||||||
mgmtPrefix = "prefix"
|
mgmtPrefix = "prefix"
|
||||||
mgmtClientToken = "clientToken"
|
mgmtClientToken = "clientToken"
|
||||||
mgmtForceStart = "forceStart"
|
mgmtForceStart = "forceStart"
|
||||||
mgmtForceStop = "forceStop"
|
mgmtForceStop = "forceStop"
|
||||||
)
|
)
|
||||||
|
|
||||||
func updateServer(u *url.URL, sha256Sum []byte, lrTime time.Time, mode string) (us madmin.ServerUpdateStatus, err error) {
|
func updateServer(u *url.URL, sha256Sum []byte, lrTime time.Time, mode string) (us madmin.ServerUpdateStatus, err error) {
|
||||||
@@ -90,8 +86,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if globalInplaceUpdateDisabled {
|
if globalInplaceUpdateDisabled {
|
||||||
// if MINIO_UPDATE=off - inplace update is disabled, mostly
|
// if MINIO_UPDATE=off - inplace update is disabled, mostly in containers.
|
||||||
// in containers.
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -132,7 +127,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if lrTime.Sub(crTime) < 0 {
|
if lrTime.Sub(crTime) <= 0 {
|
||||||
updateStatus := madmin.ServerUpdateStatus{
|
updateStatus := madmin.ServerUpdateStatus{
|
||||||
CurrentVersion: Version,
|
CurrentVersion: Version,
|
||||||
UpdatedVersion: Version,
|
UpdatedVersion: Version,
|
||||||
@@ -210,9 +205,10 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var objectAPI ObjectLayer
|
var objectAPI ObjectLayer
|
||||||
if serviceSig == serviceRestart {
|
switch serviceSig {
|
||||||
|
case serviceRestart:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
|
||||||
} else {
|
case serviceStop:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
|
||||||
}
|
}
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -298,6 +294,20 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
// ignores any errors here.
|
// ignores any errors here.
|
||||||
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
|
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
|
||||||
|
|
||||||
|
// Collect any disk healing.
|
||||||
|
healing, _ := getAggregatedBackgroundHealState(ctx)
|
||||||
|
healDisks := make(map[string]struct{}, len(healing.HealDisks))
|
||||||
|
for _, disk := range healing.HealDisks {
|
||||||
|
healDisks[disk] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all disks which belong to each respective endpoints
|
||||||
|
for i, disk := range storageInfo.Disks {
|
||||||
|
if _, ok := healDisks[disk.Endpoint]; ok {
|
||||||
|
storageInfo.Disks[i].Healing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Marshal API response
|
// Marshal API response
|
||||||
jsonBytes, err := json.Marshal(storageInfo)
|
jsonBytes, err := json.Marshal(storageInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,7 +355,9 @@ func lriToLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEn
|
|||||||
Resource: resource,
|
Resource: resource,
|
||||||
ServerList: []string{server},
|
ServerList: []string{server},
|
||||||
Source: l.Source,
|
Source: l.Source,
|
||||||
|
Owner: l.Owner,
|
||||||
ID: l.UID,
|
ID: l.UID,
|
||||||
|
Quorum: l.Quorum,
|
||||||
}
|
}
|
||||||
if l.Writer {
|
if l.Writer {
|
||||||
entry.Type = "WRITE"
|
entry.Type = "WRITE"
|
||||||
@@ -355,39 +367,40 @@ func lriToLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEn
|
|||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func topLockEntries(peerLocks []*PeerLocks, count int) madmin.LockEntries {
|
func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
|
||||||
entryMap := make(map[string]*madmin.LockEntry)
|
entryMap := make(map[string]*madmin.LockEntry)
|
||||||
for _, peerLock := range peerLocks {
|
for _, peerLock := range peerLocks {
|
||||||
if peerLock == nil {
|
if peerLock == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, locks := range peerLock.Locks {
|
for k, v := range peerLock.Locks {
|
||||||
for k, v := range locks {
|
for _, lockReqInfo := range v {
|
||||||
for _, lockReqInfo := range v {
|
if val, ok := entryMap[lockReqInfo.UID]; ok {
|
||||||
if val, ok := entryMap[lockReqInfo.UID]; ok {
|
val.ServerList = append(val.ServerList, peerLock.Addr)
|
||||||
val.ServerList = append(val.ServerList, peerLock.Addr)
|
} else {
|
||||||
} else {
|
entryMap[lockReqInfo.UID] = lriToLockEntry(lockReqInfo, k, peerLock.Addr)
|
||||||
entryMap[lockReqInfo.UID] = lriToLockEntry(lockReqInfo, k, peerLock.Addr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var lockEntries = make(madmin.LockEntries, 0, len(entryMap))
|
var lockEntries madmin.LockEntries
|
||||||
for _, v := range entryMap {
|
for _, v := range entryMap {
|
||||||
lockEntries = append(lockEntries, *v)
|
if stale {
|
||||||
|
lockEntries = append(lockEntries, *v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(v.ServerList) >= v.Quorum {
|
||||||
|
lockEntries = append(lockEntries, *v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(lockEntries)
|
sort.Sort(lockEntries)
|
||||||
if len(lockEntries) > count {
|
|
||||||
lockEntries = lockEntries[:count]
|
|
||||||
}
|
|
||||||
return lockEntries
|
return lockEntries
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerLocks holds server information result of one node
|
// PeerLocks holds server information result of one node
|
||||||
type PeerLocks struct {
|
type PeerLocks struct {
|
||||||
Addr string
|
Addr string
|
||||||
Locks GetLocksResp
|
Locks map[string][]lockRequesterInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// TopLocksHandler Get list of locks in use
|
// TopLocksHandler Get list of locks in use
|
||||||
@@ -410,23 +423,17 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stale := r.URL.Query().Get("stale") == "true" // list also stale locks
|
||||||
|
|
||||||
peerLocks := globalNotificationSys.GetLocks(ctx)
|
peerLocks := globalNotificationSys.GetLocks(ctx, r)
|
||||||
// Once we have received all the locks currently used from peers
|
|
||||||
// add the local peer locks list as well.
|
topLocks := topLockEntries(peerLocks, stale)
|
||||||
var getRespLocks GetLocksResp
|
|
||||||
for _, llocker := range globalLockServers {
|
// Marshal API response upto requested count.
|
||||||
getRespLocks = append(getRespLocks, llocker.DupLockMap())
|
if len(topLocks) > count && count > 0 {
|
||||||
|
topLocks = topLocks[:count]
|
||||||
}
|
}
|
||||||
|
|
||||||
peerLocks = append(peerLocks, &PeerLocks{
|
|
||||||
Addr: getHostName(r),
|
|
||||||
Locks: getRespLocks,
|
|
||||||
})
|
|
||||||
|
|
||||||
topLocks := topLockEntries(peerLocks, count)
|
|
||||||
|
|
||||||
// Marshal API response
|
|
||||||
jsonBytes, err := json.Marshal(topLocks)
|
jsonBytes, err := json.Marshal(topLocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -454,8 +461,15 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ProfilingAdminAction)
|
// Validate request signature.
|
||||||
if objectAPI == nil {
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,8 +566,15 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ProfilingAdminAction)
|
// Validate request signature.
|
||||||
if objectAPI == nil {
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,8 +593,8 @@ type healInitParams struct {
|
|||||||
|
|
||||||
// extractHealInitParams - Validates params for heal init API.
|
// extractHealInitParams - Validates params for heal init API.
|
||||||
func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) {
|
func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) {
|
||||||
hip.bucket = vars[string(mgmtBucket)]
|
hip.bucket = vars[mgmtBucket]
|
||||||
hip.objPrefix = vars[string(mgmtPrefix)]
|
hip.objPrefix = vars[mgmtPrefix]
|
||||||
|
|
||||||
if hip.bucket == "" {
|
if hip.bucket == "" {
|
||||||
if hip.objPrefix != "" {
|
if hip.objPrefix != "" {
|
||||||
@@ -592,13 +613,13 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(qParms[string(mgmtClientToken)]) > 0 {
|
if len(qParms[mgmtClientToken]) > 0 {
|
||||||
hip.clientToken = qParms[string(mgmtClientToken)][0]
|
hip.clientToken = qParms[mgmtClientToken][0]
|
||||||
}
|
}
|
||||||
if _, ok := qParms[string(mgmtForceStart)]; ok {
|
if _, ok := qParms[mgmtForceStart]; ok {
|
||||||
hip.forceStart = true
|
hip.forceStart = true
|
||||||
}
|
}
|
||||||
if _, ok := qParms[string(mgmtForceStop)]; ok {
|
if _, ok := qParms[mgmtForceStop]; ok {
|
||||||
hip.forceStop = true
|
hip.forceStop = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,7 +808,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case hip.clientToken == "":
|
case hip.clientToken == "":
|
||||||
nh := newHealSequence(GlobalContext, hip.bucket, hip.objPrefix, handlers.GetSourceIP(r), hip.hs, hip.forceStart)
|
nh := newHealSequence(GlobalContext, hip.bucket, hip.objPrefix, handlers.GetSourceIP(r), hip.hs, hip.forceStart)
|
||||||
go func() {
|
go func() {
|
||||||
respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh)
|
respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh, objectAPI)
|
||||||
hr := healResp{respBytes, apiErr, errMsg}
|
hr := healResp{respBytes, apiErr, errMsg}
|
||||||
respCh <- hr
|
respCh <- hr
|
||||||
}()
|
}()
|
||||||
@@ -886,14 +907,14 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var adminAPIErr APIErrorCode
|
var adminAPIErr APIErrorCode
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
|
cred, adminAPIErr = checkAdminRequestAuth(ctx, r, action, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
@@ -999,6 +1020,15 @@ func mustTrace(entry interface{}, trcAll, errOnly bool) bool {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle browser requests separately filter them and return.
|
||||||
|
if HasPrefix(trcInfo.ReqInfo.Path, minioReservedBucketPath+"/upload") {
|
||||||
|
if errOnly {
|
||||||
|
return trcInfo.RespInfo.StatusCode >= http.StatusBadRequest
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
trace := trcAll || !HasPrefix(trcInfo.ReqInfo.Path, minioReservedBucketPath+SlashSeparator)
|
trace := trcAll || !HasPrefix(trcInfo.ReqInfo.Path, minioReservedBucketPath+SlashSeparator)
|
||||||
if errOnly {
|
if errOnly {
|
||||||
return trace && trcInfo.RespInfo.StatusCode >= http.StatusBadRequest
|
return trace && trcInfo.RespInfo.StatusCode >= http.StatusBadRequest
|
||||||
@@ -1016,7 +1046,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
trcErr := r.URL.Query().Get("err") == "true"
|
trcErr := r.URL.Query().Get("err") == "true"
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.TraceAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1028,7 +1058,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||||
traceCh := make(chan interface{}, 4000)
|
traceCh := make(chan interface{}, 4000)
|
||||||
|
|
||||||
peers := newPeerRestClients(globalEndpoints)
|
peers, _ := newPeerRestClients(globalEndpoints)
|
||||||
|
|
||||||
globalHTTPTrace.Subscribe(traceCh, ctx.Done(), func(entry interface{}) bool {
|
globalHTTPTrace.Subscribe(traceCh, ctx.Done(), func(entry interface{}) bool {
|
||||||
return mustTrace(entry, trcAll, trcErr)
|
return mustTrace(entry, trcAll, trcErr)
|
||||||
@@ -1096,7 +1126,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
logCh := make(chan interface{}, 4000)
|
logCh := make(chan interface{}, 4000)
|
||||||
|
|
||||||
peers := newPeerRestClients(globalEndpoints)
|
peers, _ := newPeerRestClients(globalEndpoints)
|
||||||
|
|
||||||
globalConsoleSys.Subscribe(logCh, ctx.Done(), node, limitLines, logKind, nil)
|
globalConsoleSys.Subscribe(logCh, ctx.Done(), node, limitLines, logKind, nil)
|
||||||
|
|
||||||
@@ -1228,26 +1258,26 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeSuccessResponseJSON(w, resp)
|
writeSuccessResponseJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OBDInfoHandler - GET /minio/admin/v3/obdinfo
|
// HealthInfoHandler - GET /minio/admin/v3/healthinfo
|
||||||
// ----------
|
// ----------
|
||||||
// Get server on-board diagnostics
|
// Get server health info
|
||||||
func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "OBDInfo")
|
ctx := newContext(r, w, "HealthInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "OBDInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "HealthInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.OBDInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
obdInfo := madmin.OBDInfo{}
|
healthInfo := madmin.HealthInfo{}
|
||||||
obdInfoCh := make(chan madmin.OBDInfo)
|
healthInfoCh := make(chan madmin.HealthInfo)
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
partialWrite := func(oinfo madmin.OBDInfo) {
|
partialWrite := func(oinfo madmin.HealthInfo) {
|
||||||
obdInfoCh <- oinfo
|
healthInfoCh <- oinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
@@ -1260,8 +1290,8 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
errorResponse := getAPIErrorResponse(ctx, toAdminAPIErr(ctx, err), r.URL.String(),
|
errorResponse := getAPIErrorResponse(ctx, toAdminAPIErr(ctx, err), r.URL.String(),
|
||||||
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
||||||
encodedErrorResponse := encodeResponse(errorResponse)
|
encodedErrorResponse := encodeResponse(errorResponse)
|
||||||
obdInfo.Error = string(encodedErrorResponse)
|
healthInfo.Error = string(encodedErrorResponse)
|
||||||
logger.LogIf(ctx, enc.Encode(obdInfo))
|
logger.LogIf(ctx, enc.Encode(healthInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline := 3600 * time.Second
|
deadline := 3600 * time.Second
|
||||||
@@ -1277,113 +1307,107 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
deadlinedCtx, cancel := context.WithTimeout(ctx, deadline)
|
deadlinedCtx, cancel := context.WithTimeout(ctx, deadline)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
nsLock := objectAPI.NewNSLock(ctx, minioMetaBucket, "obd-in-progress")
|
nsLock := objectAPI.NewNSLock(minioMetaBucket, "health-check-in-progress")
|
||||||
if err := nsLock.GetLock(newDynamicTimeout(deadline, deadline)); err != nil { // returns a locked lock
|
if err := nsLock.GetLock(ctx, newDynamicTimeout(deadline, deadline)); err != nil { // returns a locked lock
|
||||||
errResp(err)
|
errResp(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer nsLock.Unlock()
|
defer nsLock.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(obdInfoCh)
|
defer close(healthInfoCh)
|
||||||
|
|
||||||
if log := query.Get("log"); log == "true" {
|
|
||||||
obdInfo.Logging.ServersLog = append(obdInfo.Logging.ServersLog, getLocalLogOBD(deadlinedCtx, r))
|
|
||||||
obdInfo.Logging.ServersLog = append(obdInfo.Logging.ServersLog, globalNotificationSys.LogOBDInfo(deadlinedCtx)...)
|
|
||||||
partialWrite(obdInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cpu := query.Get("syscpu"); cpu == "true" {
|
if cpu := query.Get("syscpu"); cpu == "true" {
|
||||||
cpuInfo := getLocalCPUOBDInfo(deadlinedCtx, r)
|
cpuInfo := getLocalCPUInfo(deadlinedCtx, r)
|
||||||
|
|
||||||
obdInfo.Sys.CPUInfo = append(obdInfo.Sys.CPUInfo, cpuInfo)
|
healthInfo.Sys.CPUInfo = append(healthInfo.Sys.CPUInfo, cpuInfo)
|
||||||
obdInfo.Sys.CPUInfo = append(obdInfo.Sys.CPUInfo, globalNotificationSys.CPUOBDInfo(deadlinedCtx)...)
|
healthInfo.Sys.CPUInfo = append(healthInfo.Sys.CPUInfo, globalNotificationSys.CPUInfo(deadlinedCtx)...)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if diskHw := query.Get("sysdiskhw"); diskHw == "true" {
|
if diskHw := query.Get("sysdiskhw"); diskHw == "true" {
|
||||||
diskHwInfo := getLocalDiskHwOBD(deadlinedCtx, r)
|
diskHwInfo := getLocalDiskHwInfo(deadlinedCtx, r)
|
||||||
|
|
||||||
obdInfo.Sys.DiskHwInfo = append(obdInfo.Sys.DiskHwInfo, diskHwInfo)
|
healthInfo.Sys.DiskHwInfo = append(healthInfo.Sys.DiskHwInfo, diskHwInfo)
|
||||||
obdInfo.Sys.DiskHwInfo = append(obdInfo.Sys.DiskHwInfo, globalNotificationSys.DiskHwOBDInfo(deadlinedCtx)...)
|
healthInfo.Sys.DiskHwInfo = append(healthInfo.Sys.DiskHwInfo, globalNotificationSys.DiskHwInfo(deadlinedCtx)...)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if osInfo := query.Get("sysosinfo"); osInfo == "true" {
|
if osInfo := query.Get("sysosinfo"); osInfo == "true" {
|
||||||
osInfo := getLocalOsInfoOBD(deadlinedCtx, r)
|
osInfo := getLocalOsInfo(deadlinedCtx, r)
|
||||||
|
|
||||||
obdInfo.Sys.OsInfo = append(obdInfo.Sys.OsInfo, osInfo)
|
healthInfo.Sys.OsInfo = append(healthInfo.Sys.OsInfo, osInfo)
|
||||||
obdInfo.Sys.OsInfo = append(obdInfo.Sys.OsInfo, globalNotificationSys.OsOBDInfo(deadlinedCtx)...)
|
healthInfo.Sys.OsInfo = append(healthInfo.Sys.OsInfo, globalNotificationSys.OsInfo(deadlinedCtx)...)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mem := query.Get("sysmem"); mem == "true" {
|
if mem := query.Get("sysmem"); mem == "true" {
|
||||||
memInfo := getLocalMemOBD(deadlinedCtx, r)
|
memInfo := getLocalMemInfo(deadlinedCtx, r)
|
||||||
|
|
||||||
obdInfo.Sys.MemInfo = append(obdInfo.Sys.MemInfo, memInfo)
|
healthInfo.Sys.MemInfo = append(healthInfo.Sys.MemInfo, memInfo)
|
||||||
obdInfo.Sys.MemInfo = append(obdInfo.Sys.MemInfo, globalNotificationSys.MemOBDInfo(deadlinedCtx)...)
|
healthInfo.Sys.MemInfo = append(healthInfo.Sys.MemInfo, globalNotificationSys.MemInfo(deadlinedCtx)...)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if proc := query.Get("sysprocess"); proc == "true" {
|
if proc := query.Get("sysprocess"); proc == "true" {
|
||||||
procInfo := getLocalProcOBD(deadlinedCtx, r)
|
procInfo := getLocalProcInfo(deadlinedCtx, r)
|
||||||
|
|
||||||
obdInfo.Sys.ProcInfo = append(obdInfo.Sys.ProcInfo, procInfo)
|
healthInfo.Sys.ProcInfo = append(healthInfo.Sys.ProcInfo, procInfo)
|
||||||
obdInfo.Sys.ProcInfo = append(obdInfo.Sys.ProcInfo, globalNotificationSys.ProcOBDInfo(deadlinedCtx)...)
|
healthInfo.Sys.ProcInfo = append(healthInfo.Sys.ProcInfo, globalNotificationSys.ProcInfo(deadlinedCtx)...)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config := query.Get("minioconfig"); config == "true" {
|
if config := query.Get("minioconfig"); config == "true" {
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
obdInfo.Minio.Config = cfg
|
healthInfo.Minio.Config = cfg
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if drive := query.Get("perfdrive"); drive == "true" {
|
if drive := query.Get("perfdrive"); drive == "true" {
|
||||||
// Get drive obd details from local server's drive(s)
|
// Get drive perf details from local server's drive(s)
|
||||||
driveOBDSerial := getLocalDrivesOBD(deadlinedCtx, false, globalEndpoints, r)
|
drivePerfSerial := getLocalDrives(deadlinedCtx, false, globalEndpoints, r)
|
||||||
driveOBDParallel := getLocalDrivesOBD(deadlinedCtx, true, globalEndpoints, r)
|
drivePerfParallel := getLocalDrives(deadlinedCtx, true, globalEndpoints, r)
|
||||||
|
|
||||||
errStr := ""
|
errStr := ""
|
||||||
if driveOBDSerial.Error != "" {
|
if drivePerfSerial.Error != "" {
|
||||||
errStr = "serial: " + driveOBDSerial.Error
|
errStr = "serial: " + drivePerfSerial.Error
|
||||||
}
|
}
|
||||||
if driveOBDParallel.Error != "" {
|
if drivePerfParallel.Error != "" {
|
||||||
errStr = errStr + " parallel: " + driveOBDParallel.Error
|
errStr = errStr + " parallel: " + drivePerfParallel.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
driveOBD := madmin.ServerDrivesOBDInfo{
|
driveInfo := madmin.ServerDrivesInfo{
|
||||||
Addr: driveOBDSerial.Addr,
|
Addr: drivePerfSerial.Addr,
|
||||||
Serial: driveOBDSerial.Serial,
|
Serial: drivePerfSerial.Serial,
|
||||||
Parallel: driveOBDParallel.Parallel,
|
Parallel: drivePerfParallel.Parallel,
|
||||||
Error: errStr,
|
Error: errStr,
|
||||||
}
|
}
|
||||||
obdInfo.Perf.DriveInfo = append(obdInfo.Perf.DriveInfo, driveOBD)
|
healthInfo.Perf.DriveInfo = append(healthInfo.Perf.DriveInfo, driveInfo)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
|
|
||||||
// Notify all other MinIO peers to report drive obd numbers
|
// Notify all other MinIO peers to report drive perf numbers
|
||||||
driveOBDs := globalNotificationSys.DriveOBDInfoChan(deadlinedCtx)
|
driveInfos := globalNotificationSys.DrivePerfInfoChan(deadlinedCtx)
|
||||||
for obd := range driveOBDs {
|
for obd := range driveInfos {
|
||||||
obdInfo.Perf.DriveInfo = append(obdInfo.Perf.DriveInfo, obd)
|
healthInfo.Perf.DriveInfo = append(healthInfo.Perf.DriveInfo, obd)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if net := query.Get("perfnet"); net == "true" && globalIsDistErasure {
|
if net := query.Get("perfnet"); net == "true" && globalIsDistErasure {
|
||||||
obdInfo.Perf.Net = append(obdInfo.Perf.Net, globalNotificationSys.NetOBDInfo(deadlinedCtx))
|
healthInfo.Perf.Net = append(healthInfo.Perf.Net, globalNotificationSys.NetInfo(deadlinedCtx))
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
|
|
||||||
netOBDs := globalNotificationSys.DispatchNetOBDChan(deadlinedCtx)
|
netInfos := globalNotificationSys.DispatchNetPerfChan(deadlinedCtx)
|
||||||
for obd := range netOBDs {
|
for netInfo := range netInfos {
|
||||||
obdInfo.Perf.Net = append(obdInfo.Perf.Net, obd)
|
healthInfo.Perf.Net = append(healthInfo.Perf.Net, netInfo)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
|
|
||||||
obdInfo.Perf.NetParallel = globalNotificationSys.NetOBDParallelInfo(deadlinedCtx)
|
healthInfo.Perf.NetParallel = globalNotificationSys.NetPerfParallelInfo(deadlinedCtx)
|
||||||
partialWrite(obdInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
@@ -1393,7 +1417,7 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case oinfo, ok := <-obdInfoCh:
|
case oinfo, ok := <-healthInfoCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1412,6 +1436,59 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BandwidthMonitorHandler - GET /minio/admin/v3/bandwidth
|
||||||
|
// ----------
|
||||||
|
// Get bandwidth consumption information
|
||||||
|
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "BandwidthMonitor")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "BandwidthMonitor", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Validate request signature.
|
||||||
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.BandwidthMonitorAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setEventStreamHeaders(w)
|
||||||
|
reportCh := make(chan bandwidth.Report, 1)
|
||||||
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer keepAliveTicker.Stop()
|
||||||
|
bucketsRequestedString := r.URL.Query().Get("buckets")
|
||||||
|
bucketsRequested := strings.Split(bucketsRequestedString, ",")
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
reportCh <- globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case report := <-reportCh:
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
err := enc.Encode(report)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-keepAliveTicker.C:
|
||||||
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ServerInfoHandler - GET /minio/admin/v3/info
|
// ServerInfoHandler - GET /minio/admin/v3/info
|
||||||
// ----------
|
// ----------
|
||||||
// Get server information
|
// Get server information
|
||||||
@@ -1420,29 +1497,14 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction)
|
// Validate request signature.
|
||||||
if objectAPI == nil {
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
vault := fetchVaultStatus()
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buckets := madmin.Buckets{}
|
|
||||||
objects := madmin.Objects{}
|
|
||||||
usage := madmin.Usage{}
|
|
||||||
|
|
||||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
|
||||||
if err == nil {
|
|
||||||
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
|
||||||
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
|
||||||
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
|
||||||
}
|
|
||||||
|
|
||||||
vault := fetchVaultStatus(cfg)
|
|
||||||
|
|
||||||
ldap := madmin.LDAP{}
|
ldap := madmin.LDAP{}
|
||||||
if globalLDAPConfig.Enabled {
|
if globalLDAPConfig.Enabled {
|
||||||
@@ -1458,40 +1520,59 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log, audit := fetchLoggerInfo(cfg)
|
log, audit := fetchLoggerInfo()
|
||||||
|
|
||||||
// Get the notification target info
|
// Get the notification target info
|
||||||
notifyTarget := fetchLambdaInfo(cfg)
|
notifyTarget := fetchLambdaInfo()
|
||||||
|
|
||||||
// Fetching the Storage information, ignore any errors.
|
|
||||||
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
|
|
||||||
|
|
||||||
var backend interface{}
|
|
||||||
if storageInfo.Backend.Type == BackendType(madmin.Erasure) {
|
|
||||||
backend = madmin.ErasureBackend{
|
|
||||||
Type: madmin.ErasureType,
|
|
||||||
OnlineDisks: storageInfo.Backend.OnlineDisks.Sum(),
|
|
||||||
OfflineDisks: storageInfo.Backend.OfflineDisks.Sum(),
|
|
||||||
StandardSCData: storageInfo.Backend.StandardSCData,
|
|
||||||
StandardSCParity: storageInfo.Backend.StandardSCParity,
|
|
||||||
RRSCData: storageInfo.Backend.RRSCData,
|
|
||||||
RRSCParity: storageInfo.Backend.RRSCParity,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
backend = madmin.FSBackend{
|
|
||||||
Type: madmin.FsType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mode := "safemode"
|
|
||||||
if newObjectLayerFn() != nil {
|
|
||||||
mode = "online"
|
|
||||||
}
|
|
||||||
|
|
||||||
server := getLocalServerProperty(globalEndpoints, r)
|
server := getLocalServerProperty(globalEndpoints, r)
|
||||||
servers := globalNotificationSys.ServerInfo()
|
servers := globalNotificationSys.ServerInfo()
|
||||||
servers = append(servers, server)
|
servers = append(servers, server)
|
||||||
|
|
||||||
|
var backend interface{}
|
||||||
|
mode := madmin.ObjectLayerInitializing
|
||||||
|
|
||||||
|
buckets := madmin.Buckets{}
|
||||||
|
objects := madmin.Objects{}
|
||||||
|
usage := madmin.Usage{}
|
||||||
|
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI != nil {
|
||||||
|
mode = madmin.ObjectLayerOnline
|
||||||
|
// Load data usage
|
||||||
|
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
|
if err == nil {
|
||||||
|
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
||||||
|
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
||||||
|
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetching the backend information
|
||||||
|
backendInfo := objectAPI.BackendInfo()
|
||||||
|
if backendInfo.Type == BackendType(madmin.Erasure) {
|
||||||
|
// Calculate the number of online/offline disks of all nodes
|
||||||
|
var allDisks []madmin.Disk
|
||||||
|
for _, s := range servers {
|
||||||
|
allDisks = append(allDisks, s.Disks...)
|
||||||
|
}
|
||||||
|
onlineDisks, offlineDisks := getOnlineOfflineDisksStats(allDisks)
|
||||||
|
|
||||||
|
backend = madmin.ErasureBackend{
|
||||||
|
Type: madmin.ErasureType,
|
||||||
|
OnlineDisks: onlineDisks.Sum(),
|
||||||
|
OfflineDisks: offlineDisks.Sum(),
|
||||||
|
StandardSCData: backendInfo.StandardSCData,
|
||||||
|
StandardSCParity: backendInfo.StandardSCParity,
|
||||||
|
RRSCData: backendInfo.RRSCData,
|
||||||
|
RRSCParity: backendInfo.RRSCParity,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
backend = madmin.FSBackend{
|
||||||
|
Type: madmin.FsType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
domain := globalDomainNames
|
domain := globalDomainNames
|
||||||
services := madmin.Services{
|
services := madmin.Services{
|
||||||
Vault: vault,
|
Vault: vault,
|
||||||
@@ -1501,38 +1582,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
Notifications: notifyTarget,
|
Notifications: notifyTarget,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect any disk healing.
|
|
||||||
healing, _ := getAggregatedBackgroundHealState(ctx)
|
|
||||||
healDisks := make(map[string]struct{}, len(healing.HealDisks))
|
|
||||||
for _, disk := range healing.HealDisks {
|
|
||||||
healDisks[disk] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all disks which belong to each respective endpoints
|
|
||||||
for i := range servers {
|
|
||||||
for _, disk := range storageInfo.Disks {
|
|
||||||
if strings.Contains(disk.Endpoint, servers[i].Endpoint) {
|
|
||||||
if _, ok := healDisks[disk.Endpoint]; ok {
|
|
||||||
disk.Healing = true
|
|
||||||
}
|
|
||||||
servers[i].Disks = append(servers[i].Disks, disk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add all the disks local to this server.
|
|
||||||
for _, disk := range storageInfo.Disks {
|
|
||||||
if disk.DrivePath == "" && disk.Endpoint == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if disk.Endpoint == disk.DrivePath {
|
|
||||||
if _, ok := healDisks[disk.Endpoint]; ok {
|
|
||||||
disk.Healing = true
|
|
||||||
}
|
|
||||||
servers[len(servers)-1].Disks = append(servers[len(servers)-1].Disks, disk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infoMsg := madmin.InfoMessage{
|
infoMsg := madmin.InfoMessage{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
@@ -1554,27 +1603,33 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Reply with storage information (across nodes in a
|
// Reply with storage information (across nodes in a
|
||||||
// distributed setup) as json.
|
// distributed setup) as json.
|
||||||
writeSuccessResponseJSON(w, jsonBytes)
|
writeSuccessResponseJSON(w, jsonBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
|
func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
||||||
|
|
||||||
// Fetch the configured targets
|
|
||||||
tr := NewGatewayHTTPTransport()
|
|
||||||
defer tr.CloseIdleConnections()
|
|
||||||
targetList, err := notify.FetchRegisteredTargets(GlobalContext, cfg, tr, true, false)
|
|
||||||
if err != nil && err != notify.ErrTargetsOffline {
|
|
||||||
logger.LogIf(GlobalContext, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lambdaMap := make(map[string][]madmin.TargetIDStatus)
|
lambdaMap := make(map[string][]madmin.TargetIDStatus)
|
||||||
|
|
||||||
for targetID, target := range targetList.TargetMap() {
|
for _, tgt := range globalConfigTargetList.Targets() {
|
||||||
targetIDStatus := make(map[string]madmin.Status)
|
targetIDStatus := make(map[string]madmin.Status)
|
||||||
active, _ := target.IsActive()
|
active, _ := tgt.IsActive()
|
||||||
|
targetID := tgt.ID()
|
||||||
|
if active {
|
||||||
|
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
|
||||||
|
} else {
|
||||||
|
targetIDStatus[targetID.ID] = madmin.Status{Status: "Offline"}
|
||||||
|
}
|
||||||
|
list := lambdaMap[targetID.Name]
|
||||||
|
list = append(list, targetIDStatus)
|
||||||
|
lambdaMap[targetID.Name] = list
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tgt := range globalEnvTargetList.Targets() {
|
||||||
|
targetIDStatus := make(map[string]madmin.Status)
|
||||||
|
active, _ := tgt.IsActive()
|
||||||
|
targetID := tgt.ID()
|
||||||
if active {
|
if active {
|
||||||
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
|
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
|
||||||
} else {
|
} else {
|
||||||
@@ -1583,8 +1638,6 @@ func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
|
|||||||
list := lambdaMap[targetID.Name]
|
list := lambdaMap[targetID.Name]
|
||||||
list = append(list, targetIDStatus)
|
list = append(list, targetIDStatus)
|
||||||
lambdaMap[targetID.Name] = list
|
lambdaMap[targetID.Name] = list
|
||||||
// Close any leaking connections
|
|
||||||
_ = target.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notify := make([]map[string][]madmin.TargetIDStatus, len(lambdaMap))
|
notify := make([]map[string][]madmin.TargetIDStatus, len(lambdaMap))
|
||||||
@@ -1599,7 +1652,7 @@ func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetchVaultStatus fetches Vault Info
|
// fetchVaultStatus fetches Vault Info
|
||||||
func fetchVaultStatus(cfg config.Config) madmin.Vault {
|
func fetchVaultStatus() madmin.Vault {
|
||||||
vault := madmin.Vault{}
|
vault := madmin.Vault{}
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
vault.Status = "disabled"
|
vault.Status = "disabled"
|
||||||
@@ -1642,41 +1695,42 @@ func fetchVaultStatus(cfg config.Config) madmin.Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetchLoggerDetails return log info
|
// fetchLoggerDetails return log info
|
||||||
func fetchLoggerInfo(cfg config.Config) ([]madmin.Logger, []madmin.Audit) {
|
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
||||||
loggerCfg, _ := logger.LookupConfig(cfg)
|
var loggerInfo []madmin.Logger
|
||||||
|
var auditloggerInfo []madmin.Audit
|
||||||
var logger []madmin.Logger
|
for _, target := range logger.Targets {
|
||||||
var auditlogger []madmin.Audit
|
if target.Endpoint() != "" {
|
||||||
for log, l := range loggerCfg.HTTP {
|
tgt := target.String()
|
||||||
if l.Enabled {
|
err := checkConnection(target.Endpoint(), 15*time.Second)
|
||||||
err := checkConnection(l.Endpoint, 15*time.Second)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mapLog := make(map[string]madmin.Status)
|
mapLog := make(map[string]madmin.Status)
|
||||||
mapLog[log] = madmin.Status{Status: "Online"}
|
mapLog[tgt] = madmin.Status{Status: "Online"}
|
||||||
logger = append(logger, mapLog)
|
loggerInfo = append(loggerInfo, mapLog)
|
||||||
} else {
|
} else {
|
||||||
mapLog := make(map[string]madmin.Status)
|
mapLog := make(map[string]madmin.Status)
|
||||||
mapLog[log] = madmin.Status{Status: "offline"}
|
mapLog[tgt] = madmin.Status{Status: "offline"}
|
||||||
logger = append(logger, mapLog)
|
loggerInfo = append(loggerInfo, mapLog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for audit, l := range loggerCfg.Audit {
|
for _, target := range logger.AuditTargets {
|
||||||
if l.Enabled {
|
if target.Endpoint() != "" {
|
||||||
err := checkConnection(l.Endpoint, 15*time.Second)
|
tgt := target.String()
|
||||||
|
err := checkConnection(target.Endpoint(), 15*time.Second)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mapAudit := make(map[string]madmin.Status)
|
mapAudit := make(map[string]madmin.Status)
|
||||||
mapAudit[audit] = madmin.Status{Status: "Online"}
|
mapAudit[tgt] = madmin.Status{Status: "Online"}
|
||||||
auditlogger = append(auditlogger, mapAudit)
|
auditloggerInfo = append(auditloggerInfo, mapAudit)
|
||||||
} else {
|
} else {
|
||||||
mapAudit := make(map[string]madmin.Status)
|
mapAudit := make(map[string]madmin.Status)
|
||||||
mapAudit[audit] = madmin.Status{Status: "Offline"}
|
mapAudit[tgt] = madmin.Status{Status: "Offline"}
|
||||||
auditlogger = append(auditlogger, mapAudit)
|
auditloggerInfo = append(auditloggerInfo, mapAudit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return logger, auditlogger
|
|
||||||
|
return loggerInfo, auditloggerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkConnection - ping an endpoint , return err in case of no connection
|
// checkConnection - ping an endpoint , return err in case of no connection
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalPolicySys = NewPolicySys()
|
globalPolicySys = NewPolicySys()
|
||||||
objLayer := &erasureZones{zones: make([]*erasureSets, 1)}
|
objLayer := &erasureServerPools{serverPools: make([]*erasureSets, 1)}
|
||||||
objLayer.zones[0], err = newErasureSets(ctx, endpoints, storageDisks, format)
|
objLayer.serverPools[0], err = newErasureSets(ctx, endpoints, storageDisks, format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -327,13 +327,13 @@ func TestExtractHealInitParams(t *testing.T) {
|
|||||||
mkParams := func(clientToken string, forceStart, forceStop bool) url.Values {
|
mkParams := func(clientToken string, forceStart, forceStop bool) url.Values {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
if clientToken != "" {
|
if clientToken != "" {
|
||||||
v.Add(string(mgmtClientToken), clientToken)
|
v.Add(mgmtClientToken, clientToken)
|
||||||
}
|
}
|
||||||
if forceStart {
|
if forceStart {
|
||||||
v.Add(string(mgmtForceStart), "")
|
v.Add(mgmtForceStart, "")
|
||||||
}
|
}
|
||||||
if forceStop {
|
if forceStop {
|
||||||
v.Add(string(mgmtForceStop), "")
|
v.Add(mgmtForceStop, "")
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@@ -351,11 +351,11 @@ func TestExtractHealInitParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
varsArr := []map[string]string{
|
varsArr := []map[string]string{
|
||||||
// Invalid cases
|
// Invalid cases
|
||||||
{string(mgmtPrefix): "objprefix"},
|
{mgmtPrefix: "objprefix"},
|
||||||
// Valid cases
|
// Valid cases
|
||||||
{},
|
{},
|
||||||
{string(mgmtBucket): "bucket"},
|
{mgmtBucket: "bucket"},
|
||||||
{string(mgmtBucket): "bucket", string(mgmtPrefix): "objprefix"},
|
{mgmtBucket: "bucket", mgmtPrefix: "objprefix"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body is always valid - we do not test JSON decoding.
|
// Body is always valid - we do not test JSON decoding.
|
||||||
|
|||||||
+70
-74
@@ -21,6 +21,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ type healSequenceStatus struct {
|
|||||||
|
|
||||||
// structure to hold state of all heal sequences in server memory
|
// structure to hold state of all heal sequences in server memory
|
||||||
type allHealState struct {
|
type allHealState struct {
|
||||||
sync.Mutex
|
sync.RWMutex
|
||||||
|
|
||||||
// map of heal path to heal sequence
|
// map of heal path to heal sequence
|
||||||
healSeqMap map[string]*healSequence
|
healSeqMap map[string]*healSequence
|
||||||
@@ -93,33 +95,33 @@ type allHealState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newHealState - initialize global heal state management
|
// newHealState - initialize global heal state management
|
||||||
func newHealState() *allHealState {
|
func newHealState(cleanup bool) *allHealState {
|
||||||
healState := &allHealState{
|
hstate := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
healLocalDisks: map[Endpoint]struct{}{},
|
healLocalDisks: map[Endpoint]struct{}{},
|
||||||
}
|
}
|
||||||
|
if cleanup {
|
||||||
go healState.periodicHealSeqsClean(GlobalContext)
|
go hstate.periodicHealSeqsClean(GlobalContext)
|
||||||
|
}
|
||||||
return healState
|
return hstate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) healDriveCount() int {
|
func (ahs *allHealState) healDriveCount() int {
|
||||||
ahs.Lock()
|
ahs.RLock()
|
||||||
defer ahs.Unlock()
|
defer ahs.RUnlock()
|
||||||
|
|
||||||
return len(ahs.healLocalDisks)
|
return len(ahs.healLocalDisks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) getHealLocalDisks() Endpoints {
|
func (ahs *allHealState) getHealLocalDisks() Endpoints {
|
||||||
ahs.Lock()
|
ahs.RLock()
|
||||||
defer ahs.Unlock()
|
defer ahs.RUnlock()
|
||||||
|
|
||||||
var healLocalDisks Endpoints
|
var endpoints Endpoints
|
||||||
for ep := range ahs.healLocalDisks {
|
for ep := range ahs.healLocalDisks {
|
||||||
healLocalDisks = append(healLocalDisks, ep)
|
endpoints = append(endpoints, ep)
|
||||||
}
|
}
|
||||||
return healLocalDisks
|
return endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
@@ -143,9 +145,13 @@ func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
|||||||
func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
||||||
// Launch clean-up routine to remove this heal sequence (after
|
// Launch clean-up routine to remove this heal sequence (after
|
||||||
// it ends) from the global state after timeout has elapsed.
|
// it ends) from the global state after timeout has elapsed.
|
||||||
|
periodicTimer := time.NewTimer(time.Minute * 5)
|
||||||
|
defer periodicTimer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Minute * 5):
|
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 {
|
||||||
@@ -228,7 +234,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
|||||||
// `keepHealSeqStateDuration`. This function also launches a
|
// `keepHealSeqStateDuration`. This function also launches a
|
||||||
// background routine to clean up heal results after the
|
// background routine to clean up heal results after the
|
||||||
// aforementioned duration.
|
// aforementioned duration.
|
||||||
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence, objAPI ObjectLayer) (
|
||||||
respBytes []byte, apiErr APIError, errMsg string) {
|
respBytes []byte, apiErr APIError, errMsg string) {
|
||||||
|
|
||||||
if h.forceStarted {
|
if h.forceStarted {
|
||||||
@@ -265,7 +271,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
|||||||
ahs.healSeqMap[hpath] = h
|
ahs.healSeqMap[hpath] = h
|
||||||
|
|
||||||
// Launch top-level background heal go-routine
|
// Launch top-level background heal go-routine
|
||||||
go h.healSequenceStart()
|
go h.healSequenceStart(objAPI)
|
||||||
|
|
||||||
clientToken := h.clientToken
|
clientToken := h.clientToken
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
@@ -528,7 +534,7 @@ func (h *healSequence) hasEnded() bool {
|
|||||||
if h.clientToken == bgHealingUUID {
|
if h.clientToken == bgHealingUUID {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return len(h.currentStatus.Items) == 0 || h.currentStatus.Summary == healStoppedStatus || h.currentStatus.Summary == healFinishedStatus
|
return !h.endTime.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
// stops the heal sequence - safe to call multiple times.
|
// stops the heal sequence - safe to call multiple times.
|
||||||
@@ -609,7 +615,7 @@ func (h *healSequence) pushHealResultItem(r madmin.HealResultItem) error {
|
|||||||
// routine for completion, and (2) listens for external stop
|
// routine for completion, and (2) listens for external stop
|
||||||
// signals. When either event happens, it sets the finish status for
|
// signals. When either event happens, it sets the finish status for
|
||||||
// the heal-sequence.
|
// the heal-sequence.
|
||||||
func (h *healSequence) healSequenceStart() {
|
func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
||||||
// Set status as running
|
// Set status as running
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
h.currentStatus.Summary = healRunningStatus
|
h.currentStatus.Summary = healRunningStatus
|
||||||
@@ -617,7 +623,7 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
h.mutex.Unlock()
|
h.mutex.Unlock()
|
||||||
|
|
||||||
if h.sourceCh == nil {
|
if h.sourceCh == nil {
|
||||||
go h.traverseAndHeal()
|
go h.traverseAndHeal(objAPI)
|
||||||
} else {
|
} else {
|
||||||
go h.healFromSourceCh()
|
go h.healFromSourceCh()
|
||||||
}
|
}
|
||||||
@@ -657,6 +663,10 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
|
globalHealConfigMu.Lock()
|
||||||
|
opts := globalHealConfig
|
||||||
|
globalHealConfigMu.Unlock()
|
||||||
|
|
||||||
// Send heal request
|
// Send heal request
|
||||||
task := healTask{
|
task := healTask{
|
||||||
bucket: source.bucket,
|
bucket: source.bucket,
|
||||||
@@ -668,6 +678,12 @@ 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
|
||||||
}
|
}
|
||||||
|
if opts.Bitrot {
|
||||||
|
task.opts.ScanMode = madmin.HealDeepScan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait and proceed if there are active requests
|
||||||
|
waitForLowHTTPReq(opts.IOCount, opts.Sleep)
|
||||||
|
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
h.scannedItemsMap[healType]++
|
h.scannedItemsMap[healType]++
|
||||||
@@ -766,28 +782,28 @@ func (h *healSequence) healFromSourceCh() {
|
|||||||
h.healItemsFromSourceCh()
|
h.healItemsFromSourceCh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healDiskMeta() error {
|
func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
|
||||||
// Start with format healing
|
// Try to pro-actively heal backend-encrypted file.
|
||||||
if err := h.healDiskFormat(); err != nil {
|
if err := h.queueHealTask(healSource{
|
||||||
return err
|
bucket: minioMetaBucket,
|
||||||
|
object: backendEncryptedFile,
|
||||||
|
}, madmin.HealItemBucketMetadata); err != nil {
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start healing the config prefix.
|
// Start healing the config prefix.
|
||||||
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
|
return h.healMinioSysMeta(objAPI, minioConfigPrefix)()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start healing the bucket config prefix.
|
|
||||||
return h.healMinioSysMeta(bucketConfigPrefix)()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healItems(bucketsOnly bool) error {
|
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||||
if err := h.healDiskMeta(); err != nil {
|
if err := h.healDiskMeta(objAPI); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal buckets and objects
|
// Heal buckets and objects
|
||||||
return h.healBuckets(bucketsOnly)
|
return h.healBuckets(objAPI, bucketsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// traverseAndHeal - traverses on-disk data and performs healing
|
// traverseAndHeal - traverses on-disk data and performs healing
|
||||||
@@ -797,30 +813,29 @@ func (h *healSequence) healItems(bucketsOnly bool) error {
|
|||||||
// quit signal is received, this routine cannot quit immediately and
|
// quit signal is received, this routine cannot quit immediately and
|
||||||
// has to wait until a safe point is reached, such as between scanning
|
// has to wait until a safe point is reached, such as between scanning
|
||||||
// two objects.
|
// two objects.
|
||||||
func (h *healSequence) traverseAndHeal() {
|
func (h *healSequence) traverseAndHeal(objAPI ObjectLayer) {
|
||||||
bucketsOnly := false // Heals buckets and objects also.
|
bucketsOnly := false // Heals buckets and objects also.
|
||||||
h.traverseAndHealDoneCh <- h.healItems(bucketsOnly)
|
h.traverseAndHealDoneCh <- h.healItems(objAPI, bucketsOnly)
|
||||||
close(h.traverseAndHealDoneCh)
|
close(h.traverseAndHealDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// healMinioSysMeta - heals all files under a given meta prefix, returns a function
|
// healMinioSysMeta - heals all files under a given meta prefix, returns a function
|
||||||
// which in-turn heals the respective meta directory path and any files in int.
|
// which in-turn heals the respective meta directory path and any files in int.
|
||||||
func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
|
func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) func() error {
|
||||||
return func() error {
|
return func() error {
|
||||||
// Get current object layer instance.
|
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
|
||||||
if objectAPI == nil {
|
|
||||||
return errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Healing on meta is run regardless
|
// NOTE: Healing on meta is run regardless
|
||||||
// of any bucket being selected, this is to ensure that
|
// of any bucket being selected, this is to ensure that
|
||||||
// meta are always upto date and correct.
|
// meta are always upto date and correct.
|
||||||
return objectAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string) error {
|
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip metacache entries healing
|
||||||
|
if strings.HasPrefix(object, "buckets/.minio.sys/.metacache/") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := h.queueHealTask(healSource{
|
err := h.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: object,
|
object: object,
|
||||||
@@ -843,39 +858,32 @@ func (h *healSequence) healDiskFormat() error {
|
|||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current object layer instance.
|
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
|
||||||
if objectAPI == nil {
|
|
||||||
return errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.queueHealTask(healSource{bucket: SlashSeparator}, madmin.HealItemMetadata)
|
return h.queueHealTask(healSource{bucket: SlashSeparator}, madmin.HealItemMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// healBuckets - check for all buckets heal or just particular bucket.
|
// healBuckets - check for all buckets heal or just particular bucket.
|
||||||
func (h *healSequence) healBuckets(bucketsOnly bool) error {
|
func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. If a bucket was specified, heal only the bucket.
|
// 1. If a bucket was specified, heal only the bucket.
|
||||||
if h.bucket != "" {
|
if h.bucket != "" {
|
||||||
return h.healBucket(h.bucket, bucketsOnly)
|
return h.healBucket(objAPI, h.bucket, bucketsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current object layer instance.
|
buckets, err := objAPI.ListBuckets(h.ctx)
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
|
||||||
if objectAPI == nil {
|
|
||||||
return errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
buckets, err := objectAPI.ListBucketsHeal(h.ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
return errFnHealFromAPIErr(h.ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Heal latest buckets first.
|
||||||
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
|
})
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
if err = h.healBucket(bucket.Name, bucketsOnly); err != nil {
|
if err = h.healBucket(objAPI, bucket.Name, bucketsOnly); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,13 +892,7 @@ func (h *healSequence) healBuckets(bucketsOnly bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// healBucket - traverses and heals given bucket
|
// healBucket - traverses and heals given bucket
|
||||||
func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
|
func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly bool) error {
|
||||||
// Get current object layer instance.
|
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
|
||||||
if objectAPI == nil {
|
|
||||||
return errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.queueHealTask(healSource{bucket: bucket}, madmin.HealItemBucket); err != nil {
|
if err := h.queueHealTask(healSource{bucket: bucket}, madmin.HealItemBucket); err != nil {
|
||||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
return err
|
return err
|
||||||
@@ -905,7 +907,7 @@ func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
|
|||||||
if h.object != "" {
|
if h.object != "" {
|
||||||
// Check if an object named as the objPrefix exists,
|
// Check if an object named as the objPrefix exists,
|
||||||
// and if so heal it.
|
// and if so heal it.
|
||||||
oi, err := objectAPI.GetObjectInfo(h.ctx, bucket, h.object, ObjectOptions{})
|
oi, err := objAPI.GetObjectInfo(h.ctx, bucket, h.object, ObjectOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = h.healObject(bucket, h.object, oi.VersionID); err != nil {
|
if err = h.healObject(bucket, h.object, oi.VersionID); err != nil {
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
@@ -919,7 +921,7 @@ func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := objectAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil {
|
if err := objAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil {
|
||||||
// Object might have been deleted, by the time heal
|
// Object might have been deleted, by the time heal
|
||||||
// was attempted we ignore this object an move on.
|
// was attempted we ignore this object an move on.
|
||||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
@@ -931,12 +933,6 @@ func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
|
|||||||
|
|
||||||
// healObject - heal the given object and record result
|
// healObject - heal the given object and record result
|
||||||
func (h *healSequence) healObject(bucket, object, versionID string) error {
|
func (h *healSequence) healObject(bucket, object, versionID string) error {
|
||||||
// Get current object layer instance.
|
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
|
||||||
if objectAPI == nil {
|
|
||||||
return errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-17
@@ -116,7 +116,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
|
|
||||||
// Add user IAM
|
// Add user IAM
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountusageinfo").HandlerFunc(httpTraceAll(adminAPI.AccountUsageInfoHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(httpTraceAll(adminAPI.AccountInfoHandler))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
@@ -180,19 +180,19 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
// PutBucketQuotaConfig
|
// PutBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
}
|
|
||||||
// Bucket replication operations
|
|
||||||
// GetBucketTargetHandler
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
|
||||||
httpTraceHdrs(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
|
||||||
// SetRemoteTargetHandler
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
|
||||||
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
|
||||||
// SetRemoteTargetHandler
|
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
|
||||||
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Bucket replication operations
|
||||||
|
// GetBucketTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||||
|
// SetRemoteTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
// RemoveRemoteTargetHandler
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
|
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
|
}
|
||||||
|
}
|
||||||
// -- Top APIs --
|
// -- Top APIs --
|
||||||
// Top locks
|
// Top locks
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
@@ -211,13 +211,18 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
|
||||||
|
|
||||||
if !globalIsGateway {
|
if !globalIsGateway {
|
||||||
// -- OBD API --
|
// Keep obdinfo for backward compatibility with mc
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
||||||
HandlerFunc(httpTraceHdrs(adminAPI.OBDInfoHandler))
|
HandlerFunc(httpTraceHdrs(adminAPI.HealthInfoHandler))
|
||||||
|
// -- Health API --
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
||||||
|
HandlerFunc(httpTraceHdrs(adminAPI.HealthInfoHandler))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
|
||||||
|
HandlerFunc(httpTraceHdrs(adminAPI.BandwidthMonitorHandler))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
adminRouter.NotFoundHandler = http.HandlerFunc(httpTraceAll(errorResponseHandler))
|
adminRouter.NotFoundHandler = httpTraceAll(errorResponseHandler)
|
||||||
adminRouter.MethodNotAllowedHandler = http.HandlerFunc(httpTraceAll(errorResponseHandler))
|
adminRouter.MethodNotAllowedHandler = httpTraceAll(methodNotAllowedHandler("Admin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import (
|
|||||||
|
|
||||||
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin.ServerProperties {
|
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
||||||
|
var localEndpoints Endpoints
|
||||||
addr := r.Host
|
addr := r.Host
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
addr = GetLocalPeer(endpointZones)
|
addr = GetLocalPeer(endpointServerPools)
|
||||||
}
|
}
|
||||||
network := make(map[string]string)
|
network := make(map[string]string)
|
||||||
for _, ep := range endpointZones {
|
for _, ep := range endpointServerPools {
|
||||||
for _, endpoint := range ep.Endpoints {
|
for _, endpoint := range ep.Endpoints {
|
||||||
nodeName := endpoint.Host
|
nodeName := endpoint.Host
|
||||||
if nodeName == "" {
|
if nodeName == "" {
|
||||||
@@ -39,6 +40,7 @@ func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin
|
|||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
network[nodeName] = "online"
|
network[nodeName] = "online"
|
||||||
|
localEndpoints = append(localEndpoints, endpoint)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, present := network[nodeName]
|
_, present := network[nodeName]
|
||||||
@@ -52,6 +54,11 @@ func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
|
||||||
|
defer closeStorageDisks(localDisks)
|
||||||
|
|
||||||
|
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
|
||||||
|
|
||||||
return madmin.ServerProperties{
|
return madmin.ServerProperties{
|
||||||
State: "ok",
|
State: "ok",
|
||||||
Endpoint: addr,
|
Endpoint: addr,
|
||||||
@@ -59,5 +66,6 @@ func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin
|
|||||||
Version: Version,
|
Version: Version,
|
||||||
CommitID: CommitID,
|
CommitID: CommitID,
|
||||||
Network: network,
|
Network: network,
|
||||||
|
Disks: storageInfo.Disks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeletedObject objects deleted
|
// DeletedObject objects deleted
|
||||||
@@ -26,12 +27,44 @@ type DeletedObject struct {
|
|||||||
DeleteMarkerVersionID string `xml:"DeleteMarkerVersionId,omitempty"`
|
DeleteMarkerVersionID string `xml:"DeleteMarkerVersionId,omitempty"`
|
||||||
ObjectName string `xml:"Key,omitempty"`
|
ObjectName string `xml:"Key,omitempty"`
|
||||||
VersionID string `xml:"VersionId,omitempty"`
|
VersionID string `xml:"VersionId,omitempty"`
|
||||||
|
|
||||||
|
// MinIO extensions to support delete marker replication
|
||||||
|
// Replication status of DeleteMarker
|
||||||
|
DeleteMarkerReplicationStatus string `xml:"DeleteMarkerReplicationStatus,omitempty"`
|
||||||
|
// MTime of DeleteMarker on source that needs to be propagated to replica
|
||||||
|
DeleteMarkerMTime DeleteMarkerMTime `xml:"DeleteMarkerMTime,omitempty"`
|
||||||
|
// Status of versioned delete (of object or DeleteMarker)
|
||||||
|
VersionPurgeStatus VersionPurgeStatusType `xml:"VersionPurgeStatus,omitempty"`
|
||||||
|
// PurgeTransitioned is nonempty if object is in transition tier
|
||||||
|
PurgeTransitioned string `xml:"PurgeTransitioned,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMarkerMTime is an embedded type containing time.Time for XML marshal
|
||||||
|
type DeleteMarkerMTime struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalXML encodes expiration date if it is non-zero and encodes
|
||||||
|
// empty string otherwise
|
||||||
|
func (t DeleteMarkerMTime) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
|
||||||
|
if t.Time.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.EncodeElement(t.Time.Format(time.RFC3339), startElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectToDelete carries key name for the object to delete.
|
// ObjectToDelete carries key name for the object to delete.
|
||||||
type ObjectToDelete struct {
|
type ObjectToDelete struct {
|
||||||
ObjectName string `xml:"Key"`
|
ObjectName string `xml:"Key"`
|
||||||
VersionID string `xml:"VersionId"`
|
VersionID string `xml:"VersionId"`
|
||||||
|
// Replication status of DeleteMarker
|
||||||
|
DeleteMarkerReplicationStatus string `xml:"DeleteMarkerReplicationStatus"`
|
||||||
|
// Status of versioned delete (of object or DeleteMarker)
|
||||||
|
VersionPurgeStatus VersionPurgeStatusType `xml:"VersionPurgeStatus"`
|
||||||
|
// Version ID of delete marker
|
||||||
|
DeleteMarkerVersionID string `xml:"DeleteMarkerVersionId"`
|
||||||
|
// PurgeTransitioned is nonempty if object is in transition tier
|
||||||
|
PurgeTransitioned string `xml:"PurgeTransitioned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// createBucketConfiguration container for bucket configuration request from client.
|
// createBucketConfiguration container for bucket configuration request from client.
|
||||||
|
|||||||
+77
-25
@@ -106,22 +106,25 @@ const (
|
|||||||
ErrNoSuchCORSConfiguration
|
ErrNoSuchCORSConfiguration
|
||||||
ErrNoSuchWebsiteConfiguration
|
ErrNoSuchWebsiteConfiguration
|
||||||
ErrReplicationConfigurationNotFoundError
|
ErrReplicationConfigurationNotFoundError
|
||||||
ErrReplicationDestinationNotFoundError
|
ErrRemoteDestinationNotFoundError
|
||||||
ErrReplicationDestinationMissingLock
|
ErrReplicationDestinationMissingLock
|
||||||
ErrReplicationTargetNotFoundError
|
ErrRemoteTargetNotFoundError
|
||||||
ErrReplicationRemoteConnectionError
|
ErrReplicationRemoteConnectionError
|
||||||
ErrBucketRemoteIdenticalToSource
|
ErrBucketRemoteIdenticalToSource
|
||||||
ErrBucketRemoteAlreadyExists
|
ErrBucketRemoteAlreadyExists
|
||||||
|
ErrBucketRemoteLabelInUse
|
||||||
ErrBucketRemoteArnTypeInvalid
|
ErrBucketRemoteArnTypeInvalid
|
||||||
ErrBucketRemoteArnInvalid
|
ErrBucketRemoteArnInvalid
|
||||||
ErrBucketRemoteRemoveDisallowed
|
ErrBucketRemoteRemoveDisallowed
|
||||||
ErrReplicationTargetNotVersionedError
|
ErrRemoteTargetNotVersionedError
|
||||||
ErrReplicationSourceNotVersionedError
|
ErrReplicationSourceNotVersionedError
|
||||||
ErrReplicationNeedsVersioningError
|
ErrReplicationNeedsVersioningError
|
||||||
ErrReplicationBucketNeedsVersioningError
|
ErrReplicationBucketNeedsVersioningError
|
||||||
ErrBucketReplicationDisabledError
|
ErrBucketReplicationDisabledError
|
||||||
|
ErrObjectRestoreAlreadyInProgress
|
||||||
ErrNoSuchKey
|
ErrNoSuchKey
|
||||||
ErrNoSuchUpload
|
ErrNoSuchUpload
|
||||||
|
ErrInvalidVersionID
|
||||||
ErrNoSuchVersion
|
ErrNoSuchVersion
|
||||||
ErrNotImplemented
|
ErrNotImplemented
|
||||||
ErrPreconditionFailed
|
ErrPreconditionFailed
|
||||||
@@ -232,6 +235,7 @@ const (
|
|||||||
ErrInvalidResourceName
|
ErrInvalidResourceName
|
||||||
ErrServerNotInitialized
|
ErrServerNotInitialized
|
||||||
ErrOperationTimedOut
|
ErrOperationTimedOut
|
||||||
|
ErrClientDisconnected
|
||||||
ErrOperationMaxedOut
|
ErrOperationMaxedOut
|
||||||
ErrInvalidRequest
|
ErrInvalidRequest
|
||||||
// MinIO storage class error codes
|
// MinIO storage class error codes
|
||||||
@@ -360,6 +364,7 @@ const (
|
|||||||
ErrInvalidDecompressedSize
|
ErrInvalidDecompressedSize
|
||||||
ErrAddUserInvalidArgument
|
ErrAddUserInvalidArgument
|
||||||
ErrAdminAccountNotEligible
|
ErrAdminAccountNotEligible
|
||||||
|
ErrAccountNotEligible
|
||||||
ErrServiceAccountNotFound
|
ErrServiceAccountNotFound
|
||||||
ErrPostPolicyConditionInvalidFormat
|
ErrPostPolicyConditionInvalidFormat
|
||||||
)
|
)
|
||||||
@@ -554,11 +559,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
|
Description: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrNoSuchVersion: {
|
ErrInvalidVersionID: {
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
Description: "Invalid version id specified",
|
Description: "Invalid version id specified",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrNoSuchVersion: {
|
||||||
|
Code: "NoSuchVersion",
|
||||||
|
Description: "The specified version does not exist.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrNotImplemented: {
|
ErrNotImplemented: {
|
||||||
Code: "NotImplemented",
|
Code: "NotImplemented",
|
||||||
Description: "A header you provided implies functionality that is not implemented",
|
Description: "A header you provided implies functionality that is not implemented",
|
||||||
@@ -809,9 +819,9 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The replication configuration was not found",
|
Description: "The replication configuration was not found",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrReplicationDestinationNotFoundError: {
|
ErrRemoteDestinationNotFoundError: {
|
||||||
Code: "ReplicationDestinationNotFoundError",
|
Code: "RemoteDestinationNotFoundError",
|
||||||
Description: "The replication destination bucket does not exist",
|
Description: "The remote destination bucket does not exist",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrReplicationDestinationMissingLock: {
|
ErrReplicationDestinationMissingLock: {
|
||||||
@@ -819,29 +829,34 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The replication destination bucket does not have object locking enabled",
|
Description: "The replication destination bucket does not have object locking enabled",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrReplicationTargetNotFoundError: {
|
ErrRemoteTargetNotFoundError: {
|
||||||
Code: "XminioAdminReplicationTargetNotFoundError",
|
Code: "XMinioAdminRemoteTargetNotFoundError",
|
||||||
Description: "The replication target does not exist",
|
Description: "The remote target does not exist",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrReplicationRemoteConnectionError: {
|
ErrReplicationRemoteConnectionError: {
|
||||||
Code: "XminioAdminReplicationRemoteConnectionError",
|
Code: "XMinioAdminReplicationRemoteConnectionError",
|
||||||
Description: "Remote service endpoint or target bucket not available",
|
Description: "Remote service connection error - please check remote service credentials and target bucket",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrBucketRemoteIdenticalToSource: {
|
ErrBucketRemoteIdenticalToSource: {
|
||||||
Code: "XminioAdminRemoteIdenticalToSource",
|
Code: "XMinioAdminRemoteIdenticalToSource",
|
||||||
Description: "The remote target cannot be identical to source",
|
Description: "The remote target cannot be identical to source",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrBucketRemoteAlreadyExists: {
|
ErrBucketRemoteAlreadyExists: {
|
||||||
Code: "XminioAdminBucketRemoteAlreadyExists",
|
Code: "XMinioAdminBucketRemoteAlreadyExists",
|
||||||
Description: "The remote target already exists",
|
Description: "The remote target already exists",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrBucketRemoteLabelInUse: {
|
||||||
|
Code: "XMinioAdminBucketRemoteLabelInUse",
|
||||||
|
Description: "The remote target with this label already exists",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrBucketRemoteRemoveDisallowed: {
|
ErrBucketRemoteRemoveDisallowed: {
|
||||||
Code: "XMinioAdminRemoteRemoveDisallowed",
|
Code: "XMinioAdminRemoteRemoveDisallowed",
|
||||||
Description: "Replication configuration exists with this ARN.",
|
Description: "This ARN is in use by an existing configuration",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrBucketRemoteArnTypeInvalid: {
|
ErrBucketRemoteArnTypeInvalid: {
|
||||||
@@ -854,9 +869,9 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The bucket remote ARN does not have correct format",
|
Description: "The bucket remote ARN does not have correct format",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrReplicationTargetNotVersionedError: {
|
ErrRemoteTargetNotVersionedError: {
|
||||||
Code: "ReplicationTargetNotVersionedError",
|
Code: "RemoteTargetNotVersionedError",
|
||||||
Description: "The replication target does not have versioning enabled",
|
Description: "The remote target does not have versioning enabled",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrReplicationSourceNotVersionedError: {
|
ErrReplicationSourceNotVersionedError: {
|
||||||
@@ -909,6 +924,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied",
|
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrObjectRestoreAlreadyInProgress: {
|
||||||
|
Code: "RestoreAlreadyInProgress",
|
||||||
|
Description: "Object restore is already in progress",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
/// Bucket notification related errors.
|
/// Bucket notification related errors.
|
||||||
ErrEventNotification: {
|
ErrEventNotification: {
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -1210,6 +1230,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "A timeout occurred while trying to lock a resource, please reduce your request rate",
|
Description: "A timeout occurred while trying to lock a resource, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
|
ErrClientDisconnected: {
|
||||||
|
Code: "ClientDisconnected",
|
||||||
|
Description: "Client disconnected before response was ready",
|
||||||
|
HTTPStatusCode: 499, // No official code, use nginx value.
|
||||||
|
},
|
||||||
ErrOperationMaxedOut: {
|
ErrOperationMaxedOut: {
|
||||||
Code: "SlowDown",
|
Code: "SlowDown",
|
||||||
Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate",
|
Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate",
|
||||||
@@ -1708,12 +1733,17 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrAddUserInvalidArgument: {
|
ErrAddUserInvalidArgument: {
|
||||||
Code: "XMinioInvalidIAMCredentials",
|
Code: "XMinioInvalidIAMCredentials",
|
||||||
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.StatusConflict,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
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",
|
||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
ErrAccountNotEligible: {
|
||||||
|
Code: "XMinioInvalidIAMCredentials",
|
||||||
|
Description: "The account key is not eligible for this operation",
|
||||||
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
ErrServiceAccountNotFound: {
|
ErrServiceAccountNotFound: {
|
||||||
Code: "XMinioInvalidIAMCredentials",
|
Code: "XMinioInvalidIAMCredentials",
|
||||||
@@ -1736,6 +1766,16 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only return ErrClientDisconnected if the provided context is actually canceled.
|
||||||
|
// This way downstream context.Canceled will still report ErrOperationTimedOut
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if ctx.Err() == context.Canceled {
|
||||||
|
return ErrClientDisconnected
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
case errInvalidArgument:
|
case errInvalidArgument:
|
||||||
apiErr = ErrAdminInvalidArgument
|
apiErr = ErrAdminInvalidArgument
|
||||||
@@ -1852,6 +1892,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrNoSuchKey
|
apiErr = ErrNoSuchKey
|
||||||
case MethodNotAllowed:
|
case MethodNotAllowed:
|
||||||
apiErr = ErrMethodNotAllowed
|
apiErr = ErrMethodNotAllowed
|
||||||
|
case InvalidVersionID:
|
||||||
|
apiErr = ErrInvalidVersionID
|
||||||
case VersionNotFound:
|
case VersionNotFound:
|
||||||
apiErr = ErrNoSuchVersion
|
apiErr = ErrNoSuchVersion
|
||||||
case ObjectAlreadyExists:
|
case ObjectAlreadyExists:
|
||||||
@@ -1906,24 +1948,26 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminNoSuchQuotaConfiguration
|
apiErr = ErrAdminNoSuchQuotaConfiguration
|
||||||
case BucketReplicationConfigNotFound:
|
case BucketReplicationConfigNotFound:
|
||||||
apiErr = ErrReplicationConfigurationNotFoundError
|
apiErr = ErrReplicationConfigurationNotFoundError
|
||||||
case BucketReplicationDestinationNotFound:
|
case BucketRemoteDestinationNotFound:
|
||||||
apiErr = ErrReplicationDestinationNotFoundError
|
apiErr = ErrRemoteDestinationNotFoundError
|
||||||
case BucketReplicationDestinationMissingLock:
|
case BucketReplicationDestinationMissingLock:
|
||||||
apiErr = ErrReplicationDestinationMissingLock
|
apiErr = ErrReplicationDestinationMissingLock
|
||||||
case BucketRemoteTargetNotFound:
|
case BucketRemoteTargetNotFound:
|
||||||
apiErr = ErrReplicationTargetNotFoundError
|
apiErr = ErrRemoteTargetNotFoundError
|
||||||
case BucketRemoteConnectionErr:
|
case BucketRemoteConnectionErr:
|
||||||
apiErr = ErrReplicationRemoteConnectionError
|
apiErr = ErrReplicationRemoteConnectionError
|
||||||
case BucketRemoteAlreadyExists:
|
case BucketRemoteAlreadyExists:
|
||||||
apiErr = ErrBucketRemoteAlreadyExists
|
apiErr = ErrBucketRemoteAlreadyExists
|
||||||
|
case BucketRemoteLabelInUse:
|
||||||
|
apiErr = ErrBucketRemoteLabelInUse
|
||||||
case BucketRemoteArnTypeInvalid:
|
case BucketRemoteArnTypeInvalid:
|
||||||
apiErr = ErrBucketRemoteArnTypeInvalid
|
apiErr = ErrBucketRemoteArnTypeInvalid
|
||||||
case BucketRemoteArnInvalid:
|
case BucketRemoteArnInvalid:
|
||||||
apiErr = ErrBucketRemoteArnInvalid
|
apiErr = ErrBucketRemoteArnInvalid
|
||||||
case BucketRemoteRemoveDisallowed:
|
case BucketRemoteRemoveDisallowed:
|
||||||
apiErr = ErrBucketRemoteRemoveDisallowed
|
apiErr = ErrBucketRemoteRemoveDisallowed
|
||||||
case BucketReplicationTargetNotVersioned:
|
case BucketRemoteTargetNotVersioned:
|
||||||
apiErr = ErrReplicationTargetNotVersionedError
|
apiErr = ErrRemoteTargetNotVersionedError
|
||||||
case BucketReplicationSourceNotVersioned:
|
case BucketReplicationSourceNotVersioned:
|
||||||
apiErr = ErrReplicationSourceNotVersionedError
|
apiErr = ErrReplicationSourceNotVersionedError
|
||||||
case BucketQuotaExceeded:
|
case BucketQuotaExceeded:
|
||||||
@@ -1956,6 +2000,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrBackendDown
|
apiErr = ErrBackendDown
|
||||||
case ObjectNameTooLong:
|
case ObjectNameTooLong:
|
||||||
apiErr = ErrKeyTooLongError
|
apiErr = ErrKeyTooLongError
|
||||||
|
case dns.ErrInvalidBucketName:
|
||||||
|
apiErr = ErrInvalidBucketName
|
||||||
default:
|
default:
|
||||||
var ie, iw int
|
var ie, iw int
|
||||||
// This work-around is to handle the issue golang/go#30648
|
// This work-around is to handle the issue golang/go#30648
|
||||||
@@ -1992,6 +2038,12 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var apiErr = errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
var apiErr = errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
||||||
|
e, ok := err.(dns.ErrInvalidBucketName)
|
||||||
|
if ok {
|
||||||
|
code := toAPIErrorCode(ctx, e)
|
||||||
|
apiErr = errorCodes.ToAPIErrWithErr(code, e)
|
||||||
|
}
|
||||||
|
|
||||||
if apiErr.Code == "InternalError" {
|
if apiErr.Code == "InternalError" {
|
||||||
// If we see an internal error try to interpret
|
// If we see an internal error try to interpret
|
||||||
// any underlying errors if possible depending on
|
// any underlying errors if possible depending on
|
||||||
|
|||||||
+19
-8
@@ -47,7 +47,9 @@ func setEventStreamHeaders(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Write http common headers
|
// Write http common headers
|
||||||
func setCommonHeaders(w http.ResponseWriter) {
|
func setCommonHeaders(w http.ResponseWriter) {
|
||||||
w.Header().Set(xhttp.ServerInfo, "MinIO/"+ReleaseTag)
|
// Set the "Server" http header.
|
||||||
|
w.Header().Set(xhttp.ServerInfo, "MinIO")
|
||||||
|
|
||||||
// Set `x-amz-bucket-region` only if region is set on the server
|
// Set `x-amz-bucket-region` only if region is set on the server
|
||||||
// by default minio uses an empty region.
|
// by default minio uses an empty region.
|
||||||
if region := globalServerRegion; region != "" {
|
if region := globalServerRegion; region != "" {
|
||||||
@@ -84,7 +86,7 @@ func setPartsCountHeaders(w http.ResponseWriter, objInfo ObjectInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write object header
|
// Write object header
|
||||||
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec) (err error) {
|
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
|
||||||
// set common headers
|
// set common headers
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
|
|
||||||
@@ -115,10 +117,11 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set tag count if object has tags
|
// Set tag count if object has tags
|
||||||
tags, _ := url.ParseQuery(objInfo.UserTags)
|
if len(objInfo.UserTags) > 0 {
|
||||||
tagCount := len(tags)
|
tags, _ := url.ParseQuery(objInfo.UserTags)
|
||||||
if tagCount > 0 {
|
if len(tags) > 0 {
|
||||||
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tagCount)}
|
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(len(tags))}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all other user defined metadata.
|
// Set all other user defined metadata.
|
||||||
@@ -147,13 +150,18 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var start, rangeLen int64
|
||||||
totalObjectSize, err := objInfo.GetActualSize()
|
totalObjectSize, err := objInfo.GetActualSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// for providing ranged content
|
if opts.PartNumber > 0 {
|
||||||
start, rangeLen, err := rs.GetOffsetLength(totalObjectSize)
|
rs = partNumberToRangeSpec(objInfo, opts.PartNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For providing ranged content
|
||||||
|
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -186,6 +194,9 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
|
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if objInfo.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
|
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.StorageClass}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+6
-14
@@ -35,11 +35,11 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
|
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
|
||||||
iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
|
iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
|
||||||
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
|
maxObjectList = metacacheBlockSize - (metacacheBlockSize / 10) // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
|
||||||
maxDeleteList = 10000 // Limit number of objects deleted in a delete call.
|
maxDeleteList = 10000 // Limit number of objects deleted in a delete call.
|
||||||
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
|
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
|
||||||
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
|
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocationResponse - format for location response.
|
// LocationResponse - format for location response.
|
||||||
@@ -388,7 +388,7 @@ func getObjectLocation(r *http.Request, domains []string, bucket, object string)
|
|||||||
}
|
}
|
||||||
proto := handlers.GetSourceScheme(r)
|
proto := handlers.GetSourceScheme(r)
|
||||||
if proto == "" {
|
if proto == "" {
|
||||||
proto = getURLScheme(globalIsSSL)
|
proto = getURLScheme(globalIsTLS)
|
||||||
}
|
}
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Host: r.Host,
|
Host: r.Host,
|
||||||
@@ -703,10 +703,6 @@ 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 newObjectLayerFn() == nil {
|
|
||||||
// Server still in safe mode.
|
|
||||||
w.Header().Set(xhttp.MinIOServerStatus, "safemode")
|
|
||||||
}
|
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
if mType != mimeNone {
|
if mType != mimeNone {
|
||||||
w.Header().Set(xhttp.ContentType, string(mType))
|
w.Header().Set(xhttp.ContentType, string(mType))
|
||||||
@@ -773,10 +769,6 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
// The request is from browser and also if browser
|
// The request is from browser and also if browser
|
||||||
// is enabled we need to redirect.
|
// is enabled we need to redirect.
|
||||||
if browser && globalBrowserEnabled {
|
if browser && globalBrowserEnabled {
|
||||||
if newObjectLayerFn() == nil {
|
|
||||||
// server still in safe mode.
|
|
||||||
w.Header().Set(xhttp.MinIOServerStatus, "safemode")
|
|
||||||
}
|
|
||||||
w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
|
w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
|
||||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
|
|||||||
+15
-16
@@ -32,31 +32,24 @@ func newHTTPServerFn() *xhttp.Server {
|
|||||||
return globalHTTPServer
|
return globalHTTPServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func newObjectLayerWithoutSafeModeFn() ObjectLayer {
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
defer globalObjLayerMutex.Unlock()
|
|
||||||
return globalObjectAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
func newObjectLayerFn() ObjectLayer {
|
func newObjectLayerFn() ObjectLayer {
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
defer globalObjLayerMutex.Unlock()
|
defer globalObjLayerMutex.Unlock()
|
||||||
if globalSafeMode {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return globalObjectAPI
|
return globalObjectAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCachedObjectLayerFn() CacheObjectLayer {
|
func newCachedObjectLayerFn() CacheObjectLayer {
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
defer globalObjLayerMutex.Unlock()
|
defer globalObjLayerMutex.Unlock()
|
||||||
|
|
||||||
if globalSafeMode {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return globalCacheObjectAPI
|
return globalCacheObjectAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setObjectLayer(o ObjectLayer) {
|
||||||
|
globalObjLayerMutex.Lock()
|
||||||
|
globalObjectAPI = o
|
||||||
|
globalObjLayerMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// objectAPIHandler implements and provides http handlers for S3 API.
|
// objectAPIHandler implements and provides http handlers for S3 API.
|
||||||
type objectAPIHandlers struct {
|
type objectAPIHandlers struct {
|
||||||
ObjectAPI func() ObjectLayer
|
ObjectAPI func() ObjectLayer
|
||||||
@@ -88,7 +81,10 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
for _, domainName := range globalDomainNames {
|
for _, domainName := range globalDomainNames {
|
||||||
if IsKubernetes() {
|
if IsKubernetes() {
|
||||||
routers = append(routers, apiRouter.MatcherFunc(func(r *http.Request, match *mux.RouteMatch) bool {
|
routers = append(routers, apiRouter.MatcherFunc(func(r *http.Request, match *mux.RouteMatch) bool {
|
||||||
host, _, _ := net.SplitHostPort(getHost(r))
|
host, _, err := net.SplitHostPort(getHost(r))
|
||||||
|
if err != nil {
|
||||||
|
host = r.Host
|
||||||
|
}
|
||||||
// Make sure to skip matching minio.<domain>` this is
|
// Make sure to skip matching minio.<domain>` this is
|
||||||
// specifically meant for operator/k8s deployment
|
// specifically meant for operator/k8s deployment
|
||||||
// The reason we need to skip this is for a special
|
// The reason we need to skip this is for a special
|
||||||
@@ -311,6 +307,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// DeleteBucket
|
// DeleteBucket
|
||||||
bucket.Methods(http.MethodDelete).HandlerFunc(
|
bucket.Methods(http.MethodDelete).HandlerFunc(
|
||||||
maxClients(collectAPIStats("deletebucket", httpTraceAll(api.DeleteBucketHandler))))
|
maxClients(collectAPIStats("deletebucket", httpTraceAll(api.DeleteBucketHandler))))
|
||||||
|
// PostRestoreObject
|
||||||
|
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
||||||
|
maxClients(collectAPIStats("restoreobject", httpTraceAll(api.PostRestoreObjectHandler)))).Queries("restore", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root operation
|
/// Root operation
|
||||||
@@ -329,8 +328,8 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
maxClients(collectAPIStats("listbuckets", httpTraceAll(api.ListBucketsHandler))))
|
maxClients(collectAPIStats("listbuckets", httpTraceAll(api.ListBucketsHandler))))
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
apiRouter.NotFoundHandler = http.HandlerFunc(collectAPIStats("notfound", httpTraceAll(errorResponseHandler)))
|
apiRouter.NotFoundHandler = collectAPIStats("notfound", httpTraceAll(errorResponseHandler))
|
||||||
apiRouter.MethodNotAllowedHandler = http.HandlerFunc(collectAPIStats("methodnotallowed", httpTraceAll(errorResponseHandler)))
|
apiRouter.MethodNotAllowedHandler = collectAPIStats("methodnotallowed", httpTraceAll(methodNotAllowedHandler("S3")))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -151,9 +151,10 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
return cred, claims, owner, ErrNone
|
return cred, claims, owner, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAdminRequestAuthType checks whether the request is a valid signature V2 or V4 request.
|
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
||||||
// It does not accept presigned or JWT or anonymous requests.
|
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
||||||
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
// are automatically rejected.
|
||||||
|
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
@@ -333,8 +334,12 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
|
|||||||
// 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 = ioutil.NopCloser(bytes.NewReader(payload))
|
||||||
}
|
}
|
||||||
|
if cred.AccessKey != "" {
|
||||||
|
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
||||||
|
}
|
||||||
|
|
||||||
if cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
|
// Anonymous checks are not meant for ListBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: action,
|
Action: action,
|
||||||
@@ -378,12 +383,13 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
|
|||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred.AccessKey, owner, ErrNone
|
return cred.AccessKey, owner, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: iampolicy.Action(policy.ListBucketAction),
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
@@ -554,7 +560,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
if retMode == objectlock.RetGovernance && byPassSet {
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
byPassSet = globalPolicySys.IsAllowed(policy.Args{
|
byPassSet = globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: policy.Action(policy.BypassGovernanceRetentionAction),
|
Action: policy.BypassGovernanceRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
@@ -563,7 +569,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
}
|
}
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: policy.Action(policy.PutObjectRetentionAction),
|
Action: policy.PutObjectRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
@@ -586,7 +592,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
if retMode == objectlock.RetGovernance && byPassSet {
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
|
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: policy.BypassGovernanceRetentionAction,
|
Action: iampolicy.BypassGovernanceRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
@@ -596,7 +602,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: policy.PutObjectRetentionAction,
|
Action: iampolicy.PutObjectRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
@@ -614,7 +620,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
||||||
// call verifies bucket policies and IAM policies, supports multi user
|
// call verifies bucket policies and IAM policies, supports multi user
|
||||||
// checks etc.
|
// checks etc.
|
||||||
func isPutActionAllowed(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
|
||||||
switch atype {
|
switch atype {
|
||||||
@@ -635,6 +641,10 @@ func isPutActionAllowed(atype authType, bucketName, objectName string, r *http.R
|
|||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cred.AccessKey != "" {
|
||||||
|
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
// Can happen when bucket has default lock config set
|
// Can happen when bucket has default lock config set
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuthType(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-34
@@ -18,7 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"path"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -55,20 +54,33 @@ func (h *healRoutine) queueHealTask(task healTask) {
|
|||||||
h.tasks <- task
|
h.tasks <- task
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForLowHTTPReq(tolerance int32, maxWait time.Duration) {
|
func waitForLowHTTPReq(maxIO int, maxWait time.Duration) {
|
||||||
const wait = 10 * time.Millisecond
|
// No need to wait run at full speed.
|
||||||
waitCount := maxWait / wait
|
if maxIO <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// At max 10 attempts to wait with 100 millisecond interval before proceeding
|
||||||
|
waitCount := 10
|
||||||
|
waitTick := 100 * time.Millisecond
|
||||||
|
|
||||||
// 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
|
||||||
tolerance += int32(globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers())
|
maxIOFn := func() int {
|
||||||
|
return maxIO + globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers()
|
||||||
|
}
|
||||||
|
|
||||||
if httpServer := newHTTPServerFn(); httpServer != nil {
|
if httpServer := newHTTPServerFn(); httpServer != nil {
|
||||||
// Any requests in progress, delay the heal.
|
// Any requests in progress, delay the heal.
|
||||||
for (httpServer.GetRequestCount() >= tolerance) &&
|
for httpServer.GetRequestCount() >= maxIOFn() {
|
||||||
waitCount > 0 {
|
time.Sleep(waitTick)
|
||||||
waitCount--
|
waitCount--
|
||||||
time.Sleep(wait)
|
if waitCount == 0 {
|
||||||
|
if intDataUpdateTracker.debug {
|
||||||
|
logger.Info("waitForLowHTTPReq: waited %d times, resuming", waitCount)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,9 +94,6 @@ func (h *healRoutine) run(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait and proceed if there are active requests
|
|
||||||
waitForLowHTTPReq(int32(globalEndpoints.NEndpoints()), time.Second)
|
|
||||||
|
|
||||||
var res madmin.HealResultItem
|
var res madmin.HealResultItem
|
||||||
var err error
|
var err error
|
||||||
switch {
|
switch {
|
||||||
@@ -93,13 +102,10 @@ func (h *healRoutine) run(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
case task.bucket == SlashSeparator:
|
case task.bucket == SlashSeparator:
|
||||||
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
||||||
case task.bucket != "" && task.object == "":
|
case task.bucket != "" && task.object == "":
|
||||||
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts.DryRun, task.opts.Remove)
|
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts)
|
||||||
case task.bucket != "" && task.object != "":
|
case task.bucket != "" && task.object != "":
|
||||||
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
|
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
|
||||||
}
|
}
|
||||||
if task.bucket != "" && task.object != "" {
|
|
||||||
ObjectPathUpdated(path.Join(task.bucket, task.object))
|
|
||||||
}
|
|
||||||
task.responseCh <- healResult{result: res, err: err}
|
task.responseCh <- healResult{result: res, err: err}
|
||||||
|
|
||||||
case <-h.doneCh:
|
case <-h.doneCh:
|
||||||
@@ -129,24 +135,5 @@ func healDiskFormat(ctx context.Context, objAPI ObjectLayer, opts madmin.HealOpt
|
|||||||
return madmin.HealResultItem{}, err
|
return madmin.HealResultItem{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Healing succeeded notify the peers to reload format and re-initialize disks.
|
|
||||||
// We will not notify peers if healing is not required.
|
|
||||||
if err == nil {
|
|
||||||
// Notify servers in background and retry if needed.
|
|
||||||
go func() {
|
|
||||||
retry:
|
|
||||||
for _, nerr := range globalNotificationSys.ReloadFormat(opts.DryRun) {
|
|
||||||
if nerr.Err != nil {
|
|
||||||
if nerr.Err.Error() == errServerNotInitialized.Error() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
goto retry
|
|
||||||
}
|
|
||||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
||||||
logger.LogIf(ctx, nerr.Err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,43 +20,45 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMonitorNewDiskInterval = time.Second * 10
|
const (
|
||||||
|
defaultMonitorNewDiskInterval = time.Second * 10
|
||||||
|
healingTrackerFilename = ".healing.bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
|
type healingTracker struct {
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// future add more tracking capabilities
|
||||||
|
}
|
||||||
|
|
||||||
func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||||
z, ok := objAPI.(*erasureZones)
|
z, ok := objAPI.(*erasureServerPools)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
||||||
|
|
||||||
var bgSeq *healSequence
|
bgSeq := mustGetHealSequence(ctx)
|
||||||
var found bool
|
|
||||||
|
|
||||||
for {
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
bgSeq, found = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ep := range getLocalDisksToHeal() {
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(ep)
|
|
||||||
}
|
|
||||||
|
|
||||||
if drivesToHeal := globalBackgroundHealState.healDriveCount(); drivesToHeal > 0 {
|
if drivesToHeal := globalBackgroundHealState.healDriveCount(); drivesToHeal > 0 {
|
||||||
logger.Info(fmt.Sprintf("Found drives to heal %d, waiting until %s to heal the content...",
|
logger.Info(fmt.Sprintf("Found drives to heal %d, waiting until %s to heal the content...",
|
||||||
drivesToHeal, defaultMonitorNewDiskInterval))
|
drivesToHeal, defaultMonitorNewDiskInterval))
|
||||||
|
|
||||||
// Heal any disk format and metadata early, if possible.
|
// Heal any disk format and metadata early, if possible.
|
||||||
if err := bgSeq.healDiskMeta(); err != nil {
|
// Start with format healing
|
||||||
|
if err := bgSeq.healDiskFormat(); err != nil {
|
||||||
if newObjectLayerFn() != nil {
|
if newObjectLayerFn() != nil {
|
||||||
// log only in situations, when object layer
|
// log only in situations, when object layer
|
||||||
// has fully initialized.
|
// has fully initialized.
|
||||||
@@ -65,6 +67,14 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := bgSeq.healDiskMeta(objAPI); 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)
|
go monitorLocalDisksAndHeal(ctx, z, bgSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +86,11 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
|||||||
}
|
}
|
||||||
// Try to connect to the current endpoint
|
// Try to connect to the current endpoint
|
||||||
// and reformat if the current disk is not formatted
|
// and reformat if the current disk is not formatted
|
||||||
_, _, err := connectEndpoint(endpoint)
|
disk, _, err := connectEndpoint(endpoint)
|
||||||
if errors.Is(err, errUnformattedDisk) {
|
if errors.Is(err, errUnformattedDisk) {
|
||||||
disksToHeal = append(disksToHeal, endpoint)
|
disksToHeal = append(disksToHeal, endpoint)
|
||||||
|
} else if err == nil && disk != nil && disk.Healing() {
|
||||||
|
disksToHeal = append(disksToHeal, disk.Endpoint())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,71 +103,123 @@ func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
globalBackgroundHealRoutine = newHealRoutine()
|
globalBackgroundHealRoutine = newHealRoutine()
|
||||||
go globalBackgroundHealRoutine.run(ctx, objAPI)
|
go globalBackgroundHealRoutine.run(ctx, objAPI)
|
||||||
|
|
||||||
globalBackgroundHealState.LaunchNewHealSequence(newBgHealSequence())
|
globalBackgroundHealState.LaunchNewHealSequence(newBgHealSequence(), objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *erasureZones, bgSeq *healSequence) {
|
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq *healSequence) {
|
||||||
// Perform automatic disk healing when a disk is replaced locally.
|
// Perform automatic disk healing when a disk is replaced locally.
|
||||||
|
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
|
||||||
|
defer diskCheckTimer.Stop()
|
||||||
|
wait:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(defaultMonitorNewDiskInterval):
|
case <-diskCheckTimer.C:
|
||||||
waitForLowHTTPReq(int32(globalEndpoints.NEndpoints()), time.Second)
|
// Reset to next interval.
|
||||||
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
|
|
||||||
var erasureSetInZoneEndpointToHeal = make([]map[int]Endpoint, len(z.zones))
|
var erasureSetInZoneDisksToHeal []map[int][]StorageAPI
|
||||||
for i := range z.zones {
|
|
||||||
erasureSetInZoneEndpointToHeal[i] = map[int]Endpoint{}
|
|
||||||
}
|
|
||||||
|
|
||||||
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
||||||
// heal only if new disks found.
|
if len(healDisks) > 0 {
|
||||||
for _, endpoint := range healDisks {
|
|
||||||
logger.Info(fmt.Sprintf("Found drives to heal %d, proceeding to heal content...",
|
|
||||||
len(healDisks)))
|
|
||||||
|
|
||||||
// Reformat disks
|
// Reformat disks
|
||||||
bgSeq.sourceCh <- healSource{bucket: SlashSeparator}
|
bgSeq.sourceCh <- healSource{bucket: SlashSeparator}
|
||||||
|
|
||||||
// Ensure that reformatting disks is finished
|
// Ensure that reformatting disks is finished
|
||||||
bgSeq.sourceCh <- healSource{bucket: nopHeal}
|
bgSeq.sourceCh <- healSource{bucket: nopHeal}
|
||||||
|
|
||||||
// Load the new format of this passed endpoint
|
logger.Info(fmt.Sprintf("Found drives to heal %d, proceeding to heal content...",
|
||||||
_, format, err := connectEndpoint(endpoint)
|
len(healDisks)))
|
||||||
|
|
||||||
|
erasureSetInZoneDisksToHeal = make([]map[int][]StorageAPI, len(z.serverPools))
|
||||||
|
for i := range z.serverPools {
|
||||||
|
erasureSetInZoneDisksToHeal[i] = map[int][]StorageAPI{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugf("disk check timer fired, attempting to heal %d drives\n", len(healDisks))
|
||||||
|
}
|
||||||
|
|
||||||
|
// heal only if new disks found.
|
||||||
|
for _, endpoint := range healDisks {
|
||||||
|
disk, format, err := connectEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printEndpointError(endpoint, err, true)
|
printEndpointError(endpoint, err, true)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneIdx := globalEndpoints.GetLocalZoneIdx(endpoint)
|
zoneIdx := globalEndpoints.GetLocalZoneIdx(disk.Endpoint())
|
||||||
if zoneIdx < 0 {
|
if zoneIdx < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the set index where the current endpoint belongs
|
// Calculate the set index where the current endpoint belongs
|
||||||
setIndex, _, err := findDiskIndex(z.zones[zoneIdx].format, format)
|
z.serverPools[zoneIdx].erasureDisksMu.RLock()
|
||||||
|
// Protect reading reference format.
|
||||||
|
setIndex, _, err := findDiskIndex(z.serverPools[zoneIdx].format, format)
|
||||||
|
z.serverPools[zoneIdx].erasureDisksMu.RUnlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printEndpointError(endpoint, err, false)
|
printEndpointError(endpoint, err, false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
erasureSetInZoneEndpointToHeal[zoneIdx][setIndex] = endpoint
|
erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, setMap := range erasureSetInZoneEndpointToHeal {
|
buckets, _ := z.ListBuckets(ctx)
|
||||||
for setIndex, endpoint := range setMap {
|
|
||||||
logger.Info("Healing disk '%s' on %s zone", endpoint, humanize.Ordinal(i+1))
|
|
||||||
|
|
||||||
if err := healErasureSet(ctx, setIndex, z.zones[i].sets[setIndex], z.zones[i].setDriveCount); err != nil {
|
// Heal latest buckets first.
|
||||||
logger.LogIf(ctx, err)
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
continue
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, setMap := range erasureSetInZoneDisksToHeal {
|
||||||
|
for setIndex, disks := range setMap {
|
||||||
|
for _, disk := range disks {
|
||||||
|
logger.Info("Healing disk '%s' on %s zone", disk, humanize.Ordinal(i+1))
|
||||||
|
|
||||||
|
// So someone changed the drives underneath, healing tracker missing.
|
||||||
|
if !disk.Healing() {
|
||||||
|
logger.Info("Healing tracker missing on '%s', disk was swapped again on %s zone", disk, humanize.Ordinal(i+1))
|
||||||
|
diskID, err := disk.GetDiskID()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
// reading format.json failed or not found, proceed to look
|
||||||
|
// for new disks to be healed again, we cannot proceed further.
|
||||||
|
goto wait
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := saveHealingTracker(disk, diskID); 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.
|
||||||
|
goto wait
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lbDisks := z.serverPools[i].sets[setIndex].getOnlineDisks()
|
||||||
|
if err := healErasureSet(ctx, setIndex, buckets, lbDisks); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Healing disk '%s' on %s zone complete", disk, humanize.Ordinal(i+1))
|
||||||
|
|
||||||
|
if err := disk.Delete(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix),
|
||||||
|
healingTrackerFilename, false); err != nil && !errors.Is(err, errFileNotFound) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only upon success pop the healed disk.
|
||||||
|
globalBackgroundHealState.popHealLocalDisks(disk.Endpoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only upon success pop the healed disk.
|
|
||||||
globalBackgroundHealState.popHealLocalDisks(endpoint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *healingTracker) 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 "ID":
|
||||||
|
z.ID, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 1
|
||||||
|
// write "ID"
|
||||||
|
err = en.Append(0x81, 0xa2, 0x49, 0x44)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.ID)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 1
|
||||||
|
// string "ID"
|
||||||
|
o = append(o, 0x81, 0xa2, 0x49, 0x44)
|
||||||
|
o = msgp.AppendString(o, z.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *healingTracker) 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 "ID":
|
||||||
|
z.ID, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ID")
|
||||||
|
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 healingTracker) Msgsize() (s int) {
|
||||||
|
s = 1 + 3 + msgp.StringPrefixSize + len(z.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalhealingTracker(t *testing.T) {
|
||||||
|
v := healingTracker{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsghealingTracker(b *testing.B) {
|
||||||
|
v := healingTracker{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsghealingTracker(b *testing.B) {
|
||||||
|
v := healingTracker{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalhealingTracker(b *testing.B) {
|
||||||
|
v := healingTracker{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodehealingTracker(t *testing.T) {
|
||||||
|
v := healingTracker{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodehealingTracker Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := healingTracker{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodehealingTracker(b *testing.B) {
|
||||||
|
v := healingTracker{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodehealingTracker(b *testing.B) {
|
||||||
|
v := healingTracker{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,8 +140,8 @@ 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) {
|
||||||
err := &errHashMismatch{fmt.Sprintf("Disk: %s - content hash does not match - expected %s, got %s",
|
err := &errHashMismatch{fmt.Sprintf("Disk: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
||||||
b.disk, 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)))}
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -71,12 +71,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 returned %w", b.disk, err))
|
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %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, errLessData)
|
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %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)
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -54,7 +52,7 @@ type bootstrapRESTServer struct{}
|
|||||||
type ServerSystemConfig struct {
|
type ServerSystemConfig struct {
|
||||||
MinioPlatform string
|
MinioPlatform string
|
||||||
MinioRuntime string
|
MinioRuntime string
|
||||||
MinioEndpoints EndpointZones
|
MinioEndpoints EndpointServerPools
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff - returns error on first difference found in two configs.
|
// Diff - returns error on first difference found in two configs.
|
||||||
@@ -161,9 +159,9 @@ func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg ServerSyst
|
|||||||
return srcCfg.Diff(recvCfg)
|
return srcCfg.Diff(recvCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyServerSystemConfig(ctx context.Context, endpointZones EndpointZones) error {
|
func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools) error {
|
||||||
srcCfg := getServerSystemCfg()
|
srcCfg := getServerSystemCfg()
|
||||||
clnts := newBootstrapRESTClients(endpointZones)
|
clnts := newBootstrapRESTClients(endpointServerPools)
|
||||||
var onlineServers int
|
var onlineServers int
|
||||||
var offlineEndpoints []string
|
var offlineEndpoints []string
|
||||||
var retries int
|
var retries int
|
||||||
@@ -188,7 +186,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointZones EndpointZones)
|
|||||||
retries++
|
retries++
|
||||||
// after 5 retries start logging that servers are not reachable yet
|
// after 5 retries start logging that servers are not reachable yet
|
||||||
if retries >= 5 {
|
if retries >= 5 {
|
||||||
logger.Info(fmt.Sprintf("Waiting for atleast %d servers to be online for bootstrap check", len(clnts)/2))
|
logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers to be online for bootstrap check", len(clnts)/2))
|
||||||
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
|
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
|
||||||
retries = 0 // reset to log again after 5 retries.
|
retries = 0 // reset to log again after 5 retries.
|
||||||
}
|
}
|
||||||
@@ -198,10 +196,10 @@ func verifyServerSystemConfig(ctx context.Context, endpointZones EndpointZones)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBootstrapRESTClients(endpointZones EndpointZones) []*bootstrapRESTClient {
|
func newBootstrapRESTClients(endpointServerPools EndpointServerPools) []*bootstrapRESTClient {
|
||||||
seenHosts := set.NewStringSet()
|
seenHosts := set.NewStringSet()
|
||||||
var clnts []*bootstrapRESTClient
|
var clnts []*bootstrapRESTClient
|
||||||
for _, ep := range endpointZones {
|
for _, ep := range endpointServerPools {
|
||||||
for _, endpoint := range ep.Endpoints {
|
for _, endpoint := range ep.Endpoints {
|
||||||
if seenHosts.Contains(endpoint.Host) {
|
if seenHosts.Contains(endpoint.Host) {
|
||||||
continue
|
continue
|
||||||
@@ -225,26 +223,8 @@ func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
|
|||||||
Path: bootstrapRESTPath,
|
Path: bootstrapRESTPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
|
||||||
if globalIsSSL {
|
restClient.HealthCheckFn = nil
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
ServerName: endpoint.Hostname(),
|
|
||||||
RootCAs: globalRootCAs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
|
||||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
|
||||||
restClient.HealthCheckFn = func() bool {
|
|
||||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
|
||||||
// Instantiate a new rest client for healthcheck
|
|
||||||
// to avoid recursive healthCheckFn()
|
|
||||||
respBody, err := rest.NewClient(serverURL, trFn, newAuthToken).Call(ctx, bootstrapRESTMethodHealth, nil, nil, -1)
|
|
||||||
xhttp.DrainBody(respBody)
|
|
||||||
cancel()
|
|
||||||
var ne *rest.NetworkError
|
|
||||||
return !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &ne)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
|
return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func NewBucketSSEConfigSys() *BucketSSEConfigSys {
|
|||||||
// Get - gets bucket encryption config for the given bucket.
|
// Get - gets bucket encryption config for the given bucket.
|
||||||
func (sys *BucketSSEConfigSys) Get(bucket string) (*bucketsse.BucketSSEConfig, error) {
|
func (sys *BucketSSEConfigSys) Get(bucket string) (*bucketsse.BucketSSEConfig, error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-17
@@ -28,6 +28,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -37,6 +38,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/bucket/replication"
|
"github.com/minio/minio/pkg/bucket/replication"
|
||||||
@@ -383,6 +385,10 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call checkRequestAuthType to populate ReqInfo.AccessKey before GetBucketInfo()
|
||||||
|
// Ignore errors here to preserve the S3 error behavior of GetBucketInfo()
|
||||||
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -400,7 +406,18 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
if api.CacheAPI() != nil {
|
if api.CacheAPI() != nil {
|
||||||
getObjectInfoFn = api.CacheAPI().GetObjectInfo
|
getObjectInfoFn = api.CacheAPI().GetObjectInfo
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
hasLockEnabled, hasLifecycleConfig bool
|
||||||
|
goi ObjectInfo
|
||||||
|
gerr error
|
||||||
|
)
|
||||||
|
replicateDeletes := hasReplicationRules(ctx, bucket, deleteObjects.Objects)
|
||||||
|
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled {
|
||||||
|
hasLockEnabled = true
|
||||||
|
}
|
||||||
|
if _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket); err == nil {
|
||||||
|
hasLifecycleConfig = true
|
||||||
|
}
|
||||||
dErrs := make([]DeleteError, len(deleteObjects.Objects))
|
dErrs := make([]DeleteError, len(deleteObjects.Objects))
|
||||||
for index, object := range deleteObjects.Objects {
|
for index, object := range deleteObjects.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 {
|
||||||
@@ -417,10 +434,47 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if object.VersionID != "" && object.VersionID != nullVersionID {
|
||||||
|
if _, err := uuid.Parse(object.VersionID); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err))
|
||||||
|
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
|
||||||
|
dErrs[index] = DeleteError{
|
||||||
|
Code: apiErr.Code,
|
||||||
|
Message: apiErr.Description,
|
||||||
|
Key: object.ObjectName,
|
||||||
|
VersionID: object.VersionID,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if replicateDeletes || hasLockEnabled || hasLifecycleConfig {
|
||||||
|
goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{
|
||||||
|
VersionID: object.VersionID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if hasLifecycleConfig && gerr == nil {
|
||||||
|
object.PurgeTransitioned = goi.TransitionStatus
|
||||||
|
}
|
||||||
|
if replicateDeletes {
|
||||||
|
delMarker, replicate := checkReplicateDelete(ctx, bucket, ObjectToDelete{
|
||||||
|
ObjectName: object.ObjectName,
|
||||||
|
VersionID: object.VersionID,
|
||||||
|
}, goi, gerr)
|
||||||
|
if replicate {
|
||||||
|
if object.VersionID != "" {
|
||||||
|
object.VersionPurgeStatus = Pending
|
||||||
|
if delMarker {
|
||||||
|
object.DeleteMarkerVersionID = object.VersionID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
object.DeleteMarkerReplicationStatus = string(replication.Pending)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if object.VersionID != "" {
|
if object.VersionID != "" {
|
||||||
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled {
|
if hasLockEnabled {
|
||||||
if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, getObjectInfoFn); apiErrCode != ErrNone {
|
if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); apiErrCode != ErrNone {
|
||||||
apiErr := errorCodes.ToAPIErr(apiErrCode)
|
apiErr := errorCodes.ToAPIErr(apiErrCode)
|
||||||
dErrs[index] = DeleteError{
|
dErrs[index] = DeleteError{
|
||||||
Code: apiErr.Code,
|
Code: apiErr.Code,
|
||||||
@@ -454,15 +508,25 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
||||||
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
||||||
})
|
})
|
||||||
|
|
||||||
deletedObjects := make([]DeletedObject, len(deleteObjects.Objects))
|
deletedObjects := make([]DeletedObject, len(deleteObjects.Objects))
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
dindex := objectsToDelete[deleteList[i]]
|
dindex := objectsToDelete[ObjectToDelete{
|
||||||
apiErr := toAPIError(ctx, errs[i])
|
ObjectName: dObjects[i].ObjectName,
|
||||||
if apiErr.Code == "" || apiErr.Code == "NoSuchKey" || apiErr.Code == "InvalidArgument" {
|
VersionID: dObjects[i].VersionID,
|
||||||
|
DeleteMarkerVersionID: dObjects[i].DeleteMarkerVersionID,
|
||||||
|
VersionPurgeStatus: dObjects[i].VersionPurgeStatus,
|
||||||
|
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus,
|
||||||
|
PurgeTransitioned: dObjects[i].PurgeTransitioned,
|
||||||
|
}]
|
||||||
|
if errs[i] == nil || isErrObjectNotFound(errs[i]) || isErrVersionNotFound(errs[i]) {
|
||||||
|
if replicateDeletes {
|
||||||
|
dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus
|
||||||
|
dObjects[i].VersionPurgeStatus = deleteList[i].VersionPurgeStatus
|
||||||
|
}
|
||||||
deletedObjects[dindex] = dObjects[i]
|
deletedObjects[dindex] = dObjects[i]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
apiErr := toAPIError(ctx, errs[i])
|
||||||
dErrs[dindex] = DeleteError{
|
dErrs[dindex] = DeleteError{
|
||||||
Code: apiErr.Code,
|
Code: apiErr.Code,
|
||||||
Message: apiErr.Description,
|
Message: apiErr.Description,
|
||||||
@@ -484,22 +548,46 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodedSuccessResponse)
|
writeSuccessResponseXML(w, encodedSuccessResponse)
|
||||||
|
for _, dobj := range deletedObjects {
|
||||||
|
if replicateDeletes {
|
||||||
|
if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending {
|
||||||
|
globalReplicationState.queueReplicaDeleteTask(DeletedObjectVersionInfo{
|
||||||
|
DeletedObject: dobj,
|
||||||
|
Bucket: bucket,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasLifecycleConfig && dobj.PurgeTransitioned == lifecycle.TransitionComplete { // clean up transitioned tier
|
||||||
|
action := lifecycle.DeleteAction
|
||||||
|
if dobj.VersionID != "" {
|
||||||
|
action = lifecycle.DeleteVersionAction
|
||||||
|
}
|
||||||
|
deleteTransitionedObject(ctx, newObjectLayerFn(), bucket, dobj.ObjectName, lifecycle.ObjectOpts{
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
VersionID: dobj.VersionID,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
}, action, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
// Notify deleted event for objects.
|
// Notify deleted event for objects.
|
||||||
for _, dobj := range deletedObjects {
|
for _, dobj := range deletedObjects {
|
||||||
|
eventName := event.ObjectRemovedDelete
|
||||||
|
|
||||||
objInfo := ObjectInfo{
|
objInfo := ObjectInfo{
|
||||||
Name: dobj.ObjectName,
|
Name: dobj.ObjectName,
|
||||||
VersionID: dobj.VersionID,
|
VersionID: dobj.VersionID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if dobj.DeleteMarker {
|
if dobj.DeleteMarker {
|
||||||
objInfo = ObjectInfo{
|
objInfo.DeleteMarker = dobj.DeleteMarker
|
||||||
Name: dobj.ObjectName,
|
objInfo.VersionID = dobj.DeleteMarkerVersionID
|
||||||
DeleteMarker: dobj.DeleteMarker,
|
eventName = event.ObjectRemovedDeleteMarkerCreated
|
||||||
VersionID: dobj.DeleteMarkerVersionID,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectRemovedDelete,
|
EventName: eventName,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: objInfo,
|
||||||
ReqParams: extractReqParams(r),
|
ReqParams: extractReqParams(r),
|
||||||
@@ -660,7 +748,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) {
|
if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -807,7 +895,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption {
|
if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption {
|
||||||
// This request header needs to be set prior to setting ObjectOptions
|
// This request header needs to be set prior to setting ObjectOptions
|
||||||
if !crypto.SSEC.IsRequested(r.Header) {
|
if !crypto.SSEC.IsRequested(r.Header) {
|
||||||
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,7 +907,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if crypto.IsRequested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
if _, ok := crypto.IsRequested(formValues); ok && !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, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@@ -1289,7 +1377,6 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(bucket, bucketReplicationConfig, configData); err != nil {
|
if err = globalBucketMetadataSys.Update(bucket, bucketReplicationConfig, configData); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -837,7 +836,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
|
|
||||||
// Verify whether the bucket obtained object is same as the one created.
|
// Verify whether the bucket obtained object is same as the one created.
|
||||||
if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
|
if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
|
||||||
fmt.Println(string(testCase.expectedContent), string(actualContent))
|
t.Log(string(testCase.expectedContent), string(actualContent))
|
||||||
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType)
|
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the transition storage ARNs
|
||||||
|
if err = validateLifecycleTransition(ctx, bucket, bucketLifecycle); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
configData, err := xml.Marshal(bucketLifecycle)
|
configData, err := xml.Marshal(bucketLifecycle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
|||||||
+607
-1
@@ -17,7 +17,25 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
sse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
|
"github.com/minio/minio/pkg/event"
|
||||||
|
"github.com/minio/minio/pkg/hash"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
"github.com/minio/minio/pkg/s3select"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -31,7 +49,7 @@ type LifecycleSys struct{}
|
|||||||
// Get - gets lifecycle config associated to a given bucket name.
|
// Get - gets lifecycle config associated to a given bucket name.
|
||||||
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
|
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -46,3 +64,591 @@ func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err er
|
|||||||
func NewLifecycleSys() *LifecycleSys {
|
func NewLifecycleSys() *LifecycleSys {
|
||||||
return &LifecycleSys{}
|
return &LifecycleSys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type transitionState struct {
|
||||||
|
// add future metrics here
|
||||||
|
transitionCh chan ObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transitionState) queueTransitionTask(oi ObjectInfo) {
|
||||||
|
select {
|
||||||
|
case t.transitionCh <- oi:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalTransitionState *transitionState
|
||||||
|
globalTransitionConcurrent = runtime.GOMAXPROCS(0) / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTransitionState() *transitionState {
|
||||||
|
|
||||||
|
// fix minimum concurrent transition to 1 for single CPU setup
|
||||||
|
if globalTransitionConcurrent == 0 {
|
||||||
|
globalTransitionConcurrent = 1
|
||||||
|
}
|
||||||
|
ts := &transitionState{
|
||||||
|
transitionCh: make(chan ObjectInfo, 10000),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-GlobalContext.Done()
|
||||||
|
close(ts.transitionCh)
|
||||||
|
}()
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// addWorker creates a new worker to process tasks
|
||||||
|
func (t *transitionState) addWorker(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
|
// Add a new worker.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case oi, ok := <-t.transitionCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := transitionObject(ctx, objectAPI, oi); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initBackgroundTransition(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
|
if globalTransitionState == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with globalTransitionConcurrent.
|
||||||
|
for i := 0; i < globalTransitionConcurrent; i++ {
|
||||||
|
globalTransitionState.addWorker(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLifecycleTransition(ctx context.Context, bucket string, lfc *lifecycle.Lifecycle) error {
|
||||||
|
for _, rule := range lfc.Rules {
|
||||||
|
if rule.Transition.StorageClass != "" {
|
||||||
|
sameTarget, destbucket, err := validateTransitionDestination(ctx, bucket, rule.Transition.StorageClass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sameTarget && destbucket == bucket {
|
||||||
|
return fmt.Errorf("Transition destination cannot be the same as the source bucket")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateTransitionDestination returns error if transition destination bucket missing or not configured
|
||||||
|
// It also returns true if transition destination is same as this server.
|
||||||
|
func validateTransitionDestination(ctx context.Context, bucket string, targetLabel string) (bool, string, error) {
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetWithLabel(ctx, bucket, targetLabel)
|
||||||
|
if tgt == nil {
|
||||||
|
return false, "", BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
arn, err := madmin.ParseARN(tgt.Arn)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
if arn.Type != madmin.ILMService {
|
||||||
|
return false, "", BucketRemoteArnTypeInvalid{}
|
||||||
|
}
|
||||||
|
clnt := globalBucketTargetSys.GetRemoteTargetClient(ctx, tgt.Arn)
|
||||||
|
if clnt == nil {
|
||||||
|
return false, "", BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
if found, _ := clnt.BucketExists(ctx, arn.Bucket); !found {
|
||||||
|
return false, "", BucketRemoteDestinationNotFound{Bucket: arn.Bucket}
|
||||||
|
}
|
||||||
|
sameTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort)
|
||||||
|
return sameTarget, arn.Bucket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// transitionSC returns storage class label for this bucket
|
||||||
|
func transitionSC(ctx context.Context, bucket string) string {
|
||||||
|
cfg, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, rule := range cfg.Rules {
|
||||||
|
if rule.Status == Disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rule.Transition.StorageClass != "" {
|
||||||
|
return rule.Transition.StorageClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if ARN representing transition storage class is present in a active rule
|
||||||
|
// for the lifecycle configured on this bucket
|
||||||
|
func transitionSCInUse(ctx context.Context, lfc *lifecycle.Lifecycle, bucket, arnStr string) bool {
|
||||||
|
tgtLabel := globalBucketTargetSys.GetRemoteLabelWithArn(ctx, bucket, arnStr)
|
||||||
|
if tgtLabel == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, rule := range lfc.Rules {
|
||||||
|
if rule.Status == Disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rule.Transition.StorageClass != "" && rule.Transition.StorageClass == tgtLabel {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// set PutObjectOptions for PUT operation to transition data to target cluster
|
||||||
|
func putTransitionOpts(objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
|
||||||
|
meta := make(map[string]string)
|
||||||
|
|
||||||
|
tag, err := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
putOpts = miniogo.PutObjectOptions{
|
||||||
|
UserMetadata: meta,
|
||||||
|
UserTags: tag.ToMap(),
|
||||||
|
ContentType: objInfo.ContentType,
|
||||||
|
ContentEncoding: objInfo.ContentEncoding,
|
||||||
|
StorageClass: objInfo.StorageClass,
|
||||||
|
Internal: miniogo.AdvancedPutOptions{
|
||||||
|
SourceVersionID: objInfo.VersionID,
|
||||||
|
SourceMTime: objInfo.ModTime,
|
||||||
|
SourceETag: objInfo.ETag,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
|
||||||
|
rmode := miniogo.RetentionMode(mode)
|
||||||
|
putOpts.Mode = rmode
|
||||||
|
}
|
||||||
|
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
||||||
|
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
putOpts.RetainUntilDate = rdate
|
||||||
|
}
|
||||||
|
if lhold, ok := objInfo.UserDefined[xhttp.AmzObjectLockLegalHold]; ok {
|
||||||
|
putOpts.LegalHold = miniogo.LegalHoldStatus(lhold)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle deletes of transitioned objects or object versions when one of the following is true:
|
||||||
|
// 1. temporarily restored copies of objects (restored with the PostRestoreObject API) expired.
|
||||||
|
// 2. life cycle expiry date is met on the object.
|
||||||
|
// 3. Object is removed through DELETE api call
|
||||||
|
func deleteTransitionedObject(ctx context.Context, objectAPI ObjectLayer, bucket, object string, lcOpts lifecycle.ObjectOpts, action lifecycle.Action, isDeleteTierOnly bool) error {
|
||||||
|
if lcOpts.TransitionStatus == "" && !isDeleteTierOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lc, err := globalLifecycleSys.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
arn := getLifecycleTransitionTargetArn(ctx, lc, bucket, lcOpts)
|
||||||
|
if arn == nil {
|
||||||
|
return fmt.Errorf("remote target not configured")
|
||||||
|
}
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, arn.String())
|
||||||
|
if tgt == nil {
|
||||||
|
return fmt.Errorf("remote target not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts ObjectOptions
|
||||||
|
opts.Versioned = globalBucketVersioningSys.Enabled(bucket)
|
||||||
|
opts.VersionID = lcOpts.VersionID
|
||||||
|
switch action {
|
||||||
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
|
// delete locally restored copy of object or object version
|
||||||
|
// from the source, while leaving metadata behind. The data on
|
||||||
|
// transitioned tier lies untouched and still accessible
|
||||||
|
opts.TransitionStatus = lcOpts.TransitionStatus
|
||||||
|
_, err = objectAPI.DeleteObject(ctx, bucket, object, opts)
|
||||||
|
return err
|
||||||
|
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
||||||
|
// When an object is past expiry, delete the data from transitioned tier and
|
||||||
|
// metadata from source
|
||||||
|
if err := tgt.RemoveObject(context.Background(), arn.Bucket, object, miniogo.RemoveObjectOptions{VersionID: lcOpts.VersionID}); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDeleteTierOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = objectAPI.DeleteObject(ctx, bucket, object, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
eventName := event.ObjectRemovedDelete
|
||||||
|
if lcOpts.DeleteMarker {
|
||||||
|
eventName = event.ObjectRemovedDeleteMarkerCreated
|
||||||
|
}
|
||||||
|
objInfo := ObjectInfo{
|
||||||
|
Name: object,
|
||||||
|
VersionID: lcOpts.VersionID,
|
||||||
|
DeleteMarker: lcOpts.DeleteMarker,
|
||||||
|
}
|
||||||
|
// Notify object deleted event.
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: eventName,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [ILM-EXPIRY]",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never reach here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// transition object to target specified by the transition ARN. When an object is transitioned to another
|
||||||
|
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
|
||||||
|
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
||||||
|
// to the transition tier without decrypting or re-encrypting.
|
||||||
|
func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo ObjectInfo) error {
|
||||||
|
lc, err := globalLifecycleSys.Get(objInfo.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lcOpts := lifecycle.ObjectOpts{
|
||||||
|
Name: objInfo.Name,
|
||||||
|
UserTags: objInfo.UserTags,
|
||||||
|
}
|
||||||
|
arn := getLifecycleTransitionTargetArn(ctx, lc, objInfo.Bucket, lcOpts)
|
||||||
|
if arn == nil {
|
||||||
|
return fmt.Errorf("remote target not configured")
|
||||||
|
}
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, arn.String())
|
||||||
|
if tgt == nil {
|
||||||
|
return fmt.Errorf("remote target not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
gr, err := objectAPI.GetObjectNInfo(ctx, objInfo.Bucket, objInfo.Name, nil, http.Header{}, readLock, ObjectOptions{
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
TransitionStatus: lifecycle.TransitionPending,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oi := gr.ObjInfo
|
||||||
|
|
||||||
|
if oi.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
putOpts := putTransitionOpts(oi)
|
||||||
|
if _, err = tgt.PutObject(ctx, arn.Bucket, oi.Name, gr, oi.Size, "", "", putOpts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gr.Close()
|
||||||
|
|
||||||
|
var opts ObjectOptions
|
||||||
|
opts.Versioned = globalBucketVersioningSys.Enabled(oi.Bucket)
|
||||||
|
opts.VersionID = oi.VersionID
|
||||||
|
opts.TransitionStatus = lifecycle.TransitionComplete
|
||||||
|
eventName := event.ObjectTransitionComplete
|
||||||
|
|
||||||
|
_, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
|
if err != nil {
|
||||||
|
eventName = event.ObjectTransitionFailed
|
||||||
|
}
|
||||||
|
// Notify object deleted event.
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: eventName,
|
||||||
|
BucketName: oi.Bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Name: oi.Name,
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
},
|
||||||
|
Host: "Internal: [ILM-Transition]",
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLifecycleTransitionTargetArn returns transition ARN for storage class specified in the config.
|
||||||
|
func getLifecycleTransitionTargetArn(ctx context.Context, lc *lifecycle.Lifecycle, bucket string, obj lifecycle.ObjectOpts) *madmin.ARN {
|
||||||
|
for _, rule := range lc.FilterActionableRules(obj) {
|
||||||
|
if rule.Transition.StorageClass != "" {
|
||||||
|
return globalBucketTargetSys.GetRemoteArnWithLabel(ctx, bucket, rule.Transition.StorageClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var lc *lifecycle.Lifecycle
|
||||||
|
lc, err = globalLifecycleSys.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arn := getLifecycleTransitionTargetArn(ctx, lc, bucket, lifecycle.ObjectOpts{
|
||||||
|
Name: object,
|
||||||
|
UserTags: oi.UserTags,
|
||||||
|
ModTime: oi.ModTime,
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
IsLatest: oi.IsLatest,
|
||||||
|
})
|
||||||
|
if arn == nil {
|
||||||
|
return nil, fmt.Errorf("remote target not configured")
|
||||||
|
}
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, arn.String())
|
||||||
|
if tgt == nil {
|
||||||
|
return nil, fmt.Errorf("remote target not configured")
|
||||||
|
}
|
||||||
|
fn, off, length, err := NewGetObjectReader(rs, oi, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrorRespToObjectError(err, bucket, object)
|
||||||
|
}
|
||||||
|
gopts := miniogo.GetObjectOptions{VersionID: opts.VersionID}
|
||||||
|
|
||||||
|
// get correct offsets for encrypted object
|
||||||
|
if off >= 0 && length >= 0 {
|
||||||
|
if err := gopts.SetRange(off, off+length-1); err != nil {
|
||||||
|
return nil, ErrorRespToObjectError(err, bucket, object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, _, _, err := tgt.GetObject(ctx, arn.Bucket, object, gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
closeReader := func() { reader.Close() }
|
||||||
|
|
||||||
|
return fn(reader, h, opts.CheckPrecondFn, closeReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreRequestType represents type of restore.
|
||||||
|
type RestoreRequestType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SelectRestoreRequest specifies select request. This is the only valid value
|
||||||
|
SelectRestoreRequest RestoreRequestType = "SELECT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encryption specifies encryption setting on restored bucket
|
||||||
|
type Encryption struct {
|
||||||
|
EncryptionType sse.SSEAlgorithm `xml:"EncryptionType"`
|
||||||
|
KMSContext string `xml:"KMSContext,omitempty"`
|
||||||
|
KMSKeyID string `xml:"KMSKeyId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataEntry denotes name and value.
|
||||||
|
type MetadataEntry struct {
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
Value string `xml:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// S3Location specifies s3 location that receives result of a restore object request
|
||||||
|
type S3Location struct {
|
||||||
|
BucketName string `xml:"BucketName,omitempty"`
|
||||||
|
Encryption Encryption `xml:"Encryption,omitempty"`
|
||||||
|
Prefix string `xml:"Prefix,omitempty"`
|
||||||
|
StorageClass string `xml:"StorageClass,omitempty"`
|
||||||
|
Tagging *tags.Tags `xml:"Tagging,omitempty"`
|
||||||
|
UserMetadata []MetadataEntry `xml:"UserMetadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputLocation specifies bucket where object needs to be restored
|
||||||
|
type OutputLocation struct {
|
||||||
|
S3 S3Location `xml:"S3,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if output location not specified.
|
||||||
|
func (o *OutputLocation) IsEmpty() bool {
|
||||||
|
return o.S3.BucketName == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectParameters specifies sql select parameters
|
||||||
|
type SelectParameters struct {
|
||||||
|
s3select.S3Select
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if no select parameters set
|
||||||
|
func (sp *SelectParameters) IsEmpty() bool {
|
||||||
|
return sp == nil || sp.S3Select == s3select.S3Select{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
selectParamsXMLName = "SelectParameters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalXML - decodes XML data.
|
||||||
|
func (sp *SelectParameters) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
// Essentially the same as S3Select barring the xml name.
|
||||||
|
if start.Name.Local == selectParamsXMLName {
|
||||||
|
start.Name = xml.Name{Space: "", Local: "SelectRequest"}
|
||||||
|
}
|
||||||
|
return sp.S3Select.UnmarshalXML(d, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreObjectRequest - xml to restore a transitioned object
|
||||||
|
type RestoreObjectRequest struct {
|
||||||
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest" json:"-"`
|
||||||
|
Days int `xml:"Days,omitempty"`
|
||||||
|
Type RestoreRequestType `xml:"Type,omitempty"`
|
||||||
|
Tier string `xml:"Tier,-"`
|
||||||
|
Description string `xml:"Description,omitempty"`
|
||||||
|
SelectParameters *SelectParameters `xml:"SelectParameters,omitempty"`
|
||||||
|
OutputLocation OutputLocation `xml:"OutputLocation,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum 2MiB size per restore object request.
|
||||||
|
const maxRestoreObjectRequestSize = 2 << 20
|
||||||
|
|
||||||
|
// parseRestoreRequest parses RestoreObjectRequest from xml
|
||||||
|
func parseRestoreRequest(reader io.Reader) (*RestoreObjectRequest, error) {
|
||||||
|
req := RestoreObjectRequest{}
|
||||||
|
if err := xml.NewDecoder(io.LimitReader(reader, maxRestoreObjectRequestSize)).Decode(&req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate a RestoreObjectRequest as per AWS S3 spec https://docs.aws.amazon.com/AmazonS3/latest/API/API_RestoreObject.html
|
||||||
|
func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer) error {
|
||||||
|
if r.Type != SelectRestoreRequest && !r.SelectParameters.IsEmpty() {
|
||||||
|
return fmt.Errorf("Select parameters can only be specified with SELECT request type")
|
||||||
|
}
|
||||||
|
if r.Type == SelectRestoreRequest && r.SelectParameters.IsEmpty() {
|
||||||
|
return fmt.Errorf("SELECT restore request requires select parameters to be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Type != SelectRestoreRequest && !r.OutputLocation.IsEmpty() {
|
||||||
|
return fmt.Errorf("OutputLocation required only for SELECT request type")
|
||||||
|
}
|
||||||
|
if r.Type == SelectRestoreRequest && r.OutputLocation.IsEmpty() {
|
||||||
|
return fmt.Errorf("OutputLocation required for SELECT requests")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Days != 0 && r.Type == SelectRestoreRequest {
|
||||||
|
return fmt.Errorf("Days cannot be specified with SELECT restore request")
|
||||||
|
}
|
||||||
|
if r.Days == 0 && r.Type != SelectRestoreRequest {
|
||||||
|
return fmt.Errorf("restoration days should be at least 1")
|
||||||
|
}
|
||||||
|
// Check if bucket exists.
|
||||||
|
if !r.OutputLocation.IsEmpty() {
|
||||||
|
if _, err := objAPI.GetBucketInfo(ctx, r.OutputLocation.S3.BucketName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.OutputLocation.S3.Prefix == "" {
|
||||||
|
return fmt.Errorf("Prefix is a required parameter in OutputLocation")
|
||||||
|
}
|
||||||
|
if r.OutputLocation.S3.Encryption.EncryptionType != xhttp.AmzEncryptionAES {
|
||||||
|
return NotImplemented{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// set ObjectOptions for PUT call to restore temporary copy of transitioned data
|
||||||
|
func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo ObjectInfo) (putOpts ObjectOptions) {
|
||||||
|
meta := make(map[string]string)
|
||||||
|
sc := rreq.OutputLocation.S3.StorageClass
|
||||||
|
if sc == "" {
|
||||||
|
sc = objInfo.StorageClass
|
||||||
|
}
|
||||||
|
meta[strings.ToLower(xhttp.AmzStorageClass)] = sc
|
||||||
|
|
||||||
|
if rreq.Type == SelectRestoreRequest {
|
||||||
|
for _, v := range rreq.OutputLocation.S3.UserMetadata {
|
||||||
|
if !strings.HasPrefix("x-amz-meta", strings.ToLower(v.Name)) {
|
||||||
|
meta["x-amz-meta-"+v.Name] = v.Value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
meta[v.Name] = v.Value
|
||||||
|
}
|
||||||
|
meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String()
|
||||||
|
if rreq.OutputLocation.S3.Encryption.EncryptionType != "" {
|
||||||
|
meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
||||||
|
}
|
||||||
|
return ObjectOptions{
|
||||||
|
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
||||||
|
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
||||||
|
UserDefined: meta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range objInfo.UserDefined {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
meta[xhttp.AmzObjectTagging] = objInfo.UserTags
|
||||||
|
|
||||||
|
return ObjectOptions{
|
||||||
|
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
||||||
|
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
||||||
|
UserDefined: meta,
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
MTime: objInfo.ModTime,
|
||||||
|
Expires: objInfo.Expires,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errRestoreHDRMissing = fmt.Errorf("x-amz-restore header not found")
|
||||||
|
errRestoreHDRMalformed = fmt.Errorf("x-amz-restore header malformed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// parse x-amz-restore header from user metadata to get the status of ongoing request and expiry of restoration
|
||||||
|
// if any. This header value is of format: ongoing-request=true|false, expires=time
|
||||||
|
func parseRestoreHeaderFromMeta(meta map[string]string) (ongoing bool, expiry time.Time, err error) {
|
||||||
|
restoreHdr, ok := meta[xhttp.AmzRestore]
|
||||||
|
if !ok {
|
||||||
|
return ongoing, expiry, errRestoreHDRMissing
|
||||||
|
}
|
||||||
|
rslc := strings.SplitN(restoreHdr, ",", 2)
|
||||||
|
if len(rslc) != 2 {
|
||||||
|
return ongoing, expiry, errRestoreHDRMalformed
|
||||||
|
}
|
||||||
|
rstatusSlc := strings.SplitN(rslc[0], "=", 2)
|
||||||
|
if len(rstatusSlc) != 2 {
|
||||||
|
return ongoing, expiry, errRestoreHDRMalformed
|
||||||
|
}
|
||||||
|
rExpSlc := strings.SplitN(rslc[1], "=", 2)
|
||||||
|
if len(rExpSlc) != 2 {
|
||||||
|
return ongoing, expiry, errRestoreHDRMalformed
|
||||||
|
}
|
||||||
|
|
||||||
|
expiry, err = time.Parse(http.TimeFormat, rExpSlc[1])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return rstatusSlc[1] == "true", expiry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreTransitionedObject is similar to PostObjectRestore from AWS GLACIER
|
||||||
|
// storage class. When PostObjectRestore API is called, a temporary copy of the object
|
||||||
|
// is restored locally to the bucket on source cluster until the restore expiry date.
|
||||||
|
// The copy that was transitioned continues to reside in the transitioned tier.
|
||||||
|
func restoreTransitionedObject(ctx context.Context, bucket, object string, objAPI ObjectLayer, objInfo ObjectInfo, rreq *RestoreObjectRequest, restoreExpiry time.Time) error {
|
||||||
|
var rs *HTTPRangeSpec
|
||||||
|
gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, http.Header{}, objInfo, ObjectOptions{
|
||||||
|
VersionID: objInfo.VersionID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gr.Close()
|
||||||
|
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size, globalCLIContext.StrictS3Compat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pReader := NewPutObjReader(hashReader, nil, nil)
|
||||||
|
opts := putRestoreOpts(bucket, object, rreq, objInfo)
|
||||||
|
opts.UserDefined[xhttp.AmzRestore] = fmt.Sprintf("ongoing-request=%t, expiry-date=%s", false, restoreExpiry.Format(http.TimeFormat))
|
||||||
|
if _, err := objAPI.PutObject(ctx, bucket, object, pReader, opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,6 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
|
"github.com/minio/minio/pkg/handlers"
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,10 +113,6 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyRequestByBucket(ctx, w, r, bucket) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
listObjectVersions := objectAPI.ListObjectVersions
|
listObjectVersions := objectAPI.ListObjectVersions
|
||||||
|
|
||||||
// Inititate a list object versions operation based on the input params.
|
// Inititate a list object versions operation based on the input params.
|
||||||
@@ -139,7 +135,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
// ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata.
|
// ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata.
|
||||||
// --------------------------
|
// --------------------------
|
||||||
// This implementation of the GET operation returns some or all (up to 10000)
|
// This implementation of the GET operation returns some or all (up to 10000)
|
||||||
// of the objects in a bucket. You can use the request parame<ters 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.
|
||||||
//
|
//
|
||||||
// NOTE: It is recommended that this API to be used for application development.
|
// NOTE: It is recommended that this API to be used for application development.
|
||||||
@@ -179,13 +175,6 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analyze continuation token and route the request accordingly
|
|
||||||
var success bool
|
|
||||||
token, success = proxyRequestByToken(ctx, w, r, token)
|
|
||||||
if success {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
listObjectsV2 := objectAPI.ListObjectsV2
|
listObjectsV2 := objectAPI.ListObjectsV2
|
||||||
|
|
||||||
// Inititate a list objects operation based on the input params.
|
// Inititate a list objects operation based on the input params.
|
||||||
@@ -201,9 +190,6 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
// The next continuation token has id@node_index format to optimize paginated listing
|
// The next continuation token has id@node_index format to optimize paginated listing
|
||||||
nextContinuationToken := listObjectsV2Info.NextContinuationToken
|
nextContinuationToken := listObjectsV2Info.NextContinuationToken
|
||||||
if nextContinuationToken != "" && listObjectsV2Info.IsTruncated {
|
|
||||||
nextContinuationToken = fmt.Sprintf("%s@%d", listObjectsV2Info.NextContinuationToken, getLocalNodeIndex())
|
|
||||||
}
|
|
||||||
|
|
||||||
response := generateListObjectsV2Response(bucket, prefix, token, nextContinuationToken, startAfter,
|
response := generateListObjectsV2Response(bucket, prefix, token, nextContinuationToken, startAfter,
|
||||||
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
||||||
@@ -256,13 +242,6 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analyze continuation token and route the request accordingly
|
|
||||||
var success bool
|
|
||||||
token, success = proxyRequestByToken(ctx, w, r, token)
|
|
||||||
if success {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
listObjectsV2 := objectAPI.ListObjectsV2
|
listObjectsV2 := objectAPI.ListObjectsV2
|
||||||
|
|
||||||
// Inititate a list objects operation based on the input params.
|
// Inititate a list objects operation based on the input params.
|
||||||
@@ -276,13 +255,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
|||||||
|
|
||||||
concurrentDecryptETag(ctx, listObjectsV2Info.Objects)
|
concurrentDecryptETag(ctx, listObjectsV2Info.Objects)
|
||||||
|
|
||||||
// The next continuation token has id@node_index format to optimize paginated listing
|
response := generateListObjectsV2Response(bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter,
|
||||||
nextContinuationToken := listObjectsV2Info.NextContinuationToken
|
|
||||||
if nextContinuationToken != "" && listObjectsV2Info.IsTruncated {
|
|
||||||
nextContinuationToken = fmt.Sprintf("%s@%d", listObjectsV2Info.NextContinuationToken, getLocalNodeIndex())
|
|
||||||
}
|
|
||||||
|
|
||||||
response := generateListObjectsV2Response(bucket, prefix, token, nextContinuationToken, startAfter,
|
|
||||||
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
||||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false)
|
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false)
|
||||||
|
|
||||||
@@ -290,18 +263,6 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
|||||||
writeSuccessResponseXML(w, encodeResponse(response))
|
writeSuccessResponseXML(w, encodeResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalNodeIndex() int {
|
|
||||||
if len(globalProxyEndpoints) == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
for i, ep := range globalProxyEndpoints {
|
|
||||||
if ep.IsLocal {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return token, -1
|
return token, -1
|
||||||
@@ -340,8 +301,8 @@ func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http
|
|||||||
return proxyRequest(ctx, w, r, ep)
|
return proxyRequest(ctx, w, r, ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
func proxyRequestByBucket(ctx context.Context, w http.ResponseWriter, r *http.Request, bucket string) (success bool) {
|
func proxyRequestByStringHash(ctx context.Context, w http.ResponseWriter, r *http.Request, str string) (success bool) {
|
||||||
return proxyRequestByNodeIndex(ctx, w, r, crcHashMod(bucket, len(globalProxyEndpoints)))
|
return proxyRequestByNodeIndex(ctx, w, r, crcHashMod(str, len(globalProxyEndpoints)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
|
// ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
|
||||||
@@ -382,7 +343,12 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyRequestByBucket(ctx, w, r, bucket) {
|
// Forward the request using Source IP or bucket
|
||||||
|
forwardStr := handlers.GetSourceIPFromHeaders(r)
|
||||||
|
if forwardStr == "" {
|
||||||
|
forwardStr = bucket
|
||||||
|
}
|
||||||
|
if proxyRequestByStringHash(ctx, w, r, forwardStr) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-17
@@ -24,6 +24,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
@@ -49,6 +50,7 @@ func (sys *BucketMetadataSys) Remove(bucket string) {
|
|||||||
}
|
}
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
delete(sys.metadataMap, bucket)
|
delete(sys.metadataMap, bucket)
|
||||||
|
globalBucketMonitor.DeleteBucket(bucket)
|
||||||
sys.Unlock()
|
sys.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +74,7 @@ func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
|
|||||||
// Update update bucket metadata for the specified config file.
|
// Update update bucket metadata for the specified config file.
|
||||||
// The configData data should not be modified after being sent here.
|
// The configData data should not be modified after being sent here.
|
||||||
func (sys *BucketMetadataSys) Update(bucket string, configFile string, configData []byte) error {
|
func (sys *BucketMetadataSys) Update(bucket string, configFile string, configData []byte) error {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -167,10 +169,10 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
|
|||||||
}
|
}
|
||||||
meta.ReplicationConfigXML = configData
|
meta.ReplicationConfigXML = configData
|
||||||
case bucketTargetsFile:
|
case bucketTargetsFile:
|
||||||
if !globalIsErasure && !globalIsDistErasure {
|
meta.BucketTargetsConfigJSON, meta.BucketTargetsConfigMetaJSON, err = encryptBucketMetadata(meta.Name, configData, crypto.Context{bucket: meta.Name, bucketTargetsFile: bucketTargetsFile})
|
||||||
return NotImplemented{}
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error encrypting bucket target metadata %w", err)
|
||||||
}
|
}
|
||||||
meta.BucketTargetsConfigJSON = configData
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
||||||
}
|
}
|
||||||
@@ -193,7 +195,6 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
|
|||||||
// This function should only be used with
|
// This function should only be used with
|
||||||
// - GetBucketInfo
|
// - GetBucketInfo
|
||||||
// - ListBuckets
|
// - ListBuckets
|
||||||
// - ListBucketsHeal (only in case of erasure coding mode)
|
|
||||||
// For all other bucket specific metadata, use the relevant
|
// For all other bucket specific metadata, use the relevant
|
||||||
// calls implemented specifically for each of those features.
|
// calls implemented specifically for each of those features.
|
||||||
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
||||||
@@ -275,7 +276,7 @@ func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Life
|
|||||||
func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
|
func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
|
||||||
if globalIsGateway && globalGatewayName == NASBackendGateway {
|
if globalIsGateway && globalGatewayName == NASBackendGateway {
|
||||||
// Only needed in case of NAS gateway.
|
// Only needed in case of NAS gateway.
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -313,7 +314,7 @@ func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEC
|
|||||||
// 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, error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -373,10 +374,24 @@ 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(bucket string) (BucketMetadata, error) {
|
func (sys *BucketMetadataSys) GetConfig(bucket string) (BucketMetadata, error) {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return newBucketMetadata(bucket), errServerNotInitialized
|
return newBucketMetadata(bucket), errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -418,16 +433,20 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load bucket metadata sys in background
|
// Load bucket metadata sys in background
|
||||||
go logger.LogIf(ctx, sys.load(ctx, buckets, objAPI))
|
go sys.load(ctx, buckets, objAPI)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// concurrently load bucket metadata to speed up loading bucket metadata.
|
// concurrently load bucket metadata to speed up loading bucket metadata.
|
||||||
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
||||||
g := errgroup.WithNErrs(len(buckets))
|
g := errgroup.WithNErrs(len(buckets))
|
||||||
for index := range buckets {
|
for index := range buckets {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
|
_, _ = objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
||||||
|
// Ensure heal opts for bucket metadata be deep healed all the time.
|
||||||
|
ScanMode: madmin.HealDeepScan,
|
||||||
|
})
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
|
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -440,22 +459,20 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
}
|
}
|
||||||
for _, err := range g.Wait() {
|
for _, err := range g.Wait() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
||||||
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
||||||
count := 100 // load 100 bucket metadata at a time.
|
count := 100 // load 100 bucket metadata at a time.
|
||||||
for {
|
for {
|
||||||
if len(buckets) < count {
|
if len(buckets) < count {
|
||||||
return sys.concurrentLoad(ctx, buckets, objAPI)
|
sys.concurrentLoad(ctx, buckets, objAPI)
|
||||||
}
|
return
|
||||||
if err := sys.concurrentLoad(ctx, buckets[:count], objAPI); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
sys.concurrentLoad(ctx, buckets[:count], objAPI)
|
||||||
buckets = buckets[count:]
|
buckets = buckets[count:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-32
@@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
@@ -28,6 +29,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
@@ -37,6 +39,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/bucket/versioning"
|
"github.com/minio/minio/pkg/bucket/versioning"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -61,31 +64,33 @@ var (
|
|||||||
// bucketMetadataFormat refers to the format.
|
// bucketMetadataFormat refers to the format.
|
||||||
// bucketMetadataVersion can be used to track a rolling upgrade of a field.
|
// bucketMetadataVersion can be used to track a rolling upgrade of a field.
|
||||||
type BucketMetadata struct {
|
type BucketMetadata struct {
|
||||||
Name string
|
Name string
|
||||||
Created time.Time
|
Created time.Time
|
||||||
LockEnabled bool // legacy not used anymore.
|
LockEnabled bool // legacy not used anymore.
|
||||||
PolicyConfigJSON []byte
|
PolicyConfigJSON []byte
|
||||||
NotificationConfigXML []byte
|
NotificationConfigXML []byte
|
||||||
LifecycleConfigXML []byte
|
LifecycleConfigXML []byte
|
||||||
ObjectLockConfigXML []byte
|
ObjectLockConfigXML []byte
|
||||||
VersioningConfigXML []byte
|
VersioningConfigXML []byte
|
||||||
EncryptionConfigXML []byte
|
EncryptionConfigXML []byte
|
||||||
TaggingConfigXML []byte
|
TaggingConfigXML []byte
|
||||||
QuotaConfigJSON []byte
|
QuotaConfigJSON []byte
|
||||||
ReplicationConfigXML []byte
|
ReplicationConfigXML []byte
|
||||||
BucketTargetsConfigJSON []byte
|
BucketTargetsConfigJSON []byte
|
||||||
|
BucketTargetsConfigMetaJSON []byte
|
||||||
|
|
||||||
// Unexported fields. Must be updated atomically.
|
// Unexported fields. Must be updated atomically.
|
||||||
policyConfig *policy.Policy
|
policyConfig *policy.Policy
|
||||||
notificationConfig *event.Config
|
notificationConfig *event.Config
|
||||||
lifecycleConfig *lifecycle.Lifecycle
|
lifecycleConfig *lifecycle.Lifecycle
|
||||||
objectLockConfig *objectlock.Config
|
objectLockConfig *objectlock.Config
|
||||||
versioningConfig *versioning.Versioning
|
versioningConfig *versioning.Versioning
|
||||||
sseConfig *bucketsse.BucketSSEConfig
|
sseConfig *bucketsse.BucketSSEConfig
|
||||||
taggingConfig *tags.Tags
|
taggingConfig *tags.Tags
|
||||||
quotaConfig *madmin.BucketQuota
|
quotaConfig *madmin.BucketQuota
|
||||||
replicationConfig *replication.Config
|
replicationConfig *replication.Config
|
||||||
bucketTargetConfig *madmin.BucketTargets
|
bucketTargetConfig *madmin.BucketTargets
|
||||||
|
bucketTargetConfigMeta map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
||||||
@@ -100,13 +105,18 @@ func newBucketMetadata(name string) BucketMetadata {
|
|||||||
versioningConfig: &versioning.Versioning{
|
versioningConfig: &versioning.Versioning{
|
||||||
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||||||
},
|
},
|
||||||
bucketTargetConfig: &madmin.BucketTargets{},
|
bucketTargetConfig: &madmin.BucketTargets{},
|
||||||
|
bucketTargetConfigMeta: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
if name == "" {
|
||||||
|
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
|
||||||
|
return errors.New("bucket name cannot be empty")
|
||||||
|
}
|
||||||
configFile := path.Join(bucketConfigPrefix, name, bucketMetadataFile)
|
configFile := path.Join(bucketConfigPrefix, name, bucketMetadataFile)
|
||||||
data, err := readConfig(ctx, api, configFile)
|
data, err := readConfig(ctx, api, configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -128,22 +138,24 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
|
|||||||
}
|
}
|
||||||
// OK, parse data.
|
// OK, parse data.
|
||||||
_, err = b.UnmarshalMsg(data[4:])
|
_, err = b.UnmarshalMsg(data[4:])
|
||||||
|
b.Name = name // in-case parsing failed for some reason, make sure bucket name is not empty.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadBucketMetadata loads and migrates to bucket metadata.
|
// loadBucketMetadata loads and migrates to bucket metadata.
|
||||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
||||||
b := newBucketMetadata(bucket)
|
b := newBucketMetadata(bucket)
|
||||||
err := b.Load(ctx, objectAPI, bucket)
|
err := b.Load(ctx, objectAPI, b.Name)
|
||||||
if err == nil {
|
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
return b, b.convertLegacyConfigs(ctx, objectAPI)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != errConfigNotFound {
|
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||||
return b, b.convertLegacyConfigs(ctx, objectAPI)
|
if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
// migrate unencrypted remote targets
|
||||||
|
return b, b.migrateTargetConfig(ctx, objectAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAllConfigs will parse all configs and populate the private fields.
|
// parseAllConfigs will parse all configs and populate the private fields.
|
||||||
@@ -228,7 +240,8 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(b.BucketTargetsConfigJSON) != 0 {
|
if len(b.BucketTargetsConfigJSON) != 0 {
|
||||||
if err = json.Unmarshal(b.BucketTargetsConfigJSON, b.bucketTargetConfig); err != nil {
|
b.bucketTargetConfig, err = parseBucketTargetConfig(b.Name, b.BucketTargetsConfigJSON, b.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -348,6 +361,7 @@ func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile)
|
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile)
|
||||||
return saveConfig(ctx, api, configFile, data)
|
return saveConfig(ctx, api, configFile, data)
|
||||||
}
|
}
|
||||||
@@ -367,3 +381,76 @@ func deleteBucketMetadata(ctx context.Context, obj objectDeleter, bucket string)
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
|
||||||
|
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
|
||||||
|
var err error
|
||||||
|
// early return if no targets or already encrypted
|
||||||
|
if len(b.BucketTargetsConfigJSON) == 0 || GlobalKMS == nil || len(b.BucketTargetsConfigMetaJSON) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
encBytes, metaBytes, err := encryptBucketMetadata(b.Name, b.BucketTargetsConfigJSON, crypto.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.BucketTargetsConfigJSON = encBytes
|
||||||
|
b.BucketTargetsConfigMetaJSON = metaBytes
|
||||||
|
return b.Save(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt bucket metadata if kms is configured.
|
||||||
|
func encryptBucketMetadata(bucket string, input []byte, kmsContext crypto.Context) (output, metabytes []byte, err error) {
|
||||||
|
var sealedKey crypto.SealedKey
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
output = input
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
key [32]byte
|
||||||
|
encKey []byte
|
||||||
|
)
|
||||||
|
metadata := make(map[string]string)
|
||||||
|
key, encKey, err = GlobalKMS.GenerateKey(GlobalKMS.DefaultKeyID(), kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outbuf := bytes.NewBuffer(nil)
|
||||||
|
objectKey := crypto.GenerateKey(key, rand.Reader)
|
||||||
|
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
|
||||||
|
crypto.S3.CreateMetadata(metadata, GlobalKMS.DefaultKeyID(), encKey, sealedKey)
|
||||||
|
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20})
|
||||||
|
if err != nil {
|
||||||
|
return output, metabytes, err
|
||||||
|
}
|
||||||
|
metabytes, err = json.Marshal(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return outbuf.Bytes(), metabytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt bucket metadata if kms is configured.
|
||||||
|
func decryptBucketMetadata(input []byte, bucket string, meta map[string]string, kmsContext crypto.Context) ([]byte, error) {
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
return nil, errKMSNotConfigured
|
||||||
|
}
|
||||||
|
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(meta)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extKey, err := GlobalKMS.UnsealKey(keyID, kmsKey, kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var objectKey crypto.ObjectKey
|
||||||
|
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbuf := bytes.NewBuffer(nil)
|
||||||
|
_, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20})
|
||||||
|
|
||||||
|
return outbuf.Bytes(), err
|
||||||
|
}
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "BucketTargetsConfigMetaJSON":
|
||||||
|
z.BucketTargetsConfigMetaJSON, err = dc.ReadBytes(z.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,9 +121,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 13
|
// map header, size 14
|
||||||
// write "Name"
|
// write "Name"
|
||||||
err = en.Append(0x8d, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
err = en.Append(0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -246,15 +252,25 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "BucketTargetsConfigMetaJSON"
|
||||||
|
err = en.Append(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)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBytes(z.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
|
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 13
|
// map header, size 14
|
||||||
// string "Name"
|
// string "Name"
|
||||||
o = append(o, 0x8d, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
o = append(o, 0x8e, 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)
|
||||||
@@ -292,6 +308,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "BucketTargetsConfigJSON"
|
// string "BucketTargetsConfigJSON"
|
||||||
o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
||||||
o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON)
|
o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON)
|
||||||
|
// 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 = msgp.AppendBytes(o, z.BucketTargetsConfigMetaJSON)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +410,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "BucketTargetsConfigMetaJSON":
|
||||||
|
z.BucketTargetsConfigMetaJSON, bts, err = msgp.ReadBytesBytes(bts, z.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -405,6 +430,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)
|
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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-19
@@ -21,10 +21,12 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
|
"github.com/minio/minio/pkg/bucket/replication"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketObjectLockSys - map of bucket and retention configuration.
|
// BucketObjectLockSys - map of bucket and retention configuration.
|
||||||
@@ -33,7 +35,7 @@ type BucketObjectLockSys struct{}
|
|||||||
// Get - Get retention configuration.
|
// Get - Get retention configuration.
|
||||||
func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) {
|
func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return r, errServerNotInitialized
|
return r, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -80,24 +82,25 @@ func enforceRetentionForDeletion(ctx context.Context, objInfo ObjectInfo) (locke
|
|||||||
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
||||||
// governance bypass headers are set and user has governance bypass permissions.
|
// governance bypass headers are set and user has governance bypass permissions.
|
||||||
// Objects in "Compliance" mode can be overwritten only if retention date is past.
|
// Objects in "Compliance" mode can be overwritten only if retention date is past.
|
||||||
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket string, object ObjectToDelete, getObjectInfoFn GetObjectInfoFn) APIErrorCode {
|
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket string, object ObjectToDelete, oi ObjectInfo, gerr error) APIErrorCode {
|
||||||
opts, err := getOpts(ctx, r, bucket, object.ObjectName)
|
opts, err := getOpts(ctx, r, bucket, object.ObjectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.VersionID = object.VersionID
|
opts.VersionID = object.VersionID
|
||||||
|
if gerr != nil { // error from GetObjectInfo
|
||||||
oi, err := getObjectInfoFn(ctx, bucket, object.ObjectName, opts)
|
switch gerr.(type) {
|
||||||
if err != nil {
|
|
||||||
switch err.(type) {
|
|
||||||
case MethodNotAllowed: // This happens usually for a delete marker
|
case MethodNotAllowed: // This happens usually for a delete marker
|
||||||
if oi.DeleteMarker {
|
if oi.DeleteMarker {
|
||||||
// Delete marker should be present and valid.
|
// Delete marker should be present and valid.
|
||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toAPIErrorCode(ctx, err)
|
if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) {
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
return toAPIErrorCode(ctx, gerr)
|
||||||
}
|
}
|
||||||
|
|
||||||
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
||||||
@@ -245,13 +248,13 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket,
|
|||||||
// For objects in "Compliance" mode, retention date cannot be shortened, and mode cannot be altered.
|
// For objects in "Compliance" mode, retention date cannot be shortened, and mode cannot be altered.
|
||||||
// For objects with legal hold header set, the s3:PutObjectLegalHold permission is expected to be set
|
// For objects with legal hold header set, the s3:PutObjectLegalHold permission is expected to be set
|
||||||
// Both legal hold and retention can be applied independently on an object
|
// Both legal hold and retention can be applied independently on an object
|
||||||
func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, retentionPermErr, legalHoldPermErr APIErrorCode) (objectlock.RetMode, objectlock.RetentionDate, objectlock.ObjectLegalHold, APIErrorCode) {
|
func checkPutObjectLockAllowed(ctx context.Context, rq *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, retentionPermErr, legalHoldPermErr APIErrorCode) (objectlock.RetMode, objectlock.RetentionDate, objectlock.ObjectLegalHold, APIErrorCode) {
|
||||||
var mode objectlock.RetMode
|
var mode objectlock.RetMode
|
||||||
var retainDate objectlock.RetentionDate
|
var retainDate objectlock.RetentionDate
|
||||||
var legalHold objectlock.ObjectLegalHold
|
var legalHold objectlock.ObjectLegalHold
|
||||||
|
|
||||||
retentionRequested := objectlock.IsObjectLockRetentionRequested(r.Header)
|
retentionRequested := objectlock.IsObjectLockRetentionRequested(rq.Header)
|
||||||
legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(r.Header)
|
legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(rq.Header)
|
||||||
|
|
||||||
retentionCfg, err := globalBucketObjectLockSys.Get(bucket)
|
retentionCfg, err := globalBucketObjectLockSys.Get(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -267,25 +270,24 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj
|
|||||||
return mode, retainDate, legalHold, ErrNone
|
return mode, retainDate, legalHold, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
opts, err := getOpts(ctx, r, bucket, object)
|
opts, err := getOpts(ctx, rq, bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.VersionID != "" {
|
replica := rq.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String()
|
||||||
|
|
||||||
|
if opts.VersionID != "" && !replica {
|
||||||
if objInfo, err := getObjectInfoFn(ctx, bucket, object, opts); err == nil {
|
if objInfo, err := getObjectInfoFn(ctx, bucket, object, opts); err == nil {
|
||||||
r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
||||||
|
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Mode == objectlock.RetCompliance && r.RetainUntilDate.After(t) {
|
if r.Mode == objectlock.RetCompliance && r.RetainUntilDate.After(t) {
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = r.Mode
|
mode = r.Mode
|
||||||
retainDate = r.RetainUntilDate
|
retainDate = r.RetainUntilDate
|
||||||
legalHold = objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
legalHold = objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
||||||
@@ -298,17 +300,17 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj
|
|||||||
|
|
||||||
if legalHoldRequested {
|
if legalHoldRequested {
|
||||||
var lerr error
|
var lerr error
|
||||||
if legalHold, lerr = objectlock.ParseObjectLockLegalHoldHeaders(r.Header); lerr != nil {
|
if legalHold, lerr = objectlock.ParseObjectLockLegalHoldHeaders(rq.Header); lerr != nil {
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if retentionRequested {
|
if retentionRequested {
|
||||||
legalHold, err := objectlock.ParseObjectLockLegalHoldHeaders(r.Header)
|
legalHold, err := objectlock.ParseObjectLockLegalHoldHeaders(rq.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
rMode, rDate, err := objectlock.ParseObjectLockRetentionHeaders(r.Header)
|
rMode, rDate, err := objectlock.ParseObjectLockRetentionHeaders(rq.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -317,7 +319,9 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj
|
|||||||
}
|
}
|
||||||
return rMode, rDate, legalHold, ErrNone
|
return rMode, rDate, legalHold, ErrNone
|
||||||
}
|
}
|
||||||
|
if replica { // replica inherits retention metadata only from source
|
||||||
|
return "", objectlock.RetentionDate{}, legalHold, ErrNone
|
||||||
|
}
|
||||||
if !retentionRequested && retentionCfg.Validity > 0 {
|
if !retentionRequested && retentionCfg.Validity > 0 {
|
||||||
if retentionPermErr != ErrNone {
|
if retentionPermErr != ErrNone {
|
||||||
return mode, retainDate, legalHold, retentionPermErr
|
return mode, retainDate, legalHold, retentionPermErr
|
||||||
|
|||||||
+3
-1
@@ -63,7 +63,7 @@ func parseBucketQuota(bucket string, data []byte) (quotaCfg *madmin.BucketQuota,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sys *BucketQuotaSys) check(ctx context.Context, bucket string, size int64) error {
|
func (sys *BucketQuotaSys) check(ctx context.Context, bucket string, size int64) error {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -71,6 +71,8 @@ func (sys *BucketQuotaSys) check(ctx context.Context, bucket string, size int64)
|
|||||||
sys.bucketStorageCache.Once.Do(func() {
|
sys.bucketStorageCache.Once.Do(func() {
|
||||||
sys.bucketStorageCache.TTL = 1 * time.Second
|
sys.bucketStorageCache.TTL = 1 * time.Second
|
||||||
sys.bucketStorageCache.Update = func() (interface{}, error) {
|
sys.bucketStorageCache.Update = func() (interface{}, error) {
|
||||||
|
ctx, done := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer done()
|
||||||
return loadDataUsageFromBackend(ctx, objAPI)
|
return loadDataUsageFromBackend(ctx, objAPI)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+392
-43
@@ -20,24 +20,28 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
minio "github.com/minio/minio-go/v7"
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/bucket/bandwidth"
|
||||||
"github.com/minio/minio/pkg/bucket/replication"
|
"github.com/minio/minio/pkg/bucket/replication"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// gets replication config associated to a given bucket name.
|
// gets replication config associated to a given bucket name.
|
||||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -51,12 +55,19 @@ func getReplicationConfig(ctx context.Context, bucketName string) (rc *replicati
|
|||||||
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
||||||
// It also returns true if replication destination is same as this server.
|
// It also returns true if replication destination is same as this server.
|
||||||
func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config) (bool, error) {
|
func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config) (bool, error) {
|
||||||
clnt := globalBucketTargetSys.GetReplicationTargetClient(ctx, rCfg.RoleArn)
|
arn, err := madmin.ParseARN(rCfg.RoleArn)
|
||||||
|
if err != nil {
|
||||||
|
return false, BucketRemoteArnInvalid{}
|
||||||
|
}
|
||||||
|
if arn.Type != madmin.ReplicationService {
|
||||||
|
return false, BucketRemoteArnTypeInvalid{}
|
||||||
|
}
|
||||||
|
clnt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rCfg.RoleArn)
|
||||||
if clnt == nil {
|
if clnt == nil {
|
||||||
return false, BucketRemoteTargetNotFound{Bucket: bucket}
|
return false, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
if found, _ := clnt.BucketExists(ctx, rCfg.GetDestination().Bucket); !found {
|
if found, _ := clnt.BucketExists(ctx, rCfg.GetDestination().Bucket); !found {
|
||||||
return false, BucketReplicationDestinationNotFound{Bucket: rCfg.GetDestination().Bucket}
|
return false, BucketRemoteDestinationNotFound{Bucket: rCfg.GetDestination().Bucket}
|
||||||
}
|
}
|
||||||
if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil {
|
if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil {
|
||||||
if ret.LockEnabled {
|
if ret.LockEnabled {
|
||||||
@@ -81,19 +92,19 @@ func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object strin
|
|||||||
if permErr != ErrNone {
|
if permErr != ErrNone {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return mustReplicater(ctx, r, bucket, object, meta, replStatus)
|
return mustReplicater(ctx, bucket, object, meta, replStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustReplicate returns true if object meets replication criteria.
|
// mustReplicate returns true if object meets replication criteria.
|
||||||
func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool {
|
func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool {
|
||||||
if s3Err := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
|
if s3Err := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, "", r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return mustReplicater(ctx, r, bucket, object, meta, replStatus)
|
return mustReplicater(ctx, bucket, object, meta, replStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustReplicater returns true if object meets replication criteria.
|
// mustReplicater returns true if object meets replication criteria.
|
||||||
func mustReplicater(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool {
|
func mustReplicater(ctx context.Context, bucket, object string, meta map[string]string, replStatus string) bool {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -118,7 +129,171 @@ func mustReplicater(ctx context.Context, r *http.Request, bucket, object string,
|
|||||||
return cfg.Replicate(opts)
|
return cfg.Replicate(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
|
// returns true if any of the objects being deleted qualifies for replication.
|
||||||
|
func hasReplicationRules(ctx context.Context, bucket string, objects []ObjectToDelete) bool {
|
||||||
|
c, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil || c == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, obj := range objects {
|
||||||
|
if c.HasActiveRules(obj.ObjectName, true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns whether object version is a deletemarker and if object qualifies for replication
|
||||||
|
func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelete, oi ObjectInfo, gerr error) (dm, replicate bool) {
|
||||||
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil || rcfg == nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
||||||
|
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
||||||
|
if gerr != nil {
|
||||||
|
validReplStatus := false
|
||||||
|
switch oi.ReplicationStatus {
|
||||||
|
case replication.Pending, replication.Complete, replication.Failed:
|
||||||
|
validReplStatus = true
|
||||||
|
}
|
||||||
|
if oi.DeleteMarker && validReplStatus {
|
||||||
|
return oi.DeleteMarker, true
|
||||||
|
}
|
||||||
|
return oi.DeleteMarker, false
|
||||||
|
}
|
||||||
|
opts := replication.ObjectOpts{
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
|
UserTags: oi.UserTags,
|
||||||
|
DeleteMarker: true,
|
||||||
|
VersionID: dobj.VersionID,
|
||||||
|
}
|
||||||
|
return oi.DeleteMarker, rcfg.Replicate(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replicate deletes to the designated replication target if replication configuration
|
||||||
|
// has delete marker replication or delete replication (MinIO extension to allow deletes where version id
|
||||||
|
// is specified) enabled.
|
||||||
|
// Similar to bucket replication for PUT operation, soft delete (a.k.a setting delete marker) and
|
||||||
|
// permanent deletes (by specifying a version ID in the delete operation) have three states "Pending", "Complete"
|
||||||
|
// and "Failed" to mark the status of the replication of "DELETE" operation. All failed operations can
|
||||||
|
// then be retried by healing. In the case of permanent deletes, until the replication is completed on the
|
||||||
|
// target cluster, the object version is marked deleted on the source and hidden from listing. It is permanently
|
||||||
|
// deleted from the source when the VersionPurgeStatus changes to "Complete", i.e after replication succeeds
|
||||||
|
// on target.
|
||||||
|
func replicateDelete(ctx context.Context, dobj DeletedObjectVersionInfo, objectAPI ObjectLayer) {
|
||||||
|
bucket := dobj.Bucket
|
||||||
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil || rcfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
||||||
|
if tgt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
versionID := dobj.DeleteMarkerVersionID
|
||||||
|
if versionID == "" {
|
||||||
|
versionID = dobj.VersionID
|
||||||
|
}
|
||||||
|
rmErr := tgt.RemoveObject(ctx, rcfg.GetDestination().Bucket, dobj.ObjectName, miniogo.RemoveObjectOptions{
|
||||||
|
VersionID: versionID,
|
||||||
|
Internal: miniogo.AdvancedRemoveOptions{
|
||||||
|
ReplicationDeleteMarker: dobj.DeleteMarkerVersionID != "",
|
||||||
|
ReplicationMTime: dobj.DeleteMarkerMTime.Time,
|
||||||
|
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
replicationStatus := dobj.DeleteMarkerReplicationStatus
|
||||||
|
versionPurgeStatus := dobj.VersionPurgeStatus
|
||||||
|
|
||||||
|
if rmErr != nil {
|
||||||
|
if dobj.VersionID == "" {
|
||||||
|
replicationStatus = string(replication.Failed)
|
||||||
|
} else {
|
||||||
|
versionPurgeStatus = Failed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dobj.VersionID == "" {
|
||||||
|
replicationStatus = string(replication.Complete)
|
||||||
|
} else {
|
||||||
|
versionPurgeStatus = Complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var eventName = event.ObjectReplicationComplete
|
||||||
|
if replicationStatus == string(replication.Failed) || versionPurgeStatus == Failed {
|
||||||
|
eventName = event.ObjectReplicationFailed
|
||||||
|
}
|
||||||
|
objInfo := ObjectInfo{
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
VersionID: versionID,
|
||||||
|
ReplicationStatus: replication.StatusType(dobj.DeleteMarkerReplicationStatus),
|
||||||
|
VersionPurgeStatus: versionPurgeStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventArg := &eventArgs{
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
EventName: eventName,
|
||||||
|
}
|
||||||
|
sendEvent(*eventArg)
|
||||||
|
|
||||||
|
// Update metadata on the delete marker or purge permanent delete if replication success.
|
||||||
|
if _, err = objectAPI.DeleteObject(ctx, bucket, dobj.ObjectName, ObjectOptions{
|
||||||
|
VersionID: versionID,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
DeleteMarkerReplicationStatus: replicationStatus,
|
||||||
|
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
||||||
|
VersionPurgeStatus: versionPurgeStatus,
|
||||||
|
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
||||||
|
}); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s %s: %w", bucket, dobj.ObjectName, dobj.VersionID, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCopyObjMetadata(oi ObjectInfo, dest replication.Destination) map[string]string {
|
||||||
|
meta := make(map[string]string, len(oi.UserDefined))
|
||||||
|
for k, v := range oi.UserDefined {
|
||||||
|
if k == xhttp.AmzBucketReplicationStatus {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
if oi.ContentEncoding != "" {
|
||||||
|
meta[xhttp.ContentEncoding] = oi.ContentEncoding
|
||||||
|
}
|
||||||
|
if oi.ContentType != "" {
|
||||||
|
meta[xhttp.ContentType] = oi.ContentType
|
||||||
|
}
|
||||||
|
tag, err := tags.ParseObjectTags(oi.UserTags)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tag != nil {
|
||||||
|
meta[xhttp.AmzObjectTagging] = tag.String()
|
||||||
|
meta[xhttp.AmzTagDirective] = "REPLACE"
|
||||||
|
}
|
||||||
|
sc := dest.StorageClass
|
||||||
|
if sc == "" {
|
||||||
|
sc = oi.StorageClass
|
||||||
|
}
|
||||||
|
meta[xhttp.AmzStorageClass] = sc
|
||||||
|
if oi.UserTags != "" {
|
||||||
|
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
||||||
|
}
|
||||||
|
meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339Nano)
|
||||||
|
meta[xhttp.MinIOSourceETag] = oi.ETag
|
||||||
|
meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func putReplicationOpts(ctx context.Context, dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
if k == xhttp.AmzBucketReplicationStatus {
|
if k == xhttp.AmzBucketReplicationStatus {
|
||||||
@@ -138,22 +313,24 @@ func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOp
|
|||||||
sc = objInfo.StorageClass
|
sc = objInfo.StorageClass
|
||||||
}
|
}
|
||||||
putOpts = miniogo.PutObjectOptions{
|
putOpts = miniogo.PutObjectOptions{
|
||||||
UserMetadata: meta,
|
UserMetadata: meta,
|
||||||
UserTags: tag.ToMap(),
|
UserTags: tag.ToMap(),
|
||||||
ContentType: objInfo.ContentType,
|
ContentType: objInfo.ContentType,
|
||||||
ContentEncoding: objInfo.ContentEncoding,
|
ContentEncoding: objInfo.ContentEncoding,
|
||||||
StorageClass: sc,
|
StorageClass: sc,
|
||||||
ReplicationVersionID: objInfo.VersionID,
|
Internal: miniogo.AdvancedPutOptions{
|
||||||
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
SourceVersionID: objInfo.VersionID,
|
||||||
ReplicationMTime: objInfo.ModTime,
|
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
||||||
ReplicationETag: objInfo.ETag,
|
SourceMTime: objInfo.ModTime,
|
||||||
|
SourceETag: objInfo.ETag,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
|
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
|
||||||
rmode := miniogo.RetentionMode(mode)
|
rmode := miniogo.RetentionMode(mode)
|
||||||
putOpts.Mode = rmode
|
putOpts.Mode = rmode
|
||||||
}
|
}
|
||||||
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
||||||
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
rdate, err := time.Parse(time.RFC3339Nano, retainDateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -165,29 +342,80 @@ func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOp
|
|||||||
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
|
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
|
||||||
putOpts.ServerSideEncryption = encrypt.NewSSE()
|
putOpts.ServerSideEncryption = encrypt.NewSSE()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type replicationAction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
replicateMetadata replicationAction = "metadata"
|
||||||
|
replicateNone replicationAction = "none"
|
||||||
|
replicateAll replicationAction = "all"
|
||||||
|
)
|
||||||
|
|
||||||
|
// returns replicationAction by comparing metadata between source and target
|
||||||
|
func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo) replicationAction {
|
||||||
|
// needs full replication
|
||||||
|
if oi1.ETag != oi2.ETag ||
|
||||||
|
oi1.VersionID != oi2.VersionID ||
|
||||||
|
oi1.Size != oi2.Size ||
|
||||||
|
oi1.DeleteMarker != oi2.IsDeleteMarker {
|
||||||
|
return replicateAll
|
||||||
|
}
|
||||||
|
|
||||||
|
if !oi1.ModTime.Equal(oi2.LastModified) ||
|
||||||
|
oi1.ContentType != oi2.ContentType ||
|
||||||
|
oi1.StorageClass != oi2.StorageClass {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
if oi1.ContentEncoding != "" {
|
||||||
|
enc, ok := oi2.UserMetadata[xhttp.ContentEncoding]
|
||||||
|
if !ok || enc != oi1.ContentEncoding {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range oi2.UserMetadata {
|
||||||
|
oi2.Metadata[k] = []string{v}
|
||||||
|
}
|
||||||
|
if len(oi2.Metadata) != len(oi1.UserDefined) {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
for k1, v1 := range oi1.UserDefined {
|
||||||
|
if v2, ok := oi2.Metadata[k1]; !ok || v1 != strings.Join(v2, "") {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, _ := tags.MapToObjectTags(oi2.UserTags)
|
||||||
|
if t.String() != oi1.UserTags {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
return replicateNone
|
||||||
|
}
|
||||||
|
|
||||||
// replicateObject replicates the specified version of the object to destination bucket
|
// replicateObject replicates the specified version of the object to destination bucket
|
||||||
// The source object is then updated to reflect the replication status.
|
// The source object is then updated to reflect the replication status.
|
||||||
func replicateObject(ctx context.Context, bucket, object, versionID string, objectAPI ObjectLayer, eventArg *eventArgs, healPending bool) {
|
func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLayer) {
|
||||||
|
bucket := objInfo.Bucket
|
||||||
|
object := objInfo.Name
|
||||||
|
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgt := globalBucketTargetSys.GetReplicationTargetClient(ctx, cfg.RoleArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, cfg.RoleArn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
|
||||||
VersionID: versionID,
|
VersionID: objInfo.VersionID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
objInfo = gr.ObjInfo
|
||||||
objInfo := gr.ObjInfo
|
|
||||||
size, err := objInfo.GetActualSize()
|
size, err := objInfo.GetActualSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -200,41 +428,70 @@ func replicateObject(ctx context.Context, bucket, object, versionID string, obje
|
|||||||
gr.Close()
|
gr.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// In the rare event that replication is in pending state either due to
|
|
||||||
// server shut down/crash before replication completed or healing and PutObject
|
rtype := replicateAll
|
||||||
// race - do an additional stat to see if the version ID exists
|
oi, err := tgt.StatObject(ctx, dest.Bucket, object, miniogo.StatObjectOptions{VersionID: objInfo.VersionID})
|
||||||
if healPending {
|
if err == nil {
|
||||||
_, err := tgt.StatObject(ctx, dest.Bucket, object, miniogo.StatObjectOptions{VersionID: objInfo.VersionID})
|
rtype = getReplicationAction(objInfo, oi)
|
||||||
if err == nil {
|
if rtype == replicateNone {
|
||||||
gr.Close()
|
gr.Close()
|
||||||
// object with same VersionID already exists, replication kicked off by
|
// object with same VersionID already exists, replication kicked off by
|
||||||
// PutObject might have completed.
|
// PutObject might have completed.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
putOpts := putReplicationOpts(dest, objInfo)
|
|
||||||
|
|
||||||
|
target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
putOpts := putReplicationOpts(ctx, dest, objInfo)
|
||||||
replicationStatus := replication.Complete
|
replicationStatus := replication.Complete
|
||||||
_, err = tgt.PutObject(ctx, dest.Bucket, object, gr, size, "", "", putOpts)
|
|
||||||
gr.Close()
|
// Setup bandwidth throttling
|
||||||
|
peers, _ := globalEndpoints.peers()
|
||||||
|
totalNodesCount := len(peers)
|
||||||
|
if totalNodesCount == 0 {
|
||||||
|
totalNodesCount = 1 // For standalone erasure coding
|
||||||
|
}
|
||||||
|
b := target.BandwidthLimit / int64(totalNodesCount)
|
||||||
|
var headerSize int
|
||||||
|
for k, v := range putOpts.Header() {
|
||||||
|
headerSize += len(k) + len(v)
|
||||||
|
}
|
||||||
|
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
|
||||||
|
if rtype == replicateAll {
|
||||||
|
_, err = tgt.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts)
|
||||||
|
} else {
|
||||||
|
// replicate metadata for object tagging/copy with metadata replacement
|
||||||
|
dstOpts := miniogo.PutObjectOptions{Internal: miniogo.AdvancedPutOptions{SourceVersionID: objInfo.VersionID}}
|
||||||
|
_, err = tgt.CopyObject(ctx, dest.Bucket, object, dest.Bucket, object, getCopyObjMetadata(objInfo, dest), dstOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replicationStatus = replication.Failed
|
replicationStatus = replication.Failed
|
||||||
// Notify replication failure event.
|
|
||||||
if eventArg == nil {
|
|
||||||
eventArg = &eventArgs{
|
|
||||||
BucketName: bucket,
|
|
||||||
Object: objInfo,
|
|
||||||
Host: "Internal: [Replication]",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventArg.EventName = event.OperationReplicationFailed
|
|
||||||
eventArg.Object.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
|
||||||
sendEvent(*eventArg)
|
|
||||||
}
|
}
|
||||||
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
||||||
if objInfo.UserTags != "" {
|
if objInfo.UserTags != "" {
|
||||||
objInfo.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
objInfo.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: add support for missing replication events
|
||||||
|
// - event.ObjectReplicationNotTracked
|
||||||
|
// - event.ObjectReplicationMissedThreshold
|
||||||
|
// - event.ObjectReplicationReplicatedAfterThreshold
|
||||||
|
var eventName = event.ObjectReplicationComplete
|
||||||
|
if replicationStatus == replication.Failed {
|
||||||
|
eventName = event.ObjectReplicationFailed
|
||||||
|
}
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: eventName,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
objInfo.metadataOnly = true // Perform only metadata updates.
|
objInfo.metadataOnly = true // Perform only metadata updates.
|
||||||
if _, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{
|
if _, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: objInfo.VersionID,
|
||||||
@@ -267,3 +524,95 @@ func filterReplicationStatusMetadata(metadata map[string]string) map[string]stri
|
|||||||
delKey(xhttp.AmzBucketReplicationStatus)
|
delKey(xhttp.AmzBucketReplicationStatus)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletedObjectVersionInfo has info on deleted object
|
||||||
|
type DeletedObjectVersionInfo struct {
|
||||||
|
DeletedObject
|
||||||
|
Bucket string
|
||||||
|
}
|
||||||
|
type replicationState struct {
|
||||||
|
// add future metrics here
|
||||||
|
replicaCh chan ObjectInfo
|
||||||
|
replicaDeleteCh chan DeletedObjectVersionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replicationState) queueReplicaTask(oi ObjectInfo) {
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case r.replicaCh <- oi:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replicationState) queueReplicaDeleteTask(doi DeletedObjectVersionInfo) {
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case r.replicaDeleteCh <- doi:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalReplicationState *replicationState
|
||||||
|
// TODO: currently keeping it conservative
|
||||||
|
// but eventually can be tuned in future,
|
||||||
|
// take only half the CPUs for replication
|
||||||
|
// conservatively.
|
||||||
|
globalReplicationConcurrent = runtime.GOMAXPROCS(0) / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func newReplicationState() *replicationState {
|
||||||
|
|
||||||
|
// fix minimum concurrent replication to 1 for single CPU setup
|
||||||
|
if globalReplicationConcurrent == 0 {
|
||||||
|
globalReplicationConcurrent = 1
|
||||||
|
}
|
||||||
|
rs := &replicationState{
|
||||||
|
replicaCh: make(chan ObjectInfo, 10000),
|
||||||
|
replicaDeleteCh: make(chan DeletedObjectVersionInfo, 10000),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-GlobalContext.Done()
|
||||||
|
close(rs.replicaCh)
|
||||||
|
close(rs.replicaDeleteCh)
|
||||||
|
}()
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// addWorker creates a new worker to process tasks
|
||||||
|
func (r *replicationState) addWorker(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
|
// Add a new worker.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case oi, ok := <-r.replicaCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replicateObject(ctx, oi, objectAPI)
|
||||||
|
case doi, ok := <-r.replicaDeleteCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replicateDelete(ctx, doi, objectAPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
|
if globalReplicationState == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with globalReplicationConcurrent.
|
||||||
|
for i := 0; i < globalReplicationConcurrent; i++ {
|
||||||
|
globalReplicationState.addWorker(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+150
-39
@@ -18,14 +18,20 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/pkg/bucket/versioning"
|
"github.com/minio/minio/pkg/bucket/versioning"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
sha256 "github.com/minio/sha256-simd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketTargetSys represents bucket targets subsystem
|
// BucketTargetSys represents bucket targets subsystem
|
||||||
@@ -33,7 +39,6 @@ type BucketTargetSys struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
arnRemotesMap map[string]*miniogo.Core
|
arnRemotesMap map[string]*miniogo.Core
|
||||||
targetsMap map[string][]madmin.BucketTarget
|
targetsMap map[string][]madmin.BucketTarget
|
||||||
clientsCache map[string]*miniogo.Core
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTargets lists bucket targets across tenant or for individual bucket, and returns
|
// ListTargets lists bucket targets across tenant or for individual bucket, and returns
|
||||||
@@ -75,59 +80,84 @@ func (sys *BucketTargetSys) ListBucketTargets(ctx context.Context, bucket string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetTarget - sets a new minio-go client target for this bucket.
|
// SetTarget - sets a new minio-go client target for this bucket.
|
||||||
func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget) error {
|
func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !tgt.Type.IsValid() {
|
if !tgt.Type.IsValid() && !update {
|
||||||
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
clnt, err := sys.getRemoteTargetClient(tgt)
|
clnt, err := sys.getRemoteTargetClient(tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
|
// validate if target credentials are ok
|
||||||
|
if _, err = clnt.BucketExists(ctx, tgt.TargetBucket); err != nil {
|
||||||
|
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
||||||
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
|
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
if tgt.Type == madmin.ReplicationService {
|
if tgt.Type == madmin.ReplicationService {
|
||||||
|
if !globalIsErasure {
|
||||||
|
return NotImplemented{}
|
||||||
|
}
|
||||||
if !globalBucketVersioningSys.Enabled(bucket) {
|
if !globalBucketVersioningSys.Enabled(bucket) {
|
||||||
return BucketReplicationSourceNotVersioned{Bucket: bucket}
|
return BucketReplicationSourceNotVersioned{Bucket: bucket}
|
||||||
}
|
}
|
||||||
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
|
||||||
}
|
|
||||||
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
|
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
if vcfg.Status != string(versioning.Enabled) {
|
if vcfg.Status != string(versioning.Enabled) {
|
||||||
return BucketReplicationTargetNotVersioned{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tgt.Type == madmin.ILMService {
|
||||||
|
if globalBucketVersioningSys.Enabled(bucket) {
|
||||||
|
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
||||||
|
if err != nil {
|
||||||
|
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
||||||
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
|
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
|
if vcfg.Status != string(versioning.Enabled) {
|
||||||
|
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
|
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts := sys.targetsMap[bucket]
|
||||||
newtgts := make([]madmin.BucketTarget, len(tgts))
|
newtgts := make([]madmin.BucketTarget, len(tgts))
|
||||||
|
labels := make(map[string]struct{})
|
||||||
found := false
|
found := false
|
||||||
for idx, t := range tgts {
|
for idx, t := range tgts {
|
||||||
|
labels[t.Label] = struct{}{}
|
||||||
if t.Type == tgt.Type {
|
if t.Type == tgt.Type {
|
||||||
if t.Arn == tgt.Arn {
|
if t.Arn == tgt.Arn && !update {
|
||||||
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
|
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
|
||||||
}
|
}
|
||||||
|
if t.Label == tgt.Label && !update {
|
||||||
|
return BucketRemoteLabelInUse{Bucket: t.TargetBucket}
|
||||||
|
}
|
||||||
newtgts[idx] = *tgt
|
newtgts[idx] = *tgt
|
||||||
found = true
|
found = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newtgts[idx] = t
|
newtgts[idx] = t
|
||||||
}
|
}
|
||||||
if !found {
|
if _, ok := labels[tgt.Label]; ok && !update {
|
||||||
|
return BucketRemoteLabelInUse{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
|
if !found && !update {
|
||||||
newtgts = append(newtgts, *tgt)
|
newtgts = append(newtgts, *tgt)
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.targetsMap[bucket] = newtgts
|
sys.targetsMap[bucket] = newtgts
|
||||||
sys.arnRemotesMap[tgt.Arn] = clnt
|
sys.arnRemotesMap[tgt.Arn] = clnt
|
||||||
if _, ok := sys.clientsCache[clnt.EndpointURL().String()]; !ok {
|
|
||||||
sys.clientsCache[clnt.EndpointURL().String()] = clnt
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +174,9 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
return BucketRemoteArnInvalid{Bucket: bucket}
|
return BucketRemoteArnInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
if arn.Type == madmin.ReplicationService {
|
if arn.Type == madmin.ReplicationService {
|
||||||
|
if !globalIsErasure {
|
||||||
|
return NotImplemented{}
|
||||||
|
}
|
||||||
// reject removal of remote target if replication configuration is present
|
// reject removal of remote target if replication configuration is present
|
||||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err == nil && rcfg.RoleArn == arnStr {
|
if err == nil && rcfg.RoleArn == arnStr {
|
||||||
@@ -152,6 +185,16 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if arn.Type == madmin.ILMService {
|
||||||
|
// reject removal of remote target if lifecycle transition uses this arn
|
||||||
|
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||||
|
if err == nil && transitionSCInUse(ctx, config, bucket, arnStr) {
|
||||||
|
if _, ok := sys.arnRemotesMap[arnStr]; ok {
|
||||||
|
return BucketRemoteRemoveDisallowed{Bucket: bucket}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// delete ARN type from list of matching targets
|
// delete ARN type from list of matching targets
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
@@ -173,19 +216,56 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReplicationTargetClient returns minio-go client for replication target instance
|
// GetRemoteTargetClient returns minio-go client for replication target instance
|
||||||
func (sys *BucketTargetSys) GetReplicationTargetClient(ctx context.Context, arn string) *miniogo.Core {
|
func (sys *BucketTargetSys) GetRemoteTargetClient(ctx context.Context, arn string) *miniogo.Core {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
defer sys.RUnlock()
|
||||||
return sys.arnRemotesMap[arn]
|
return sys.arnRemotesMap[arn]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRemoteTargetWithLabel returns bucket target given a target label
|
||||||
|
func (sys *BucketTargetSys) GetRemoteTargetWithLabel(ctx context.Context, bucket, targetLabel string) *madmin.BucketTarget {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
for _, t := range sys.targetsMap[bucket] {
|
||||||
|
if strings.ToUpper(t.Label) == strings.ToUpper(targetLabel) {
|
||||||
|
tgt := t.Clone()
|
||||||
|
return &tgt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRemoteArnWithLabel returns bucket target's ARN given its target label
|
||||||
|
func (sys *BucketTargetSys) GetRemoteArnWithLabel(ctx context.Context, bucket, tgtLabel string) *madmin.ARN {
|
||||||
|
tgt := sys.GetRemoteTargetWithLabel(ctx, bucket, tgtLabel)
|
||||||
|
if tgt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
arn, err := madmin.ParseARN(tgt.Arn)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return arn
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRemoteLabelWithArn returns a bucket target's label given its ARN
|
||||||
|
func (sys *BucketTargetSys) GetRemoteLabelWithArn(ctx context.Context, bucket, arnStr string) string {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
for _, t := range sys.targetsMap[bucket] {
|
||||||
|
if t.Arn == arnStr {
|
||||||
|
return t.Label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// NewBucketTargetSys - creates new replication system.
|
// NewBucketTargetSys - creates new replication system.
|
||||||
func NewBucketTargetSys() *BucketTargetSys {
|
func NewBucketTargetSys() *BucketTargetSys {
|
||||||
return &BucketTargetSys{
|
return &BucketTargetSys{
|
||||||
arnRemotesMap: make(map[string]*miniogo.Core),
|
arnRemotesMap: make(map[string]*miniogo.Core),
|
||||||
targetsMap: make(map[string][]madmin.BucketTarget),
|
targetsMap: make(map[string][]madmin.BucketTarget),
|
||||||
clientsCache: make(map[string]*miniogo.Core),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,14 +285,14 @@ func (sys *BucketTargetSys) Init(ctx context.Context, buckets []BucketInfo, objA
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTarget updates target to reflect metadata updates
|
// UpdateAllTargets updates target to reflect metadata updates
|
||||||
func (sys *BucketTargetSys) UpdateTarget(bucket string, cfg *madmin.BucketTargets) {
|
func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketTargets) {
|
||||||
if sys == nil {
|
if sys == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
if cfg == nil || cfg.Empty() {
|
if tgts == nil || tgts.Empty() {
|
||||||
// remove target and arn association
|
// remove target and arn association
|
||||||
if tgts, ok := sys.targetsMap[bucket]; ok {
|
if tgts, ok := sys.targetsMap[bucket]; ok {
|
||||||
for _, t := range tgts {
|
for _, t := range tgts {
|
||||||
@@ -223,20 +303,17 @@ func (sys *BucketTargetSys) UpdateTarget(bucket string, cfg *madmin.BucketTarget
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Targets) > 0 {
|
if len(tgts.Targets) > 0 {
|
||||||
sys.targetsMap[bucket] = cfg.Targets
|
sys.targetsMap[bucket] = tgts.Targets
|
||||||
}
|
}
|
||||||
for _, tgt := range cfg.Targets {
|
for _, tgt := range tgts.Targets {
|
||||||
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
if _, ok := sys.clientsCache[tgtClient.EndpointURL().String()]; !ok {
|
|
||||||
sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket] = cfg.Targets
|
sys.targetsMap[bucket] = tgts.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
// create minio-go clients for buckets having remote targets
|
// create minio-go clients for buckets having remote targets
|
||||||
@@ -258,9 +335,6 @@ func (sys *BucketTargetSys) load(ctx context.Context, buckets []BucketInfo, objA
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
if _, ok := sys.clientsCache[tgtClient.EndpointURL().String()]; !ok {
|
|
||||||
sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||||
}
|
}
|
||||||
@@ -272,16 +346,14 @@ var getRemoteTargetInstanceTransportOnce sync.Once
|
|||||||
|
|
||||||
// Returns a minio-go Client configured to access remote host described in replication target config.
|
// Returns a minio-go Client configured to access remote host described in replication target config.
|
||||||
func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*miniogo.Core, error) {
|
func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*miniogo.Core, error) {
|
||||||
if clnt, ok := sys.clientsCache[tcfg.Endpoint]; ok {
|
|
||||||
return clnt, nil
|
|
||||||
}
|
|
||||||
config := tcfg.Credentials
|
config := tcfg.Credentials
|
||||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||||
|
|
||||||
getRemoteTargetInstanceTransportOnce.Do(func() {
|
getRemoteTargetInstanceTransportOnce.Do(func() {
|
||||||
getRemoteTargetInstanceTransport = NewGatewayHTTPTransport()
|
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(10 * time.Minute)
|
||||||
})
|
})
|
||||||
core, err := miniogo.NewCore(tcfg.Endpoint, &miniogo.Options{
|
|
||||||
|
core, err := miniogo.NewCore(tcfg.URL().Host, &miniogo.Options{
|
||||||
Creds: creds,
|
Creds: creds,
|
||||||
Secure: tcfg.Secure,
|
Secure: tcfg.Secure,
|
||||||
Transport: getRemoteTargetInstanceTransport,
|
Transport: getRemoteTargetInstanceTransport,
|
||||||
@@ -296,18 +368,57 @@ func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTar
|
|||||||
}
|
}
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts := sys.targetsMap[bucket]
|
||||||
for _, tgt := range tgts {
|
for _, tgt := range tgts {
|
||||||
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL() == tgt.URL() {
|
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL().String() == tgt.URL().String() {
|
||||||
return tgt.Arn
|
return tgt.Arn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !madmin.ServiceType(target.Type).IsValid() {
|
if !madmin.ServiceType(target.Type).IsValid() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return generateARN(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate ARN that is unique to this target type
|
||||||
|
func generateARN(t *madmin.BucketTarget) string {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(t.Type))
|
||||||
|
hash.Write([]byte(t.Region))
|
||||||
|
hash.Write([]byte(t.TargetBucket))
|
||||||
|
hashSum := hex.EncodeToString(hash.Sum(nil))
|
||||||
arn := madmin.ARN{
|
arn := madmin.ARN{
|
||||||
Type: target.Type,
|
Type: t.Type,
|
||||||
ID: mustGetUUID(),
|
ID: hashSum,
|
||||||
Region: target.Region,
|
Region: t.Region,
|
||||||
Bucket: target.TargetBucket,
|
Bucket: t.TargetBucket,
|
||||||
}
|
}
|
||||||
return arn.String()
|
return arn.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns parsed target config. If KMS is configured, remote target is decrypted
|
||||||
|
func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.BucketTargets, error) {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
t madmin.BucketTargets
|
||||||
|
meta map[string]string
|
||||||
|
)
|
||||||
|
if len(cdata) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
data = cdata
|
||||||
|
if len(cmetadata) != 0 {
|
||||||
|
if err := json.Unmarshal(cmetadata, &meta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if crypto.S3.IsEncrypted(meta) {
|
||||||
|
if data, err = decryptBucketMetadata(cdata, bucket, meta, crypto.Context{bucket: bucket, bucketTargetsFile: bucketTargetsFile}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(data, &t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r
|
|||||||
}, r.URL, guessIsBrowserReq(r))
|
}, r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if _, err := getReplicationConfig(ctx, bucket); err == nil && v.Suspended() {
|
||||||
|
writeErrorResponse(ctx, w, APIError{
|
||||||
|
Code: "InvalidBucketState",
|
||||||
|
Description: "A replication configuration is present on this bucket, so the versioning state cannot be changed.",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
}, r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
configData, err := xml.Marshal(v)
|
configData, err := xml.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+37
-13
@@ -21,6 +21,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -29,20 +30,36 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
dns2 "github.com/miekg/dns"
|
dns2 "github.com/miekg/dns"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/certs"
|
"github.com/minio/minio/pkg/certs"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// serverDebugLog will enable debug printing
|
||||||
|
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logger.Init(GOPATH, GOROOT)
|
logger.Init(GOPATH, GOROOT)
|
||||||
logger.RegisterError(config.FmtError)
|
logger.RegisterError(config.FmtError)
|
||||||
|
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 3*time.Second)
|
||||||
|
|
||||||
|
initGlobalContext()
|
||||||
|
|
||||||
|
globalReplicationState = newReplicationState()
|
||||||
|
globalTransitionState = newTransitionState()
|
||||||
|
|
||||||
|
console.SetColor("Debug", color.New())
|
||||||
|
|
||||||
gob.Register(StorageErr(""))
|
gob.Register(StorageErr(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +76,12 @@ func verifyObjectLayerFeatures(name string, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalCompressConfigMu.Lock()
|
||||||
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
|
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
logger.Fatal(errInvalidArgument,
|
logger.Fatal(errInvalidArgument,
|
||||||
"Compression support is requested but '%s' does not support compression", name)
|
"Compression support is requested but '%s' does not support compression", name)
|
||||||
}
|
}
|
||||||
|
globalCompressConfigMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for updates and print a notification message
|
// Check for updates and print a notification message
|
||||||
@@ -100,11 +119,7 @@ func checkUpdate(mode string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalInplaceUpdateDisabled {
|
logStartupMessage(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
|
||||||
logStartupMessage(updateMsg)
|
|
||||||
} else {
|
|
||||||
logStartupMessage(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
|
func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
|
||||||
@@ -334,14 +349,23 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
|
|||||||
return nil, nil, false, err
|
return nil, nil, false, err
|
||||||
}
|
}
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
// We exclude any regular file and the "CAs/" directory.
|
// Ignore all
|
||||||
// The "CAs/" directory contains (root) CA certificates
|
// - regular files
|
||||||
// that MinIO adds to its list of trusted roots (tls.Config.RootCAs).
|
// - "CAs" directory
|
||||||
// Therefore, "CAs/" does not contain X.509 certificates that
|
// - any directory which starts with ".."
|
||||||
// are meant to be served by MinIO.
|
if file.Mode().IsRegular() || file.Name() == "CAs" || strings.HasPrefix(file.Name(), "..") {
|
||||||
if !file.IsDir() || file.Name() == "CAs" {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
file, err = os.Stat(filepath.Join(root.Name(), file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
// not accessible ignore
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
certFile = filepath.Join(root.Name(), file.Name(), publicCertFile)
|
certFile = filepath.Join(root.Name(), file.Name(), publicCertFile)
|
||||||
@@ -350,8 +374,8 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
|
|||||||
if !isFile(certFile) || !isFile(keyFile) {
|
if !isFile(certFile) || !isFile(keyFile) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := manager.AddCertificate(certFile, keyFile); err != nil {
|
if err = manager.AddCertificate(certFile, keyFile); err != nil {
|
||||||
err = fmt.Errorf("Failed to load TLS certificate '%s': %v", certFile, err)
|
err = fmt.Errorf("Unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err)
|
||||||
logger.LogIf(GlobalContext, err, logger.Minio)
|
logger.LogIf(GlobalContext, err, logger.Minio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+94
-15
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -28,6 +29,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config/crawler"
|
"github.com/minio/minio/cmd/config/crawler"
|
||||||
"github.com/minio/minio/cmd/config/dns"
|
"github.com/minio/minio/cmd/config/dns"
|
||||||
"github.com/minio/minio/cmd/config/etcd"
|
"github.com/minio/minio/cmd/config/etcd"
|
||||||
|
"github.com/minio/minio/cmd/config/heal"
|
||||||
xldap "github.com/minio/minio/cmd/config/identity/ldap"
|
xldap "github.com/minio/minio/cmd/config/identity/ldap"
|
||||||
"github.com/minio/minio/cmd/config/identity/openid"
|
"github.com/minio/minio/cmd/config/identity/openid"
|
||||||
"github.com/minio/minio/cmd/config/notify"
|
"github.com/minio/minio/cmd/config/notify"
|
||||||
@@ -56,6 +58,7 @@ func initHelp() {
|
|||||||
config.KmsKesSubSys: crypto.DefaultKesKVS,
|
config.KmsKesSubSys: crypto.DefaultKesKVS,
|
||||||
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
||||||
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
||||||
|
config.HealSubSys: heal.DefaultKVS,
|
||||||
config.CrawlerSubSys: crawler.DefaultKVS,
|
config.CrawlerSubSys: crawler.DefaultKVS,
|
||||||
}
|
}
|
||||||
for k, v := range notify.DefaultNotificationKVS {
|
for k, v := range notify.DefaultNotificationKVS {
|
||||||
@@ -108,9 +111,13 @@ func initHelp() {
|
|||||||
Key: config.APISubSys,
|
Key: config.APISubSys,
|
||||||
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
|
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.HealSubSys,
|
||||||
|
Description: "manage object healing frequency and bitrot verification checks",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CrawlerSubSys,
|
Key: config.CrawlerSubSys,
|
||||||
Description: "manage continuous disk crawling for bucket disk usage, lifecycle, quota and data integrity checks",
|
Description: "manage crawling for usage calculation, lifecycle, healing and more",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.LoggerWebhookSubSys,
|
Key: config.LoggerWebhookSubSys,
|
||||||
@@ -191,6 +198,7 @@ func initHelp() {
|
|||||||
config.EtcdSubSys: etcd.Help,
|
config.EtcdSubSys: etcd.Help,
|
||||||
config.CacheSubSys: cache.Help,
|
config.CacheSubSys: cache.Help,
|
||||||
config.CompressionSubSys: compress.Help,
|
config.CompressionSubSys: compress.Help,
|
||||||
|
config.HealSubSys: heal.Help,
|
||||||
config.CrawlerSubSys: crawler.Help,
|
config.CrawlerSubSys: crawler.Help,
|
||||||
config.IdentityOpenIDSubSys: openid.Help,
|
config.IdentityOpenIDSubSys: openid.Help,
|
||||||
config.IdentityLDAPSubSys: xldap.Help,
|
config.IdentityLDAPSubSys: xldap.Help,
|
||||||
@@ -221,6 +229,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func validateConfig(s config.Config, setDriveCount int) error {
|
func validateConfig(s config.Config, setDriveCount int) error {
|
||||||
|
// We must have a global lock for this so nobody else modifies env while we do.
|
||||||
|
defer env.LockSetEnv()()
|
||||||
|
|
||||||
// Disable merging env values with config for validation.
|
// Disable merging env values with config for validation.
|
||||||
env.SetEnvOff()
|
env.SetEnvOff()
|
||||||
|
|
||||||
@@ -249,7 +260,18 @@ func validateConfig(s config.Config, setDriveCount int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
|
compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI != nil {
|
||||||
|
if compCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
|
return fmt.Errorf("Backend does not support compression")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,10 +460,6 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalCrawlerConfig, err = crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to read crawler config: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
|
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -459,11 +477,6 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption))
|
logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalCompressConfig, err = compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup Compression: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody)
|
NewGatewayHTTPTransport(), xhttp.DrainBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -493,11 +506,13 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize logger: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize logger: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, l := range loggerCfg.HTTP {
|
for k, l := range loggerCfg.HTTP {
|
||||||
if l.Enabled {
|
if l.Enabled {
|
||||||
// Enable http logging
|
// Enable http logging
|
||||||
if err = logger.AddTarget(
|
if err = logger.AddTarget(
|
||||||
http.New(http.WithEndpoint(l.Endpoint),
|
http.New(
|
||||||
|
http.WithTargetName(k),
|
||||||
|
http.WithEndpoint(l.Endpoint),
|
||||||
http.WithAuthToken(l.AuthToken),
|
http.WithAuthToken(l.AuthToken),
|
||||||
http.WithUserAgent(loggerUserAgent),
|
http.WithUserAgent(loggerUserAgent),
|
||||||
http.WithLogKind(string(logger.All)),
|
http.WithLogKind(string(logger.All)),
|
||||||
@@ -509,11 +524,13 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, l := range loggerCfg.Audit {
|
for k, l := range loggerCfg.Audit {
|
||||||
if l.Enabled {
|
if l.Enabled {
|
||||||
// Enable http audit logging
|
// Enable http audit logging
|
||||||
if err = logger.AddAuditTarget(
|
if err = logger.AddAuditTarget(
|
||||||
http.New(http.WithEndpoint(l.Endpoint),
|
http.New(
|
||||||
|
http.WithTargetName(k),
|
||||||
|
http.WithEndpoint(l.Endpoint),
|
||||||
http.WithAuthToken(l.AuthToken),
|
http.WithAuthToken(l.AuthToken),
|
||||||
http.WithUserAgent(loggerUserAgent),
|
http.WithUserAgent(loggerUserAgent),
|
||||||
http.WithLogKind(string(logger.All)),
|
http.WithLogKind(string(logger.All)),
|
||||||
@@ -534,6 +551,68 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply dynamic config values
|
||||||
|
logger.LogIf(ctx, applyDynamicConfig(ctx, s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyDynamicConfig will apply dynamic config values.
|
||||||
|
// Dynamic systems should be in config.SubSystemsDynamic as well.
|
||||||
|
func applyDynamicConfig(ctx context.Context, s config.Config) error {
|
||||||
|
// Read all dynamic configs.
|
||||||
|
// API
|
||||||
|
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compression
|
||||||
|
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to setup Compression: %w", err)
|
||||||
|
}
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI != nil {
|
||||||
|
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
|
return fmt.Errorf("Backend does not support compression")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heal
|
||||||
|
healCfg, err := heal.LookupConfig(s[config.HealSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to apply heal config: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crawler
|
||||||
|
crawlerCfg, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to apply crawler config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply configurations.
|
||||||
|
// We should not fail after this.
|
||||||
|
globalAPIConfig.init(apiConfig, globalAPIConfig.setDriveCount)
|
||||||
|
|
||||||
|
globalCompressConfigMu.Lock()
|
||||||
|
globalCompressConfig = cmpCfg
|
||||||
|
globalCompressConfigMu.Unlock()
|
||||||
|
|
||||||
|
globalHealConfigMu.Lock()
|
||||||
|
globalHealConfig = healCfg
|
||||||
|
globalHealConfigMu.Unlock()
|
||||||
|
|
||||||
|
logger.LogIf(ctx, crawlerSleeper.Update(crawlerCfg.Delay, crawlerCfg.MaxWait))
|
||||||
|
|
||||||
|
// Update all dynamic config values in memory.
|
||||||
|
globalServerConfigMu.Lock()
|
||||||
|
defer globalServerConfigMu.Unlock()
|
||||||
|
if globalServerConfig != nil {
|
||||||
|
for k := range config.SubSystemsDynamic {
|
||||||
|
globalServerConfig[k] = s[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help - return sub-system level help
|
// Help - return sub-system level help
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ func mkdirAllIgnorePerm(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// It is possible in kubernetes like deployments this directory
|
// It is possible in kubernetes like deployments this directory
|
||||||
// is already mounted and is not writable, ignore any write errors.
|
// is already mounted and is not writable, ignore any write errors.
|
||||||
if os.IsPermission(err) {
|
if osIsPermission(err) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -28,7 +27,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
etcd "go.etcd.io/etcd/v3/clientv3"
|
etcd "go.etcd.io/etcd/clientv3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
|
func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
|
||||||
@@ -194,7 +193,7 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
|||||||
// Config is already encrypted with right keys
|
// Config is already encrypted with right keys
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return errors.New("config data not in plain-text form or encrypted")
|
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
|
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||||
@@ -274,7 +273,7 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
|
|||||||
// Config is already encrypted with right keys
|
// Config is already encrypted with right keys
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return errors.New("config data not in plain-text form or encrypted")
|
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
|
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||||
|
|||||||
+31
-31
@@ -74,7 +74,7 @@ func migrateConfig() error {
|
|||||||
// Load only config version information.
|
// Load only config version information.
|
||||||
version, err := GetVersion(getConfigFile())
|
version, err := GetVersion(getConfigFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -243,7 +243,7 @@ func purgeV1() error {
|
|||||||
|
|
||||||
cv1 := &configV1{}
|
cv1 := &configV1{}
|
||||||
_, err := Load(configFile, cv1)
|
_, err := Load(configFile, cv1)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘1’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘1’. %w", err)
|
||||||
@@ -264,7 +264,7 @@ func migrateV2ToV3() error {
|
|||||||
|
|
||||||
cv2 := &configV2{}
|
cv2 := &configV2{}
|
||||||
_, err := Load(configFile, cv2)
|
_, err := Load(configFile, cv2)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘2’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘2’. %w", err)
|
||||||
@@ -323,7 +323,7 @@ func migrateV3ToV4() error {
|
|||||||
|
|
||||||
cv3 := &configV3{}
|
cv3 := &configV3{}
|
||||||
_, err := Load(configFile, cv3)
|
_, err := Load(configFile, cv3)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘3’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘3’. %w", err)
|
||||||
@@ -361,7 +361,7 @@ func migrateV4ToV5() error {
|
|||||||
|
|
||||||
cv4 := &configV4{}
|
cv4 := &configV4{}
|
||||||
_, err := Load(configFile, cv4)
|
_, err := Load(configFile, cv4)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘4’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘4’. %w", err)
|
||||||
@@ -402,7 +402,7 @@ func migrateV5ToV6() error {
|
|||||||
|
|
||||||
cv5 := &configV5{}
|
cv5 := &configV5{}
|
||||||
_, err := Load(configFile, cv5)
|
_, err := Load(configFile, cv5)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘5’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘5’. %w", err)
|
||||||
@@ -491,7 +491,7 @@ func migrateV6ToV7() error {
|
|||||||
|
|
||||||
cv6 := &configV6{}
|
cv6 := &configV6{}
|
||||||
_, err := Load(configFile, cv6)
|
_, err := Load(configFile, cv6)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘6’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘6’. %w", err)
|
||||||
@@ -547,7 +547,7 @@ func migrateV7ToV8() error {
|
|||||||
|
|
||||||
cv7 := &serverConfigV7{}
|
cv7 := &serverConfigV7{}
|
||||||
_, err := Load(configFile, cv7)
|
_, err := Load(configFile, cv7)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘7’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘7’. %w", err)
|
||||||
@@ -609,7 +609,7 @@ func migrateV8ToV9() error {
|
|||||||
|
|
||||||
cv8 := &serverConfigV8{}
|
cv8 := &serverConfigV8{}
|
||||||
_, err := Load(configFile, cv8)
|
_, err := Load(configFile, cv8)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘8’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘8’. %w", err)
|
||||||
@@ -679,7 +679,7 @@ func migrateV9ToV10() error {
|
|||||||
|
|
||||||
cv9 := &serverConfigV9{}
|
cv9 := &serverConfigV9{}
|
||||||
_, err := Load(configFile, cv9)
|
_, err := Load(configFile, cv9)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘9’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘9’. %w", err)
|
||||||
@@ -747,7 +747,7 @@ func migrateV10ToV11() error {
|
|||||||
|
|
||||||
cv10 := &serverConfigV10{}
|
cv10 := &serverConfigV10{}
|
||||||
_, err := Load(configFile, cv10)
|
_, err := Load(configFile, cv10)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘10’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘10’. %w", err)
|
||||||
@@ -818,7 +818,7 @@ func migrateV11ToV12() error {
|
|||||||
|
|
||||||
cv11 := &serverConfigV11{}
|
cv11 := &serverConfigV11{}
|
||||||
_, err := Load(configFile, cv11)
|
_, err := Load(configFile, cv11)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘11’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘11’. %w", err)
|
||||||
@@ -915,7 +915,7 @@ func migrateV12ToV13() error {
|
|||||||
|
|
||||||
cv12 := &serverConfigV12{}
|
cv12 := &serverConfigV12{}
|
||||||
_, err := Load(configFile, cv12)
|
_, err := Load(configFile, cv12)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘12’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘12’. %w", err)
|
||||||
@@ -995,7 +995,7 @@ func migrateV13ToV14() error {
|
|||||||
|
|
||||||
cv13 := &serverConfigV13{}
|
cv13 := &serverConfigV13{}
|
||||||
_, err := Load(configFile, cv13)
|
_, err := Load(configFile, cv13)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘13’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘13’. %w", err)
|
||||||
@@ -1080,7 +1080,7 @@ func migrateV14ToV15() error {
|
|||||||
|
|
||||||
cv14 := &serverConfigV14{}
|
cv14 := &serverConfigV14{}
|
||||||
_, err := Load(configFile, cv14)
|
_, err := Load(configFile, cv14)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘14’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘14’. %w", err)
|
||||||
@@ -1170,7 +1170,7 @@ func migrateV15ToV16() error {
|
|||||||
|
|
||||||
cv15 := &serverConfigV15{}
|
cv15 := &serverConfigV15{}
|
||||||
_, err := Load(configFile, cv15)
|
_, err := Load(configFile, cv15)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘15’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘15’. %w", err)
|
||||||
@@ -1260,7 +1260,7 @@ func migrateV16ToV17() error {
|
|||||||
|
|
||||||
cv16 := &serverConfigV16{}
|
cv16 := &serverConfigV16{}
|
||||||
_, err := Load(configFile, cv16)
|
_, err := Load(configFile, cv16)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘16’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘16’. %w", err)
|
||||||
@@ -1381,7 +1381,7 @@ func migrateV17ToV18() error {
|
|||||||
|
|
||||||
cv17 := &serverConfigV17{}
|
cv17 := &serverConfigV17{}
|
||||||
_, err := Load(configFile, cv17)
|
_, err := Load(configFile, cv17)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘17’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘17’. %w", err)
|
||||||
@@ -1483,7 +1483,7 @@ func migrateV18ToV19() error {
|
|||||||
|
|
||||||
cv18 := &serverConfigV18{}
|
cv18 := &serverConfigV18{}
|
||||||
_, err := Load(configFile, cv18)
|
_, err := Load(configFile, cv18)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘18’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘18’. %w", err)
|
||||||
@@ -1589,7 +1589,7 @@ func migrateV19ToV20() error {
|
|||||||
|
|
||||||
cv19 := &serverConfigV19{}
|
cv19 := &serverConfigV19{}
|
||||||
_, err := Load(configFile, cv19)
|
_, err := Load(configFile, cv19)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘18’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘18’. %w", err)
|
||||||
@@ -1694,7 +1694,7 @@ func migrateV20ToV21() error {
|
|||||||
|
|
||||||
cv20 := &serverConfigV20{}
|
cv20 := &serverConfigV20{}
|
||||||
_, err := Load(configFile, cv20)
|
_, err := Load(configFile, cv20)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘20’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘20’. %w", err)
|
||||||
@@ -1798,7 +1798,7 @@ func migrateV21ToV22() error {
|
|||||||
|
|
||||||
cv21 := &serverConfigV21{}
|
cv21 := &serverConfigV21{}
|
||||||
_, err := Load(configFile, cv21)
|
_, err := Load(configFile, cv21)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘21’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘21’. %w", err)
|
||||||
@@ -1902,7 +1902,7 @@ func migrateV22ToV23() error {
|
|||||||
|
|
||||||
cv22 := &serverConfigV22{}
|
cv22 := &serverConfigV22{}
|
||||||
_, err := Load(configFile, cv22)
|
_, err := Load(configFile, cv22)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘22’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘22’. %w", err)
|
||||||
@@ -2015,7 +2015,7 @@ func migrateV23ToV24() error {
|
|||||||
|
|
||||||
cv23 := &serverConfigV23{}
|
cv23 := &serverConfigV23{}
|
||||||
_, err := quick.LoadConfig(configFile, globalEtcdClient, cv23)
|
_, err := quick.LoadConfig(configFile, globalEtcdClient, cv23)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘23’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘23’. %w", err)
|
||||||
@@ -2128,7 +2128,7 @@ func migrateV24ToV25() error {
|
|||||||
|
|
||||||
cv24 := &serverConfigV24{}
|
cv24 := &serverConfigV24{}
|
||||||
_, err := quick.LoadConfig(configFile, globalEtcdClient, cv24)
|
_, err := quick.LoadConfig(configFile, globalEtcdClient, cv24)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘24’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘24’. %w", err)
|
||||||
@@ -2246,7 +2246,7 @@ func migrateV25ToV26() error {
|
|||||||
|
|
||||||
cv25 := &serverConfigV25{}
|
cv25 := &serverConfigV25{}
|
||||||
_, err := quick.LoadConfig(configFile, globalEtcdClient, cv25)
|
_, err := quick.LoadConfig(configFile, globalEtcdClient, cv25)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config version ‘25’. %w", err)
|
return fmt.Errorf("Unable to load config version ‘25’. %w", err)
|
||||||
@@ -2368,7 +2368,7 @@ func migrateV26ToV27() error {
|
|||||||
// in the new `logger` field
|
// in the new `logger` field
|
||||||
srvConfig := &serverConfigV27{}
|
srvConfig := &serverConfigV27{}
|
||||||
_, err := quick.LoadConfig(configFile, globalEtcdClient, srvConfig)
|
_, err := quick.LoadConfig(configFile, globalEtcdClient, srvConfig)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config file. %w", err)
|
return fmt.Errorf("Unable to load config file. %w", err)
|
||||||
@@ -2401,7 +2401,7 @@ func migrateV27ToV28() error {
|
|||||||
|
|
||||||
srvConfig := &serverConfigV28{}
|
srvConfig := &serverConfigV28{}
|
||||||
_, err := quick.LoadConfig(configFile, globalEtcdClient, srvConfig)
|
_, err := quick.LoadConfig(configFile, globalEtcdClient, srvConfig)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Unable to load config file. %w", err)
|
return fmt.Errorf("Unable to load config file. %w", err)
|
||||||
@@ -2452,17 +2452,17 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
|||||||
var config = &serverConfigV27{}
|
var config = &serverConfigV27{}
|
||||||
for _, cfgFile := range configFiles {
|
for _, cfgFile := range configFiles {
|
||||||
if _, err = Load(cfgFile, config); err != nil {
|
if _, err = Load(cfgFile, config); err != nil {
|
||||||
if !os.IsNotExist(err) && !os.IsPermission(err) {
|
if !osIsNotExist(err) && !osIsPermission(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if os.IsPermission(err) {
|
if osIsPermission(err) {
|
||||||
logger.Info("Older config found but not readable %s, proceeding to initialize new config anyways", err)
|
logger.Info("Older config found but not readable %s, proceeding to initialize new config anyways", err)
|
||||||
}
|
}
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if osIsNotExist(err) || osIsPermission(err) {
|
||||||
// Initialize the server config, if no config exists.
|
// Initialize the server config, if no config exists.
|
||||||
return newSrvConfig(objAPI)
|
return newSrvConfig(objAPI)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func TestServerConfigMigrateV1(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if config v1 is removed from filesystem
|
// Check if config v1 is removed from filesystem
|
||||||
if _, err := os.Stat(configPath); err == nil || !os.IsNotExist(err) {
|
if _, err := os.Stat(configPath); err == nil || !osIsNotExist(err) {
|
||||||
t.Fatal("Config V1 file is not purged")
|
t.Fatal("Config V1 file is not purged")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+60
-6
@@ -31,15 +31,26 @@ import (
|
|||||||
const (
|
const (
|
||||||
apiRequestsMax = "requests_max"
|
apiRequestsMax = "requests_max"
|
||||||
apiRequestsDeadline = "requests_deadline"
|
apiRequestsDeadline = "requests_deadline"
|
||||||
apiReadyDeadline = "ready_deadline"
|
apiClusterDeadline = "cluster_deadline"
|
||||||
apiCorsAllowOrigin = "cors_allow_origin"
|
apiCorsAllowOrigin = "cors_allow_origin"
|
||||||
apiRemoteTransportDeadline = "remote_transport_deadline"
|
apiRemoteTransportDeadline = "remote_transport_deadline"
|
||||||
|
apiListQuorum = "list_quorum"
|
||||||
|
apiExtendListCacheLife = "extend_list_cache_life"
|
||||||
|
|
||||||
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
|
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
|
||||||
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
|
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
|
||||||
EnvAPIReadyDeadline = "MINIO_API_READY_DEADLINE"
|
EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE"
|
||||||
EnvAPICorsAllowOrigin = "MINIO_API_CORS_ALLOW_ORIGIN"
|
EnvAPICorsAllowOrigin = "MINIO_API_CORS_ALLOW_ORIGIN"
|
||||||
EnvAPIRemoteTransportDeadline = "MINIO_API_REMOTE_TRANSPORT_DEADLINE"
|
EnvAPIRemoteTransportDeadline = "MINIO_API_REMOTE_TRANSPORT_DEADLINE"
|
||||||
|
EnvAPIListQuorum = "MINIO_API_LIST_QUORUM"
|
||||||
|
EnvAPIExtendListCacheLife = "MINIO_API_EXTEND_LIST_CACHE_LIFE"
|
||||||
|
EnvAPISecureCiphers = "MINIO_API_SECURE_CIPHERS"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated key and ENVs
|
||||||
|
const (
|
||||||
|
apiReadyDeadline = "ready_deadline"
|
||||||
|
EnvAPIReadyDeadline = "MINIO_API_READY_DEADLINE"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultKVS - default storage class config
|
// DefaultKVS - default storage class config
|
||||||
@@ -54,7 +65,7 @@ var (
|
|||||||
Value: "10s",
|
Value: "10s",
|
||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: apiReadyDeadline,
|
Key: apiClusterDeadline,
|
||||||
Value: "10s",
|
Value: "10s",
|
||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
@@ -65,6 +76,14 @@ var (
|
|||||||
Key: apiRemoteTransportDeadline,
|
Key: apiRemoteTransportDeadline,
|
||||||
Value: "2h",
|
Value: "2h",
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: apiListQuorum,
|
||||||
|
Value: "optimal",
|
||||||
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: apiExtendListCacheLife,
|
||||||
|
Value: "0s",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,9 +91,11 @@ var (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
RequestsMax int `json:"requests_max"`
|
RequestsMax int `json:"requests_max"`
|
||||||
RequestsDeadline time.Duration `json:"requests_deadline"`
|
RequestsDeadline time.Duration `json:"requests_deadline"`
|
||||||
ReadyDeadline time.Duration `json:"ready_deadline"`
|
ClusterDeadline time.Duration `json:"cluster_deadline"`
|
||||||
CorsAllowOrigin []string `json:"cors_allow_origin"`
|
CorsAllowOrigin []string `json:"cors_allow_origin"`
|
||||||
RemoteTransportDeadline time.Duration `json:"remote_transport_deadline"`
|
RemoteTransportDeadline time.Duration `json:"remote_transport_deadline"`
|
||||||
|
ListQuorum string `json:"list_strict_quorum"`
|
||||||
|
ExtendListLife time.Duration `json:"extend_list_cache_life"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
||||||
@@ -88,8 +109,27 @@ func (sCfg *Config) UnmarshalJSON(data []byte) error {
|
|||||||
return json.Unmarshal(data, &aux)
|
return json.Unmarshal(data, &aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetListQuorum interprets list quorum values and returns appropriate
|
||||||
|
// acceptable quorum expected for list operations
|
||||||
|
func (sCfg Config) GetListQuorum() int {
|
||||||
|
switch sCfg.ListQuorum {
|
||||||
|
case "reduced":
|
||||||
|
return 2
|
||||||
|
case "disk":
|
||||||
|
// smallest possible value, generally meant for testing.
|
||||||
|
return 1
|
||||||
|
case "strict":
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// Defaults to 3 drives per set, defaults to "optimal" value
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
// LookupConfig - lookup api config and override with valid environment settings if any.
|
// LookupConfig - lookup api config and override with valid environment settings if any.
|
||||||
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
||||||
|
// remove this since we have removed this already.
|
||||||
|
kvs.Delete(apiReadyDeadline)
|
||||||
|
|
||||||
if err = config.CheckValidKeys(config.APISubSys, kvs, DefaultKVS); err != nil {
|
if err = config.CheckValidKeys(config.APISubSys, kvs, DefaultKVS); err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
@@ -109,7 +149,7 @@ func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
|||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
readyDeadline, err := time.ParseDuration(env.Get(EnvAPIReadyDeadline, kvs.Get(apiReadyDeadline)))
|
clusterDeadline, err := time.ParseDuration(env.Get(EnvAPIClusterDeadline, kvs.Get(apiClusterDeadline)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
@@ -121,11 +161,25 @@ func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
|||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listQuorum := env.Get(EnvAPIListQuorum, kvs.Get(apiListQuorum))
|
||||||
|
switch listQuorum {
|
||||||
|
case "strict", "optimal", "reduced", "disk":
|
||||||
|
default:
|
||||||
|
return cfg, errors.New("invalid value for list strict quorum")
|
||||||
|
}
|
||||||
|
|
||||||
|
listLife, err := time.ParseDuration(env.Get(EnvAPIExtendListCacheLife, kvs.Get(apiExtendListCacheLife)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
RequestsMax: requestsMax,
|
RequestsMax: requestsMax,
|
||||||
RequestsDeadline: requestsDeadline,
|
RequestsDeadline: requestsDeadline,
|
||||||
ReadyDeadline: readyDeadline,
|
ClusterDeadline: clusterDeadline,
|
||||||
CorsAllowOrigin: corsAllowOrigin,
|
CorsAllowOrigin: corsAllowOrigin,
|
||||||
RemoteTransportDeadline: remoteTransportDeadline,
|
RemoteTransportDeadline: remoteTransportDeadline,
|
||||||
|
ListQuorum: listQuorum,
|
||||||
|
ExtendListLife: listLife,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+21
-10
@@ -28,16 +28,17 @@ import (
|
|||||||
|
|
||||||
// Config represents cache config settings
|
// Config represents cache config settings
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Enabled bool `json:"-"`
|
Enabled bool `json:"-"`
|
||||||
Drives []string `json:"drives"`
|
Drives []string `json:"drives"`
|
||||||
Expiry int `json:"expiry"`
|
Expiry int `json:"expiry"`
|
||||||
MaxUse int `json:"maxuse"`
|
MaxUse int `json:"maxuse"`
|
||||||
Quota int `json:"quota"`
|
Quota int `json:"quota"`
|
||||||
Exclude []string `json:"exclude"`
|
Exclude []string `json:"exclude"`
|
||||||
After int `json:"after"`
|
After int `json:"after"`
|
||||||
WatermarkLow int `json:"watermark_low"`
|
WatermarkLow int `json:"watermark_low"`
|
||||||
WatermarkHigh int `json:"watermark_high"`
|
WatermarkHigh int `json:"watermark_high"`
|
||||||
Range bool `json:"range"`
|
Range bool `json:"range"`
|
||||||
|
CommitWriteback bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON - implements JSON unmarshal interface for unmarshalling
|
// UnmarshalJSON - implements JSON unmarshal interface for unmarshalling
|
||||||
@@ -152,3 +153,13 @@ func parseCacheExcludes(excludes string) ([]string, error) {
|
|||||||
|
|
||||||
return excludesSlice, nil
|
return excludesSlice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseCacheCommitMode(commitStr string) (bool, error) {
|
||||||
|
switch strings.ToLower(commitStr) {
|
||||||
|
case "writeback":
|
||||||
|
return true, nil
|
||||||
|
case "writethrough":
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, config.ErrInvalidCacheCommitValue(nil).Msg("cache commit value must be `writeback` or `writethrough`")
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+6
@@ -74,5 +74,11 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: Commit,
|
||||||
|
Description: `set to control cache commit behavior, defaults to "writethrough"`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Vendored
+17
@@ -35,6 +35,7 @@ const (
|
|||||||
WatermarkLow = "watermark_low"
|
WatermarkLow = "watermark_low"
|
||||||
WatermarkHigh = "watermark_high"
|
WatermarkHigh = "watermark_high"
|
||||||
Range = "range"
|
Range = "range"
|
||||||
|
Commit = "commit"
|
||||||
|
|
||||||
EnvCacheDrives = "MINIO_CACHE_DRIVES"
|
EnvCacheDrives = "MINIO_CACHE_DRIVES"
|
||||||
EnvCacheExclude = "MINIO_CACHE_EXCLUDE"
|
EnvCacheExclude = "MINIO_CACHE_EXCLUDE"
|
||||||
@@ -45,6 +46,7 @@ const (
|
|||||||
EnvCacheWatermarkLow = "MINIO_CACHE_WATERMARK_LOW"
|
EnvCacheWatermarkLow = "MINIO_CACHE_WATERMARK_LOW"
|
||||||
EnvCacheWatermarkHigh = "MINIO_CACHE_WATERMARK_HIGH"
|
EnvCacheWatermarkHigh = "MINIO_CACHE_WATERMARK_HIGH"
|
||||||
EnvCacheRange = "MINIO_CACHE_RANGE"
|
EnvCacheRange = "MINIO_CACHE_RANGE"
|
||||||
|
EnvCacheCommit = "MINIO_CACHE_COMMIT"
|
||||||
|
|
||||||
EnvCacheEncryptionMasterKey = "MINIO_CACHE_ENCRYPTION_MASTER_KEY"
|
EnvCacheEncryptionMasterKey = "MINIO_CACHE_ENCRYPTION_MASTER_KEY"
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ const (
|
|||||||
DefaultAfter = "0"
|
DefaultAfter = "0"
|
||||||
DefaultWaterMarkLow = "70"
|
DefaultWaterMarkLow = "70"
|
||||||
DefaultWaterMarkHigh = "80"
|
DefaultWaterMarkHigh = "80"
|
||||||
|
DefaultCacheCommit = "writethrough"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultKVS - default KV settings for caching.
|
// DefaultKVS - default KV settings for caching.
|
||||||
@@ -90,6 +93,10 @@ var (
|
|||||||
Key: Range,
|
Key: Range,
|
||||||
Value: config.EnableOn,
|
Value: config.EnableOn,
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: Commit,
|
||||||
|
Value: DefaultCacheCommit,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -210,6 +217,16 @@ func LookupConfig(kvs config.KVS) (Config, error) {
|
|||||||
}
|
}
|
||||||
cfg.Range = rng
|
cfg.Range = rng
|
||||||
}
|
}
|
||||||
|
if commit := env.Get(EnvCacheCommit, kvs.Get(Commit)); commit != "" {
|
||||||
|
cfg.CommitWriteback, err = parseCacheCommitMode(commit)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if cfg.After > 0 && cfg.CommitWriteback {
|
||||||
|
err := errors.New("cache after cannot be used with commit writeback")
|
||||||
|
return cfg, config.ErrInvalidCacheSetting(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-18
@@ -76,6 +76,7 @@ const (
|
|||||||
KmsKesSubSys = "kms_kes"
|
KmsKesSubSys = "kms_kes"
|
||||||
LoggerWebhookSubSys = "logger_webhook"
|
LoggerWebhookSubSys = "logger_webhook"
|
||||||
AuditWebhookSubSys = "audit_webhook"
|
AuditWebhookSubSys = "audit_webhook"
|
||||||
|
HealSubSys = "heal"
|
||||||
CrawlerSubSys = "crawler"
|
CrawlerSubSys = "crawler"
|
||||||
|
|
||||||
// Add new constants here if you add new fields to config.
|
// Add new constants here if you add new fields to config.
|
||||||
@@ -98,7 +99,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SubSystems - all supported sub-systems
|
// SubSystems - all supported sub-systems
|
||||||
var SubSystems = set.CreateStringSet([]string{
|
var SubSystems = set.CreateStringSet(
|
||||||
CredentialsSubSys,
|
CredentialsSubSys,
|
||||||
RegionSubSys,
|
RegionSubSys,
|
||||||
EtcdSubSys,
|
EtcdSubSys,
|
||||||
@@ -114,6 +115,7 @@ var SubSystems = set.CreateStringSet([]string{
|
|||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
CrawlerSubSys,
|
CrawlerSubSys,
|
||||||
|
HealSubSys,
|
||||||
NotifyAMQPSubSys,
|
NotifyAMQPSubSys,
|
||||||
NotifyESSubSys,
|
NotifyESSubSys,
|
||||||
NotifyKafkaSubSys,
|
NotifyKafkaSubSys,
|
||||||
@@ -124,7 +126,15 @@ var SubSystems = set.CreateStringSet([]string{
|
|||||||
NotifyPostgresSubSys,
|
NotifyPostgresSubSys,
|
||||||
NotifyRedisSubSys,
|
NotifyRedisSubSys,
|
||||||
NotifyWebhookSubSys,
|
NotifyWebhookSubSys,
|
||||||
}...)
|
)
|
||||||
|
|
||||||
|
// SubSystemsDynamic - all sub-systems that have dynamic config.
|
||||||
|
var SubSystemsDynamic = set.CreateStringSet(
|
||||||
|
APISubSys,
|
||||||
|
CompressionSubSys,
|
||||||
|
CrawlerSubSys,
|
||||||
|
HealSubSys,
|
||||||
|
)
|
||||||
|
|
||||||
// SubSystemsSingleTargets - subsystems which only support single target.
|
// SubSystemsSingleTargets - subsystems which only support single target.
|
||||||
var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
||||||
@@ -140,6 +150,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
|||||||
PolicyOPASubSys,
|
PolicyOPASubSys,
|
||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
|
HealSubSys,
|
||||||
CrawlerSubSys,
|
CrawlerSubSys,
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
@@ -267,6 +278,16 @@ func (kvs KVS) Get(key string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete - deletes the key if present from the KV list.
|
||||||
|
func (kvs *KVS) Delete(key string) {
|
||||||
|
for i, kv := range *kvs {
|
||||||
|
if kv.Key == key {
|
||||||
|
*kvs = append((*kvs)[:i], (*kvs)[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Lookup - lookup a key in a list of KVS
|
// Lookup - lookup a key in a list of KVS
|
||||||
func (kvs KVS) Lookup(key string) (string, bool) {
|
func (kvs KVS) Lookup(key string) (string, bool) {
|
||||||
for _, kv := range kvs {
|
for _, kv := range kvs {
|
||||||
@@ -299,25 +320,29 @@ func (c Config) DelFrom(r io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom - implements io.ReaderFrom interface
|
// ReadConfig - read content from input and write into c.
|
||||||
func (c Config) ReadFrom(r io.Reader) (int64, error) {
|
// Returns whether all parameters were dynamic.
|
||||||
|
func (c Config) ReadConfig(r io.Reader) (dynOnly bool, err error) {
|
||||||
var n int
|
var n int
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
dynOnly = true
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// Skip any empty lines, or comment like characters
|
// Skip any empty lines, or comment like characters
|
||||||
text := scanner.Text()
|
text := scanner.Text()
|
||||||
if text == "" || strings.HasPrefix(text, KvComment) {
|
if text == "" || strings.HasPrefix(text, KvComment) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := c.SetKVS(text, DefaultKVS); err != nil {
|
dynamic, err := c.SetKVS(text, DefaultKVS)
|
||||||
return 0, err
|
if err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
dynOnly = dynOnly && dynamic
|
||||||
n += len(text)
|
n += len(text)
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return 0, err
|
return false, err
|
||||||
}
|
}
|
||||||
return int64(n), nil
|
return dynOnly, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type configWriteTo struct {
|
type configWriteTo struct {
|
||||||
@@ -608,26 +633,27 @@ func (c Config) Clone() Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetKVS - set specific key values per sub-system.
|
// SetKVS - set specific key values per sub-system.
|
||||||
func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
func (c Config) SetKVS(s string, defaultKVS map[string]KVS) (dynamic bool, err error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return Errorf("input arguments cannot be empty")
|
return false, Errorf("input arguments cannot be empty")
|
||||||
}
|
}
|
||||||
inputs := strings.SplitN(s, KvSpaceSeparator, 2)
|
inputs := strings.SplitN(s, KvSpaceSeparator, 2)
|
||||||
if len(inputs) <= 1 {
|
if len(inputs) <= 1 {
|
||||||
return Errorf("invalid number of arguments '%s'", s)
|
return false, Errorf("invalid number of arguments '%s'", s)
|
||||||
}
|
}
|
||||||
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
|
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
|
||||||
if len(subSystemValue) == 0 {
|
if len(subSystemValue) == 0 {
|
||||||
return Errorf("invalid number of arguments %s", s)
|
return false, Errorf("invalid number of arguments %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !SubSystems.Contains(subSystemValue[0]) {
|
if !SubSystems.Contains(subSystemValue[0]) {
|
||||||
return Errorf("unknown sub-system %s", s)
|
return false, Errorf("unknown sub-system %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
|
if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
|
||||||
return Errorf("sub-system '%s' only supports single target", subSystemValue[0])
|
return false, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
|
||||||
}
|
}
|
||||||
|
dynamic = SubSystemsDynamic.Contains(subSystemValue[0])
|
||||||
|
|
||||||
tgt := Default
|
tgt := Default
|
||||||
subSys := subSystemValue[0]
|
subSys := subSystemValue[0]
|
||||||
@@ -637,7 +663,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
|||||||
|
|
||||||
fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys())
|
fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys())
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return Errorf("sub-system '%s' cannot have empty keys", subSys)
|
return false, Errorf("sub-system '%s' cannot have empty keys", subSys)
|
||||||
}
|
}
|
||||||
|
|
||||||
var kvs = KVS{}
|
var kvs = KVS{}
|
||||||
@@ -660,7 +686,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
|||||||
kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
|
kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return Errorf("key '%s', cannot have empty value", kv[0])
|
return false, Errorf("key '%s', cannot have empty value", kv[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := kvs.Lookup(Enable)
|
_, ok := kvs.Lookup(Enable)
|
||||||
@@ -710,11 +736,11 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
|||||||
// Return error only if the
|
// Return error only if the
|
||||||
// key is enabled, for state=off
|
// key is enabled, for state=off
|
||||||
// let it be empty.
|
// let it be empty.
|
||||||
return Errorf(
|
return false, Errorf(
|
||||||
"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
|
"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
|
||||||
hkv.Key, subSys, subSys)
|
hkv.Key, subSys, subSys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c[subSys][tgt] = currKVS
|
c[subSys][tgt] = currKVS
|
||||||
return nil
|
return dynamic, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,38 +17,56 @@
|
|||||||
package crawler
|
package crawler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
|
"github.com/minio/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compression environment variables
|
// Compression environment variables
|
||||||
const (
|
const (
|
||||||
BitrotScan = "bitrotscan"
|
Delay = "delay"
|
||||||
|
MaxWait = "max_wait"
|
||||||
|
|
||||||
|
EnvDelay = "MINIO_CRAWLER_DELAY"
|
||||||
|
EnvMaxWait = "MINIO_CRAWLER_MAX_WAIT"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config represents the crawler settings.
|
// Config represents the heal settings.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Bitrot will perform bitrot scan on local disk when checking objects.
|
// Delay is the sleep multiplier.
|
||||||
Bitrot bool `json:"bitrotscan"`
|
Delay float64 `json:"delay"`
|
||||||
|
// MaxWait is maximum wait time between operations
|
||||||
|
MaxWait time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultKVS - default KV config for crawler settings
|
// DefaultKVS - default KV config for heal settings
|
||||||
DefaultKVS = config.KVS{
|
DefaultKVS = config.KVS{
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: BitrotScan,
|
Key: Delay,
|
||||||
Value: config.EnableOff,
|
Value: "10",
|
||||||
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: MaxWait,
|
||||||
|
Value: "15s",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help provides help for config values
|
// Help provides help for config values
|
||||||
Help = config.HelpKVS{
|
Help = config.HelpKVS{
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: BitrotScan,
|
Key: Delay,
|
||||||
Description: `perform bitrot scan on disks when checking objects during crawl`,
|
Description: `crawler delay multiplier, defaults to '10.0'`,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "on|off",
|
Type: "float",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: MaxWait,
|
||||||
|
Description: `maximum wait time between operations, defaults to '15s'`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "duration",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -58,10 +76,13 @@ func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
|||||||
if err = config.CheckValidKeys(config.CrawlerSubSys, kvs, DefaultKVS); err != nil {
|
if err = config.CheckValidKeys(config.CrawlerSubSys, kvs, DefaultKVS); err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
bitrot := kvs.Get(BitrotScan)
|
cfg.Delay, err = strconv.ParseFloat(env.Get(EnvDelay, kvs.Get(Delay)), 64)
|
||||||
if bitrot != config.EnableOn && bitrot != config.EnableOff {
|
if err != nil {
|
||||||
return cfg, errors.New(BitrotScan + ": must be 'on' or 'off'")
|
return cfg, err
|
||||||
|
}
|
||||||
|
cfg.MaxWait, err = time.ParseDuration(env.Get(EnvMaxWait, kvs.Get(MaxWait)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
}
|
}
|
||||||
cfg.Bitrot = bitrot == config.EnableOn
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"go.etcd.io/etcd/v3/clientv3"
|
"go.etcd.io/etcd/clientv3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
|
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
@@ -78,28 +80,39 @@ func (c *OperatorDNS) Put(bucket string) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
e, err := c.endpoint(bucket, false)
|
e, err := c.endpoint(bucket, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return newError(bucket, err)
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, e, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, e, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return newError(bucket, err)
|
||||||
}
|
}
|
||||||
if err = c.addAuthHeader(req); err != nil {
|
if err = c.addAuthHeader(req); err != nil {
|
||||||
return err
|
return newError(bucket, err)
|
||||||
}
|
}
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if derr := c.Delete(bucket); derr != nil {
|
if derr := c.Delete(bucket); derr != nil {
|
||||||
return derr
|
return newError(bucket, derr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var errorStringBuilder strings.Builder
|
||||||
|
io.Copy(&errorStringBuilder, io.LimitReader(resp.Body, resp.ContentLength))
|
||||||
xhttp.DrainBody(resp.Body)
|
xhttp.DrainBody(resp.Body)
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("request to create the service for bucket %s, failed with status %s", bucket, resp.Status)
|
errorString := errorStringBuilder.String()
|
||||||
|
return newError(bucket, fmt.Errorf("service create for bucket %s, failed with status %s, error %s", bucket, resp.Status, errorString))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newError(bucket string, err error) error {
|
||||||
|
e := Error{bucket, err}
|
||||||
|
if strings.Contains(err.Error(), "invalid bucket name") {
|
||||||
|
return ErrInvalidBucketName(e)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
// Delete - Removes DNS entries added in Put().
|
// Delete - Removes DNS entries added in Put().
|
||||||
func (c *OperatorDNS) Delete(bucket string) error {
|
func (c *OperatorDNS) Delete(bucket string) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultOperatorContextTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultOperatorContextTimeout)
|
||||||
|
|||||||
@@ -16,6 +16,22 @@
|
|||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
|
// Error - DNS related errors error.
|
||||||
|
type Error struct {
|
||||||
|
Bucket string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidBucketName for buckets with invalid name
|
||||||
|
type ErrInvalidBucketName Error
|
||||||
|
|
||||||
|
func (e ErrInvalidBucketName) Error() string {
|
||||||
|
return "invalid bucket name error: " + e.Err.Error()
|
||||||
|
}
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return "dns related error: " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
// Store dns record store
|
// Store dns record store
|
||||||
type Store interface {
|
type Store interface {
|
||||||
Put(bucket string) error
|
Put(bucket string) error
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package config
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -111,18 +110,16 @@ func ErrorToErr(err error) Err {
|
|||||||
case *net.OpError:
|
case *net.OpError:
|
||||||
return ErrPortAccess(err).Msg("Insufficient permissions to use specified port")
|
return ErrPortAccess(err).Msg("Insufficient permissions to use specified port")
|
||||||
}
|
}
|
||||||
return ErrNoPermissionsToAccessDirFiles(err).Msg("Insufficient permissions to access path")
|
|
||||||
} else if errors.Is(err, io.ErrUnexpectedEOF) {
|
|
||||||
return ErrUnexpectedDataContent(err)
|
|
||||||
} else {
|
|
||||||
// Failed to identify what type of error this, return a simple UI error
|
|
||||||
return Err{msg: err.Error()}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Failed to identify what type of error this, return a simple UI error
|
||||||
|
return Err{msg: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FmtError converts a fatal error message to a more clear error
|
// FmtError converts a fatal error message to a more clear error
|
||||||
// using some colors
|
// using some colors
|
||||||
func FmtError(introMsg string, err error, jsonFlag bool) string {
|
func FmtError(introMsg string, err error, jsonFlag bool) string {
|
||||||
|
|
||||||
renderedTxt := ""
|
renderedTxt := ""
|
||||||
uiErr := ErrorToErr(err)
|
uiErr := ErrorToErr(err)
|
||||||
// JSON print
|
// JSON print
|
||||||
|
|||||||
+11
-12
@@ -102,6 +102,17 @@ var (
|
|||||||
"MINIO_CACHE_RANGE: Valid expected value is `on` or `off`",
|
"MINIO_CACHE_RANGE: Valid expected value is `on` or `off`",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ErrInvalidCacheCommitValue = newErrFn(
|
||||||
|
"Invalid cache commit value",
|
||||||
|
"Please check the passed value",
|
||||||
|
"MINIO_CACHE_COMMIT: Valid expected value is `writeback` or `writethrough`",
|
||||||
|
)
|
||||||
|
|
||||||
|
ErrInvalidCacheSetting = newErrFn(
|
||||||
|
"Incompatible cache setting",
|
||||||
|
"Please check the passed value",
|
||||||
|
"MINIO_CACHE_AFTER cannot be used with MINIO_CACHE_COMMIT setting",
|
||||||
|
)
|
||||||
ErrInvalidRotatingCredentialsBackendEncrypted = newErrFn(
|
ErrInvalidRotatingCredentialsBackendEncrypted = newErrFn(
|
||||||
"Invalid rotating credentials",
|
"Invalid rotating credentials",
|
||||||
"Please set correct rotating credentials in the environment for decryption",
|
"Please set correct rotating credentials in the environment for decryption",
|
||||||
@@ -205,12 +216,6 @@ Example 1:
|
|||||||
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions`,
|
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrNoPermissionsToAccessDirFiles = newErrFn(
|
|
||||||
"Missing permissions to access the specified path",
|
|
||||||
"Please ensure the specified path can be accessed",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
ErrSSLUnexpectedError = newErrFn(
|
ErrSSLUnexpectedError = newErrFn(
|
||||||
"Invalid TLS certificate",
|
"Invalid TLS certificate",
|
||||||
"Please check the content of your certificate data",
|
"Please check the content of your certificate data",
|
||||||
@@ -247,12 +252,6 @@ Example 1:
|
|||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrUnexpectedDataContent = newErrFn(
|
|
||||||
"Unexpected data content",
|
|
||||||
"Please contact MinIO at https://slack.min.io",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
ErrUnexpectedError = newErrFn(
|
ErrUnexpectedError = newErrFn(
|
||||||
"Unexpected error",
|
"Unexpected error",
|
||||||
"Please contact MinIO at https://slack.min.io",
|
"Please contact MinIO at https://slack.min.io",
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"go.etcd.io/etcd/v3/clientv3"
|
"go.etcd.io/etcd/clientv3"
|
||||||
"go.etcd.io/etcd/v3/clientv3/namespace"
|
"go.etcd.io/etcd/clientv3/namespace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package heal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/config"
|
||||||
|
"github.com/minio/minio/pkg/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compression environment variables
|
||||||
|
const (
|
||||||
|
Bitrot = "bitrotscan"
|
||||||
|
Sleep = "max_sleep"
|
||||||
|
IOCount = "max_io"
|
||||||
|
|
||||||
|
EnvBitrot = "MINIO_HEAL_BITROTSCAN"
|
||||||
|
EnvSleep = "MINIO_HEAL_MAX_SLEEP"
|
||||||
|
EnvIOCount = "MINIO_HEAL_MAX_IO"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents the heal settings.
|
||||||
|
type Config struct {
|
||||||
|
// Bitrot will perform bitrot scan on local disk when checking objects.
|
||||||
|
Bitrot bool `json:"bitrotscan"`
|
||||||
|
// maximum sleep duration between objects to slow down heal operation.
|
||||||
|
Sleep time.Duration `json:"sleep"`
|
||||||
|
IOCount int `json:"iocount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultKVS - default KV config for heal settings
|
||||||
|
DefaultKVS = config.KVS{
|
||||||
|
config.KV{
|
||||||
|
Key: Bitrot,
|
||||||
|
Value: config.EnableOff,
|
||||||
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: Sleep,
|
||||||
|
Value: "1s",
|
||||||
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: IOCount,
|
||||||
|
Value: "10",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help provides help for config values
|
||||||
|
Help = config.HelpKVS{
|
||||||
|
config.HelpKV{
|
||||||
|
Key: Bitrot,
|
||||||
|
Description: `perform bitrot scan on disks when checking objects during crawl`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "on|off",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: Sleep,
|
||||||
|
Description: `maximum sleep duration between objects to slow down heal operation. eg. 2s`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "duration",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: IOCount,
|
||||||
|
Description: `maximum IO requests allowed between objects to slow down heal operation. eg. 3`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// LookupConfig - lookup config and override with valid environment settings if any.
|
||||||
|
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
||||||
|
if err = config.CheckValidKeys(config.HealSubSys, kvs, DefaultKVS); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
cfg.Bitrot, err = config.ParseBool(env.Get(EnvBitrot, kvs.Get(Bitrot)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, fmt.Errorf("'heal:bitrotscan' value invalid: %w", err)
|
||||||
|
}
|
||||||
|
cfg.Sleep, err = time.ParseDuration(env.Get(EnvSleep, kvs.Get(Sleep)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, fmt.Errorf("'heal:max_sleep' value invalid: %w", err)
|
||||||
|
}
|
||||||
|
cfg.IOCount, err = strconv.Atoi(env.Get(EnvIOCount, kvs.Get(IOCount)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, fmt.Errorf("'heal:max_io' value invalid: %w", err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
@@ -340,6 +340,12 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "sentence",
|
Type: "sentence",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: target.PostgresMaxOpenConnections,
|
||||||
|
Description: "To set the maximum number of open connections to the database. The value is set to `2` by default.",
|
||||||
|
Optional: true,
|
||||||
|
Type: "number",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
HelpMySQL = config.HelpKVS{
|
HelpMySQL = config.HelpKVS{
|
||||||
@@ -377,6 +383,12 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "sentence",
|
Type: "sentence",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: target.MySQLMaxOpenConnections,
|
||||||
|
Description: "To set the maximum number of open connections to the database. The value is set to `2` by default.",
|
||||||
|
Optional: true,
|
||||||
|
Type: "number",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
HelpNATS = config.HelpKVS{
|
HelpNATS = config.HelpKVS{
|
||||||
|
|||||||
@@ -357,6 +357,10 @@ func SetNotifyPostgres(s config.Config, psqName string, cfg target.PostgreSQLArg
|
|||||||
Key: target.PostgresQueueLimit,
|
Key: target.PostgresQueueLimit,
|
||||||
Value: strconv.Itoa(int(cfg.QueueLimit)),
|
Value: strconv.Itoa(int(cfg.QueueLimit)),
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: target.PostgresMaxOpenConnections,
|
||||||
|
Value: strconv.Itoa(cfg.MaxOpenConnections),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -554,6 +558,10 @@ func SetNotifyMySQL(s config.Config, sqlName string, cfg target.MySQLArgs) error
|
|||||||
Key: target.MySQLQueueLimit,
|
Key: target.MySQLQueueLimit,
|
||||||
Value: strconv.Itoa(int(cfg.QueueLimit)),
|
Value: strconv.Itoa(int(cfg.QueueLimit)),
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: target.MySQLMaxOpenConnections,
|
||||||
|
Value: strconv.Itoa(cfg.MaxOpenConnections),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+43
-12
@@ -807,6 +807,10 @@ var (
|
|||||||
Key: target.MySQLQueueLimit,
|
Key: target.MySQLQueueLimit,
|
||||||
Value: "0",
|
Value: "0",
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: target.MySQLMaxOpenConnections,
|
||||||
|
Value: "2",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -855,13 +859,25 @@ func GetNotifyMySQL(mysqlKVS map[string]config.KVS) (map[string]target.MySQLArgs
|
|||||||
if k != config.Default {
|
if k != config.Default {
|
||||||
queueDirEnv = queueDirEnv + config.Default + k
|
queueDirEnv = queueDirEnv + config.Default + k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxOpenConnectionsEnv := target.EnvMySQLMaxOpenConnections
|
||||||
|
if k != config.Default {
|
||||||
|
maxOpenConnectionsEnv = maxOpenConnectionsEnv + config.Default + k
|
||||||
|
}
|
||||||
|
|
||||||
|
maxOpenConnections, cErr := strconv.Atoi(env.Get(maxOpenConnectionsEnv, kv.Get(target.MySQLMaxOpenConnections)))
|
||||||
|
if cErr != nil {
|
||||||
|
return nil, cErr
|
||||||
|
}
|
||||||
|
|
||||||
mysqlArgs := target.MySQLArgs{
|
mysqlArgs := target.MySQLArgs{
|
||||||
Enable: enabled,
|
Enable: enabled,
|
||||||
Format: env.Get(formatEnv, kv.Get(target.MySQLFormat)),
|
Format: env.Get(formatEnv, kv.Get(target.MySQLFormat)),
|
||||||
DSN: env.Get(dsnStringEnv, kv.Get(target.MySQLDSNString)),
|
DSN: env.Get(dsnStringEnv, kv.Get(target.MySQLDSNString)),
|
||||||
Table: env.Get(tableEnv, kv.Get(target.MySQLTable)),
|
Table: env.Get(tableEnv, kv.Get(target.MySQLTable)),
|
||||||
QueueDir: env.Get(queueDirEnv, kv.Get(target.MySQLQueueDir)),
|
QueueDir: env.Get(queueDirEnv, kv.Get(target.MySQLQueueDir)),
|
||||||
QueueLimit: queueLimit,
|
QueueLimit: queueLimit,
|
||||||
|
MaxOpenConnections: maxOpenConnections,
|
||||||
}
|
}
|
||||||
if err = mysqlArgs.Validate(); err != nil {
|
if err = mysqlArgs.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1235,6 +1251,10 @@ var (
|
|||||||
Key: target.PostgresQueueLimit,
|
Key: target.PostgresQueueLimit,
|
||||||
Value: "0",
|
Value: "0",
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: target.PostgresMaxOpenConnections,
|
||||||
|
Value: "2",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1285,13 +1305,24 @@ func GetNotifyPostgres(postgresKVS map[string]config.KVS) (map[string]target.Pos
|
|||||||
queueDirEnv = queueDirEnv + config.Default + k
|
queueDirEnv = queueDirEnv + config.Default + k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxOpenConnectionsEnv := target.EnvPostgresMaxOpenConnections
|
||||||
|
if k != config.Default {
|
||||||
|
maxOpenConnectionsEnv = maxOpenConnectionsEnv + config.Default + k
|
||||||
|
}
|
||||||
|
|
||||||
|
maxOpenConnections, cErr := strconv.Atoi(env.Get(maxOpenConnectionsEnv, kv.Get(target.PostgresMaxOpenConnections)))
|
||||||
|
if cErr != nil {
|
||||||
|
return nil, cErr
|
||||||
|
}
|
||||||
|
|
||||||
psqlArgs := target.PostgreSQLArgs{
|
psqlArgs := target.PostgreSQLArgs{
|
||||||
Enable: enabled,
|
Enable: enabled,
|
||||||
Format: env.Get(formatEnv, kv.Get(target.PostgresFormat)),
|
Format: env.Get(formatEnv, kv.Get(target.PostgresFormat)),
|
||||||
ConnectionString: env.Get(connectionStringEnv, kv.Get(target.PostgresConnectionString)),
|
ConnectionString: env.Get(connectionStringEnv, kv.Get(target.PostgresConnectionString)),
|
||||||
Table: env.Get(tableEnv, kv.Get(target.PostgresTable)),
|
Table: env.Get(tableEnv, kv.Get(target.PostgresTable)),
|
||||||
QueueDir: env.Get(queueDirEnv, kv.Get(target.PostgresQueueDir)),
|
QueueDir: env.Get(queueDirEnv, kv.Get(target.PostgresQueueDir)),
|
||||||
QueueLimit: uint64(queueLimit),
|
QueueLimit: uint64(queueLimit),
|
||||||
|
MaxOpenConnections: maxOpenConnections,
|
||||||
}
|
}
|
||||||
if err = psqlArgs.Validate(); err != nil {
|
if err = psqlArgs.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: ClassDMA,
|
||||||
|
Description: `enable O_DIRECT for both read and write, defaults to "write" e.g. "read+write"`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.Comment,
|
Key: config.Comment,
|
||||||
Description: config.DefaultComment,
|
Description: config.DefaultComment,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package storageclass
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -32,17 +33,26 @@ const (
|
|||||||
RRS = "REDUCED_REDUNDANCY"
|
RRS = "REDUCED_REDUNDANCY"
|
||||||
// Standard storage class
|
// Standard storage class
|
||||||
STANDARD = "STANDARD"
|
STANDARD = "STANDARD"
|
||||||
|
// DMA storage class
|
||||||
|
DMA = "DMA"
|
||||||
|
|
||||||
|
// Valid values are "write" and "read+write"
|
||||||
|
DMAWrite = "write"
|
||||||
|
DMAReadWrite = "read+write"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Standard constats for config info storage class
|
// Standard constats for config info storage class
|
||||||
const (
|
const (
|
||||||
ClassStandard = "standard"
|
ClassStandard = "standard"
|
||||||
ClassRRS = "rrs"
|
ClassRRS = "rrs"
|
||||||
|
ClassDMA = "dma"
|
||||||
|
|
||||||
// Reduced redundancy storage class environment variable
|
// Reduced redundancy storage class environment variable
|
||||||
RRSEnv = "MINIO_STORAGE_CLASS_RRS"
|
RRSEnv = "MINIO_STORAGE_CLASS_RRS"
|
||||||
// Standard storage class environment variable
|
// Standard storage class environment variable
|
||||||
StandardEnv = "MINIO_STORAGE_CLASS_STANDARD"
|
StandardEnv = "MINIO_STORAGE_CLASS_STANDARD"
|
||||||
|
// DMA storage class environment variable
|
||||||
|
DMAEnv = "MINIO_STORAGE_CLASS_DMA"
|
||||||
|
|
||||||
// Supported storage class scheme is EC
|
// Supported storage class scheme is EC
|
||||||
schemePrefix = "EC"
|
schemePrefix = "EC"
|
||||||
@@ -52,6 +62,9 @@ const (
|
|||||||
|
|
||||||
// Default RRS parity is always minimum parity.
|
// Default RRS parity is always minimum parity.
|
||||||
defaultRRSParity = minParityDisks
|
defaultRRSParity = minParityDisks
|
||||||
|
|
||||||
|
// Default DMA value
|
||||||
|
defaultDMA = DMAWrite
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultKVS - default storage class config
|
// DefaultKVS - default storage class config
|
||||||
@@ -65,18 +78,24 @@ var (
|
|||||||
Key: ClassRRS,
|
Key: ClassRRS,
|
||||||
Value: "EC:2",
|
Value: "EC:2",
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: ClassDMA,
|
||||||
|
Value: defaultDMA,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// StorageClass - holds storage class information
|
// StorageClass - holds storage class information
|
||||||
type StorageClass struct {
|
type StorageClass struct {
|
||||||
Parity int
|
Parity int
|
||||||
|
DMA string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config storage class configuration
|
// Config storage class configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Standard StorageClass `json:"standard"`
|
Standard StorageClass `json:"standard"`
|
||||||
RRS StorageClass `json:"rrs"`
|
RRS StorageClass `json:"rrs"`
|
||||||
|
DMA StorageClass `json:"dma"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
||||||
@@ -93,7 +112,7 @@ func (sCfg *Config) UnmarshalJSON(data []byte) error {
|
|||||||
// IsValid - returns true if input string is a valid
|
// IsValid - returns true if input string is a valid
|
||||||
// storage class kind supported.
|
// storage class kind supported.
|
||||||
func IsValid(sc string) bool {
|
func IsValid(sc string) bool {
|
||||||
return sc == RRS || sc == STANDARD
|
return sc == RRS || sc == STANDARD || sc == DMA
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText unmarshals storage class from its textual form into
|
// UnmarshalText unmarshals storage class from its textual form into
|
||||||
@@ -103,6 +122,14 @@ func (sc *StorageClass) UnmarshalText(b []byte) error {
|
|||||||
if scStr == "" {
|
if scStr == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if scStr == DMAWrite {
|
||||||
|
sc.DMA = DMAWrite
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if scStr == DMAReadWrite {
|
||||||
|
sc.DMA = DMAReadWrite
|
||||||
|
return nil
|
||||||
|
}
|
||||||
s, err := parseStorageClass(scStr)
|
s, err := parseStorageClass(scStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -116,14 +143,14 @@ func (sc *StorageClass) MarshalText() ([]byte, error) {
|
|||||||
if sc.Parity != 0 {
|
if sc.Parity != 0 {
|
||||||
return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
|
return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
|
||||||
}
|
}
|
||||||
return []byte(""), nil
|
return []byte(sc.DMA), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StorageClass) String() string {
|
func (sc *StorageClass) String() string {
|
||||||
if sc.Parity != 0 {
|
if sc.Parity != 0 {
|
||||||
return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
|
return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
|
||||||
}
|
}
|
||||||
return ""
|
return sc.DMA
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses given storageClassEnv and returns a storageClass structure.
|
// Parses given storageClassEnv and returns a storageClass structure.
|
||||||
@@ -212,6 +239,11 @@ func (sCfg Config) GetParityForSC(sc string) (parity int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDMA - returns DMA configuration.
|
||||||
|
func (sCfg Config) GetDMA() string {
|
||||||
|
return sCfg.DMA.DMA
|
||||||
|
}
|
||||||
|
|
||||||
// Enabled returns if etcd is enabled.
|
// Enabled returns if etcd is enabled.
|
||||||
func Enabled(kvs config.KVS) bool {
|
func Enabled(kvs config.KVS) bool {
|
||||||
ssc := kvs.Get(ClassStandard)
|
ssc := kvs.Get(ClassStandard)
|
||||||
@@ -231,6 +263,7 @@ func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
|
|||||||
|
|
||||||
ssc := env.Get(StandardEnv, kvs.Get(ClassStandard))
|
ssc := env.Get(StandardEnv, kvs.Get(ClassStandard))
|
||||||
rrsc := env.Get(RRSEnv, kvs.Get(ClassRRS))
|
rrsc := env.Get(RRSEnv, kvs.Get(ClassRRS))
|
||||||
|
dma := env.Get(DMAEnv, kvs.Get(ClassDMA))
|
||||||
// Check for environment variables and parse into storageClass struct
|
// Check for environment variables and parse into storageClass struct
|
||||||
if ssc != "" {
|
if ssc != "" {
|
||||||
cfg.Standard, err = parseStorageClass(ssc)
|
cfg.Standard, err = parseStorageClass(ssc)
|
||||||
@@ -252,6 +285,14 @@ func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
|
|||||||
cfg.RRS.Parity = defaultRRSParity
|
cfg.RRS.Parity = defaultRRSParity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dma == "" {
|
||||||
|
dma = defaultDMA
|
||||||
|
}
|
||||||
|
if dma != DMAReadWrite && dma != DMAWrite {
|
||||||
|
return Config{}, errors.New(`valid dma values are "read-write" and "write"`)
|
||||||
|
}
|
||||||
|
cfg.DMA.DMA = dma
|
||||||
|
|
||||||
// Validation is done after parsing both the storage classes. This is needed because we need one
|
// Validation is done after parsing both the storage classes. This is needed because we need one
|
||||||
// storage class value to deduce the correct value of the other storage class.
|
// storage class value to deduce the correct value of the other storage class.
|
||||||
if err = validateParity(cfg.Standard.Parity, cfg.RRS.Parity, setDriveCount); err != nil {
|
if err = validateParity(cfg.Standard.Parity, cfg.RRS.Parity, setDriveCount); err != nil {
|
||||||
|
|||||||
+14
-4
@@ -40,8 +40,8 @@ type HTTPConsoleLoggerSys struct {
|
|||||||
logBuf *ring.Ring
|
logBuf *ring.Ring
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetNodeName(endpointZones EndpointZones) (nodeName string) {
|
func mustGetNodeName(endpointServerPools EndpointServerPools) (nodeName string) {
|
||||||
host, err := xnet.ParseHost(GetLocalPeer(endpointZones))
|
host, err := xnet.ParseHost(GetLocalPeer(endpointServerPools))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.FatalIf(err, "Unable to start console logging subsystem")
|
logger.FatalIf(err, "Unable to start console logging subsystem")
|
||||||
}
|
}
|
||||||
@@ -63,8 +63,8 @@ func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetNodeName - sets the node name if any after distributed setup has initialized
|
// SetNodeName - sets the node name if any after distributed setup has initialized
|
||||||
func (sys *HTTPConsoleLoggerSys) SetNodeName(endpointZones EndpointZones) {
|
func (sys *HTTPConsoleLoggerSys) SetNodeName(endpointServerPools EndpointServerPools) {
|
||||||
sys.nodeName = mustGetNodeName(endpointZones)
|
sys.nodeName = mustGetNodeName(endpointServerPools)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasLogListeners returns true if console log listeners are registered
|
// HasLogListeners returns true if console log listeners are registered
|
||||||
@@ -122,6 +122,16 @@ func (sys *HTTPConsoleLoggerSys) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Endpoint - dummy function for interface compatibility
|
||||||
|
func (sys *HTTPConsoleLoggerSys) Endpoint() string {
|
||||||
|
return sys.console.Endpoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String - stringer function for interface compatibility
|
||||||
|
func (sys *HTTPConsoleLoggerSys) String() string {
|
||||||
|
return "console+http"
|
||||||
|
}
|
||||||
|
|
||||||
// Content returns the console stdout log
|
// Content returns the console stdout log
|
||||||
func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) {
|
func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
|
|||||||
+10
-179
@@ -19,231 +19,62 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSEHeader is the general AWS SSE HTTP header key.
|
|
||||||
const SSEHeader = "X-Amz-Server-Side-Encryption"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSEKmsID is the HTTP header key referencing the SSE-KMS
|
|
||||||
// key ID.
|
|
||||||
SSEKmsID = SSEHeader + "-Aws-Kms-Key-Id"
|
|
||||||
|
|
||||||
// SSEKmsContext is the HTTP header key referencing the
|
|
||||||
// SSE-KMS encryption context.
|
|
||||||
SSEKmsContext = SSEHeader + "-Context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSECAlgorithm is the HTTP header key referencing
|
|
||||||
// the SSE-C algorithm.
|
|
||||||
SSECAlgorithm = SSEHeader + "-Customer-Algorithm"
|
|
||||||
|
|
||||||
// SSECKey is the HTTP header key referencing the
|
|
||||||
// SSE-C client-provided key..
|
|
||||||
SSECKey = SSEHeader + "-Customer-Key"
|
|
||||||
|
|
||||||
// SSECKeyMD5 is the HTTP header key referencing
|
|
||||||
// the MD5 sum of the client-provided key.
|
|
||||||
SSECKeyMD5 = SSEHeader + "-Customer-Key-Md5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSECopyAlgorithm is the HTTP header key referencing
|
|
||||||
// the SSE-C algorithm for SSE-C copy requests.
|
|
||||||
SSECopyAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
|
|
||||||
|
|
||||||
// SSECopyKey is the HTTP header key referencing the SSE-C
|
|
||||||
// client-provided key for SSE-C copy requests.
|
|
||||||
SSECopyKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
|
|
||||||
|
|
||||||
// SSECopyKeyMD5 is the HTTP header key referencing the
|
|
||||||
// MD5 sum of the client key for SSE-C copy requests.
|
|
||||||
SSECopyKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSEAlgorithmAES256 is the only supported value for the SSE-S3 or SSE-C algorithm header.
|
|
||||||
// For SSE-S3 see: https://docs.aws.amazon.com/AmazonS3/latest/dev/SSEUsingRESTAPI.html
|
|
||||||
// For SSE-C see: https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
|
|
||||||
SSEAlgorithmAES256 = "AES256"
|
|
||||||
|
|
||||||
// SSEAlgorithmKMS is the value of 'X-Amz-Server-Side-Encryption' for SSE-KMS.
|
|
||||||
// See: https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
|
|
||||||
SSEAlgorithmKMS = "aws:kms"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RemoveSensitiveHeaders removes confidential encryption
|
// RemoveSensitiveHeaders removes confidential encryption
|
||||||
// information - e.g. the SSE-C key - from the HTTP headers.
|
// information - e.g. the SSE-C key - from the HTTP headers.
|
||||||
// It has the same semantics as RemoveSensitiveEntires.
|
// It has the same semantics as RemoveSensitiveEntires.
|
||||||
func RemoveSensitiveHeaders(h http.Header) {
|
func RemoveSensitiveHeaders(h http.Header) {
|
||||||
h.Del(SSECKey)
|
h.Del(xhttp.AmzServerSideEncryptionCustomerKey)
|
||||||
h.Del(SSECopyKey)
|
h.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey)
|
||||||
h.Del(xhttp.AmzMetaUnencryptedContentLength)
|
h.Del(xhttp.AmzMetaUnencryptedContentLength)
|
||||||
h.Del(xhttp.AmzMetaUnencryptedContentMD5)
|
h.Del(xhttp.AmzMetaUnencryptedContentMD5)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers indicates
|
|
||||||
// that any form server-side encryption (SSE-C, SSE-S3 or SSE-KMS)
|
|
||||||
// is requested.
|
|
||||||
func IsRequested(h http.Header) bool {
|
|
||||||
return S3.IsRequested(h) || SSEC.IsRequested(h) || SSECopy.IsRequested(h) || S3KMS.IsRequested(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// S3 represents AWS SSE-S3. It provides functionality to handle
|
|
||||||
// SSE-S3 requests.
|
|
||||||
var S3 = s3{}
|
|
||||||
|
|
||||||
type s3 struct{}
|
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers indicates that
|
|
||||||
// the S3 client requests SSE-S3.
|
|
||||||
func (s3) IsRequested(h http.Header) bool {
|
|
||||||
_, ok := h[SSEHeader]
|
|
||||||
return ok && strings.ToLower(h.Get(SSEHeader)) != SSEAlgorithmKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-S3 related HTTP headers and checks
|
|
||||||
// whether they contain valid values.
|
|
||||||
func (s3) ParseHTTP(h http.Header) (err error) {
|
|
||||||
if h.Get(SSEHeader) != SSEAlgorithmAES256 {
|
|
||||||
err = ErrInvalidEncryptionMethod
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// S3KMS represents AWS SSE-KMS. It provides functionality to
|
|
||||||
// handle SSE-KMS requests.
|
|
||||||
var S3KMS = s3KMS{}
|
|
||||||
|
|
||||||
type s3KMS struct{}
|
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers indicates that
|
|
||||||
// the S3 client requests SSE-KMS.
|
|
||||||
func (s3KMS) IsRequested(h http.Header) bool {
|
|
||||||
if _, ok := h[SSEKmsID]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSEKmsContext]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSEHeader]; ok {
|
|
||||||
return strings.ToUpper(h.Get(SSEHeader)) != SSEAlgorithmAES256 // Return only true if the SSE header is specified and does not contain the SSE-S3 value
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID
|
|
||||||
// and context, if present, on success.
|
|
||||||
func (s3KMS) ParseHTTP(h http.Header) (string, interface{}, error) {
|
|
||||||
algorithm := h.Get(SSEHeader)
|
|
||||||
if algorithm != SSEAlgorithmKMS {
|
|
||||||
return "", nil, ErrInvalidEncryptionMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
contextStr, ok := h[SSEKmsContext]
|
|
||||||
if ok {
|
|
||||||
var context map[string]interface{}
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
if err := json.Unmarshal([]byte(contextStr[0]), &context); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return h.Get(SSEKmsID), context, nil
|
|
||||||
}
|
|
||||||
return h.Get(SSEKmsID), nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// SSEC represents AWS SSE-C. It provides functionality to handle
|
|
||||||
// SSE-C requests.
|
|
||||||
SSEC = ssec{}
|
|
||||||
|
|
||||||
// SSECopy represents AWS SSE-C for copy requests. It provides
|
// SSECopy represents AWS SSE-C for copy requests. It provides
|
||||||
// functionality to handle SSE-C copy requests.
|
// functionality to handle SSE-C copy requests.
|
||||||
SSECopy = ssecCopy{}
|
SSECopy = ssecCopy{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type ssec struct{}
|
|
||||||
type ssecCopy struct{}
|
type ssecCopy struct{}
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers contains
|
|
||||||
// at least one SSE-C header. SSE-C copy headers are ignored.
|
|
||||||
func (ssec) IsRequested(h http.Header) bool {
|
|
||||||
if _, ok := h[SSECAlgorithm]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSECKey]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSECKeyMD5]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers contains
|
// IsRequested returns true if the HTTP headers contains
|
||||||
// at least one SSE-C copy header. Regular SSE-C headers
|
// at least one SSE-C copy header. Regular SSE-C headers
|
||||||
// are ignored.
|
// are ignored.
|
||||||
func (ssecCopy) IsRequested(h http.Header) bool {
|
func (ssecCopy) IsRequested(h http.Header) bool {
|
||||||
if _, ok := h[SSECopyAlgorithm]; ok {
|
if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := h[SSECopyKey]; ok {
|
if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKey]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := h[SSECopyKeyMD5]; ok {
|
if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-C headers and returns the SSE-C client key
|
|
||||||
// on success. SSE-C copy headers are ignored.
|
|
||||||
func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
|
||||||
if h.Get(SSECAlgorithm) != SSEAlgorithmAES256 {
|
|
||||||
return key, ErrInvalidCustomerAlgorithm
|
|
||||||
}
|
|
||||||
if h.Get(SSECKey) == "" {
|
|
||||||
return key, ErrMissingCustomerKey
|
|
||||||
}
|
|
||||||
if h.Get(SSECKeyMD5) == "" {
|
|
||||||
return key, ErrMissingCustomerKeyMD5
|
|
||||||
}
|
|
||||||
|
|
||||||
clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECKey))
|
|
||||||
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
|
||||||
return key, ErrInvalidCustomerKey
|
|
||||||
}
|
|
||||||
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECKeyMD5))
|
|
||||||
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
|
||||||
return key, ErrCustomerKeyMD5Mismatch
|
|
||||||
}
|
|
||||||
copy(key[:], clientKey)
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
|
// ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
|
||||||
// on success. Regular SSE-C headers are ignored.
|
// on success. Regular SSE-C headers are ignored.
|
||||||
func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
||||||
if h.Get(SSECopyAlgorithm) != SSEAlgorithmAES256 {
|
if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) != xhttp.AmzEncryptionAES {
|
||||||
return key, ErrInvalidCustomerAlgorithm
|
return key, ErrInvalidCustomerAlgorithm
|
||||||
}
|
}
|
||||||
if h.Get(SSECopyKey) == "" {
|
if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) == "" {
|
||||||
return key, ErrMissingCustomerKey
|
return key, ErrMissingCustomerKey
|
||||||
}
|
}
|
||||||
if h.Get(SSECopyKeyMD5) == "" {
|
if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) == "" {
|
||||||
return key, ErrMissingCustomerKeyMD5
|
return key, ErrMissingCustomerKeyMD5
|
||||||
}
|
}
|
||||||
|
|
||||||
clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKey))
|
clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey))
|
||||||
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
||||||
return key, ErrInvalidCustomerKey
|
return key, ErrInvalidCustomerKey
|
||||||
}
|
}
|
||||||
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKeyMD5))
|
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5))
|
||||||
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
||||||
return key, ErrCustomerKeyMD5Mismatch
|
return key, ErrCustomerKeyMD5Mismatch
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-36
@@ -18,26 +18,29 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsRequested(t *testing.T) {
|
func TestIsRequested(t *testing.T) {
|
||||||
for i, test := range kmsIsRequestedTests {
|
for i, test := range kmsIsRequestedTests {
|
||||||
if got := IsRequested(test.Header) && S3KMS.IsRequested(test.Header); got != test.Expected {
|
_, got := IsRequested(test.Header)
|
||||||
|
got = got && S3KMS.IsRequested(test.Header)
|
||||||
|
if got != test.Expected {
|
||||||
t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, test := range s3IsRequestedTests {
|
for i, test := range s3IsRequestedTests {
|
||||||
if got := IsRequested(test.Header) && S3.IsRequested(test.Header); got != test.Expected {
|
_, got := IsRequested(test.Header)
|
||||||
|
got = got && S3.IsRequested(test.Header)
|
||||||
|
if got != test.Expected {
|
||||||
t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, test := range ssecIsRequestedTests {
|
for i, test := range ssecIsRequestedTests {
|
||||||
if got := IsRequested(test.Header) && SSEC.IsRequested(test.Header); got != test.Expected {
|
_, got := IsRequested(test.Header)
|
||||||
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
got = got && SSEC.IsRequested(test.Header)
|
||||||
}
|
if got != test.Expected {
|
||||||
}
|
|
||||||
for i, test := range ssecCopyIsRequestedTests {
|
|
||||||
if got := IsRequested(test.Header) && SSECopy.IsRequested(test.Header); got != test.Expected {
|
|
||||||
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,11 +134,11 @@ var s3IsRequestedTests = []struct {
|
|||||||
Header http.Header
|
Header http.Header
|
||||||
Expected bool
|
Expected bool
|
||||||
}{
|
}{
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3
|
{Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{SSEAlgorithmKMS}}, Expected: false}, // 4
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{xhttp.AmzEncryptionKMS}}, Expected: false}, // 4
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestS3IsRequested(t *testing.T) {
|
func TestS3IsRequested(t *testing.T) {
|
||||||
@@ -403,7 +406,7 @@ func TestSSECopyParse(t *testing.T) {
|
|||||||
if err == nil && key == zeroKey {
|
if err == nil && key == zeroKey {
|
||||||
t.Errorf("Test %d: parsed client key is zero key", i)
|
t.Errorf("Test %d: parsed client key is zero key", i)
|
||||||
}
|
}
|
||||||
if _, ok := test.Header[SSECKey]; ok {
|
if _, ok := test.Header[xhttp.AmzServerSideEncryptionCustomerKey]; ok {
|
||||||
t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i)
|
t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,47 +417,47 @@ var removeSensitiveHeadersTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECKey: []string{""},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{""},
|
||||||
SSECopyKey: []string{""},
|
xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{""},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{},
|
ExpectedHeader: http.Header{},
|
||||||
},
|
},
|
||||||
{ // Standard SSE-C request headers
|
{ // Standard SSE-C request headers
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{
|
ExpectedHeader: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ // Standard SSE-C + SSE-C-copy request headers
|
{ // Standard SSE-C + SSE-C-copy request headers
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{
|
ExpectedHeader: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ // Standard SSE-C + metadata request headers
|
{ // Standard SSE-C + metadata request headers
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{
|
ExpectedHeader: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
{ // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user