mirror of
https://github.com/pgsty/minio.git
synced 2026-08-11 00:33:28 +03:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea66a52ed1 | |||
| 55dd017e62 | |||
| 12353caf35 | |||
| a57c747667 | |||
| 28661c0413 | |||
| 04a152be12 | |||
| bce3f8237d | |||
| 16a45e5aff | |||
| 000a60f238 | |||
| bf278ca36f | |||
| 97f2bc26b9 | |||
| bba562235b | |||
| 2337e5f803 | |||
| ffd7b7059c | |||
| 5c0acbc6fc | |||
| 5a52bc7ff6 | |||
| 045e1fed2b | |||
| a861d38532 | |||
| 20a15567b8 | |||
| 94f67ad224 | |||
| 36ee110563 | |||
| 1dc25bcf5f | |||
| 2d96745156 | |||
| 9d49688c87 | |||
| 8e09374cb8 | |||
| 58d90ed73c | |||
| e857b6741d | |||
| 0505ef83b5 | |||
| 22bc15d89b | |||
| a2e904b966 | |||
| cc7dc61eb4 | |||
| c4f480a839 | |||
| 60831e3299 | |||
| 037319066f | |||
| bb871a7c31 | |||
| 0ebbd3caef | |||
| bd56f80250 | |||
| a39e810965 | |||
| 09103991ea | |||
| c43f745449 | |||
| 9610a74c19 | |||
| 0bcd8abc5c | |||
| 70b350c383 | |||
| 338e9a9be9 | |||
| edbd8709ec | |||
| 5db60a6c59 | |||
| 36c19f1d65 | |||
| d3f9f8be88 | |||
| 183ec094c4 | |||
| c1d2b3d5c3 | |||
| be72609d1f | |||
| 48f2c98052 | |||
| 286c663495 | |||
| 941fed8e4a | |||
| 55092bede1 | |||
| 90a3b830f4 | |||
| 23b9df0694 | |||
| 48cb271a46 | |||
| 61229b38f7 | |||
| 90ca73af13 | |||
| a04b6561a0 | |||
| 219d841496 | |||
| baef49b4a2 | |||
| 3022f60561 | |||
| 680fdf6f90 | |||
| 1af6e8cb72 | |||
| 98d3913a1e | |||
| 35c38e4bd8 | |||
| e43d3a075c | |||
| 43e0ef4248 | |||
| cd7d5b59e5 | |||
| 299ef9b188 | |||
| b30c436715 | |||
| 7001fe407f | |||
| 59f7266081 | |||
| 99bf4d0c42 | |||
| 7b8beecc81 | |||
| 510ec153b9 | |||
| e29a37e95c | |||
| 4a4048fe27 | |||
| da2887f914 | |||
| c22439c82e | |||
| 38224a4c1a | |||
| b4ab778cb2 | |||
| 22f6756ce6 | |||
| 0394a8f013 | |||
| 1039311184 | |||
| a075015293 | |||
| d90d4841b8 | |||
| d3a2efbf91 | |||
| 002a205c9c | |||
| 91ceae23d0 | |||
| 1008c2c069 | |||
| 7abadfccc2 | |||
| 97090aa16c | |||
| fb531235de | |||
| cb1566c6e6 | |||
| 6d89435356 | |||
| 975237cbf8 | |||
| 501af06001 | |||
| a69f74533c | |||
| a19cf063b5 | |||
| 8b0c86298d | |||
| df19546cc8 | |||
| ff7799452b | |||
| 97be455f63 | |||
| d16a409943 | |||
| 74efbb4153 | |||
| 0cfd5a21ba | |||
| 1ce2d29bbb | |||
| 48ffb7731a | |||
| 1cfd4a48d9 | |||
| 993a79d9c6 | |||
| 0c16b1c9a7 | |||
| 900cc27b51 | |||
| 8528017ad3 | |||
| a73da7755e | |||
| 2c0b3cadfc | |||
| 763fce909b | |||
| da8214845a | |||
| 74e2fe0879 | |||
| cb7f9ba286 | |||
| 7906f464fd | |||
| 7e4c9a9e1e | |||
| f6fd407e47 | |||
| 46ced81f41 | |||
| 7a02faab72 |
@@ -1,33 +1,19 @@
|
|||||||
<!--- Provide a general summary of your changes in the Title above -->
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
<!--- Describe your changes in detail -->
|
|
||||||
|
|
||||||
## Motivation and Context
|
## Motivation and Context
|
||||||
<!--- Why is this change required? What problem does it solve? -->
|
|
||||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
|
||||||
|
|
||||||
## Regression
|
|
||||||
<!-- Is this PR fixing a regression? (Yes / No) -->
|
|
||||||
<!-- If Yes, optionally please include minio version or commit id or PR# that caused this regression, if you have these details. -->
|
|
||||||
|
|
||||||
## How Has This Been Tested?
|
## How to test this PR?
|
||||||
<!--- Please describe in detail how you tested your changes. -->
|
|
||||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
|
||||||
<!--- see how your change affects other areas of the code, etc. -->
|
|
||||||
|
|
||||||
## Types of changes
|
## Types of changes
|
||||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
||||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
- [ ] Documentation needed
|
||||||
- [ ] My code follows the code style of this project.
|
- [ ] Unit tests needed
|
||||||
- [ ] My change requires a change to the documentation.
|
- [ ] Functional tests needed (If yes, add [mint](https://github.com/minio/mint) PR # here: )
|
||||||
- [ ] I have updated the documentation accordingly.
|
|
||||||
- [ ] I have added unit tests to cover my changes.
|
|
||||||
- [ ] I have added/updated functional tests in [mint](https://github.com/minio/mint). (If yes, add `mint` PR # here: )
|
|
||||||
- [ ] All new and existing tests passed.
|
|
||||||
|
|||||||
+1
-2
@@ -9,7 +9,6 @@ site/
|
|||||||
/.idea/
|
/.idea/
|
||||||
/Minio.iml
|
/Minio.iml
|
||||||
**/access.log
|
**/access.log
|
||||||
build/
|
|
||||||
vendor/**/*.js
|
vendor/**/*.js
|
||||||
vendor/**/*.json
|
vendor/**/*.json
|
||||||
release
|
release
|
||||||
@@ -23,4 +22,4 @@ prime/
|
|||||||
stage/
|
stage/
|
||||||
.sia_temp/
|
.sia_temp/
|
||||||
config.json
|
config.json
|
||||||
healthcheck
|
dockerscripts/healthcheck
|
||||||
+12
-2
@@ -2,6 +2,14 @@ go_import_path: github.com/minio/minio
|
|||||||
|
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- shellcheck
|
||||||
|
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
# this ensures PRs based on a local branch are not built twice
|
# this ensures PRs based on a local branch are not built twice
|
||||||
# the downside is that a PR targeting a different branch is not built
|
# the downside is that a PR targeting a different branch is not built
|
||||||
# but as a workaround you can add the branch to this list
|
# but as a workaround you can add the branch to this list
|
||||||
@@ -26,7 +34,7 @@ matrix:
|
|||||||
- $HOME/gopath/pkg/mod
|
- $HOME/gopath/pkg/mod
|
||||||
- $HOME/go/pkg/mod
|
- $HOME/go/pkg/mod
|
||||||
|
|
||||||
go: 1.12.1
|
go: 1.12.5
|
||||||
script:
|
script:
|
||||||
- make
|
- make
|
||||||
- diff -au <(gofmt -s -d cmd) <(printf "")
|
- diff -au <(gofmt -s -d cmd) <(printf "")
|
||||||
@@ -37,12 +45,14 @@ matrix:
|
|||||||
- make verify
|
- make verify
|
||||||
- make coverage
|
- make coverage
|
||||||
- cd browser && yarn && yarn test && cd ..
|
- cd browser && yarn && yarn test && cd ..
|
||||||
|
- bash -c 'shopt -s globstar; shellcheck mint/**/*.sh'
|
||||||
|
|
||||||
- os: windows
|
- os: windows
|
||||||
env:
|
env:
|
||||||
- ARCH=x86_64
|
- ARCH=x86_64
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
go: 1.12.1
|
go: 1.12.5
|
||||||
script:
|
script:
|
||||||
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
|
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
|
||||||
- bash buildscripts/go-coverage.sh
|
- bash buildscripts/go-coverage.sh
|
||||||
|
|||||||
+4
-3
@@ -10,13 +10,14 @@ RUN \
|
|||||||
apk add --no-cache git && \
|
apk add --no-cache git && \
|
||||||
git clone https://github.com/minio/minio && cd minio && \
|
git clone https://github.com/minio/minio && cd minio && \
|
||||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
|
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
|
||||||
cd dockerscripts; go build -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
cd dockerscripts; go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
||||||
|
|
||||||
FROM alpine:3.9
|
FROM alpine:3.9
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
|||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' && \
|
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
||||||
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"]
|
||||||
|
|||||||
+3
-2
@@ -7,10 +7,11 @@ COPY minio /usr/bin/
|
|||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' && \
|
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||||
chmod +x /usr/bin/minio && \
|
chmod +x /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
FROM ubuntu:16.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
|
ENV GOROOT /usr/local/go
|
||||||
|
|
||||||
|
ENV GOPATH /usr/local
|
||||||
|
|
||||||
|
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
||||||
|
|
||||||
|
ENV MINT_ROOT_DIR /mint
|
||||||
|
|
||||||
|
COPY mint /mint
|
||||||
|
|
||||||
|
RUN apt-get --yes update && apt-get --yes upgrade && \
|
||||||
|
apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
||||||
|
cd /mint && /mint/release.sh
|
||||||
|
|
||||||
|
WORKDIR /mint
|
||||||
|
|
||||||
|
ENTRYPOINT ["/mint/entrypoint.sh"]
|
||||||
+4
-3
@@ -7,7 +7,7 @@ ENV GO111MODULE on
|
|||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache git && \
|
apk add --no-cache git && \
|
||||||
git clone https://github.com/minio/minio && cd minio/dockerscripts && \
|
git clone https://github.com/minio/minio && cd minio/dockerscripts && \
|
||||||
go build -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
||||||
|
|
||||||
FROM alpine:3.9
|
FROM alpine:3.9
|
||||||
|
|
||||||
@@ -18,10 +18,11 @@ COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
|||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' && \
|
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||||
curl https://dl.min.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
|
curl https://dl.min.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/minio && \
|
chmod +x /usr/bin/minio && \
|
||||||
|
|||||||
+5
-3
@@ -33,7 +33,7 @@ USER ci
|
|||||||
RUN make
|
RUN make
|
||||||
RUN bash -c 'diff -au <(gofmt -s -d cmd) <(printf "")'
|
RUN bash -c 'diff -au <(gofmt -s -d cmd) <(printf "")'
|
||||||
RUN bash -c 'diff -au <(gofmt -s -d pkg) <(printf "")'
|
RUN bash -c 'diff -au <(gofmt -s -d pkg) <(printf "")'
|
||||||
RUN for d in $(go list ./... | grep -v browser); do go test -v -race --timeout 15m "$d"; done
|
RUN for d in $(go list ./... | grep -v browser); do go test -v -race --timeout 20m "$d"; done
|
||||||
RUN make verifiers
|
RUN make verifiers
|
||||||
RUN make crosscompile
|
RUN make crosscompile
|
||||||
RUN make verify
|
RUN make verify
|
||||||
@@ -56,15 +56,17 @@ FROM ubuntu:16.04
|
|||||||
|
|
||||||
COPY --from=0 /go/src/github.com/minio/minio/minio /usr/bin/minio
|
COPY --from=0 /go/src/github.com/minio/minio/minio /usr/bin/minio
|
||||||
COPY buildscripts/gateway-tests.sh /usr/bin/gateway-tests.sh
|
COPY buildscripts/gateway-tests.sh /usr/bin/gateway-tests.sh
|
||||||
|
COPY mint /mint
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
ENV GOROOT /usr/local/go
|
ENV GOROOT /usr/local/go
|
||||||
ENV GOPATH /usr/local
|
ENV GOPATH /usr/local
|
||||||
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
||||||
|
ENV MINT_ROOT_DIR /mint
|
||||||
|
|
||||||
RUN apt-get --yes update && apt-get --yes upgrade && apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
RUN apt-get --yes update && apt-get --yes upgrade && \
|
||||||
git clone https://github.com/minio/mint.git /mint && \
|
apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
||||||
cd /mint && /mint/release.sh
|
cd /mint && /mint/release.sh
|
||||||
|
|
||||||
WORKDIR /mint
|
WORKDIR /mint
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ PWD := $(shell pwd)
|
|||||||
GOPATH := $(shell go env GOPATH)
|
GOPATH := $(shell go env GOPATH)
|
||||||
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
|
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
|
||||||
|
|
||||||
|
GOOS := $(shell go env GOOS)
|
||||||
|
GOOSALT ?= 'linux'
|
||||||
|
ifeq ($(GOOS),'darwin')
|
||||||
|
GOOSALT = 'mac'
|
||||||
|
endif
|
||||||
|
|
||||||
TAG ?= $(USER)
|
TAG ?= $(USER)
|
||||||
BUILD_LDFLAGS := '$(LDFLAGS)'
|
BUILD_LDFLAGS := '$(LDFLAGS)'
|
||||||
|
|
||||||
@@ -14,8 +20,8 @@ checks:
|
|||||||
getdeps:
|
getdeps:
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@which golint 1>/dev/null || (echo "Installing golint" && go get -u golang.org/x/lint/golint)
|
@which golint 1>/dev/null || (echo "Installing golint" && go get -u golang.org/x/lint/golint)
|
||||||
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet -O ${GOPATH}/bin/staticcheck https://github.com/dominikh/go-tools/releases/download/2019.1/staticcheck_linux_amd64 && chmod +x ${GOPATH}/bin/staticcheck)
|
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet -O ${GOPATH}/bin/staticcheck https://github.com/dominikh/go-tools/releases/download/2019.1/staticcheck_${GOOS}_amd64 && chmod +x ${GOPATH}/bin/staticcheck)
|
||||||
@which misspell 1>/dev/null || (echo "Installing misspell" && wget --quiet https://github.com/client9/misspell/releases/download/v0.3.4/misspell_0.3.4_linux_64bit.tar.gz && tar xf misspell_0.3.4_linux_64bit.tar.gz && mv misspell ${GOPATH}/bin/misspell && chmod +x ${GOPATH}/bin/misspell && rm -f misspell_0.3.4_linux_64bit.tar.gz)
|
@which misspell 1>/dev/null || (echo "Installing misspell" && wget --quiet https://github.com/client9/misspell/releases/download/v0.3.4/misspell_0.3.4_${GOOSALT}_64bit.tar.gz && tar xf misspell_0.3.4_${GOOSALT}_64bit.tar.gz && mv misspell ${GOPATH}/bin/misspell && chmod +x ${GOPATH}/bin/misspell && rm -f misspell_0.3.4_${GOOSALT}_64bit.tar.gz)
|
||||||
|
|
||||||
crosscompile:
|
crosscompile:
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
We always provide security updates for the [latest release](https://github.com/minio/minio/releases/latest).
|
||||||
|
Whenever there is a security update you just need to upgrade to the latest version.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
All security bugs in [minio/minio](https://github,com/minio/minio) (or other minio/* repositories)
|
||||||
|
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
|
||||||
|
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
|
||||||
|
in handling your report.
|
||||||
|
|
||||||
|
Please, provide a detailed explanation of the issue. In particular, outline the type of the security
|
||||||
|
issue (DoS, authentication bypass, information disclouse, ...) and the assumptions you're making (e.g. do
|
||||||
|
you need access credentials for a successful exploit).
|
||||||
|
|
||||||
|
If you have not received a reply to your email within 48 hours or you have not heard from the security team
|
||||||
|
for the past five days please contact the security team directly:
|
||||||
|
- Primary security coordinator: aead@min.io
|
||||||
|
- Secondary coordinator: harsha@min.io
|
||||||
|
- If you receive no response: dev@min.io
|
||||||
|
|
||||||
|
### Disclosure Process
|
||||||
|
|
||||||
|
MinIO uses the following disclosure process:
|
||||||
|
|
||||||
|
1. Once the security report is received one member of the security team tries to verify and reproduce
|
||||||
|
the issue and determines the impact it has.
|
||||||
|
2. A member of the security team will respond and either confrim or reject the security report.
|
||||||
|
If the report is rejected the repsonse explains why.
|
||||||
|
3. Code is audited to find any potential similar problems.
|
||||||
|
4. Fixes are prepared for the latest release.
|
||||||
|
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
|
||||||
|
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
|
||||||
|
the security issue. By default MinIO will **not** publish this information to protect your privacy.
|
||||||
|
|
||||||
|
This process can take some time, especially when coordination is required with maintainers of other projects.
|
||||||
|
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we
|
||||||
|
follow the process described above to ensure that disclosures are handled consistently.
|
||||||
+9
-8
@@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Install yarn
|
### Install node
|
||||||
```sh
|
```sh
|
||||||
curl -o- -L https://yarnpkg.com/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
|
||||||
yarn
|
exec -l $SHELL
|
||||||
|
nvm install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install `go-bindata` and `go-bindata-assetfs`
|
### Install `go-bindata` and `go-bindata-assetfs`
|
||||||
@@ -15,8 +16,8 @@ yarn
|
|||||||
If you do not have a working Golang environment, please follow [Install Golang](https://docs.min.io/docs/how-to-install-golang)
|
If you do not have a working Golang environment, please follow [Install Golang](https://docs.min.io/docs/how-to-install-golang)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get github.com/jteeuwen/go-bindata/...
|
go get github.com/go-bindata/go-bindata/go-bindata
|
||||||
go get github.com/elazarl/go-bindata-assetfs/...
|
go get github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generating Assets
|
## Generating Assets
|
||||||
@@ -24,7 +25,7 @@ go get github.com/elazarl/go-bindata-assetfs/...
|
|||||||
### Generate ui-assets.go
|
### Generate ui-assets.go
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn release
|
npm run release
|
||||||
```
|
```
|
||||||
|
|
||||||
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
||||||
@@ -32,7 +33,7 @@ This generates ui-assets.go in the current directory. Now do `make` in the paren
|
|||||||
### Run MinIO Browser with live reload
|
### Run MinIO Browser with live reload
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application
|
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application
|
||||||
@@ -66,7 +67,7 @@ index 3ccdaba..9496c56 100644
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application
|
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application
|
||||||
|
|||||||
@@ -18,157 +18,237 @@ import React from "react"
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import web from "../web"
|
import web from "../web"
|
||||||
import * as alertActions from "../alert/actions"
|
import * as alertActions from "../alert/actions"
|
||||||
|
import { getRandomAccessKey, getRandomSecretKey } from "../utils"
|
||||||
|
import jwtDecode from "jwt-decode"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
import {
|
import { Modal, ModalBody, ModalHeader } from "react-bootstrap"
|
||||||
Tooltip,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalHeader,
|
|
||||||
OverlayTrigger
|
|
||||||
} from "react-bootstrap"
|
|
||||||
import InputGroup from "./InputGroup"
|
import InputGroup from "./InputGroup"
|
||||||
|
import { ACCESS_KEY_MIN_LENGTH, SECRET_KEY_MIN_LENGTH } from "../constants"
|
||||||
|
|
||||||
export class ChangePasswordModal extends React.Component {
|
export class ChangePasswordModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
accessKey: "",
|
currentAccessKey: "",
|
||||||
secretKey: "",
|
currentSecretKey: "",
|
||||||
keysReadOnly: false
|
currentSecretKeyVisible: false,
|
||||||
|
newAccessKey: "",
|
||||||
|
newSecretKey: "",
|
||||||
|
newSecretKeyVisible: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// When its shown, it loads the access key and secret key.
|
// When its shown, it loads the access key from JWT token
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const { serverInfo } = this.props
|
const token = jwtDecode(web.GetToken())
|
||||||
|
|
||||||
// Check environment variables first.
|
|
||||||
if (serverInfo.info.isEnvCreds || serverInfo.info.isWorm) {
|
|
||||||
this.setState({
|
|
||||||
accessKey: "xxxxxxxxx",
|
|
||||||
secretKey: "xxxxxxxxx",
|
|
||||||
keysReadOnly: true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
web.GetAuth().then(data => {
|
|
||||||
this.setState({
|
|
||||||
accessKey: data.accessKey,
|
|
||||||
secretKey: data.secretKey
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle field changes from inside the modal.
|
|
||||||
accessKeyChange(e) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
accessKey: e.target.value
|
currentAccessKey: token.sub,
|
||||||
})
|
newAccessKey: token.sub
|
||||||
}
|
|
||||||
|
|
||||||
secretKeyChange(e) {
|
|
||||||
this.setState({
|
|
||||||
secretKey: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
secretKeyVisible(secretKeyVisible) {
|
|
||||||
this.setState({
|
|
||||||
secretKeyVisible
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the auth params and set them.
|
// Save the auth params and set them.
|
||||||
setAuth(e) {
|
setAuth(e) {
|
||||||
const { showAlert } = this.props
|
const { showAlert } = this.props
|
||||||
const accessKey = this.state.accessKey
|
|
||||||
const secretKey = this.state.secretKey
|
if (this.canUpdateCredentials()) {
|
||||||
web
|
const currentAccessKey = this.state.currentAccessKey
|
||||||
.SetAuth({
|
const currentSecretKey = this.state.currentSecretKey
|
||||||
accessKey,
|
const newAccessKey = this.state.newAccessKey
|
||||||
secretKey
|
const newSecretKey = this.state.newSecretKey
|
||||||
})
|
web
|
||||||
.then(data => {
|
.SetAuth({
|
||||||
showAlert({
|
currentAccessKey,
|
||||||
type: "success",
|
currentSecretKey,
|
||||||
message: "Changed credentials"
|
newAccessKey,
|
||||||
|
newSecretKey
|
||||||
})
|
})
|
||||||
})
|
.then(data => {
|
||||||
.catch(err => {
|
showAlert({
|
||||||
showAlert({
|
type: "success",
|
||||||
type: "danger",
|
message: "Credentials updated successfully."
|
||||||
message: err.message
|
})
|
||||||
})
|
})
|
||||||
})
|
.catch(err => {
|
||||||
|
showAlert({
|
||||||
|
type: "danger",
|
||||||
|
message: err.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAuth(e) {
|
generateAuth(e) {
|
||||||
web.GenerateAuth().then(data => {
|
const { serverInfo } = this.props
|
||||||
|
// Generate random access key only for root user
|
||||||
|
if (!serverInfo.userInfo.isIAMUser) {
|
||||||
this.setState({
|
this.setState({
|
||||||
accessKey: data.accessKey,
|
newAccessKey: getRandomAccessKey()
|
||||||
secretKey: data.secretKey,
|
|
||||||
secretKeyVisible: true
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
newSecretKey: getRandomSecretKey(),
|
||||||
|
newSecretKeyVisible: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canChangePassword() {
|
||||||
|
const { serverInfo } = this.props
|
||||||
|
// Password change is not allowed in WORM mode
|
||||||
|
if (serverInfo.info.isWorm) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// When credentials are set on ENV, password change not allowed for owner
|
||||||
|
if (serverInfo.info.isEnvCreds && !serverInfo.userInfo.isIAMUser) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
canUpdateCredentials() {
|
||||||
|
return (
|
||||||
|
this.state.currentAccessKey.length > 0 &&
|
||||||
|
this.state.currentSecretKey.length > 0 &&
|
||||||
|
this.state.newAccessKey.length >= ACCESS_KEY_MIN_LENGTH &&
|
||||||
|
this.state.newSecretKey.length >= SECRET_KEY_MIN_LENGTH
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hideChangePassword } = this.props
|
const { hideChangePassword, serverInfo } = this.props
|
||||||
|
const allowChangePassword = this.canChangePassword()
|
||||||
|
|
||||||
|
if (!allowChangePassword) {
|
||||||
|
return (
|
||||||
|
<Modal bsSize="sm" animation={false} show={true}>
|
||||||
|
<ModalHeader>Change Password</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
Credentials of this user cannot be updated through MinIO Browser.
|
||||||
|
</ModalBody>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button
|
||||||
|
id="cancel-change-password"
|
||||||
|
className="btn btn-link"
|
||||||
|
onClick={hideChangePassword}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal bsSize="sm" animation={false} show={true}>
|
<Modal bsSize="sm" animation={false} show={true}>
|
||||||
<ModalHeader>Change Password</ModalHeader>
|
<ModalHeader>Change Password</ModalHeader>
|
||||||
<ModalBody className="m-t-20">
|
<ModalBody className="m-t-20">
|
||||||
<InputGroup
|
<div className="has-toggle-password">
|
||||||
value={this.state.accessKey}
|
<InputGroup
|
||||||
onChange={this.accessKeyChange.bind(this)}
|
value={this.state.currentAccessKey}
|
||||||
id="accessKey"
|
id="currentAccessKey"
|
||||||
label="Access Key"
|
label="Current Access Key"
|
||||||
name="accesskey"
|
name="currentAccesskey"
|
||||||
type="text"
|
type="text"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
required="required"
|
required="required"
|
||||||
autoComplete="false"
|
autoComplete="false"
|
||||||
align="ig-left"
|
align="ig-left"
|
||||||
readonly={this.state.keysReadOnly}
|
readonly={true}
|
||||||
/>
|
/>
|
||||||
<i
|
|
||||||
onClick={this.secretKeyVisible.bind(
|
<i
|
||||||
this,
|
onClick={() => {
|
||||||
!this.state.secretKeyVisible
|
this.setState({
|
||||||
|
currentSecretKeyVisible: !this.state.currentSecretKeyVisible
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"toggle-password fa fa-eye " +
|
||||||
|
(this.state.currentSecretKeyVisible ? "toggled" : "")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
value={this.state.currentSecretKey}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ currentSecretKey: e.target.value })
|
||||||
|
}}
|
||||||
|
id="currentSecretKey"
|
||||||
|
label="Current Secret Key"
|
||||||
|
name="currentSecretKey"
|
||||||
|
type={this.state.currentSecretKeyVisible ? "text" : "password"}
|
||||||
|
spellCheck="false"
|
||||||
|
required="required"
|
||||||
|
autoComplete="false"
|
||||||
|
align="ig-left"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="has-toggle-password m-t-30">
|
||||||
|
{!serverInfo.userInfo.isIAMUser && (
|
||||||
|
<InputGroup
|
||||||
|
value={this.state.newAccessKey}
|
||||||
|
id="newAccessKey"
|
||||||
|
label={"New Access Key"}
|
||||||
|
name="newAccesskey"
|
||||||
|
type="text"
|
||||||
|
spellCheck="false"
|
||||||
|
required="required"
|
||||||
|
autoComplete="false"
|
||||||
|
align="ig-left"
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ newAccessKey: e.target.value })
|
||||||
|
}}
|
||||||
|
readonly={serverInfo.userInfo.isIAMUser}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
className={
|
|
||||||
"toggle-password fa fa-eye " +
|
<i
|
||||||
(this.state.secretKeyVisible ? "toggled" : "")
|
onClick={() => {
|
||||||
}
|
this.setState({
|
||||||
/>
|
newSecretKeyVisible: !this.state.newSecretKeyVisible
|
||||||
<InputGroup
|
})
|
||||||
value={this.state.secretKey}
|
}}
|
||||||
onChange={this.secretKeyChange.bind(this)}
|
className={
|
||||||
id="secretKey"
|
"toggle-password fa fa-eye " +
|
||||||
label="Secret Key"
|
(this.state.newSecretKeyVisible ? "toggled" : "")
|
||||||
name="accesskey"
|
}
|
||||||
type={this.state.secretKeyVisible ? "text" : "password"}
|
/>
|
||||||
spellCheck="false"
|
<InputGroup
|
||||||
required="required"
|
value={this.state.newSecretKey}
|
||||||
autoComplete="false"
|
onChange={e => {
|
||||||
align="ig-left"
|
this.setState({ newSecretKey: e.target.value })
|
||||||
readonly={this.state.keysReadOnly}
|
}}
|
||||||
/>
|
id="newSecretKey"
|
||||||
|
label="New Secret Key"
|
||||||
|
name="newSecretKey"
|
||||||
|
type={this.state.newSecretKeyVisible ? "text" : "password"}
|
||||||
|
spellCheck="false"
|
||||||
|
required="required"
|
||||||
|
autoComplete="false"
|
||||||
|
align="ig-left"
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ newSecretKey: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button
|
<button
|
||||||
id="generate-keys"
|
id="generate-keys"
|
||||||
className={
|
className={"btn btn-primary"}
|
||||||
"btn btn-primary " + (this.state.keysReadOnly ? "hidden" : "")
|
|
||||||
}
|
|
||||||
onClick={this.generateAuth.bind(this)}
|
onClick={this.generateAuth.bind(this)}
|
||||||
>
|
>
|
||||||
Generate
|
Generate
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="update-keys"
|
id="update-keys"
|
||||||
className={
|
className={classNames({
|
||||||
"btn btn-success " + (this.state.keysReadOnly ? "hidden" : "")
|
btn: true,
|
||||||
}
|
"btn-success": this.canUpdateCredentials()
|
||||||
|
})}
|
||||||
|
disabled={!this.canUpdateCredentials()}
|
||||||
onClick={this.setAuth.bind(this)}
|
onClick={this.setAuth.bind(this)}
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
@@ -198,4 +278,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChangePasswordModal)
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ChangePasswordModal)
|
||||||
|
|||||||
@@ -17,21 +17,38 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { shallow, mount } from "enzyme"
|
import { shallow, mount } from "enzyme"
|
||||||
import { ChangePasswordModal } from "../ChangePasswordModal"
|
import { ChangePasswordModal } from "../ChangePasswordModal"
|
||||||
|
import jwtDecode from "jwt-decode"
|
||||||
|
|
||||||
|
jest.mock("jwt-decode")
|
||||||
|
|
||||||
|
jwtDecode.mockImplementation(() => ({ sub: "minio" }))
|
||||||
|
|
||||||
jest.mock("../../web", () => ({
|
jest.mock("../../web", () => ({
|
||||||
GetAuth: jest.fn(() => {
|
|
||||||
return Promise.resolve({ accessKey: "test1", secretKey: "test2" })
|
|
||||||
}),
|
|
||||||
GenerateAuth: jest.fn(() => {
|
GenerateAuth: jest.fn(() => {
|
||||||
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" })
|
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" })
|
||||||
}),
|
}),
|
||||||
SetAuth: jest.fn(({ accessKey, secretKey }) => {
|
SetAuth: jest.fn(
|
||||||
if (accessKey == "test3" && secretKey == "test4") {
|
({ currentAccessKey, currentSecretKey, newAccessKey, newSecretKey }) => {
|
||||||
return Promise.resolve({})
|
if (
|
||||||
} else {
|
currentAccessKey == "minio" &&
|
||||||
return Promise.reject({ message: "Error" })
|
currentSecretKey == "minio123" &&
|
||||||
|
newAccessKey == "test" &&
|
||||||
|
newSecretKey == "test1234"
|
||||||
|
) {
|
||||||
|
return Promise.resolve({})
|
||||||
|
} else {
|
||||||
|
return Promise.reject({
|
||||||
|
message: "Error"
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
),
|
||||||
|
GetToken: jest.fn(() => "")
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock("../../utils", () => ({
|
||||||
|
getRandomAccessKey: () => "raccesskey",
|
||||||
|
getRandomSecretKey: () => "rsecretkey"
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe("ChangePasswordModal", () => {
|
describe("ChangePasswordModal", () => {
|
||||||
@@ -40,57 +57,112 @@ describe("ChangePasswordModal", () => {
|
|||||||
memory: "test",
|
memory: "test",
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test",
|
runtime: "test",
|
||||||
info: { isEnvCreds: false }
|
info: { isEnvCreds: false },
|
||||||
|
userInfo: { isIAMUser: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should get the keys when its rendered", () => {
|
it("should not allow changing password when isWorm is true", () => {
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
const newServerInfo = { ...serverInfo, info: { isWorm: true } }
|
||||||
setImmediate(() => {
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
expect(wrapper.state("accessKey")).toBe("test1")
|
expect(
|
||||||
expect(wrapper.state("secretKey")).toBe("test2")
|
wrapper
|
||||||
})
|
.find("ModalBody")
|
||||||
|
.childAt(0)
|
||||||
|
.text()
|
||||||
|
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should show readonly keys when isEnvCreds is true", () => {
|
it("should not allow changing password when isEnvCreds is true and not IAM user", () => {
|
||||||
const newServerInfo = { ...serverInfo, info: { isEnvCreds: true } }
|
const newServerInfo = {
|
||||||
|
...serverInfo,
|
||||||
|
info: { isEnvCreds: true },
|
||||||
|
userInfo: { isIAMUser: false }
|
||||||
|
}
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
expect(wrapper.state("accessKey")).toBe("xxxxxxxxx")
|
expect(
|
||||||
expect(wrapper.state("secretKey")).toBe("xxxxxxxxx")
|
wrapper
|
||||||
expect(wrapper.find("#accessKey").prop("readonly")).toBeTruthy()
|
.find("ModalBody")
|
||||||
expect(wrapper.find("#secretKey").prop("readonly")).toBeTruthy()
|
.childAt(0)
|
||||||
expect(wrapper.find("#generate-keys").hasClass("hidden")).toBeTruthy()
|
.text()
|
||||||
expect(wrapper.find("#update-keys").hasClass("hidden")).toBeTruthy()
|
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should generate accessKey and secretKey when Generate buttons is clicked", () => {
|
it("should generate accessKey and secretKey when Generate buttons is clicked", () => {
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
wrapper.find("#generate-keys").simulate("click")
|
wrapper.find("#generate-keys").simulate("click")
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(wrapper.state("accessKey")).toBe("gen1")
|
expect(wrapper.state("newAccessKey")).toBe("raccesskey")
|
||||||
expect(wrapper.state("secretKey")).toBe("gen2")
|
expect(wrapper.state("newSecretKey")).toBe("rsecretkey")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not generate accessKey for IAM User", () => {
|
||||||
|
const newServerInfo = {
|
||||||
|
...serverInfo,
|
||||||
|
userInfo: { isIAMUser: true }
|
||||||
|
}
|
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
|
wrapper.find("#generate-keys").simulate("click")
|
||||||
|
setImmediate(() => {
|
||||||
|
expect(wrapper.state("newAccessKey")).toBe("minio")
|
||||||
|
expect(wrapper.state("newSecretKey")).toBe("rsecretkey")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not show new accessKey field for IAM User", () => {
|
||||||
|
const newServerInfo = {
|
||||||
|
...serverInfo,
|
||||||
|
userInfo: { isIAMUser: true }
|
||||||
|
}
|
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
|
expect(wrapper.find("#newAccesskey").exists()).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should disble Update button for invalid accessKey or secretKey", () => {
|
||||||
|
const showAlert = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("#currentAccessKey")
|
||||||
|
.simulate("change", { target: { value: "minio" } })
|
||||||
|
wrapper
|
||||||
|
.find("#currentSecretKey")
|
||||||
|
.simulate("change", { target: { value: "minio123" } })
|
||||||
|
wrapper.find("#newAccessKey").simulate("change", { target: { value: "t" } })
|
||||||
|
wrapper
|
||||||
|
.find("#newSecretKey")
|
||||||
|
.simulate("change", { target: { value: "t1" } })
|
||||||
|
expect(wrapper.find("#update-keys").prop("disabled")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it("should update accessKey and secretKey when Update button is clicked", () => {
|
it("should update accessKey and secretKey when Update button is clicked", () => {
|
||||||
const showAlert = jest.fn()
|
const showAlert = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
||||||
)
|
)
|
||||||
wrapper
|
wrapper
|
||||||
.find("#accessKey")
|
.find("#currentAccessKey")
|
||||||
.simulate("change", { target: { value: "test3" } })
|
.simulate("change", { target: { value: "minio" } })
|
||||||
wrapper
|
wrapper
|
||||||
.find("#secretKey")
|
.find("#currentSecretKey")
|
||||||
.simulate("change", { target: { value: "test4" } })
|
.simulate("change", { target: { value: "minio123" } })
|
||||||
|
wrapper
|
||||||
|
.find("#newAccessKey")
|
||||||
|
.simulate("change", { target: { value: "test" } })
|
||||||
|
wrapper
|
||||||
|
.find("#newSecretKey")
|
||||||
|
.simulate("change", { target: { value: "test1234" } })
|
||||||
|
expect(wrapper.find("#update-keys").prop("disabled")).toBeFalsy()
|
||||||
wrapper.find("#update-keys").simulate("click")
|
wrapper.find("#update-keys").simulate("click")
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(showAlert).toHaveBeenCalledWith({
|
expect(showAlert).toHaveBeenCalledWith({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Changed credentials"
|
message: "Credentials updated successfully."
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const fetchStorageInfo = () => {
|
|||||||
return web.StorageInfo().then(res => {
|
return web.StorageInfo().then(res => {
|
||||||
const storageInfo = {
|
const storageInfo = {
|
||||||
total: res.storageInfo.Total,
|
total: res.storageInfo.Total,
|
||||||
used: res.storageInfo.Used
|
used: res.storageInfo.Used
|
||||||
}
|
}
|
||||||
dispatch(setStorageInfo(storageInfo))
|
dispatch(setStorageInfo(storageInfo))
|
||||||
})
|
})
|
||||||
@@ -54,7 +54,8 @@ export const fetchServerInfo = () => {
|
|||||||
memory: res.MinioMemory,
|
memory: res.MinioMemory,
|
||||||
platform: res.MinioPlatform,
|
platform: res.MinioPlatform,
|
||||||
runtime: res.MinioRuntime,
|
runtime: res.MinioRuntime,
|
||||||
info: res.MinioGlobalInfo
|
info: res.MinioGlobalInfo,
|
||||||
|
userInfo: res.MinioUserInfo
|
||||||
}
|
}
|
||||||
dispatch(setServerInfo(serverInfo))
|
dispatch(setServerInfo(serverInfo))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,3 +28,13 @@ export const NONE = "none"
|
|||||||
export const SHARE_OBJECT_EXPIRY_DAYS = 5
|
export const SHARE_OBJECT_EXPIRY_DAYS = 5
|
||||||
export const SHARE_OBJECT_EXPIRY_HOURS = 0
|
export const SHARE_OBJECT_EXPIRY_HOURS = 0
|
||||||
export const SHARE_OBJECT_EXPIRY_MINUTES = 0
|
export const SHARE_OBJECT_EXPIRY_MINUTES = 0
|
||||||
|
|
||||||
|
export const ACCESS_KEY_MIN_LENGTH = 3
|
||||||
|
export const SECRET_KEY_MIN_LENGTH = 8
|
||||||
|
|
||||||
|
export const SORT_BY_NAME = "name"
|
||||||
|
export const SORT_BY_SIZE = "size"
|
||||||
|
export const SORT_BY_LAST_MODIFIED = "last-modified"
|
||||||
|
|
||||||
|
export const SORT_ORDER_ASC = "asc"
|
||||||
|
export const SORT_ORDER_DESC = "desc"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const ObjectItem = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="fesl-item fesl-item-name">
|
<div className="fesl-item fesl-item-name">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href={getDataType(name, contentType) === "folder" ? name : "#"}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
|
|||||||
@@ -18,11 +18,19 @@ import React from "react"
|
|||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import * as actionsObjects from "./actions"
|
import * as actionsObjects from "./actions"
|
||||||
|
import {
|
||||||
|
SORT_BY_NAME,
|
||||||
|
SORT_BY_SIZE,
|
||||||
|
SORT_BY_LAST_MODIFIED,
|
||||||
|
SORT_ORDER_DESC,
|
||||||
|
SORT_ORDER_ASC
|
||||||
|
} from "../constants"
|
||||||
|
|
||||||
export const ObjectsHeader = ({
|
export const ObjectsHeader = ({
|
||||||
sortNameOrder,
|
sortedByName,
|
||||||
sortSizeOrder,
|
sortedBySize,
|
||||||
sortLastModifiedOrder,
|
sortedByLastModified,
|
||||||
|
sortOrder,
|
||||||
sortObjects
|
sortObjects
|
||||||
}) => (
|
}) => (
|
||||||
<div className="feb-container">
|
<div className="feb-container">
|
||||||
@@ -31,48 +39,54 @@ export const ObjectsHeader = ({
|
|||||||
<div
|
<div
|
||||||
className="fesl-item fesl-item-name"
|
className="fesl-item fesl-item-name"
|
||||||
id="sort-by-name"
|
id="sort-by-name"
|
||||||
onClick={() => sortObjects("name")}
|
onClick={() => sortObjects(SORT_BY_NAME)}
|
||||||
data-sort="name"
|
data-sort="name"
|
||||||
>
|
>
|
||||||
Name
|
Name
|
||||||
<i
|
<i
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"fesli-sort": true,
|
"fesli-sort": true,
|
||||||
|
"fesli-sort--active": sortedByName,
|
||||||
fa: true,
|
fa: true,
|
||||||
"fa-sort-alpha-desc": sortNameOrder,
|
"fa-sort-alpha-desc": sortedByName && sortOrder === SORT_ORDER_DESC,
|
||||||
"fa-sort-alpha-asc": !sortNameOrder
|
"fa-sort-alpha-asc": sortedByName && sortOrder === SORT_ORDER_ASC
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="fesl-item fesl-item-size"
|
className="fesl-item fesl-item-size"
|
||||||
id="sort-by-size"
|
id="sort-by-size"
|
||||||
onClick={() => sortObjects("size")}
|
onClick={() => sortObjects(SORT_BY_SIZE)}
|
||||||
data-sort="size"
|
data-sort="size"
|
||||||
>
|
>
|
||||||
Size
|
Size
|
||||||
<i
|
<i
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"fesli-sort": true,
|
"fesli-sort": true,
|
||||||
|
"fesli-sort--active": sortedBySize,
|
||||||
fa: true,
|
fa: true,
|
||||||
"fa-sort-amount-desc": sortSizeOrder,
|
"fa-sort-amount-desc":
|
||||||
"fa-sort-amount-asc": !sortSizeOrder
|
sortedBySize && sortOrder === SORT_ORDER_DESC,
|
||||||
|
"fa-sort-amount-asc": sortedBySize && sortOrder === SORT_ORDER_ASC
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="fesl-item fesl-item-modified"
|
className="fesl-item fesl-item-modified"
|
||||||
id="sort-by-last-modified"
|
id="sort-by-last-modified"
|
||||||
onClick={() => sortObjects("last-modified")}
|
onClick={() => sortObjects(SORT_BY_LAST_MODIFIED)}
|
||||||
data-sort="last-modified"
|
data-sort="last-modified"
|
||||||
>
|
>
|
||||||
Last Modified
|
Last Modified
|
||||||
<i
|
<i
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"fesli-sort": true,
|
"fesli-sort": true,
|
||||||
|
"fesli-sort--active": sortedByLastModified,
|
||||||
fa: true,
|
fa: true,
|
||||||
"fa-sort-numeric-desc": sortLastModifiedOrder,
|
"fa-sort-numeric-desc":
|
||||||
"fa-sort-numeric-asc": !sortLastModifiedOrder
|
sortedByLastModified && sortOrder === SORT_ORDER_DESC,
|
||||||
|
"fa-sort-numeric-asc":
|
||||||
|
sortedByLastModified && sortOrder === SORT_ORDER_ASC
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,10 +97,10 @@ export const ObjectsHeader = ({
|
|||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
sortNameOrder: state.objects.sortBy == "name" && state.objects.sortOrder,
|
sortedByName: state.objects.sortBy == SORT_BY_NAME,
|
||||||
sortSizeOrder: state.objects.sortBy == "size" && state.objects.sortOrder,
|
sortedBySize: state.objects.sortBy == SORT_BY_SIZE,
|
||||||
sortLastModifiedOrder:
|
sortedByLastModified: state.objects.sortBy == SORT_BY_LAST_MODIFIED,
|
||||||
state.objects.sortBy == "last-modified" && state.objects.sortOrder
|
sortOrder: state.objects.sortOrder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,4 +110,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ObjectsHeader)
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ObjectsHeader)
|
||||||
|
|||||||
@@ -15,32 +15,52 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import classNames from "classnames"
|
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import InfiniteScroll from "react-infinite-scroller"
|
import InfiniteScroll from "react-infinite-scroller"
|
||||||
import * as actionsObjects from "./actions"
|
|
||||||
import ObjectsList from "./ObjectsList"
|
import ObjectsList from "./ObjectsList"
|
||||||
|
|
||||||
export class ObjectsListContainer extends React.Component {
|
export class ObjectsListContainer extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
this.loadNextPage = this.loadNextPage.bind(this)
|
||||||
|
}
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (
|
||||||
|
nextProps.currentBucket !== this.props.currentBucket ||
|
||||||
|
nextProps.currentPrefix !== this.props.currentPrefix ||
|
||||||
|
nextProps.sortBy !== this.props.sortBy ||
|
||||||
|
nextProps.sortOrder !== this.props.sortOrder
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
page: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadNextPage() {
|
||||||
|
this.setState(state => {
|
||||||
|
return { page: state.page + 1 }
|
||||||
|
})
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
const { objects, isTruncated, currentBucket, loadObjects } = this.props
|
const { objects, listLoading } = this.props
|
||||||
|
|
||||||
|
const visibleObjects = objects.slice(0, this.state.page * 100)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="feb-container">
|
<div style={{ position: "relative" }}>
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
pageStart={0}
|
pageStart={0}
|
||||||
loadMore={() => loadObjects(true)}
|
loadMore={this.loadNextPage}
|
||||||
hasMore={isTruncated}
|
hasMore={objects.length > visibleObjects.length}
|
||||||
useWindow={true}
|
useWindow={true}
|
||||||
initialLoad={false}
|
initialLoad={false}
|
||||||
>
|
>
|
||||||
<ObjectsList objects={objects} />
|
<ObjectsList objects={visibleObjects} />
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
<div
|
{listLoading && <div className="loading" />}
|
||||||
className="text-center"
|
|
||||||
style={{ display: isTruncated && currentBucket ? "block" : "none" }}
|
|
||||||
>
|
|
||||||
<span>Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -51,16 +71,10 @@ const mapStateToProps = state => {
|
|||||||
currentBucket: state.buckets.currentBucket,
|
currentBucket: state.buckets.currentBucket,
|
||||||
currentPrefix: state.objects.currentPrefix,
|
currentPrefix: state.objects.currentPrefix,
|
||||||
objects: state.objects.list,
|
objects: state.objects.list,
|
||||||
isTruncated: state.objects.isTruncated
|
sortBy: state.objects.sortBy,
|
||||||
|
sortOrder: state.objects.sortOrder,
|
||||||
|
listLoading: state.objects.listLoading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
export default connect(mapStateToProps)(ObjectsListContainer)
|
||||||
return {
|
|
||||||
loadObjects: append => dispatch(actionsObjects.fetchObjects(append))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
|
||||||
ObjectsListContainer
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { shallow } from "enzyme"
|
import { shallow } from "enzyme"
|
||||||
import { ObjectsHeader } from "../ObjectsHeader"
|
import { ObjectsHeader } from "../ObjectsHeader"
|
||||||
|
import { SORT_ORDER_ASC, SORT_ORDER_DESC } from "../../constants"
|
||||||
|
|
||||||
describe("ObjectsHeader", () => {
|
describe("ObjectsHeader", () => {
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
@@ -24,44 +25,84 @@ describe("ObjectsHeader", () => {
|
|||||||
shallow(<ObjectsHeader sortObjects={sortObjects} />)
|
shallow(<ObjectsHeader sortObjects={sortObjects} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render columns with asc classes by default", () => {
|
it("should render the name column with asc class when objects are sorted by name asc", () => {
|
||||||
const sortObjects = jest.fn()
|
const sortObjects = jest.fn()
|
||||||
const wrapper = shallow(<ObjectsHeader sortObjects={sortObjects} />)
|
const wrapper = shallow(
|
||||||
|
<ObjectsHeader
|
||||||
|
sortObjects={sortObjects}
|
||||||
|
sortedByName={true}
|
||||||
|
sortOrder={SORT_ORDER_ASC}
|
||||||
|
/>
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-asc")
|
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-asc")
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
expect(
|
|
||||||
wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-asc")
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-asc")
|
|
||||||
).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render name column with desc class when objects are sorted by name", () => {
|
it("should render the name column with desc class when objects are sorted by name desc", () => {
|
||||||
const sortObjects = jest.fn()
|
const sortObjects = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsHeader sortObjects={sortObjects} sortNameOrder={true} />
|
<ObjectsHeader
|
||||||
|
sortObjects={sortObjects}
|
||||||
|
sortedByName={true}
|
||||||
|
sortOrder={SORT_ORDER_DESC}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-desc")
|
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-desc")
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render size column with desc class when objects are sorted by size", () => {
|
it("should render the size column with asc class when objects are sorted by size asc", () => {
|
||||||
const sortObjects = jest.fn()
|
const sortObjects = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsHeader sortObjects={sortObjects} sortSizeOrder={true} />
|
<ObjectsHeader
|
||||||
|
sortObjects={sortObjects}
|
||||||
|
sortedBySize={true}
|
||||||
|
sortOrder={SORT_ORDER_ASC}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-asc")
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should render the size column with desc class when objects are sorted by size desc", () => {
|
||||||
|
const sortObjects = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectsHeader
|
||||||
|
sortObjects={sortObjects}
|
||||||
|
sortedBySize={true}
|
||||||
|
sortOrder={SORT_ORDER_DESC}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-desc")
|
wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-desc")
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render last modified column with desc class when objects are sorted by last modified", () => {
|
it("should render the date column with asc class when objects are sorted by date asc", () => {
|
||||||
const sortObjects = jest.fn()
|
const sortObjects = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsHeader sortObjects={sortObjects} sortLastModifiedOrder={true} />
|
<ObjectsHeader
|
||||||
|
sortObjects={sortObjects}
|
||||||
|
sortedByLastModified={true}
|
||||||
|
sortOrder={SORT_ORDER_ASC}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-asc")
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should render the date column with desc class when objects are sorted by date desc", () => {
|
||||||
|
const sortObjects = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ObjectsHeader
|
||||||
|
sortObjects={sortObjects}
|
||||||
|
sortedByLastModified={true}
|
||||||
|
sortOrder={SORT_ORDER_DESC}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-desc")
|
wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-desc")
|
||||||
|
|||||||
@@ -20,14 +20,13 @@ import { ObjectsListContainer } from "../ObjectsListContainer"
|
|||||||
|
|
||||||
describe("ObjectsList", () => {
|
describe("ObjectsList", () => {
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<ObjectsListContainer loadObjects={jest.fn()} />)
|
shallow(<ObjectsListContainer objects={[]} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
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" }]}
|
objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
|
||||||
loadObjects={jest.fn()}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(wrapper.find("ObjectsList").length).toBe(1)
|
expect(wrapper.find("ObjectsList").length).toBe(1)
|
||||||
@@ -37,10 +36,14 @@ describe("ObjectsList", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should show the loading indicator at the bottom if there are more elements to display", () => {
|
it("should show the loading indicator when the objects are being loaded", () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsListContainer currentBucket="test1" isTruncated={true} />
|
<ObjectsListContainer
|
||||||
|
currentBucket="test1"
|
||||||
|
objects={[]}
|
||||||
|
listLoading={true}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
expect(wrapper.find(".text-center").prop("style")).toHaveProperty("display", "block")
|
expect(wrapper.find(".loading").exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ import configureStore from "redux-mock-store"
|
|||||||
import thunk from "redux-thunk"
|
import thunk from "redux-thunk"
|
||||||
import * as actionsObjects from "../actions"
|
import * as actionsObjects from "../actions"
|
||||||
import * as alertActions from "../../alert/actions"
|
import * as alertActions from "../../alert/actions"
|
||||||
import { minioBrowserPrefix } from "../../constants"
|
import {
|
||||||
|
minioBrowserPrefix,
|
||||||
|
SORT_BY_NAME,
|
||||||
|
SORT_ORDER_ASC,
|
||||||
|
SORT_BY_LAST_MODIFIED,
|
||||||
|
SORT_ORDER_DESC
|
||||||
|
} from "../../constants"
|
||||||
import history from "../../history"
|
import history from "../../history"
|
||||||
|
|
||||||
jest.mock("../../web", () => ({
|
jest.mock("../../web", () => ({
|
||||||
@@ -37,8 +43,6 @@ jest.mock("../../web", () => ({
|
|||||||
} else {
|
} else {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
objects: [{ name: "test1" }, { name: "test2" }],
|
objects: [{ name: "test1" }, { name: "test2" }],
|
||||||
istruncated: false,
|
|
||||||
nextmarker: "test2",
|
|
||||||
writable: false
|
writable: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -77,17 +81,11 @@ describe("Objects actions", () => {
|
|||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_LIST",
|
type: "objects/SET_LIST",
|
||||||
objects: [{ name: "test1" }, { name: "test2" }],
|
objects: [{ name: "test1" }, { name: "test2" }]
|
||||||
isTruncated: false,
|
|
||||||
marker: "test2"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
actionsObjects.setList(
|
actionsObjects.setList([{ name: "test1" }, { name: "test2" }])
|
||||||
[{ name: "test1" }, { name: "test2" }],
|
|
||||||
"test2",
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
@@ -98,10 +96,10 @@ describe("Objects actions", () => {
|
|||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_SORT_BY",
|
type: "objects/SET_SORT_BY",
|
||||||
sortBy: "name"
|
sortBy: SORT_BY_NAME
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
store.dispatch(actionsObjects.setSortBy("name"))
|
store.dispatch(actionsObjects.setSortBy(SORT_BY_NAME))
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
@@ -111,10 +109,10 @@ describe("Objects actions", () => {
|
|||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_SORT_ORDER",
|
type: "objects/SET_SORT_ORDER",
|
||||||
sortOrder: true
|
sortOrder: SORT_ORDER_ASC
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
store.dispatch(actionsObjects.setSortOrder(true))
|
store.dispatch(actionsObjects.setSortOrder(SORT_ORDER_ASC))
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
@@ -126,23 +124,26 @@ describe("Objects actions", () => {
|
|||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_LIST",
|
type: "objects/RESET_LIST"
|
||||||
objects: [{ name: "test1" }, { name: "test2" }],
|
|
||||||
marker: "test2",
|
|
||||||
isTruncated: false
|
|
||||||
},
|
},
|
||||||
|
{ listLoading: true, type: "objects/SET_LIST_LOADING" },
|
||||||
{
|
{
|
||||||
type: "objects/SET_SORT_BY",
|
type: "objects/SET_SORT_BY",
|
||||||
sortBy: ""
|
sortBy: SORT_BY_LAST_MODIFIED
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "objects/SET_SORT_ORDER",
|
type: "objects/SET_SORT_ORDER",
|
||||||
sortOrder: false
|
sortOrder: SORT_ORDER_DESC
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "objects/SET_LIST",
|
||||||
|
objects: [{ name: "test2" }, { name: "test1" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "objects/SET_PREFIX_WRITABLE",
|
type: "objects/SET_PREFIX_WRITABLE",
|
||||||
prefixWritable: false
|
prefixWritable: false
|
||||||
}
|
},
|
||||||
|
{ listLoading: false, type: "objects/SET_LIST_LOADING" }
|
||||||
]
|
]
|
||||||
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
|
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
@@ -150,35 +151,16 @@ describe("Objects actions", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates objects/APPEND_LIST after fetching more objects", () => {
|
|
||||||
const store = mockStore({
|
|
||||||
buckets: { currentBucket: "bk1" },
|
|
||||||
objects: { currentPrefix: "" }
|
|
||||||
})
|
|
||||||
const expectedActions = [
|
|
||||||
{
|
|
||||||
type: "objects/APPEND_LIST",
|
|
||||||
objects: [{ name: "test1" }, { name: "test2" }],
|
|
||||||
marker: "test2",
|
|
||||||
isTruncated: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "objects/SET_PREFIX_WRITABLE",
|
|
||||||
prefixWritable: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
return store.dispatch(actionsObjects.fetchObjects(true)).then(() => {
|
|
||||||
const actions = store.getActions()
|
|
||||||
expect(actions).toEqual(expectedActions)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("creates objects/RESET_LIST after failing to fetch the objects from bucket with ListObjects denied for LoggedIn users", () => {
|
it("creates objects/RESET_LIST after failing to fetch the objects from bucket with ListObjects denied for LoggedIn users", () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
buckets: { currentBucket: "test-deny" },
|
buckets: { currentBucket: "test-deny" },
|
||||||
objects: { currentPrefix: "" }
|
objects: { currentPrefix: "" }
|
||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: "objects/RESET_LIST"
|
||||||
|
},
|
||||||
|
{ listLoading: true, type: "objects/SET_LIST_LOADING" },
|
||||||
{
|
{
|
||||||
type: "alert/SET",
|
type: "alert/SET",
|
||||||
alert: {
|
alert: {
|
||||||
@@ -189,8 +171,9 @@ describe("Objects actions", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "object/RESET_LIST"
|
type: "objects/RESET_LIST"
|
||||||
}
|
},
|
||||||
|
{ listLoading: false, type: "objects/SET_LIST_LOADING" }
|
||||||
]
|
]
|
||||||
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
|
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
@@ -213,28 +196,24 @@ describe("Objects actions", () => {
|
|||||||
objects: {
|
objects: {
|
||||||
list: [],
|
list: [],
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: false,
|
sortOrder: SORT_ORDER_ASC
|
||||||
isTruncated: false,
|
|
||||||
marker: ""
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{
|
{
|
||||||
type: "objects/SET_SORT_BY",
|
type: "objects/SET_SORT_BY",
|
||||||
sortBy: "name"
|
sortBy: SORT_BY_NAME
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "objects/SET_SORT_ORDER",
|
type: "objects/SET_SORT_ORDER",
|
||||||
sortOrder: true
|
sortOrder: SORT_ORDER_ASC
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "objects/SET_LIST",
|
type: "objects/SET_LIST",
|
||||||
objects: [],
|
objects: []
|
||||||
isTruncated: false,
|
|
||||||
marker: ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
store.dispatch(actionsObjects.sortObjects("name"))
|
store.dispatch(actionsObjects.sortObjects(SORT_BY_NAME))
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
@@ -246,6 +225,10 @@ describe("Objects actions", () => {
|
|||||||
})
|
})
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{ type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" },
|
{ type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" },
|
||||||
|
{
|
||||||
|
type: "objects/RESET_LIST"
|
||||||
|
},
|
||||||
|
{ listLoading: true, type: "objects/SET_LIST_LOADING" },
|
||||||
{ type: "objects/CHECKED_LIST_RESET" }
|
{ type: "objects/CHECKED_LIST_RESET" }
|
||||||
]
|
]
|
||||||
store.dispatch(actionsObjects.selectPrefix("abc/"))
|
store.dispatch(actionsObjects.selectPrefix("abc/"))
|
||||||
|
|||||||
@@ -16,17 +16,17 @@
|
|||||||
|
|
||||||
import reducer from "../reducer"
|
import reducer from "../reducer"
|
||||||
import * as actions from "../actions"
|
import * as actions from "../actions"
|
||||||
|
import { SORT_ORDER_ASC, SORT_BY_NAME } from "../../constants"
|
||||||
|
|
||||||
describe("objects reducer", () => {
|
describe("objects reducer", () => {
|
||||||
it("should return the initial state", () => {
|
it("should return the initial state", () => {
|
||||||
const initialState = reducer(undefined, {})
|
const initialState = reducer(undefined, {})
|
||||||
expect(initialState).toEqual({
|
expect(initialState).toEqual({
|
||||||
list: [],
|
list: [],
|
||||||
|
listLoading: false,
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: false,
|
sortOrder: SORT_ORDER_ASC,
|
||||||
currentPrefix: "",
|
currentPrefix: "",
|
||||||
marker: "",
|
|
||||||
isTruncated: false,
|
|
||||||
prefixWritable: false,
|
prefixWritable: false,
|
||||||
shareObject: {
|
shareObject: {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -40,37 +40,9 @@ describe("objects reducer", () => {
|
|||||||
it("should handle SET_LIST", () => {
|
it("should handle SET_LIST", () => {
|
||||||
const newState = reducer(undefined, {
|
const newState = reducer(undefined, {
|
||||||
type: actions.SET_LIST,
|
type: actions.SET_LIST,
|
||||||
objects: [{ name: "obj1" }, { name: "obj2" }],
|
objects: [{ name: "obj1" }, { name: "obj2" }]
|
||||||
marker: "obj2",
|
|
||||||
isTruncated: false
|
|
||||||
})
|
})
|
||||||
expect(newState.list).toEqual([{ name: "obj1" }, { name: "obj2" }])
|
expect(newState.list).toEqual([{ name: "obj1" }, { name: "obj2" }])
|
||||||
expect(newState.marker).toBe("obj2")
|
|
||||||
expect(newState.isTruncated).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle APPEND_LIST", () => {
|
|
||||||
const newState = reducer(
|
|
||||||
{
|
|
||||||
list: [{ name: "obj1" }, { name: "obj2" }],
|
|
||||||
marker: "obj2",
|
|
||||||
isTruncated: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: actions.APPEND_LIST,
|
|
||||||
objects: [{ name: "obj3" }, { name: "obj4" }],
|
|
||||||
marker: "obj4",
|
|
||||||
isTruncated: false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(newState.list).toEqual([
|
|
||||||
{ name: "obj1" },
|
|
||||||
{ name: "obj2" },
|
|
||||||
{ name: "obj3" },
|
|
||||||
{ name: "obj4" }
|
|
||||||
])
|
|
||||||
expect(newState.marker).toBe("obj4")
|
|
||||||
expect(newState.isTruncated).toBeFalsy()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle REMOVE", () => {
|
it("should handle REMOVE", () => {
|
||||||
@@ -98,30 +70,28 @@ describe("objects reducer", () => {
|
|||||||
it("should handle SET_SORT_BY", () => {
|
it("should handle SET_SORT_BY", () => {
|
||||||
const newState = reducer(undefined, {
|
const newState = reducer(undefined, {
|
||||||
type: actions.SET_SORT_BY,
|
type: actions.SET_SORT_BY,
|
||||||
sortBy: "name"
|
sortBy: SORT_BY_NAME
|
||||||
})
|
})
|
||||||
expect(newState.sortBy).toEqual("name")
|
expect(newState.sortBy).toEqual(SORT_BY_NAME)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle SET_SORT_ORDER", () => {
|
it("should handle SET_SORT_ORDER", () => {
|
||||||
const newState = reducer(undefined, {
|
const newState = reducer(undefined, {
|
||||||
type: actions.SET_SORT_ORDER,
|
type: actions.SET_SORT_ORDER,
|
||||||
sortOrder: true
|
sortOrder: SORT_ORDER_ASC
|
||||||
})
|
})
|
||||||
expect(newState.sortOrder).toEqual(true)
|
expect(newState.sortOrder).toEqual(SORT_ORDER_ASC)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle SET_CURRENT_PREFIX", () => {
|
it("should handle SET_CURRENT_PREFIX", () => {
|
||||||
const newState = reducer(
|
const newState = reducer(
|
||||||
{ currentPrefix: "test1/", marker: "abc", isTruncated: true },
|
{ currentPrefix: "test1/" },
|
||||||
{
|
{
|
||||||
type: actions.SET_CURRENT_PREFIX,
|
type: actions.SET_CURRENT_PREFIX,
|
||||||
prefix: "test2/"
|
prefix: "test2/"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(newState.currentPrefix).toEqual("test2/")
|
expect(newState.currentPrefix).toEqual("test2/")
|
||||||
expect(newState.marker).toEqual("")
|
|
||||||
expect(newState.isTruncated).toBeFalsy()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle SET_PREFIX_WRITABLE", () => {
|
it("should handle SET_PREFIX_WRITABLE", () => {
|
||||||
|
|||||||
@@ -16,15 +16,26 @@
|
|||||||
|
|
||||||
import web from "../web"
|
import web from "../web"
|
||||||
import history from "../history"
|
import history from "../history"
|
||||||
import { sortObjectsByName, sortObjectsBySize, sortObjectsByDate } from "../utils"
|
import {
|
||||||
|
sortObjectsByName,
|
||||||
|
sortObjectsBySize,
|
||||||
|
sortObjectsByDate
|
||||||
|
} from "../utils"
|
||||||
import { getCurrentBucket } from "../buckets/selectors"
|
import { getCurrentBucket } from "../buckets/selectors"
|
||||||
import { getCurrentPrefix, getCheckedList } from "./selectors"
|
import { getCurrentPrefix, getCheckedList } from "./selectors"
|
||||||
import * as alertActions from "../alert/actions"
|
import * as alertActions from "../alert/actions"
|
||||||
import * as bucketActions from "../buckets/actions"
|
import * as bucketActions from "../buckets/actions"
|
||||||
import { minioBrowserPrefix } from "../constants"
|
import {
|
||||||
|
minioBrowserPrefix,
|
||||||
|
SORT_BY_NAME,
|
||||||
|
SORT_BY_SIZE,
|
||||||
|
SORT_BY_LAST_MODIFIED,
|
||||||
|
SORT_ORDER_ASC,
|
||||||
|
SORT_ORDER_DESC
|
||||||
|
} from "../constants"
|
||||||
|
|
||||||
export const SET_LIST = "objects/SET_LIST"
|
export const SET_LIST = "objects/SET_LIST"
|
||||||
export const RESET_LIST = "object/RESET_LIST"
|
export const RESET_LIST = "objects/RESET_LIST"
|
||||||
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"
|
||||||
@@ -35,53 +46,63 @@ export const SET_SHARE_OBJECT = "objects/SET_SHARE_OBJECT"
|
|||||||
export const CHECKED_LIST_ADD = "objects/CHECKED_LIST_ADD"
|
export const CHECKED_LIST_ADD = "objects/CHECKED_LIST_ADD"
|
||||||
export const CHECKED_LIST_REMOVE = "objects/CHECKED_LIST_REMOVE"
|
export const CHECKED_LIST_REMOVE = "objects/CHECKED_LIST_REMOVE"
|
||||||
export const CHECKED_LIST_RESET = "objects/CHECKED_LIST_RESET"
|
export const CHECKED_LIST_RESET = "objects/CHECKED_LIST_RESET"
|
||||||
|
export const SET_LIST_LOADING = "objects/SET_LIST_LOADING"
|
||||||
|
|
||||||
export const setList = (objects, marker, isTruncated) => ({
|
export const setList = objects => ({
|
||||||
type: SET_LIST,
|
type: SET_LIST,
|
||||||
objects,
|
objects
|
||||||
marker,
|
|
||||||
isTruncated
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const resetList = () => ({
|
export const resetList = () => ({
|
||||||
type: RESET_LIST
|
type: RESET_LIST
|
||||||
})
|
})
|
||||||
|
|
||||||
export const appendList = (objects, marker, isTruncated) => ({
|
export const setListLoading = listLoading => ({
|
||||||
type: APPEND_LIST,
|
type: SET_LIST_LOADING,
|
||||||
objects,
|
listLoading
|
||||||
marker,
|
|
||||||
isTruncated
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const fetchObjects = append => {
|
export const fetchObjects = () => {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const {buckets: {currentBucket}, objects: {currentPrefix, marker}} = getState()
|
dispatch(resetList())
|
||||||
|
const {
|
||||||
|
buckets: { currentBucket },
|
||||||
|
objects: { currentPrefix }
|
||||||
|
} = getState()
|
||||||
if (currentBucket) {
|
if (currentBucket) {
|
||||||
|
dispatch(setListLoading(true))
|
||||||
return web
|
return web
|
||||||
.ListObjects({
|
.ListObjects({
|
||||||
bucketName: currentBucket,
|
bucketName: currentBucket,
|
||||||
prefix: currentPrefix,
|
prefix: currentPrefix
|
||||||
marker: append ? marker : ""
|
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let objects = []
|
// we need to check if the bucket name and prefix are the same as
|
||||||
if (res.objects) {
|
// when the request was made before updating the displayed objects
|
||||||
objects = res.objects.map(object => {
|
if (
|
||||||
return {
|
currentBucket === getCurrentBucket(getState()) &&
|
||||||
...object,
|
currentPrefix === getCurrentPrefix(getState())
|
||||||
name: object.name.replace(currentPrefix, "")
|
) {
|
||||||
}
|
let objects = []
|
||||||
})
|
if (res.objects) {
|
||||||
|
objects = res.objects.map(object => {
|
||||||
|
return {
|
||||||
|
...object,
|
||||||
|
name: object.name.replace(currentPrefix, "")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortBy = SORT_BY_LAST_MODIFIED
|
||||||
|
const sortOrder = SORT_ORDER_DESC
|
||||||
|
dispatch(setSortBy(sortBy))
|
||||||
|
dispatch(setSortOrder(sortOrder))
|
||||||
|
const sortedList = sortObjectsList(objects, sortBy, sortOrder)
|
||||||
|
dispatch(setList(sortedList))
|
||||||
|
|
||||||
|
dispatch(setPrefixWritable(res.writable))
|
||||||
|
dispatch(setListLoading(false))
|
||||||
}
|
}
|
||||||
if (append) {
|
|
||||||
dispatch(appendList(objects, res.nextmarker, res.istruncated))
|
|
||||||
} else {
|
|
||||||
dispatch(setList(objects, res.nextmarker, res.istruncated))
|
|
||||||
dispatch(setSortBy(""))
|
|
||||||
dispatch(setSortOrder(false))
|
|
||||||
}
|
|
||||||
dispatch(setPrefixWritable(res.writable))
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
@@ -96,6 +117,7 @@ export const fetchObjects = append => {
|
|||||||
} else {
|
} else {
|
||||||
history.push("/login")
|
history.push("/login")
|
||||||
}
|
}
|
||||||
|
dispatch(setListLoading(false))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,26 +125,27 @@ export const fetchObjects = append => {
|
|||||||
|
|
||||||
export const sortObjects = sortBy => {
|
export const sortObjects = sortBy => {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const {objects} = getState()
|
const { objects } = getState()
|
||||||
const sortOrder = objects.sortBy == sortBy ? !objects.sortOrder : true
|
let sortOrder = SORT_ORDER_ASC
|
||||||
|
// Reverse sort order if the list is already sorted on same field
|
||||||
|
if (objects.sortBy === sortBy && objects.sortOrder === SORT_ORDER_ASC) {
|
||||||
|
sortOrder = SORT_ORDER_DESC
|
||||||
|
}
|
||||||
dispatch(setSortBy(sortBy))
|
dispatch(setSortBy(sortBy))
|
||||||
dispatch(setSortOrder(sortOrder))
|
dispatch(setSortOrder(sortOrder))
|
||||||
let list
|
const sortedList = sortObjectsList(objects.list, sortBy, sortOrder)
|
||||||
switch (sortBy) {
|
dispatch(setList(sortedList))
|
||||||
case "name":
|
}
|
||||||
list = sortObjectsByName(objects.list, sortOrder)
|
}
|
||||||
break
|
|
||||||
case "size":
|
const sortObjectsList = (list, sortBy, sortOrder) => {
|
||||||
list = sortObjectsBySize(objects.list, sortOrder)
|
switch (sortBy) {
|
||||||
break
|
case SORT_BY_NAME:
|
||||||
case "last-modified":
|
return sortObjectsByName(list, sortOrder)
|
||||||
list = sortObjectsByDate(objects.list, sortOrder)
|
case SORT_BY_SIZE:
|
||||||
break
|
return sortObjectsBySize(list, sortOrder)
|
||||||
default:
|
case SORT_BY_LAST_MODIFIED:
|
||||||
list = objects.list
|
return sortObjectsByDate(list, sortOrder)
|
||||||
break
|
|
||||||
}
|
|
||||||
dispatch(setList(list, objects.marker, objects.isTruncated))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +252,16 @@ export const shareObject = (object, days, hours, minutes) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
dispatch(showShareObject(object, `${location.host}` + '/' + `${currentBucket}` + '/' + encodeURI(objectName)))
|
dispatch(
|
||||||
|
showShareObject(
|
||||||
|
object,
|
||||||
|
`${location.host}` +
|
||||||
|
"/" +
|
||||||
|
`${currentBucket}` +
|
||||||
|
"/" +
|
||||||
|
encodeURI(objectName)
|
||||||
|
)
|
||||||
|
)
|
||||||
dispatch(
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -322,13 +354,14 @@ export const downloadCheckedObjects = () => {
|
|||||||
}${minioBrowserPrefix}/zip?token=${res.token}`
|
}${minioBrowserPrefix}/zip?token=${res.token}`
|
||||||
downloadZip(requestUrl, req, dispatch)
|
downloadZip(requestUrl, req, dispatch)
|
||||||
})
|
})
|
||||||
.catch(err => dispatch(
|
.catch(err =>
|
||||||
alertActions.set({
|
dispatch(
|
||||||
type: "danger",
|
alertActions.set({
|
||||||
message: err.message
|
type: "danger",
|
||||||
})
|
message: err.message
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +384,8 @@ const downloadZip = (url, req, dispatch) => {
|
|||||||
var separator = req.prefix.length > 1 ? "-" : ""
|
var separator = req.prefix.length > 1 ? "-" : ""
|
||||||
|
|
||||||
anchor.href = blobUrl
|
anchor.href = blobUrl
|
||||||
anchor.download = req.bucketName + separator + req.prefix.slice(0, -1) + ".zip"
|
anchor.download =
|
||||||
|
req.bucketName + separator + req.prefix.slice(0, -1) + ".zip"
|
||||||
|
|
||||||
anchor.click()
|
anchor.click()
|
||||||
window.URL.revokeObjectURL(blobUrl)
|
window.URL.revokeObjectURL(blobUrl)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as actionsObjects from "./actions"
|
import * as actionsObjects from "./actions"
|
||||||
|
import { SORT_ORDER_ASC } from "../constants"
|
||||||
|
|
||||||
const removeObject = (list, objectToRemove, lookup) => {
|
const removeObject = (list, objectToRemove, lookup) => {
|
||||||
const idx = list.findIndex(object => lookup(object) === objectToRemove)
|
const idx = list.findIndex(object => lookup(object) === objectToRemove)
|
||||||
@@ -27,11 +28,10 @@ const removeObject = (list, objectToRemove, lookup) => {
|
|||||||
export default (
|
export default (
|
||||||
state = {
|
state = {
|
||||||
list: [],
|
list: [],
|
||||||
|
listLoading: false,
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: false,
|
sortOrder: SORT_ORDER_ASC,
|
||||||
currentPrefix: "",
|
currentPrefix: "",
|
||||||
marker: "",
|
|
||||||
isTruncated: false,
|
|
||||||
prefixWritable: false,
|
prefixWritable: false,
|
||||||
shareObject: {
|
shareObject: {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -46,23 +46,17 @@ export default (
|
|||||||
case actionsObjects.SET_LIST:
|
case actionsObjects.SET_LIST:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
list: action.objects,
|
list: action.objects
|
||||||
marker: action.marker,
|
|
||||||
isTruncated: action.isTruncated
|
|
||||||
}
|
}
|
||||||
case actionsObjects.RESET_LIST:
|
case actionsObjects.RESET_LIST:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
list: [],
|
list: []
|
||||||
marker: "",
|
|
||||||
isTruncated: false
|
|
||||||
}
|
}
|
||||||
case actionsObjects.APPEND_LIST:
|
case actionsObjects.SET_LIST_LOADING:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
list: [...state.list, ...action.objects],
|
listLoading: action.listLoading
|
||||||
marker: action.marker,
|
|
||||||
isTruncated: action.isTruncated
|
|
||||||
}
|
}
|
||||||
case actionsObjects.REMOVE:
|
case actionsObjects.REMOVE:
|
||||||
return {
|
return {
|
||||||
@@ -82,9 +76,7 @@ export default (
|
|||||||
case actionsObjects.SET_CURRENT_PREFIX:
|
case actionsObjects.SET_CURRENT_PREFIX:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
currentPrefix: action.prefix,
|
currentPrefix: action.prefix
|
||||||
marker: "",
|
|
||||||
isTruncated: false
|
|
||||||
}
|
}
|
||||||
case actionsObjects.SET_PREFIX_WRITABLE:
|
case actionsObjects.SET_PREFIX_WRITABLE:
|
||||||
return {
|
return {
|
||||||
|
|||||||
+47
-23
@@ -14,11 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { minioBrowserPrefix } from './constants.js'
|
import { minioBrowserPrefix, SORT_ORDER_DESC } from "./constants.js"
|
||||||
|
|
||||||
export const sortObjectsByName = (objects, order) => {
|
export const sortObjectsByName = (objects, order) => {
|
||||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
let folders = objects.filter(object => object.name.endsWith("/"))
|
||||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
let files = objects.filter(object => !object.name.endsWith("/"))
|
||||||
folders = folders.sort((a, b) => {
|
folders = folders.sort((a, b) => {
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||||
@@ -29,7 +29,7 @@ export const sortObjectsByName = (objects, order) => {
|
|||||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
if (order) {
|
if (order === SORT_ORDER_DESC) {
|
||||||
folders = folders.reverse()
|
folders = folders.reverse()
|
||||||
files = files.reverse()
|
files = files.reverse()
|
||||||
}
|
}
|
||||||
@@ -37,32 +37,34 @@ export const sortObjectsByName = (objects, order) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sortObjectsBySize = (objects, order) => {
|
export const sortObjectsBySize = (objects, order) => {
|
||||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
let folders = objects.filter(object => object.name.endsWith("/"))
|
||||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
let files = objects.filter(object => !object.name.endsWith("/"))
|
||||||
files = files.sort((a, b) => a.size - b.size)
|
files = files.sort((a, b) => a.size - b.size)
|
||||||
if (order)
|
if (order === SORT_ORDER_DESC) files = files.reverse()
|
||||||
files = files.reverse()
|
|
||||||
return [...folders, ...files]
|
return [...folders, ...files]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sortObjectsByDate = (objects, order) => {
|
export const sortObjectsByDate = (objects, order) => {
|
||||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
let folders = objects.filter(object => object.name.endsWith("/"))
|
||||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
let files = objects.filter(object => !object.name.endsWith("/"))
|
||||||
files = files.sort((a, b) => new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime())
|
files = files.sort(
|
||||||
if (order)
|
(a, b) =>
|
||||||
files = files.reverse()
|
new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
|
||||||
|
)
|
||||||
|
if (order === SORT_ORDER_DESC) files = files.reverse()
|
||||||
return [...folders, ...files]
|
return [...folders, ...files]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pathSlice = (path) => {
|
export const pathSlice = path => {
|
||||||
path = path.replace(minioBrowserPrefix, '')
|
path = path.replace(minioBrowserPrefix, "")
|
||||||
let prefix = ''
|
let prefix = ""
|
||||||
let bucket = ''
|
let bucket = ""
|
||||||
if (!path) return {
|
if (!path)
|
||||||
|
return {
|
||||||
bucket,
|
bucket,
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
let objectIndex = path.indexOf('/', 1)
|
let objectIndex = path.indexOf("/", 1)
|
||||||
if (objectIndex == -1) {
|
if (objectIndex == -1) {
|
||||||
bucket = path.slice(1)
|
bucket = path.slice(1)
|
||||||
return {
|
return {
|
||||||
@@ -79,7 +81,29 @@ export const pathSlice = (path) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const pathJoin = (bucket, prefix) => {
|
export const pathJoin = (bucket, prefix) => {
|
||||||
if (!prefix)
|
if (!prefix) prefix = ""
|
||||||
prefix = ''
|
return minioBrowserPrefix + "/" + bucket + "/" + prefix
|
||||||
return minioBrowserPrefix + '/' + bucket + '/' + prefix
|
}
|
||||||
|
|
||||||
|
export const getRandomAccessKey = () => {
|
||||||
|
const alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
let arr = new Uint8Array(20)
|
||||||
|
window.crypto.getRandomValues(arr)
|
||||||
|
const random = Array.prototype.map.call(arr, v => {
|
||||||
|
const i = v % alphaNumericTable.length
|
||||||
|
return alphaNumericTable.charAt(i)
|
||||||
|
})
|
||||||
|
return random.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRandomSecretKey = () => {
|
||||||
|
let arr = new Uint8Array(40)
|
||||||
|
window.crypto.getRandomValues(arr)
|
||||||
|
const binStr = Array.prototype.map
|
||||||
|
.call(arr, v => {
|
||||||
|
return String.fromCharCode(v)
|
||||||
|
})
|
||||||
|
.join("")
|
||||||
|
const base64Str = btoa(binStr)
|
||||||
|
return base64Str.replace(/\//g, "+").substr(0, 40)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ class Web {
|
|||||||
Logout() {
|
Logout() {
|
||||||
storage.removeItem('token')
|
storage.removeItem('token')
|
||||||
}
|
}
|
||||||
|
GetToken() {
|
||||||
|
return storage.getItem('token')
|
||||||
|
}
|
||||||
ServerInfo() {
|
ServerInfo() {
|
||||||
return this.makeCall('ServerInfo')
|
return this.makeCall('ServerInfo')
|
||||||
}
|
}
|
||||||
@@ -99,12 +102,6 @@ class Web {
|
|||||||
RemoveObject(args) {
|
RemoveObject(args) {
|
||||||
return this.makeCall('RemoveObject', args)
|
return this.makeCall('RemoveObject', args)
|
||||||
}
|
}
|
||||||
GetAuth() {
|
|
||||||
return this.makeCall('GetAuth')
|
|
||||||
}
|
|
||||||
GenerateAuth() {
|
|
||||||
return this.makeCall('GenerateAuth')
|
|
||||||
}
|
|
||||||
SetAuth(args) {
|
SetAuth(args) {
|
||||||
return this.makeCall('SetAuth', args)
|
return this.makeCall('SetAuth', args)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ header.fesl-row {
|
|||||||
color: @dark-gray;
|
color: @dark-gray;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
& > .fesli-sort--active {
|
||||||
|
.opacity(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover:not(.fesl-item-actions) {
|
&:hover:not(.fesl-item-actions) {
|
||||||
background: lighten(@text-muted-color, 22%);
|
background: lighten(@text-muted-color, 22%);
|
||||||
|
|||||||
@@ -113,4 +113,41 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
position: absolute;
|
||||||
|
margin: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-top: 1px solid @loading-track-bg;
|
||||||
|
border-right: 1px solid @loading-track-bg;
|
||||||
|
border-bottom: 1px solid @loading-track-bg;
|
||||||
|
border-left: 1px solid @loading-point-bg;
|
||||||
|
transform: translateZ(0);
|
||||||
|
animation: loading 1.1s infinite linear;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes loading {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -190,8 +190,8 @@
|
|||||||
----------------------------*/
|
----------------------------*/
|
||||||
.toggle-password {
|
.toggle-password {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
bottom: 0 ;
|
||||||
right: 35px;
|
right: 0;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
@@ -206,6 +206,10 @@
|
|||||||
background: #eee;
|
background: #eee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-toggle-password {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
//--------------------------
|
//--------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -100,4 +100,10 @@
|
|||||||
List
|
List
|
||||||
--------------------------*/
|
--------------------------*/
|
||||||
@list-row-selected-bg: #fbf2bf;
|
@list-row-selected-bg: #fbf2bf;
|
||||||
@list-row-even-bg: #fafafa;
|
@list-row-even-bg: #fafafa;
|
||||||
|
|
||||||
|
/*--------------------------
|
||||||
|
Loading
|
||||||
|
---------------------------*/
|
||||||
|
@loading-track-bg: #eeeeee;
|
||||||
|
@loading-point-bg: #00303f;
|
||||||
Generated
+14239
File diff suppressed because it is too large
Load Diff
@@ -29,8 +29,8 @@
|
|||||||
"homepage": "https://github.com/minio/minio",
|
"homepage": "https://github.com/minio/minio",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^1.5.2",
|
"async": "^1.5.2",
|
||||||
"babel-cli": "^6.14.0",
|
"babel-cli": "^6.26.0",
|
||||||
"babel-core": "^6.14.0",
|
"babel-core": "^6.26.3",
|
||||||
"babel-jest": "^22.1.0",
|
"babel-jest": "^22.1.0",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
"babel-polyfill": "^6.23.0",
|
"babel-polyfill": "^6.23.0",
|
||||||
"babel-preset-es2015": "^6.14.0",
|
"babel-preset-es2015": "^6.14.0",
|
||||||
"babel-preset-react": "^6.11.1",
|
"babel-preset-react": "^6.11.1",
|
||||||
"babel-register": "^6.14.0",
|
"babel-register": "^6.26.0",
|
||||||
"copy-webpack-plugin": "^4.6.0",
|
"copy-webpack-plugin": "^4.6.0",
|
||||||
"css-loader": "^0.23.1",
|
"css-loader": "^0.23.1",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.10.0",
|
||||||
"enzyme-adapter-react-16": "^1.1.1",
|
"enzyme-adapter-react-16": "^1.1.1",
|
||||||
"esformatter": "^0.10.0",
|
"esformatter": "^0.10.0",
|
||||||
"esformatter-jsx": "^7.4.1",
|
"esformatter-jsx": "^7.4.1",
|
||||||
@@ -68,11 +68,12 @@
|
|||||||
"humanize": "0.0.9",
|
"humanize": "0.0.9",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
|
"jwt-decode": "^2.2.0",
|
||||||
"local-storage-fallback": "^4.0.2",
|
"local-storage-fallback": "^4.0.2",
|
||||||
"material-design-iconic-font": "^2.2.0",
|
"material-design-iconic-font": "^2.2.0",
|
||||||
"mime-db": "^1.25.0",
|
"mime-db": "^1.25.0",
|
||||||
"mime-types": "^2.1.13",
|
"mime-types": "^2.1.13",
|
||||||
"moment": "^2.15.1",
|
"moment": "^2.24.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-addons-test-utils": "^0.14.8",
|
"react-addons-test-utils": "^0.14.8",
|
||||||
"react-bootstrap": "^0.32.0",
|
"react-bootstrap": "^0.32.0",
|
||||||
|
|||||||
+31
-31
File diff suppressed because one or more lines are too long
+220
-234
@@ -251,22 +251,10 @@ ajv@^6.1.0:
|
|||||||
json-schema-traverse "^0.4.1"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
align-text@^0.1.1, align-text@^0.1.3:
|
|
||||||
version "0.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
|
|
||||||
dependencies:
|
|
||||||
kind-of "^3.0.2"
|
|
||||||
longest "^1.0.1"
|
|
||||||
repeat-string "^1.5.2"
|
|
||||||
|
|
||||||
alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
|
alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||||
|
|
||||||
amdefine@>=0.0.4:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
|
||||||
|
|
||||||
ansi-colors@^3.0.0:
|
ansi-colors@^3.0.0:
|
||||||
version "3.2.3"
|
version "3.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
|
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
|
||||||
@@ -378,6 +366,11 @@ array-equal@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
|
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
|
||||||
|
|
||||||
|
array-filter@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
|
||||||
|
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
|
||||||
|
|
||||||
array-flatten@1.1.1:
|
array-flatten@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
@@ -410,6 +403,15 @@ array-unique@^0.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||||
|
|
||||||
|
array.prototype.flat@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4"
|
||||||
|
integrity sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==
|
||||||
|
dependencies:
|
||||||
|
define-properties "^1.1.2"
|
||||||
|
es-abstract "^1.10.0"
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
arrify@^1.0.0, arrify@^1.0.1:
|
arrify@^1.0.0, arrify@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||||
@@ -467,7 +469,7 @@ async-limiter@~1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||||
|
|
||||||
async@^1.4.0, async@^1.5.2:
|
async@^1.5.2:
|
||||||
version "1.5.2"
|
version "1.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||||
|
|
||||||
@@ -520,24 +522,25 @@ aws4@^1.6.0:
|
|||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
||||||
|
|
||||||
babel-cli@^6.14.0:
|
babel-cli@^6.26.0:
|
||||||
version "6.24.1"
|
version "6.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.24.1.tgz#207cd705bba61489b2ea41b5312341cf6aca2283"
|
resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1"
|
||||||
|
integrity sha1-UCq1SHTX24itALiHoGODzgPQAvE=
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-core "^6.24.1"
|
babel-core "^6.26.0"
|
||||||
babel-polyfill "^6.23.0"
|
babel-polyfill "^6.26.0"
|
||||||
babel-register "^6.24.1"
|
babel-register "^6.26.0"
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.26.0"
|
||||||
commander "^2.8.1"
|
commander "^2.11.0"
|
||||||
convert-source-map "^1.1.0"
|
convert-source-map "^1.5.0"
|
||||||
fs-readdir-recursive "^1.0.0"
|
fs-readdir-recursive "^1.0.0"
|
||||||
glob "^7.0.0"
|
glob "^7.1.2"
|
||||||
lodash "^4.2.0"
|
lodash "^4.17.4"
|
||||||
output-file-sync "^1.1.0"
|
output-file-sync "^1.1.2"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.1"
|
||||||
slash "^1.0.0"
|
slash "^1.0.0"
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.6"
|
||||||
v8flags "^2.0.10"
|
v8flags "^2.1.1"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
chokidar "^1.6.1"
|
chokidar "^1.6.1"
|
||||||
|
|
||||||
@@ -581,29 +584,30 @@ babel-core@^6.0.0, babel-core@^6.26.0:
|
|||||||
slash "^1.0.0"
|
slash "^1.0.0"
|
||||||
source-map "^0.5.6"
|
source-map "^0.5.6"
|
||||||
|
|
||||||
babel-core@^6.14.0, babel-core@^6.24.1:
|
babel-core@^6.26.3:
|
||||||
version "6.25.0"
|
version "6.26.3"
|
||||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729"
|
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
|
||||||
|
integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-code-frame "^6.22.0"
|
babel-code-frame "^6.26.0"
|
||||||
babel-generator "^6.25.0"
|
babel-generator "^6.26.0"
|
||||||
babel-helpers "^6.24.1"
|
babel-helpers "^6.24.1"
|
||||||
babel-messages "^6.23.0"
|
babel-messages "^6.23.0"
|
||||||
babel-register "^6.24.1"
|
babel-register "^6.26.0"
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.26.0"
|
||||||
babel-template "^6.25.0"
|
babel-template "^6.26.0"
|
||||||
babel-traverse "^6.25.0"
|
babel-traverse "^6.26.0"
|
||||||
babel-types "^6.25.0"
|
babel-types "^6.26.0"
|
||||||
babylon "^6.17.2"
|
babylon "^6.18.0"
|
||||||
convert-source-map "^1.1.0"
|
convert-source-map "^1.5.1"
|
||||||
debug "^2.1.1"
|
debug "^2.6.9"
|
||||||
json5 "^0.5.0"
|
json5 "^0.5.1"
|
||||||
lodash "^4.2.0"
|
lodash "^4.17.4"
|
||||||
minimatch "^3.0.2"
|
minimatch "^3.0.4"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.1"
|
||||||
private "^0.1.6"
|
private "^0.1.8"
|
||||||
slash "^1.0.0"
|
slash "^1.0.0"
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.7"
|
||||||
|
|
||||||
babel-generator@^6.18.0, babel-generator@^6.26.0:
|
babel-generator@^6.18.0, babel-generator@^6.26.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
@@ -618,19 +622,6 @@ babel-generator@^6.18.0, babel-generator@^6.26.0:
|
|||||||
source-map "^0.5.6"
|
source-map "^0.5.6"
|
||||||
trim-right "^1.0.1"
|
trim-right "^1.0.1"
|
||||||
|
|
||||||
babel-generator@^6.25.0:
|
|
||||||
version "6.25.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc"
|
|
||||||
dependencies:
|
|
||||||
babel-messages "^6.23.0"
|
|
||||||
babel-runtime "^6.22.0"
|
|
||||||
babel-types "^6.25.0"
|
|
||||||
detect-indent "^4.0.0"
|
|
||||||
jsesc "^1.3.0"
|
|
||||||
lodash "^4.2.0"
|
|
||||||
source-map "^0.5.0"
|
|
||||||
trim-right "^1.0.1"
|
|
||||||
|
|
||||||
babel-helper-builder-react-jsx@^6.24.1:
|
babel-helper-builder-react-jsx@^6.24.1:
|
||||||
version "6.24.1"
|
version "6.24.1"
|
||||||
resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.24.1.tgz#0ad7917e33c8d751e646daca4e77cc19377d2cbc"
|
resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.24.1.tgz#0ad7917e33c8d751e646daca4e77cc19377d2cbc"
|
||||||
@@ -997,6 +988,15 @@ babel-polyfill@6.23.0, babel-polyfill@^6.23.0:
|
|||||||
core-js "^2.4.0"
|
core-js "^2.4.0"
|
||||||
regenerator-runtime "^0.10.0"
|
regenerator-runtime "^0.10.0"
|
||||||
|
|
||||||
|
babel-polyfill@^6.26.0:
|
||||||
|
version "6.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
|
||||||
|
integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=
|
||||||
|
dependencies:
|
||||||
|
babel-runtime "^6.26.0"
|
||||||
|
core-js "^2.5.0"
|
||||||
|
regenerator-runtime "^0.10.5"
|
||||||
|
|
||||||
babel-preset-es2015@^6.14.0:
|
babel-preset-es2015@^6.14.0:
|
||||||
version "6.24.1"
|
version "6.24.1"
|
||||||
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
|
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
|
||||||
@@ -1050,18 +1050,6 @@ babel-preset-react@^6.11.1:
|
|||||||
babel-plugin-transform-react-jsx-source "^6.22.0"
|
babel-plugin-transform-react-jsx-source "^6.22.0"
|
||||||
babel-preset-flow "^6.23.0"
|
babel-preset-flow "^6.23.0"
|
||||||
|
|
||||||
babel-register@^6.14.0, babel-register@^6.24.1:
|
|
||||||
version "6.24.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f"
|
|
||||||
dependencies:
|
|
||||||
babel-core "^6.24.1"
|
|
||||||
babel-runtime "^6.22.0"
|
|
||||||
core-js "^2.4.0"
|
|
||||||
home-or-tmp "^2.0.0"
|
|
||||||
lodash "^4.2.0"
|
|
||||||
mkdirp "^0.5.1"
|
|
||||||
source-map-support "^0.4.2"
|
|
||||||
|
|
||||||
babel-register@^6.26.0:
|
babel-register@^6.26.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
|
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
|
||||||
@@ -1098,7 +1086,7 @@ babel-template@^6.16.0, babel-template@^6.26.0:
|
|||||||
babylon "^6.18.0"
|
babylon "^6.18.0"
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
|
|
||||||
babel-template@^6.24.1, babel-template@^6.25.0:
|
babel-template@^6.24.1:
|
||||||
version "6.25.0"
|
version "6.25.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071"
|
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1509,10 +1497,6 @@ camel-case@3.0.x:
|
|||||||
no-case "^2.2.0"
|
no-case "^2.2.0"
|
||||||
upper-case "^1.1.1"
|
upper-case "^1.1.1"
|
||||||
|
|
||||||
camelcase@^1.0.2:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
|
|
||||||
|
|
||||||
camelcase@^4.1.0:
|
camelcase@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
|
||||||
@@ -1540,13 +1524,6 @@ caseless@~0.12.0:
|
|||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||||
|
|
||||||
center-align@^0.1.1:
|
|
||||||
version "0.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
|
|
||||||
dependencies:
|
|
||||||
align-text "^0.1.3"
|
|
||||||
lazy-cache "^1.0.3"
|
|
||||||
|
|
||||||
chain-function@^1.0.0:
|
chain-function@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
|
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
|
||||||
@@ -1695,14 +1672,6 @@ cli-width@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||||
|
|
||||||
cliui@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
|
|
||||||
dependencies:
|
|
||||||
center-align "^0.1.1"
|
|
||||||
right-align "^0.1.1"
|
|
||||||
wordwrap "0.0.2"
|
|
||||||
|
|
||||||
cliui@^4.0.0:
|
cliui@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
|
||||||
@@ -1802,7 +1771,12 @@ commander@2.17.x, commander@~2.17.1:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
||||||
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
||||||
|
|
||||||
commander@^2.2.0, commander@^2.8.1, commander@^2.9.0:
|
commander@^2.11.0, commander@~2.20.0:
|
||||||
|
version "2.20.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||||
|
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
|
||||||
|
|
||||||
|
commander@^2.2.0, commander@^2.9.0:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1893,14 +1867,17 @@ content-type@~1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||||
|
|
||||||
convert-source-map@^1.1.0:
|
|
||||||
version "1.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
|
|
||||||
|
|
||||||
convert-source-map@^1.4.0, convert-source-map@^1.5.0:
|
convert-source-map@^1.4.0, convert-source-map@^1.5.0:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
|
||||||
|
|
||||||
|
convert-source-map@^1.5.1:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
|
||||||
|
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "~5.1.1"
|
||||||
|
|
||||||
cookie-signature@1.0.6:
|
cookie-signature@1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
@@ -2170,7 +2147,7 @@ date-now@^0.1.4:
|
|||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
|
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2187,7 +2164,7 @@ debug@^0.7.4:
|
|||||||
version "0.7.4"
|
version "0.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
|
||||||
|
|
||||||
debug@^2.1.1, debug@^2.1.3:
|
debug@^2.1.3:
|
||||||
version "2.6.8"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2207,7 +2184,7 @@ debug@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
|
decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||||
@@ -2627,26 +2604,32 @@ enzyme-to-json@^3.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
|
|
||||||
enzyme@^3.3.0:
|
enzyme@^3.10.0:
|
||||||
version "3.3.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479"
|
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6"
|
||||||
|
integrity sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
array.prototype.flat "^1.2.1"
|
||||||
cheerio "^1.0.0-rc.2"
|
cheerio "^1.0.0-rc.2"
|
||||||
function.prototype.name "^1.0.3"
|
function.prototype.name "^1.1.0"
|
||||||
has "^1.0.1"
|
has "^1.0.3"
|
||||||
|
html-element-map "^1.0.0"
|
||||||
is-boolean-object "^1.0.0"
|
is-boolean-object "^1.0.0"
|
||||||
is-callable "^1.1.3"
|
is-callable "^1.1.4"
|
||||||
is-number-object "^1.0.3"
|
is-number-object "^1.0.3"
|
||||||
|
is-regex "^1.0.4"
|
||||||
is-string "^1.0.4"
|
is-string "^1.0.4"
|
||||||
is-subset "^0.1.1"
|
is-subset "^0.1.1"
|
||||||
lodash "^4.17.4"
|
lodash.escape "^4.0.1"
|
||||||
object-inspect "^1.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
|
object-inspect "^1.6.0"
|
||||||
object-is "^1.0.1"
|
object-is "^1.0.1"
|
||||||
object.assign "^4.1.0"
|
object.assign "^4.1.0"
|
||||||
object.entries "^1.0.4"
|
object.entries "^1.0.4"
|
||||||
object.values "^1.0.4"
|
object.values "^1.0.4"
|
||||||
raf "^3.4.0"
|
raf "^3.4.0"
|
||||||
rst-selector-parser "^2.2.3"
|
rst-selector-parser "^2.2.3"
|
||||||
|
string.prototype.trim "^1.1.2"
|
||||||
|
|
||||||
errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
|
errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
@@ -2662,6 +2645,18 @@ error-ex@^1.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.2.1"
|
is-arrayish "^0.2.1"
|
||||||
|
|
||||||
|
es-abstract@^1.10.0, es-abstract@^1.5.0:
|
||||||
|
version "1.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
|
||||||
|
integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
|
||||||
|
dependencies:
|
||||||
|
es-to-primitive "^1.2.0"
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
has "^1.0.3"
|
||||||
|
is-callable "^1.1.4"
|
||||||
|
is-regex "^1.0.4"
|
||||||
|
object-keys "^1.0.12"
|
||||||
|
|
||||||
es-abstract@^1.5.1:
|
es-abstract@^1.5.1:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
|
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
|
||||||
@@ -2681,7 +2676,7 @@ es-abstract@^1.6.1:
|
|||||||
is-callable "^1.1.3"
|
is-callable "^1.1.3"
|
||||||
is-regex "^1.0.3"
|
is-regex "^1.0.3"
|
||||||
|
|
||||||
es-to-primitive@^1.1.1:
|
es-to-primitive@^1.1.1, es-to-primitive@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
|
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
|
||||||
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
|
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
|
||||||
@@ -3364,27 +3359,28 @@ fstream-ignore@^1.0.5:
|
|||||||
minimatch "^3.0.0"
|
minimatch "^3.0.0"
|
||||||
|
|
||||||
fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
|
fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
|
||||||
version "1.0.11"
|
version "1.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
|
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
|
||||||
integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=
|
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs "^4.1.2"
|
graceful-fs "^4.1.2"
|
||||||
inherits "~2.0.0"
|
inherits "~2.0.0"
|
||||||
mkdirp ">=0.5 0"
|
mkdirp ">=0.5 0"
|
||||||
rimraf "2"
|
rimraf "2"
|
||||||
|
|
||||||
function-bind@^1.1.0:
|
function-bind@^1.0.2, function-bind@^1.1.1:
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
|
|
||||||
|
|
||||||
function-bind@^1.1.1:
|
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||||
|
|
||||||
function.prototype.name@^1.0.3:
|
function-bind@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
|
||||||
|
|
||||||
|
function.prototype.name@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
|
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
|
||||||
|
integrity sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==
|
||||||
dependencies:
|
dependencies:
|
||||||
define-properties "^1.1.2"
|
define-properties "^1.1.2"
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
@@ -3471,7 +3467,19 @@ glob-parent@^3.1.0:
|
|||||||
is-glob "^3.1.0"
|
is-glob "^3.1.0"
|
||||||
path-dirname "^1.0.0"
|
path-dirname "^1.0.0"
|
||||||
|
|
||||||
glob@^7.0.0, glob@^7.1.1, glob@^7.1.2:
|
glob@^7.0.3, glob@^7.0.5:
|
||||||
|
version "7.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||||
|
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
once "^1.3.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
glob@^7.1.1, glob@^7.1.2:
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3482,10 +3490,10 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2:
|
|||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
glob@^7.0.3, glob@^7.0.5, glob@^7.1.3:
|
glob@^7.1.3:
|
||||||
version "7.1.3"
|
version "7.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||||
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||||
dependencies:
|
dependencies:
|
||||||
fs.realpath "^1.0.0"
|
fs.realpath "^1.0.0"
|
||||||
inflight "^1.0.4"
|
inflight "^1.0.4"
|
||||||
@@ -3546,11 +3554,7 @@ globby@^7.1.1:
|
|||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
slash "^1.0.0"
|
slash "^1.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.11, graceful-fs@^4.1.4:
|
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4:
|
||||||
version "4.1.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
|
||||||
|
|
||||||
graceful-fs@^4.1.15, graceful-fs@^4.1.2:
|
|
||||||
version "4.1.15"
|
version "4.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||||
@@ -3570,14 +3574,15 @@ handle-thing@^2.0.0:
|
|||||||
integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
|
integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
|
||||||
|
|
||||||
handlebars@^4.0.3:
|
handlebars@^4.0.3:
|
||||||
version "4.0.11"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
|
||||||
|
integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
|
||||||
dependencies:
|
dependencies:
|
||||||
async "^1.4.0"
|
neo-async "^2.6.0"
|
||||||
optimist "^0.6.1"
|
optimist "^0.6.1"
|
||||||
source-map "^0.4.4"
|
source-map "^0.6.1"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
uglify-js "^2.6"
|
uglify-js "^3.1.4"
|
||||||
|
|
||||||
har-schema@^1.0.5:
|
har-schema@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
@@ -3667,7 +3672,7 @@ has-values@^1.0.0:
|
|||||||
is-number "^3.0.0"
|
is-number "^3.0.0"
|
||||||
kind-of "^4.0.0"
|
kind-of "^4.0.0"
|
||||||
|
|
||||||
has@^1.0.1:
|
has@^1.0.1, has@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||||
@@ -3781,6 +3786,13 @@ html-comment-regex@^1.1.0:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
|
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
|
||||||
|
|
||||||
|
html-element-map@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.0.1.tgz#3c4fcb4874ebddfe4283b51c8994e7713782b592"
|
||||||
|
integrity sha512-BZSfdEm6n706/lBfXKWa4frZRZcT5k1cOusw95ijZsHlI+GdgY0v95h6IzO3iIDf2ROwq570YTwqNPqHcNMozw==
|
||||||
|
dependencies:
|
||||||
|
array-filter "^1.0.0"
|
||||||
|
|
||||||
html-encoding-sniffer@^1.0.2:
|
html-encoding-sniffer@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
|
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
|
||||||
@@ -4907,6 +4919,11 @@ jsprim@^1.2.2:
|
|||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.10.0"
|
verror "1.10.0"
|
||||||
|
|
||||||
|
jwt-decode@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
|
||||||
|
integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=
|
||||||
|
|
||||||
keycode@^2.1.2:
|
keycode@^2.1.2:
|
||||||
version "2.1.9"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
|
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
|
||||||
@@ -4940,10 +4957,6 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
|
||||||
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
|
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
|
||||||
|
|
||||||
lazy-cache@^1.0.3:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
|
|
||||||
|
|
||||||
lcid@^1.0.0:
|
lcid@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
||||||
@@ -5062,8 +5075,9 @@ locate-path@^3.0.0:
|
|||||||
path-exists "^3.0.0"
|
path-exists "^3.0.0"
|
||||||
|
|
||||||
lodash-es@^4.2.0, lodash-es@^4.2.1:
|
lodash-es@^4.2.0, lodash-es@^4.2.1:
|
||||||
version "4.17.4"
|
version "4.17.14"
|
||||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
|
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.14.tgz#12a95a963cc5955683cee3b74e85458954f37ecc"
|
||||||
|
integrity sha512-7zchRrGa8UZXjD/4ivUWP1867jDkhzTG2c/uj739utSd7O/pFFdxspCemIFKEEjErbcqRzn8nKnGsi7mvTgRPA==
|
||||||
|
|
||||||
lodash._baseisequal@^3.0.0:
|
lodash._baseisequal@^3.0.0:
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
@@ -5109,6 +5123,11 @@ lodash.deburr@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash._root "^3.0.0"
|
lodash._root "^3.0.0"
|
||||||
|
|
||||||
|
lodash.escape@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
|
||||||
|
integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
|
||||||
|
|
||||||
lodash.flattendeep@^4.4.0:
|
lodash.flattendeep@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||||
@@ -5128,6 +5147,11 @@ lodash.isequal@^3.0:
|
|||||||
lodash._baseisequal "^3.0.0"
|
lodash._baseisequal "^3.0.0"
|
||||||
lodash._bindcallback "^3.0.0"
|
lodash._bindcallback "^3.0.0"
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
||||||
|
|
||||||
lodash.isplainobject@^4.0.6:
|
lodash.isplainobject@^4.0.6:
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
@@ -5162,24 +5186,16 @@ lodash.words@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash._root "^3.0.0"
|
lodash._root "^3.0.0"
|
||||||
|
|
||||||
lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1:
|
lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
|
||||||
version "4.17.4"
|
version "4.17.14"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
|
||||||
|
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
|
||||||
lodash@^4.17.3, lodash@^4.17.5, lodash@^4.3.0:
|
|
||||||
version "4.17.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
|
||||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
|
||||||
|
|
||||||
loglevel@^1.4.1:
|
loglevel@^1.4.1:
|
||||||
version "1.6.1"
|
version "1.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
|
||||||
integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=
|
integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=
|
||||||
|
|
||||||
longest@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
|
|
||||||
|
|
||||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
|
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
||||||
@@ -5220,8 +5236,9 @@ lru-cache@^5.1.1:
|
|||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
macaddress@^0.2.8:
|
macaddress@^0.2.8:
|
||||||
version "0.2.8"
|
version "0.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.9.tgz#3579b8b9acd5b96b4553abf0f394185a86813cb3"
|
||||||
|
integrity sha512-k4F1JUof6cQXxNFzx3thLby4oJzXTXQueAOOts944Vqizn+Rjc2QNFenT9FJSLU1CH3PmrHRSyZs2E+Cqw+P2w==
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
@@ -5441,7 +5458,7 @@ minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@0.0.8, minimist@~0.0.1:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||||
@@ -5456,6 +5473,11 @@ minimist@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
|
||||||
integrity sha1-md9lelJXTCHJBXSX33QnkLK0wN4=
|
integrity sha1-md9lelJXTCHJBXSX33QnkLK0wN4=
|
||||||
|
|
||||||
|
minimist@~0.0.1:
|
||||||
|
version "0.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||||
|
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
|
||||||
|
|
||||||
minipass@^2.2.1, minipass@^2.3.4:
|
minipass@^2.2.1, minipass@^2.3.4:
|
||||||
version "2.3.5"
|
version "2.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
|
||||||
@@ -5517,9 +5539,10 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
moment@^2.15.1:
|
moment@^2.24.0:
|
||||||
version "2.18.1"
|
version "2.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||||
|
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||||
|
|
||||||
"mout@>=0.9 <2.0":
|
"mout@>=0.9 <2.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@@ -5617,10 +5640,10 @@ negotiator@0.6.1:
|
|||||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||||
integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
|
integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
|
||||||
|
|
||||||
neo-async@^2.5.0:
|
neo-async@^2.5.0, neo-async@^2.6.0:
|
||||||
version "2.6.0"
|
version "2.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
|
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||||
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
|
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||||
|
|
||||||
nice-try@^1.0.4:
|
nice-try@^1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
@@ -5848,8 +5871,9 @@ number-is-nan@^1.0.0:
|
|||||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||||
|
|
||||||
nwmatcher@^1.4.3:
|
nwmatcher@^1.4.3:
|
||||||
version "1.4.3"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c"
|
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e"
|
||||||
|
integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==
|
||||||
|
|
||||||
oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
||||||
version "0.8.2"
|
version "0.8.2"
|
||||||
@@ -5872,9 +5896,10 @@ object-inspect@^1.1.0:
|
|||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.2.2.tgz#c82115e4fcc888aea14d64c22e4f17f6a70d5e5a"
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.2.2.tgz#c82115e4fcc888aea14d64c22e4f17f6a70d5e5a"
|
||||||
|
|
||||||
object-inspect@^1.5.0:
|
object-inspect@^1.6.0:
|
||||||
version "1.5.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.5.0.tgz#9d876c11e40f485c79215670281b767488f9bfe3"
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
|
||||||
|
integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
|
||||||
|
|
||||||
object-is@^1.0.1:
|
object-is@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
@@ -5965,6 +5990,7 @@ on-headers@~1.0.1:
|
|||||||
once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
|
once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
@@ -6005,6 +6031,7 @@ opn@^5.1.0:
|
|||||||
optimist@^0.6.1:
|
optimist@^0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
||||||
|
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "~0.0.1"
|
minimist "~0.0.1"
|
||||||
wordwrap "~0.0.2"
|
wordwrap "~0.0.2"
|
||||||
@@ -6066,9 +6093,10 @@ osenv@^0.1.4:
|
|||||||
os-homedir "^1.0.0"
|
os-homedir "^1.0.0"
|
||||||
os-tmpdir "^1.0.0"
|
os-tmpdir "^1.0.0"
|
||||||
|
|
||||||
output-file-sync@^1.1.0:
|
output-file-sync@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76"
|
resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76"
|
||||||
|
integrity sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs "^4.1.4"
|
graceful-fs "^4.1.4"
|
||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
@@ -6642,7 +6670,7 @@ private@^0.1.6:
|
|||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
|
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
|
||||||
|
|
||||||
private@^0.1.7:
|
private@^0.1.7, private@^0.1.8:
|
||||||
version "0.1.8"
|
version "0.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
||||||
|
|
||||||
@@ -7153,7 +7181,7 @@ regenerate@^1.2.1:
|
|||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
|
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
|
||||||
|
|
||||||
regenerator-runtime@^0.10.0:
|
regenerator-runtime@^0.10.0, regenerator-runtime@^0.10.5:
|
||||||
version "0.10.5"
|
version "0.10.5"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
|
||||||
|
|
||||||
@@ -7386,25 +7414,13 @@ ret@~0.1.10:
|
|||||||
version "0.1.15"
|
version "0.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||||
|
|
||||||
right-align@^0.1.1:
|
rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
|
||||||
version "0.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
|
|
||||||
dependencies:
|
|
||||||
align-text "^0.1.1"
|
|
||||||
|
|
||||||
rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1, rimraf@^2.6.2:
|
|
||||||
version "2.6.3"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||||
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
rimraf@^2.5.4:
|
|
||||||
version "2.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
|
|
||||||
dependencies:
|
|
||||||
glob "^7.0.5"
|
|
||||||
|
|
||||||
ripemd160@^2.0.0, ripemd160@^2.0.1:
|
ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
|
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
|
||||||
@@ -7774,12 +7790,6 @@ source-map-support@^0.4.15:
|
|||||||
dependencies:
|
dependencies:
|
||||||
source-map "^0.5.6"
|
source-map "^0.5.6"
|
||||||
|
|
||||||
source-map-support@^0.4.2:
|
|
||||||
version "0.4.15"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
|
|
||||||
dependencies:
|
|
||||||
source-map "^0.5.6"
|
|
||||||
|
|
||||||
source-map-support@^0.5.0:
|
source-map-support@^0.5.0:
|
||||||
version "0.5.3"
|
version "0.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76"
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76"
|
||||||
@@ -7799,17 +7809,7 @@ source-map-url@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
||||||
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
|
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
|
||||||
|
|
||||||
source-map@^0.4.4:
|
source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.6:
|
||||||
version "0.4.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
|
||||||
dependencies:
|
|
||||||
amdefine ">=0.0.4"
|
|
||||||
|
|
||||||
source-map@^0.5.0, source-map@^0.5.3:
|
|
||||||
version "0.5.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
|
||||||
|
|
||||||
source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6:
|
|
||||||
version "0.5.7"
|
version "0.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||||
@@ -7817,6 +7817,7 @@ source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6:
|
|||||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
spdx-correct@^3.0.0:
|
spdx-correct@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
@@ -8002,6 +8003,15 @@ string-width@^2.0.0:
|
|||||||
is-fullwidth-code-point "^2.0.0"
|
is-fullwidth-code-point "^2.0.0"
|
||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
|
|
||||||
|
string.prototype.trim@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
|
||||||
|
integrity sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=
|
||||||
|
dependencies:
|
||||||
|
define-properties "^1.1.2"
|
||||||
|
es-abstract "^1.5.0"
|
||||||
|
function-bind "^1.0.2"
|
||||||
|
|
||||||
string_decoder@^1.0.0, string_decoder@~1.0.3:
|
string_decoder@^1.0.0, string_decoder@~1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||||
@@ -8028,15 +8038,11 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
stringstream@~0.0.4:
|
stringstream@~0.0.4, stringstream@~0.0.5:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
|
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
|
||||||
integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
|
integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
|
||||||
|
|
||||||
stringstream@~0.0.5:
|
|
||||||
version "0.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
|
||||||
|
|
||||||
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||||
@@ -8400,18 +8406,13 @@ uglify-js@3.4.x:
|
|||||||
commander "~2.17.1"
|
commander "~2.17.1"
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
uglify-js@^2.6:
|
uglify-js@^3.1.4:
|
||||||
version "2.8.29"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
|
||||||
|
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
|
||||||
dependencies:
|
dependencies:
|
||||||
source-map "~0.5.1"
|
commander "~2.20.0"
|
||||||
yargs "~3.10.0"
|
source-map "~0.6.1"
|
||||||
optionalDependencies:
|
|
||||||
uglify-to-browserify "~1.0.0"
|
|
||||||
|
|
||||||
uglify-to-browserify@~1.0.0:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
|
||||||
|
|
||||||
uid-number@^0.0.6:
|
uid-number@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
@@ -8584,9 +8585,10 @@ v8-compile-cache@^2.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
|
||||||
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
|
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
|
||||||
|
|
||||||
v8flags@^2.0.10:
|
v8flags@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
||||||
|
integrity sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=
|
||||||
dependencies:
|
dependencies:
|
||||||
user-home "^1.1.1"
|
user-home "^1.1.1"
|
||||||
|
|
||||||
@@ -8847,17 +8849,10 @@ wide-align@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
string-width "^1.0.2 || 2"
|
string-width "^1.0.2 || 2"
|
||||||
|
|
||||||
window-size@0.1.0:
|
|
||||||
version "0.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
|
|
||||||
|
|
||||||
wordwrap@0.0.2:
|
|
||||||
version "0.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
|
||||||
|
|
||||||
wordwrap@~0.0.2:
|
wordwrap@~0.0.2:
|
||||||
version "0.0.3"
|
version "0.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||||
|
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
|
||||||
|
|
||||||
wordwrap@~1.0.0:
|
wordwrap@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@@ -9011,12 +9006,3 @@ yargs@~1.2.6:
|
|||||||
integrity sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=
|
integrity sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^0.1.0"
|
minimist "^0.1.0"
|
||||||
|
|
||||||
yargs@~3.10.0:
|
|
||||||
version "3.10.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
|
|
||||||
dependencies:
|
|
||||||
camelcase "^1.0.2"
|
|
||||||
cliui "^2.1.0"
|
|
||||||
decamelize "^1.0.0"
|
|
||||||
window-size "0.1.0"
|
|
||||||
|
|||||||
+143
-29
@@ -34,6 +34,7 @@ import (
|
|||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/cpu"
|
"github.com/minio/minio/pkg/cpu"
|
||||||
"github.com/minio/minio/pkg/disk"
|
"github.com/minio/minio/pkg/disk"
|
||||||
@@ -43,6 +44,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/mem"
|
"github.com/minio/minio/pkg/mem"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"github.com/minio/minio/pkg/quick"
|
"github.com/minio/minio/pkg/quick"
|
||||||
|
trace "github.com/minio/minio/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -239,8 +241,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
hostName, err := getHostName(r)
|
||||||
thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -250,7 +251,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
// Once we have received all the ServerInfo from peers
|
// Once we have received all the ServerInfo from peers
|
||||||
// add the local peer server info as well.
|
// add the local peer server info as well.
|
||||||
serverInfo = append(serverInfo, ServerInfo{
|
serverInfo = append(serverInfo, ServerInfo{
|
||||||
Addr: thisAddr.String(),
|
Addr: hostName,
|
||||||
Data: &ServerInfoData{
|
Data: &ServerInfoData{
|
||||||
StorageInfo: objectAPI.StorageInfo(ctx),
|
StorageInfo: objectAPI.StorageInfo(ctx),
|
||||||
ConnStats: globalConnStats.toServerConnStats(),
|
ConnStats: globalConnStats.toServerConnStats(),
|
||||||
@@ -328,7 +329,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get drive performance details from local server's drive(s)
|
// Get drive performance details from local server's drive(s)
|
||||||
dp := localEndpointsDrivePerf(globalEndpoints)
|
dp := localEndpointsDrivePerf(globalEndpoints, r)
|
||||||
|
|
||||||
// Notify all other MinIO peers to report drive performance numbers
|
// Notify all other MinIO peers to report drive performance numbers
|
||||||
dps := globalNotificationSys.DrivePerfInfo()
|
dps := globalNotificationSys.DrivePerfInfo()
|
||||||
@@ -346,7 +347,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
|
|||||||
writeSuccessResponseJSON(w, jsonBytes)
|
writeSuccessResponseJSON(w, jsonBytes)
|
||||||
case "cpu":
|
case "cpu":
|
||||||
// Get CPU load details from local server's cpu(s)
|
// Get CPU load details from local server's cpu(s)
|
||||||
cpu := localEndpointsCPULoad(globalEndpoints)
|
cpu := localEndpointsCPULoad(globalEndpoints, r)
|
||||||
// Notify all other MinIO peers to report cpu load numbers
|
// Notify all other MinIO peers to report cpu load numbers
|
||||||
cpus := globalNotificationSys.CPULoadInfo()
|
cpus := globalNotificationSys.CPULoadInfo()
|
||||||
cpus = append(cpus, cpu)
|
cpus = append(cpus, cpu)
|
||||||
@@ -363,7 +364,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
|
|||||||
writeSuccessResponseJSON(w, jsonBytes)
|
writeSuccessResponseJSON(w, jsonBytes)
|
||||||
case "mem":
|
case "mem":
|
||||||
// Get mem usage details from local server(s)
|
// Get mem usage details from local server(s)
|
||||||
m := localEndpointsMemUsage(globalEndpoints)
|
m := localEndpointsMemUsage(globalEndpoints, r)
|
||||||
// Notify all other MinIO peers to report mem usage numbers
|
// Notify all other MinIO peers to report mem usage numbers
|
||||||
mems := globalNotificationSys.MemUsageInfo()
|
mems := globalNotificationSys.MemUsageInfo()
|
||||||
mems = append(mems, m)
|
mems = append(mems, m)
|
||||||
@@ -441,8 +442,7 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
hostName, err := getHostName(r)
|
||||||
thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -453,7 +453,7 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request
|
|||||||
// add the local peer locks list as well.
|
// add the local peer locks list as well.
|
||||||
localLocks := globalLockServer.ll.DupLockMap()
|
localLocks := globalLockServer.ll.DupLockMap()
|
||||||
peerLocks = append(peerLocks, &PeerLocks{
|
peerLocks = append(peerLocks, &PeerLocks{
|
||||||
Addr: thisAddr.String(),
|
Addr: hostName,
|
||||||
Locks: localLocks,
|
Locks: localLocks,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -681,12 +681,12 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Start writing response to client
|
// Start writing response to client
|
||||||
started = true
|
started = true
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
w.Header().Set("Content-Type", string(mimeJSON))
|
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||||
// Set 200 OK status
|
// Set 200 OK status
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
}
|
}
|
||||||
// Send whitespace and keep connection open
|
// Send whitespace and keep connection open
|
||||||
w.Write([]byte("\n\r"))
|
w.Write([]byte(" "))
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
case hr := <-respCh:
|
case hr := <-respCh:
|
||||||
switch hr.apiErr {
|
switch hr.apiErr {
|
||||||
@@ -701,20 +701,20 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
var errorRespJSON []byte
|
var errorRespJSON []byte
|
||||||
if hr.errBody == "" {
|
if hr.errBody == "" {
|
||||||
errorRespJSON = encodeResponseJSON(getAPIErrorResponse(ctx, hr.apiErr,
|
errorRespJSON = encodeResponseJSON(getAPIErrorResponse(ctx, hr.apiErr,
|
||||||
r.URL.Path, w.Header().Get(responseRequestIDKey),
|
r.URL.Path, w.Header().Get(xhttp.AmzRequestID),
|
||||||
w.Header().Get(responseDeploymentIDKey)))
|
globalDeploymentID))
|
||||||
} else {
|
} else {
|
||||||
errorRespJSON = encodeResponseJSON(APIErrorResponse{
|
errorRespJSON = encodeResponseJSON(APIErrorResponse{
|
||||||
Code: hr.apiErr.Code,
|
Code: hr.apiErr.Code,
|
||||||
Message: hr.errBody,
|
Message: hr.errBody,
|
||||||
Resource: r.URL.Path,
|
Resource: r.URL.Path,
|
||||||
RequestID: w.Header().Get(responseRequestIDKey),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
HostID: w.Header().Get(responseDeploymentIDKey),
|
HostID: globalDeploymentID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !started {
|
if !started {
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
w.Header().Set("Content-Type", string(mimeJSON))
|
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||||
w.WriteHeader(hr.apiErr.HTTPStatusCode)
|
w.WriteHeader(hr.apiErr.HTTPStatusCode)
|
||||||
}
|
}
|
||||||
w.Write(errorRespJSON)
|
w.Write(errorRespJSON)
|
||||||
@@ -785,6 +785,49 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
keepConnLive(w, respCh)
|
keepConnLive(w, respCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "HealBackgroundStatus")
|
||||||
|
|
||||||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this setup has an erasure coded backend.
|
||||||
|
if !globalIsXL {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrHealNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var bgHealStates []madmin.BgHealState
|
||||||
|
|
||||||
|
// Get local heal status first
|
||||||
|
bgHealStates = append(bgHealStates, getLocalBackgroundHealStatus())
|
||||||
|
|
||||||
|
if globalIsDistXL {
|
||||||
|
// Get heal status from other peers
|
||||||
|
peersHealStates := globalNotificationSys.BackgroundHealStatus()
|
||||||
|
bgHealStates = append(bgHealStates, peersHealStates...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate healing result
|
||||||
|
var aggregatedHealStateResult = madmin.BgHealState{}
|
||||||
|
for _, state := range bgHealStates {
|
||||||
|
aggregatedHealStateResult.ScannedItemsCount += state.ScannedItemsCount
|
||||||
|
if aggregatedHealStateResult.LastHealActivity.Before(state.LastHealActivity) {
|
||||||
|
aggregatedHealStateResult.LastHealActivity = state.LastHealActivity
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(aggregatedHealStateResult); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigHandler - GET /minio/admin/v1/config
|
// GetConfigHandler - GET /minio/admin/v1/config
|
||||||
// Get config.json of this minio setup.
|
// Get config.json of this minio setup.
|
||||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -940,8 +983,18 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
accessKey := vars["accessKey"]
|
accessKey := vars["accessKey"]
|
||||||
|
|
||||||
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
|
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to delete user.
|
||||||
|
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,8 +1059,8 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all other MinIO peers to reload users
|
// Notify all other MinIO peers to reload user.
|
||||||
for _, nerr := range globalNotificationSys.LoadUsers() {
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
||||||
if nerr.Err != nil {
|
if nerr.Err != nil {
|
||||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
logger.LogIf(ctx, nerr.Err)
|
logger.LogIf(ctx, nerr.Err)
|
||||||
@@ -1065,8 +1118,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all other MinIO peers to reload users
|
// Notify all other Minio peers to reload user
|
||||||
for _, nerr := range globalNotificationSys.LoadUsers() {
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
||||||
if nerr.Err != nil {
|
if nerr.Err != nil {
|
||||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
logger.LogIf(ctx, nerr.Err)
|
logger.LogIf(ctx, nerr.Err)
|
||||||
@@ -1083,7 +1136,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
policies, err := globalIAMSys.ListCannedPolicies()
|
policies, err := globalIAMSys.ListPolicies()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1115,13 +1168,13 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalIAMSys.DeleteCannedPolicy(policyName); err != nil {
|
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all other MinIO peers to reload users
|
// Notify all other MinIO peers to delete policy
|
||||||
for _, nerr := range globalNotificationSys.LoadUsers() {
|
for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
|
||||||
if nerr.Err != nil {
|
if nerr.Err != nil {
|
||||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
logger.LogIf(ctx, nerr.Err)
|
logger.LogIf(ctx, nerr.Err)
|
||||||
@@ -1171,13 +1224,13 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalIAMSys.SetCannedPolicy(policyName, *iamPolicy); err != nil {
|
if err = globalIAMSys.SetPolicy(policyName, *iamPolicy); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all other MinIO peers to reload users
|
// Notify all other MinIO peers to reload policy
|
||||||
for _, nerr := range globalNotificationSys.LoadUsers() {
|
for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
|
||||||
if nerr.Err != nil {
|
if nerr.Err != nil {
|
||||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
logger.LogIf(ctx, nerr.Err)
|
logger.LogIf(ctx, nerr.Err)
|
||||||
@@ -1214,8 +1267,8 @@ func (a adminAPIHandlers) SetUserPolicy(w http.ResponseWriter, r *http.Request)
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all other MinIO peers to reload users
|
// Notify all other Minio peers to reload user
|
||||||
for _, nerr := range globalNotificationSys.LoadUsers() {
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
||||||
if nerr.Err != nil {
|
if nerr.Err != nil {
|
||||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
logger.LogIf(ctx, nerr.Err)
|
logger.LogIf(ctx, nerr.Err)
|
||||||
@@ -1415,3 +1468,64 @@ func (a adminAPIHandlers) SetConfigKeysHandler(w http.ResponseWriter, r *http.Re
|
|||||||
// Send success response
|
// Send success response
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TraceHandler - POST /minio/admin/v1/trace
|
||||||
|
// ----------
|
||||||
|
// The handler sends http trace to the connected HTTP client.
|
||||||
|
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "HTTPTrace")
|
||||||
|
trcAll := r.URL.Query().Get("all") == "true"
|
||||||
|
|
||||||
|
// Validate request signature.
|
||||||
|
adminAPIErr := checkAdminRequestAuthType(ctx, r, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid reusing tcp connection if read timeout is hit
|
||||||
|
// This is needed to make r.Context().Done() work as
|
||||||
|
// expected in case of read timeout
|
||||||
|
w.Header().Set(xhttp.Connection, "close")
|
||||||
|
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||||
|
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
defer close(doneCh)
|
||||||
|
|
||||||
|
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
||||||
|
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||||
|
traceCh := make(chan interface{}, 4000)
|
||||||
|
|
||||||
|
filter := func(entry interface{}) bool {
|
||||||
|
if trcAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
trcInfo := entry.(trace.Info)
|
||||||
|
return !strings.HasPrefix(trcInfo.ReqInfo.Path, minioReservedBucketPath)
|
||||||
|
}
|
||||||
|
remoteHosts := getRemoteHosts(globalEndpoints)
|
||||||
|
peers, err := getRestClients(remoteHosts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
globalHTTPTrace.Subscribe(traceCh, doneCh, filter)
|
||||||
|
|
||||||
|
for _, peer := range peers {
|
||||||
|
peer.Trace(traceCh, doneCh, trcAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry := <-traceCh:
|
||||||
|
if err := enc.Encode(entry); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-r.Context().Done():
|
||||||
|
return
|
||||||
|
case <-GlobalServiceDoneCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,7 +91,9 @@ var (
|
|||||||
"enable": false,
|
"enable": false,
|
||||||
"format": "namespace",
|
"format": "namespace",
|
||||||
"url": "",
|
"url": "",
|
||||||
"index": ""
|
"index": "",
|
||||||
|
"queueDir": "",
|
||||||
|
"queueLimit": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"kafka": {
|
"kafka": {
|
||||||
@@ -99,6 +101,8 @@ var (
|
|||||||
"enable": false,
|
"enable": false,
|
||||||
"brokers": null,
|
"brokers": null,
|
||||||
"topic": "",
|
"topic": "",
|
||||||
|
"queueDir": "",
|
||||||
|
"queueLimit": 0,
|
||||||
"tls": {
|
"tls": {
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"skipVerify": false,
|
"skipVerify": false,
|
||||||
@@ -164,7 +168,9 @@ var (
|
|||||||
"tls": {
|
"tls": {
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"skipVerify": false
|
"skipVerify": false
|
||||||
}
|
},
|
||||||
|
"queueDir": "",
|
||||||
|
"queueLimit": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postgresql": {
|
"postgresql": {
|
||||||
@@ -192,7 +198,9 @@ var (
|
|||||||
"webhook": {
|
"webhook": {
|
||||||
"1": {
|
"1": {
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"endpoint": ""
|
"endpoint": "",
|
||||||
|
"queueDir": "",
|
||||||
|
"queueLimit": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -266,7 +274,9 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) {
|
|||||||
initNSLock(isDistXL)
|
initNSLock(isDistXL)
|
||||||
|
|
||||||
// Init global heal state
|
// Init global heal state
|
||||||
initAllHealState(globalIsXL)
|
if globalIsXL {
|
||||||
|
globalAllHealState = initHealState()
|
||||||
|
}
|
||||||
|
|
||||||
globalConfigSys = NewConfigSys()
|
globalConfigSys = NewConfigSys()
|
||||||
|
|
||||||
@@ -700,9 +710,6 @@ func TestAdminServerInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, serverInfo := range results {
|
for _, serverInfo := range results {
|
||||||
if len(serverInfo.Addr) == 0 {
|
|
||||||
t.Error("Expected server address to be non empty")
|
|
||||||
}
|
|
||||||
if serverInfo.Error != "" {
|
if serverInfo.Error != "" {
|
||||||
t.Errorf("Unexpected error = %v\n", serverInfo.Error)
|
t.Errorf("Unexpected error = %v\n", serverInfo.Error)
|
||||||
}
|
}
|
||||||
|
|||||||
+146
-99
@@ -96,22 +96,15 @@ type allHealState struct {
|
|||||||
healSeqMap map[string]*healSequence
|
healSeqMap map[string]*healSequence
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// initHealState - initialize healing apparatus
|
||||||
// global server heal state
|
func initHealState() *allHealState {
|
||||||
globalAllHealState allHealState
|
healState := &allHealState{
|
||||||
)
|
|
||||||
|
|
||||||
// initAllHealState - initialize healing apparatus
|
|
||||||
func initAllHealState(isErasureMode bool) {
|
|
||||||
if !isErasureMode {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
globalAllHealState = allHealState{
|
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
}
|
}
|
||||||
|
|
||||||
go globalAllHealState.periodicHealSeqsClean()
|
go healState.periodicHealSeqsClean()
|
||||||
|
|
||||||
|
return healState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) periodicHealSeqsClean() {
|
func (ahs *allHealState) periodicHealSeqsClean() {
|
||||||
@@ -138,6 +131,19 @@ func (ahs *allHealState) periodicHealSeqsClean() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHealSequenceByToken - Retrieve a heal sequence by token. The second
|
||||||
|
// argument returns if a heal sequence actually exists.
|
||||||
|
func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) {
|
||||||
|
ahs.Lock()
|
||||||
|
defer ahs.Unlock()
|
||||||
|
for _, healSeq := range ahs.healSeqMap {
|
||||||
|
if healSeq.clientToken == token {
|
||||||
|
return healSeq, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// getHealSequence - Retrieve a heal sequence by path. The second
|
// getHealSequence - Retrieve a heal sequence by path. The second
|
||||||
// argument returns if a heal sequence actually exists.
|
// argument returns if a heal sequence actually exists.
|
||||||
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
||||||
@@ -305,6 +311,14 @@ type healSequence struct {
|
|||||||
// path is just pathJoin(bucket, objPrefix)
|
// path is just pathJoin(bucket, objPrefix)
|
||||||
path string
|
path string
|
||||||
|
|
||||||
|
// List of entities (format, buckets, objects) to heal
|
||||||
|
sourceCh chan string
|
||||||
|
|
||||||
|
// Report healing progress, false if this is a background
|
||||||
|
// healing since currently there is no entity which will
|
||||||
|
// receive realtime healing status
|
||||||
|
reportProgress bool
|
||||||
|
|
||||||
// time at which heal sequence was started
|
// time at which heal sequence was started
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
|
|
||||||
@@ -334,6 +348,12 @@ type healSequence struct {
|
|||||||
// the last result index sent to client
|
// the last result index sent to client
|
||||||
lastSentResultIndex int64
|
lastSentResultIndex int64
|
||||||
|
|
||||||
|
// Number of total items scanned
|
||||||
|
scannedItemsCount int64
|
||||||
|
|
||||||
|
// The time of the last scan/heal activity
|
||||||
|
lastHealActivity time.Time
|
||||||
|
|
||||||
// Holds the request-info for logging
|
// Holds the request-info for logging
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
@@ -348,14 +368,15 @@ func newHealSequence(bucket, objPrefix, clientAddr string,
|
|||||||
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
||||||
|
|
||||||
return &healSequence{
|
return &healSequence{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
objPrefix: objPrefix,
|
objPrefix: objPrefix,
|
||||||
path: pathJoin(bucket, objPrefix),
|
path: pathJoin(bucket, objPrefix),
|
||||||
startTime: UTCNow(),
|
reportProgress: true,
|
||||||
clientToken: mustGetUUID(),
|
startTime: UTCNow(),
|
||||||
clientAddress: clientAddr,
|
clientToken: mustGetUUID(),
|
||||||
forceStarted: forceStart,
|
clientAddress: clientAddr,
|
||||||
settings: hs,
|
forceStarted: forceStart,
|
||||||
|
settings: hs,
|
||||||
currentStatus: healSequenceStatus{
|
currentStatus: healSequenceStatus{
|
||||||
Summary: healNotStartedStatus,
|
Summary: healNotStartedStatus,
|
||||||
HealSettings: hs,
|
HealSettings: hs,
|
||||||
@@ -484,7 +505,11 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
h.currentStatus.StartTime = UTCNow()
|
h.currentStatus.StartTime = UTCNow()
|
||||||
h.currentStatus.updateLock.Unlock()
|
h.currentStatus.updateLock.Unlock()
|
||||||
|
|
||||||
go h.traverseAndHeal()
|
if h.sourceCh == nil {
|
||||||
|
go h.traverseAndHeal()
|
||||||
|
} else {
|
||||||
|
go h.healFromSourceCh()
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err, ok := <-h.traverseAndHealDoneCh:
|
case err, ok := <-h.traverseAndHealDoneCh:
|
||||||
@@ -519,6 +544,95 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) queueHealTask(path string, healType madmin.HealItemType) error {
|
||||||
|
var respCh = make(chan healResult)
|
||||||
|
defer close(respCh)
|
||||||
|
// Send heal request
|
||||||
|
globalBackgroundHealing.queueHealTask(healTask{path: path, responseCh: respCh, opts: h.settings})
|
||||||
|
// Wait for answer and push result to the client
|
||||||
|
res := <-respCh
|
||||||
|
if !h.reportProgress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
res.result.Type = healType
|
||||||
|
if res.err != nil {
|
||||||
|
// Object might have been deleted, by the time heal
|
||||||
|
// was attempted, we should ignore this object and return success.
|
||||||
|
if isErrObjectNotFound(res.err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Only report object error
|
||||||
|
if healType != madmin.HealItemObject {
|
||||||
|
return res.err
|
||||||
|
}
|
||||||
|
res.result.Detail = res.err.Error()
|
||||||
|
}
|
||||||
|
return h.pushHealResultItem(res.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) healItemsFromSourceCh() error {
|
||||||
|
h.lastHealActivity = UTCNow()
|
||||||
|
|
||||||
|
// Start healing the config prefix.
|
||||||
|
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
|
||||||
|
logger.LogIf(h.ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start healing the bucket config prefix.
|
||||||
|
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
|
||||||
|
logger.LogIf(h.ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for path := range h.sourceCh {
|
||||||
|
|
||||||
|
var itemType madmin.HealItemType
|
||||||
|
switch {
|
||||||
|
case path == "/":
|
||||||
|
itemType = madmin.HealItemMetadata
|
||||||
|
case !strings.Contains(path, "/"):
|
||||||
|
itemType = madmin.HealItemBucket
|
||||||
|
default:
|
||||||
|
itemType = madmin.HealItemObject
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.queueHealTask(path, itemType); err != nil {
|
||||||
|
logger.LogIf(h.ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.scannedItemsCount++
|
||||||
|
h.lastHealActivity = UTCNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) healFromSourceCh() {
|
||||||
|
if err := h.healItemsFromSourceCh(); err != nil {
|
||||||
|
h.traverseAndHealDoneCh <- err
|
||||||
|
}
|
||||||
|
close(h.traverseAndHealDoneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) healItems() error {
|
||||||
|
// Start with format healing
|
||||||
|
if err := h.healDiskFormat(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start healing the config prefix.
|
||||||
|
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start healing the bucket config prefix.
|
||||||
|
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heal buckets and objects
|
||||||
|
return h.healBuckets()
|
||||||
|
}
|
||||||
|
|
||||||
// traverseAndHeal - traverses on-disk data and performs healing
|
// traverseAndHeal - traverses on-disk data and performs healing
|
||||||
// according to settings. At each "safe" point it also checks if an
|
// according to settings. At each "safe" point it also checks if an
|
||||||
// external quit signal has been received and quits if so. Since the
|
// external quit signal has been received and quits if so. Since the
|
||||||
@@ -527,31 +641,10 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
// 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() {
|
||||||
var err error
|
if err := h.healItems(); err != nil {
|
||||||
checkErr := func(f func() error) {
|
if h.isQuitting() {
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return
|
|
||||||
case h.isQuitting():
|
|
||||||
err = errHealStopSignalled
|
err = errHealStopSignalled
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = f()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with format healing
|
|
||||||
checkErr(h.healDiskFormat)
|
|
||||||
|
|
||||||
// Start healing the config prefix.
|
|
||||||
checkErr(h.healMinioSysMeta(minioConfigPrefix))
|
|
||||||
|
|
||||||
// Start healing the bucket config prefix.
|
|
||||||
checkErr(h.healMinioSysMeta(bucketConfigPrefix))
|
|
||||||
|
|
||||||
// Heal buckets and objects
|
|
||||||
checkErr(h.healBuckets)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
h.traverseAndHealDoneCh <- err
|
h.traverseAndHealDoneCh <- err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,17 +668,14 @@ func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
|
|||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
res, herr := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun, h.settings.Remove, h.settings.ScanMode)
|
|
||||||
|
herr := h.queueHealTask(pathJoin(bucket, object), madmin.HealItemBucketMetadata)
|
||||||
// Object might have been deleted, by the time heal
|
// Object might have been deleted, by the time heal
|
||||||
// was attempted we ignore this object an move on.
|
// was attempted we ignore this object an move on.
|
||||||
if isErrObjectNotFound(herr) {
|
if isErrObjectNotFound(herr) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if herr != nil {
|
return herr
|
||||||
return herr
|
|
||||||
}
|
|
||||||
res.Type = madmin.HealItemBucketMetadata
|
|
||||||
return h.pushHealResultItem(res)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,26 +693,7 @@ func (h *healSequence) healDiskFormat() error {
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := objectAPI.HealFormat(h.ctx, h.settings.DryRun)
|
return h.queueHealTask("/", madmin.HealItemMetadata)
|
||||||
// return any error, ignore error returned when disks have
|
|
||||||
// already healed.
|
|
||||||
if err != nil && err != errNoHealRequired {
|
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Healing succeeded notify the peers to reload format and re-initialize disks.
|
|
||||||
// We will not notify peers only if healing succeeded.
|
|
||||||
if err == nil {
|
|
||||||
for _, nerr := range globalNotificationSys.ReloadFormat(h.settings.DryRun) {
|
|
||||||
if nerr.Err != nil {
|
|
||||||
logger.GetReqInfo(h.ctx).SetTags("peerAddress", nerr.Host.String())
|
|
||||||
logger.LogIf(h.ctx, nerr.Err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push format heal result
|
|
||||||
return h.pushHealResultItem(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// healBuckets - check for all buckets heal or just particular bucket.
|
// healBuckets - check for all buckets heal or just particular bucket.
|
||||||
@@ -664,13 +735,7 @@ func (h *healSequence) healBucket(bucket string) error {
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := objectAPI.HealBucket(h.ctx, bucket, h.settings.DryRun, h.settings.Remove)
|
if err := h.queueHealTask(bucket, madmin.HealItemBucket); err != nil {
|
||||||
// handle heal-bucket error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.pushHealResultItem(result); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +743,7 @@ func (h *healSequence) healBucket(bucket string) error {
|
|||||||
if h.objPrefix != "" {
|
if h.objPrefix != "" {
|
||||||
// Check if an object named as the objPrefix exists,
|
// Check if an object named as the objPrefix exists,
|
||||||
// and if so heal it.
|
// and if so heal it.
|
||||||
_, err = objectAPI.GetObjectInfo(h.ctx, bucket, h.objPrefix, ObjectOptions{})
|
_, err := objectAPI.GetObjectInfo(h.ctx, bucket, h.objPrefix, ObjectOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = h.healObject(bucket, h.objPrefix); err != nil {
|
if err = h.healObject(bucket, h.objPrefix); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -689,8 +754,7 @@ func (h *healSequence) healBucket(bucket string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = objectAPI.HealObjects(h.ctx, bucket,
|
if err := objectAPI.HealObjects(h.ctx, bucket, h.objPrefix, h.healObject); err != nil {
|
||||||
h.objPrefix, h.healObject); err != nil {
|
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
return errFnHealFromAPIErr(h.ctx, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -702,28 +766,11 @@ func (h *healSequence) healObject(bucket, object string) error {
|
|||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalHTTPServer != nil {
|
|
||||||
// Wait at max 1 minute for an inprogress request
|
|
||||||
// before proceeding to heal
|
|
||||||
waitCount := 60
|
|
||||||
// Any requests in progress, delay the heal.
|
|
||||||
for globalHTTPServer.GetRequestCount() > 2 && waitCount > 0 {
|
|
||||||
waitCount--
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
hri, err := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun, h.settings.Remove, h.settings.ScanMode)
|
return h.queueHealTask(pathJoin(bucket, object), madmin.HealItemObject)
|
||||||
if isErrObjectNotFound(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
hri.Detail = err.Error()
|
|
||||||
}
|
|
||||||
return h.pushHealResultItem(hri)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
adminV1Router.Methods(http.MethodPost).Path("/heal/{bucket}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
adminV1Router.Methods(http.MethodPost).Path("/heal/{bucket}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
||||||
adminV1Router.Methods(http.MethodPost).Path("/heal/{bucket}/{prefix:.*}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
adminV1Router.Methods(http.MethodPost).Path("/heal/{bucket}/{prefix:.*}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
||||||
|
|
||||||
|
adminV1Router.Methods(http.MethodPost).Path("/background-heal/status").HandlerFunc(httpTraceAll(adminAPI.BackgroundHealStatusHandler))
|
||||||
|
|
||||||
/// Health operations
|
/// Health operations
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -116,6 +118,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
// Top locks
|
// Top locks
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
adminV1Router.Methods(http.MethodGet).Path("/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
||||||
|
|
||||||
|
// HTTP Trace
|
||||||
|
adminV1Router.Methods(http.MethodGet).Path("/trace").HandlerFunc(adminAPI.TraceHandler)
|
||||||
// If none of the routes match, return error.
|
// If none of the routes match, return error.
|
||||||
adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON))
|
adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,6 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// Response request id.
|
|
||||||
responseRequestIDKey = "x-amz-request-id"
|
|
||||||
// Deployment id.
|
|
||||||
responseDeploymentIDKey = "x-minio-deployment-id"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ObjectIdentifier carries key name for the object to delete.
|
// ObjectIdentifier carries key name for the object to delete.
|
||||||
type ObjectIdentifier struct {
|
type ObjectIdentifier struct {
|
||||||
ObjectName string `xml:"Key"`
|
ObjectName string `xml:"Key"`
|
||||||
|
|||||||
+22
-14
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go/v6"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
@@ -51,6 +51,7 @@ type APIErrorResponse struct {
|
|||||||
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
||||||
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
||||||
Resource string
|
Resource string
|
||||||
|
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
||||||
RequestID string `xml:"RequestId" json:"RequestId"`
|
RequestID string `xml:"RequestId" json:"RequestId"`
|
||||||
HostID string `xml:"HostId" json:"HostId"`
|
HostID string `xml:"HostId" json:"HostId"`
|
||||||
}
|
}
|
||||||
@@ -138,6 +139,7 @@ const (
|
|||||||
ErrSlowDown
|
ErrSlowDown
|
||||||
ErrInvalidPrefixMarker
|
ErrInvalidPrefixMarker
|
||||||
ErrBadRequest
|
ErrBadRequest
|
||||||
|
ErrKeyTooLongError
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
|
|
||||||
// SSE-S3 related API errors
|
// SSE-S3 related API errors
|
||||||
@@ -186,6 +188,7 @@ const (
|
|||||||
ErrRequestBodyParse
|
ErrRequestBodyParse
|
||||||
ErrObjectExistsAsDirectory
|
ErrObjectExistsAsDirectory
|
||||||
ErrInvalidObjectName
|
ErrInvalidObjectName
|
||||||
|
ErrInvalidObjectNamePrefixSlash
|
||||||
ErrInvalidResourceName
|
ErrInvalidResourceName
|
||||||
ErrServerNotInitialized
|
ErrServerNotInitialized
|
||||||
ErrOperationTimedOut
|
ErrOperationTimedOut
|
||||||
@@ -681,6 +684,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "400 BadRequest",
|
Description: "400 BadRequest",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrKeyTooLongError: {
|
||||||
|
Code: "KeyTooLongError",
|
||||||
|
Description: "Your key is too long",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
// FIXME: Actual XML error response also contains the header which missed in list of signed header parameters.
|
// FIXME: Actual XML error response also contains the header which missed in list of signed header parameters.
|
||||||
ErrUnsignedHeaders: {
|
ErrUnsignedHeaders: {
|
||||||
@@ -879,21 +887,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Object name already exists as a directory.",
|
Description: "Object name already exists as a directory.",
|
||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusConflict,
|
||||||
},
|
},
|
||||||
ErrReadQuorum: {
|
|
||||||
Code: "XMinioReadQuorum",
|
|
||||||
Description: "Multiple disk failures, unable to reconstruct data.",
|
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
|
||||||
},
|
|
||||||
ErrWriteQuorum: {
|
|
||||||
Code: "XMinioWriteQuorum",
|
|
||||||
Description: "Multiple disks failures, unable to write data.",
|
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
|
||||||
},
|
|
||||||
ErrInvalidObjectName: {
|
ErrInvalidObjectName: {
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: "XMinioInvalidObjectName",
|
||||||
Description: "Object name contains unsupported characters.",
|
Description: "Object name contains unsupported characters.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidObjectNamePrefixSlash: {
|
||||||
|
Code: "XMinioInvalidObjectName",
|
||||||
|
Description: "Object name contains a leading slash.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidResourceName: {
|
ErrInvalidResourceName: {
|
||||||
Code: "XMinioInvalidResourceName",
|
Code: "XMinioInvalidResourceName",
|
||||||
Description: "Resource name contains bad components such as \"..\" or \".\".",
|
Description: "Resource name contains bad components such as \"..\" or \".\".",
|
||||||
@@ -1534,7 +1537,7 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrKMSAuthFailure
|
apiErr = ErrKMSAuthFailure
|
||||||
case errOperationTimedOut, context.Canceled, context.DeadlineExceeded:
|
case errOperationTimedOut, context.Canceled, context.DeadlineExceeded:
|
||||||
apiErr = ErrOperationTimedOut
|
apiErr = ErrOperationTimedOut
|
||||||
case errNetworkConnReset:
|
case errDiskNotFound:
|
||||||
apiErr = ErrSlowDown
|
apiErr = ErrSlowDown
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1588,14 +1591,16 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrMethodNotAllowed
|
apiErr = ErrMethodNotAllowed
|
||||||
case ObjectNameInvalid:
|
case ObjectNameInvalid:
|
||||||
apiErr = ErrInvalidObjectName
|
apiErr = ErrInvalidObjectName
|
||||||
|
case ObjectNamePrefixAsSlash:
|
||||||
|
apiErr = ErrInvalidObjectNamePrefixSlash
|
||||||
case InvalidUploadID:
|
case InvalidUploadID:
|
||||||
apiErr = ErrNoSuchUpload
|
apiErr = ErrNoSuchUpload
|
||||||
case InvalidPart:
|
case InvalidPart:
|
||||||
apiErr = ErrInvalidPart
|
apiErr = ErrInvalidPart
|
||||||
case InsufficientWriteQuorum:
|
case InsufficientWriteQuorum:
|
||||||
apiErr = ErrWriteQuorum
|
apiErr = ErrSlowDown
|
||||||
case InsufficientReadQuorum:
|
case InsufficientReadQuorum:
|
||||||
apiErr = ErrReadQuorum
|
apiErr = ErrSlowDown
|
||||||
case UnsupportedDelimiter:
|
case UnsupportedDelimiter:
|
||||||
apiErr = ErrNotImplemented
|
apiErr = ErrNotImplemented
|
||||||
case InvalidMarkerPrefixCombination:
|
case InvalidMarkerPrefixCombination:
|
||||||
@@ -1648,6 +1653,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrBackendDown
|
apiErr = ErrBackendDown
|
||||||
case crypto.Error:
|
case crypto.Error:
|
||||||
apiErr = ErrObjectTampered
|
apiErr = ErrObjectTampered
|
||||||
|
case ObjectNameTooLong:
|
||||||
|
apiErr = ErrKeyTooLongError
|
||||||
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
|
||||||
@@ -1745,6 +1752,7 @@ func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID,
|
|||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
Resource: resource,
|
Resource: resource,
|
||||||
|
Region: globalServerConfig.GetRegion(),
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
HostID: hostID,
|
HostID: hostID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ var toAPIErrorTests = []struct {
|
|||||||
{err: ObjectNameInvalid{}, errCode: ErrInvalidObjectName},
|
{err: ObjectNameInvalid{}, errCode: ErrInvalidObjectName},
|
||||||
{err: InvalidUploadID{}, errCode: ErrNoSuchUpload},
|
{err: InvalidUploadID{}, errCode: ErrNoSuchUpload},
|
||||||
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
||||||
{err: InsufficientReadQuorum{}, errCode: ErrReadQuorum},
|
{err: InsufficientReadQuorum{}, errCode: ErrSlowDown},
|
||||||
{err: InsufficientWriteQuorum{}, errCode: ErrWriteQuorum},
|
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDown},
|
||||||
{err: UnsupportedDelimiter{}, errCode: ErrNotImplemented},
|
{err: UnsupportedDelimiter{}, errCode: ErrNotImplemented},
|
||||||
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
|
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
|
||||||
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
||||||
|
|||||||
+11
-10
@@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns a hexadecimal representation of time at the
|
// Returns a hexadecimal representation of time at the
|
||||||
@@ -36,13 +37,13 @@ func mustGetRequestID(t time.Time) string {
|
|||||||
|
|
||||||
// Write http common headers
|
// Write http common headers
|
||||||
func setCommonHeaders(w http.ResponseWriter) {
|
func setCommonHeaders(w http.ResponseWriter) {
|
||||||
w.Header().Set("Server", "MinIO/"+ReleaseTag)
|
w.Header().Set(xhttp.ServerInfo, "MinIO/"+ReleaseTag)
|
||||||
// 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 := globalServerConfig.GetRegion(); region != "" {
|
if region := globalServerConfig.GetRegion(); region != "" {
|
||||||
w.Header().Set("X-Amz-Bucket-Region", region)
|
w.Header().Set(xhttp.AmzBucketRegion, region)
|
||||||
}
|
}
|
||||||
w.Header().Set("Accept-Ranges", "bytes")
|
w.Header().Set(xhttp.AcceptRanges, "bytes")
|
||||||
|
|
||||||
// Remove sensitive information
|
// Remove sensitive information
|
||||||
crypto.RemoveSensitiveHeaders(w.Header())
|
crypto.RemoveSensitiveHeaders(w.Header())
|
||||||
@@ -72,23 +73,23 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
|
|
||||||
// Set last modified time.
|
// Set last modified time.
|
||||||
lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat)
|
lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat)
|
||||||
w.Header().Set("Last-Modified", lastModified)
|
w.Header().Set(xhttp.LastModified, lastModified)
|
||||||
|
|
||||||
// Set Etag if available.
|
// Set Etag if available.
|
||||||
if objInfo.ETag != "" {
|
if objInfo.ETag != "" {
|
||||||
w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
|
w.Header()[xhttp.ETag] = []string{"\"" + objInfo.ETag + "\""}
|
||||||
}
|
}
|
||||||
|
|
||||||
if objInfo.ContentType != "" {
|
if objInfo.ContentType != "" {
|
||||||
w.Header().Set("Content-Type", objInfo.ContentType)
|
w.Header().Set(xhttp.ContentType, objInfo.ContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if objInfo.ContentEncoding != "" {
|
if objInfo.ContentEncoding != "" {
|
||||||
w.Header().Set("Content-Encoding", objInfo.ContentEncoding)
|
w.Header().Set(xhttp.ContentEncoding, objInfo.ContentEncoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objInfo.Expires.IsZero() {
|
if !objInfo.Expires.IsZero() {
|
||||||
w.Header().Set("Expires", objInfo.Expires.UTC().Format(http.TimeFormat))
|
w.Header().Set(xhttp.Expires, objInfo.Expires.UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all other user defined metadata.
|
// Set all other user defined metadata.
|
||||||
@@ -124,10 +125,10 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set content length.
|
// Set content length.
|
||||||
w.Header().Set("Content-Length", strconv.FormatInt(rangeLen, 10))
|
w.Header().Set(xhttp.ContentLength, strconv.FormatInt(rangeLen, 10))
|
||||||
if rs != nil {
|
if rs != nil {
|
||||||
contentRange := fmt.Sprintf("bytes %d-%d/%d", start, start+rangeLen-1, totalObjectSize)
|
contentRange := fmt.Sprintf("bytes %d-%d/%d", start, start+rangeLen-1, totalObjectSize)
|
||||||
w.Header().Set("Content-Range", contentRange)
|
w.Header().Set(xhttp.ContentRange, contentRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+16
-13
@@ -22,9 +22,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
)
|
)
|
||||||
@@ -521,8 +523,9 @@ func generateMultiDeleteResponse(quiet bool, deletedObjects []ObjectIdentifier,
|
|||||||
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
|
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
if mType != mimeNone {
|
if mType != mimeNone {
|
||||||
w.Header().Set("Content-Type", string(mType))
|
w.Header().Set(xhttp.ContentType, string(mType))
|
||||||
}
|
}
|
||||||
|
w.Header().Set(xhttp.ContentLength, strconv.Itoa(len(response)))
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
w.Write(response)
|
w.Write(response)
|
||||||
@@ -561,7 +564,7 @@ func writeSuccessNoContent(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// writeRedirectSeeOther writes Location header with http status 303
|
// writeRedirectSeeOther writes Location header with http status 303
|
||||||
func writeRedirectSeeOther(w http.ResponseWriter, location string) {
|
func writeRedirectSeeOther(w http.ResponseWriter, location string) {
|
||||||
w.Header().Set("Location", location)
|
w.Header().Set(xhttp.Location, location)
|
||||||
writeResponse(w, http.StatusSeeOther, nil, mimeNone)
|
writeResponse(w, http.StatusSeeOther, nil, mimeNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,12 +578,12 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
|
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
|
||||||
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||||
w.Header().Set("Retry-After", "120")
|
w.Header().Set(xhttp.RetryAfter, "120")
|
||||||
case "AccessDenied":
|
case "AccessDenied":
|
||||||
// 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 {
|
if browser && globalIsBrowserEnabled {
|
||||||
w.Header().Set("Location", minioReservedBucketPath+reqURL.Path)
|
w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
|
||||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -588,7 +591,7 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
|
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
||||||
w.Header().Get(responseRequestIDKey), w.Header().Get(responseDeploymentIDKey))
|
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
||||||
encodedErrorResponse := encodeResponse(errorResponse)
|
encodedErrorResponse := encodeResponse(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||||
}
|
}
|
||||||
@@ -601,7 +604,7 @@ func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
|
|||||||
// useful for admin APIs.
|
// useful for admin APIs.
|
||||||
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(responseRequestIDKey), w.Header().Get(responseDeploymentIDKey))
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
||||||
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
||||||
}
|
}
|
||||||
@@ -619,8 +622,8 @@ func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, er
|
|||||||
Resource: reqURL.Path,
|
Resource: reqURL.Path,
|
||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
RequestID: w.Header().Get(responseRequestIDKey),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
HostID: w.Header().Get(responseDeploymentIDKey),
|
HostID: globalDeploymentID,
|
||||||
}
|
}
|
||||||
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
||||||
@@ -635,12 +638,12 @@ func writeCustomErrorResponseXML(ctx context.Context, w http.ResponseWriter, err
|
|||||||
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
|
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
|
||||||
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||||
w.Header().Set("Retry-After", "120")
|
w.Header().Set(xhttp.RetryAfter, "120")
|
||||||
case "AccessDenied":
|
case "AccessDenied":
|
||||||
// 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 && globalIsBrowserEnabled {
|
if browser && globalIsBrowserEnabled {
|
||||||
w.Header().Set("Location", minioReservedBucketPath+reqURL.Path)
|
w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
|
||||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -653,8 +656,8 @@ func writeCustomErrorResponseXML(ctx context.Context, w http.ResponseWriter, err
|
|||||||
Resource: reqURL.Path,
|
Resource: reqURL.Path,
|
||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
RequestID: w.Header().Get(responseRequestIDKey),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
HostID: w.Header().Get(responseDeploymentIDKey),
|
HostID: globalDeploymentID,
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedErrorResponse := encodeResponse(errorResponse)
|
encodedErrorResponse := encodeResponse(errorResponse)
|
||||||
|
|||||||
+49
-43
@@ -20,6 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// objectAPIHandler implements and provides http handlers for S3 API.
|
// objectAPIHandler implements and provides http handlers for S3 API.
|
||||||
@@ -28,10 +29,12 @@ type objectAPIHandlers struct {
|
|||||||
CacheAPI func() CacheObjectLayer
|
CacheAPI func() CacheObjectLayer
|
||||||
// Returns true of handlers should interpret encryption.
|
// Returns true of handlers should interpret encryption.
|
||||||
EncryptionEnabled func() bool
|
EncryptionEnabled func() bool
|
||||||
|
// Returns true if handlers allow SSE-KMS encryption headers.
|
||||||
|
AllowSSEKMS func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerAPIRouter - registers S3 compatible APIs.
|
// registerAPIRouter - registers S3 compatible APIs.
|
||||||
func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
|
func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) {
|
||||||
// Initialize API.
|
// Initialize API.
|
||||||
api := objectAPIHandlers{
|
api := objectAPIHandlers{
|
||||||
ObjectAPI: newObjectLayerFn,
|
ObjectAPI: newObjectLayerFn,
|
||||||
@@ -39,6 +42,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
|
|||||||
EncryptionEnabled: func() bool {
|
EncryptionEnabled: func() bool {
|
||||||
return encryptionEnabled
|
return encryptionEnabled
|
||||||
},
|
},
|
||||||
|
AllowSSEKMS: func() bool {
|
||||||
|
return allowSSEKMS
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Router
|
// API Router
|
||||||
@@ -53,98 +59,98 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
|
|||||||
for _, bucket := range routers {
|
for _, bucket := range routers {
|
||||||
// Object operations
|
// Object operations
|
||||||
// HeadObject
|
// HeadObject
|
||||||
bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.HeadObjectHandler))
|
bucket.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.HeadObjectHandler))
|
||||||
// CopyObjectPart
|
// CopyObjectPart
|
||||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||||
// PutObjectPart
|
// PutObjectPart
|
||||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||||
// ListObjectPxarts
|
// ListObjectPxarts
|
||||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}")
|
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}")
|
||||||
// CompleteMultipartUpload
|
// CompleteMultipartUpload
|
||||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
|
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
|
||||||
// NewMultipartUpload
|
// NewMultipartUpload
|
||||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.NewMultipartUploadHandler)).Queries("uploads", "")
|
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.NewMultipartUploadHandler)).Queries("uploads", "")
|
||||||
// AbortMultipartUpload
|
// AbortMultipartUpload
|
||||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.AbortMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
|
bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.AbortMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
|
||||||
// GetObjectACL - this is a dummy call.
|
// GetObjectACL - this is a dummy call.
|
||||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectACLHandler)).Queries("acl", "")
|
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectACLHandler)).Queries("acl", "")
|
||||||
// GetObjectTagging - this is a dummy call.
|
// GetObjectTagging - this is a dummy call.
|
||||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectTaggingHandler)).Queries("tagging", "")
|
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectTaggingHandler)).Queries("tagging", "")
|
||||||
// SelectObjectContent
|
// SelectObjectContent
|
||||||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.SelectObjectContentHandler)).Queries("select", "").Queries("select-type", "2")
|
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.SelectObjectContentHandler)).Queries("select", "").Queries("select-type", "2")
|
||||||
// GetObject
|
// GetObject
|
||||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectHandler))
|
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectHandler))
|
||||||
// CopyObject
|
// CopyObject
|
||||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectHandler))
|
bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectHandler))
|
||||||
// PutObject
|
// PutObject
|
||||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectHandler))
|
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectHandler))
|
||||||
// DeleteObject
|
// DeleteObject
|
||||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.DeleteObjectHandler))
|
bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.DeleteObjectHandler))
|
||||||
|
|
||||||
/// Bucket operations
|
/// Bucket operations
|
||||||
// GetBucketLocation
|
// GetBucketLocation
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLocationHandler)).Queries("location", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketLocationHandler)).Queries("location", "")
|
||||||
// GetBucketPolicy
|
// GetBucketPolicy
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketPolicyHandler)).Queries("policy", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketPolicyHandler)).Queries("policy", "")
|
||||||
|
|
||||||
// Dummy Bucket Calls
|
// Dummy Bucket Calls
|
||||||
// GetBucketACL -- this is a dummy call.
|
// GetBucketACL -- this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketACLHandler)).Queries("acl", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketACLHandler)).Queries("acl", "")
|
||||||
// GetBucketCors - this is a dummy call.
|
// GetBucketCors - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketCorsHandler)).Queries("cors", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketCorsHandler)).Queries("cors", "")
|
||||||
// GetBucketWebsiteHandler - this is a dummy call.
|
// GetBucketWebsiteHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketWebsiteHandler)).Queries("website", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketWebsiteHandler)).Queries("website", "")
|
||||||
// GetBucketVersioningHandler - this is a dummy call.
|
// GetBucketVersioningHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketVersioningHandler)).Queries("versioning", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketVersioningHandler)).Queries("versioning", "")
|
||||||
// GetBucketAccelerateHandler - this is a dummy call.
|
// GetBucketAccelerateHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketAccelerateHandler)).Queries("accelerate", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketAccelerateHandler)).Queries("accelerate", "")
|
||||||
// GetBucketRequestPaymentHandler - this is a dummy call.
|
// GetBucketRequestPaymentHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketRequestPaymentHandler)).Queries("requestPayment", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketRequestPaymentHandler)).Queries("requestPayment", "")
|
||||||
// GetBucketLoggingHandler - this is a dummy call.
|
// GetBucketLoggingHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLoggingHandler)).Queries("logging", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketLoggingHandler)).Queries("logging", "")
|
||||||
// GetBucketLifecycleHandler - this is a dummy call.
|
// GetBucketLifecycleHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLifecycleHandler)).Queries("lifecycle", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketLifecycleHandler)).Queries("lifecycle", "")
|
||||||
// GetBucketReplicationHandler - this is a dummy call.
|
// GetBucketReplicationHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketReplicationHandler)).Queries("replication", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketReplicationHandler)).Queries("replication", "")
|
||||||
// GetBucketTaggingHandler - this is a dummy call.
|
// GetBucketTaggingHandler - this is a dummy call.
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketTaggingHandler)).Queries("tagging", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketTaggingHandler)).Queries("tagging", "")
|
||||||
//DeleteBucketWebsiteHandler
|
//DeleteBucketWebsiteHandler
|
||||||
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketWebsiteHandler)).Queries("website", "")
|
bucket.Methods(http.MethodDelete).HandlerFunc(httpTraceAll(api.DeleteBucketWebsiteHandler)).Queries("website", "")
|
||||||
// DeleteBucketTaggingHandler
|
// DeleteBucketTaggingHandler
|
||||||
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketTaggingHandler)).Queries("tagging", "")
|
bucket.Methods(http.MethodDelete).HandlerFunc(httpTraceAll(api.DeleteBucketTaggingHandler)).Queries("tagging", "")
|
||||||
|
|
||||||
// GetBucketNotification
|
// GetBucketNotification
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketNotificationHandler)).Queries("notification", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.GetBucketNotificationHandler)).Queries("notification", "")
|
||||||
// ListenBucketNotification
|
// ListenBucketNotification
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListenBucketNotificationHandler)).Queries("events", "{events:.*}")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListenBucketNotificationHandler)).Queries("events", "{events:.*}")
|
||||||
// ListMultipartUploads
|
// ListMultipartUploads
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "")
|
||||||
// ListObjectsV2
|
// ListObjectsV2
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2")
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2")
|
||||||
// ListObjectsV1 (Legacy)
|
// ListObjectsV1 (Legacy)
|
||||||
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV1Handler))
|
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListObjectsV1Handler))
|
||||||
// PutBucketPolicy
|
// PutBucketPolicy
|
||||||
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketPolicyHandler)).Queries("policy", "")
|
bucket.Methods(http.MethodPut).HandlerFunc(httpTraceAll(api.PutBucketPolicyHandler)).Queries("policy", "")
|
||||||
// PutBucketNotification
|
// PutBucketNotification
|
||||||
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketNotificationHandler)).Queries("notification", "")
|
bucket.Methods(http.MethodPut).HandlerFunc(httpTraceAll(api.PutBucketNotificationHandler)).Queries("notification", "")
|
||||||
// PutBucket
|
// PutBucket
|
||||||
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketHandler))
|
bucket.Methods(http.MethodPut).HandlerFunc(httpTraceAll(api.PutBucketHandler))
|
||||||
// HeadBucket
|
// HeadBucket
|
||||||
bucket.Methods("HEAD").HandlerFunc(httpTraceAll(api.HeadBucketHandler))
|
bucket.Methods(http.MethodHead).HandlerFunc(httpTraceAll(api.HeadBucketHandler))
|
||||||
// PostPolicy
|
// PostPolicy
|
||||||
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(httpTraceHdrs(api.PostPolicyBucketHandler))
|
bucket.Methods(http.MethodPost).HeadersRegexp(xhttp.ContentType, "multipart/form-data*").HandlerFunc(httpTraceHdrs(api.PostPolicyBucketHandler))
|
||||||
// DeleteMultipleObjects
|
// DeleteMultipleObjects
|
||||||
bucket.Methods("POST").HandlerFunc(httpTraceAll(api.DeleteMultipleObjectsHandler)).Queries("delete", "")
|
bucket.Methods(http.MethodPost).HandlerFunc(httpTraceAll(api.DeleteMultipleObjectsHandler)).Queries("delete", "")
|
||||||
// DeleteBucketPolicy
|
// DeleteBucketPolicy
|
||||||
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketPolicyHandler)).Queries("policy", "")
|
bucket.Methods(http.MethodDelete).HandlerFunc(httpTraceAll(api.DeleteBucketPolicyHandler)).Queries("policy", "")
|
||||||
// DeleteBucket
|
// DeleteBucket
|
||||||
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketHandler))
|
bucket.Methods(http.MethodDelete).HandlerFunc(httpTraceAll(api.DeleteBucketHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root operation
|
/// Root operation
|
||||||
|
|
||||||
// ListBuckets
|
// ListBuckets
|
||||||
apiRouter.Methods("GET").Path("/").HandlerFunc(httpTraceAll(api.ListBucketsHandler))
|
apiRouter.Methods(http.MethodGet).Path("/").HandlerFunc(httpTraceAll(api.ListBucketsHandler))
|
||||||
|
|
||||||
// If none of the routes match.
|
// If none of the routes match.
|
||||||
apiRouter.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
|
apiRouter.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
|
||||||
|
|||||||
+54
-19
@@ -29,6 +29,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
jwtgo "github.com/dgrijalva/jwt-go"
|
||||||
|
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/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
@@ -38,41 +39,41 @@ import (
|
|||||||
|
|
||||||
// Verify if request has JWT.
|
// Verify if request has JWT.
|
||||||
func isRequestJWT(r *http.Request) bool {
|
func isRequestJWT(r *http.Request) bool {
|
||||||
return strings.HasPrefix(r.Header.Get("Authorization"), jwtAlgorithm)
|
return strings.HasPrefix(r.Header.Get(xhttp.Authorization), jwtAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request has AWS Signature Version '4'.
|
// Verify if request has AWS Signature Version '4'.
|
||||||
func isRequestSignatureV4(r *http.Request) bool {
|
func isRequestSignatureV4(r *http.Request) bool {
|
||||||
return strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm)
|
return strings.HasPrefix(r.Header.Get(xhttp.Authorization), signV4Algorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request has AWS Signature Version '2'.
|
// Verify if request has AWS Signature Version '2'.
|
||||||
func isRequestSignatureV2(r *http.Request) bool {
|
func isRequestSignatureV2(r *http.Request) bool {
|
||||||
return (!strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) &&
|
return (!strings.HasPrefix(r.Header.Get(xhttp.Authorization), signV4Algorithm) &&
|
||||||
strings.HasPrefix(r.Header.Get("Authorization"), signV2Algorithm))
|
strings.HasPrefix(r.Header.Get(xhttp.Authorization), signV2Algorithm))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request has AWS PreSign Version '4'.
|
// Verify if request has AWS PreSign Version '4'.
|
||||||
func isRequestPresignedSignatureV4(r *http.Request) bool {
|
func isRequestPresignedSignatureV4(r *http.Request) bool {
|
||||||
_, ok := r.URL.Query()["X-Amz-Credential"]
|
_, ok := r.URL.Query()[xhttp.AmzCredential]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify request has AWS PreSign Version '2'.
|
// Verify request has AWS PreSign Version '2'.
|
||||||
func isRequestPresignedSignatureV2(r *http.Request) bool {
|
func isRequestPresignedSignatureV2(r *http.Request) bool {
|
||||||
_, ok := r.URL.Query()["AWSAccessKeyId"]
|
_, ok := r.URL.Query()[xhttp.AmzAccessKeyID]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request has AWS Post policy Signature Version '4'.
|
// Verify if request has AWS Post policy Signature Version '4'.
|
||||||
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
||||||
return strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") &&
|
return strings.Contains(r.Header.Get(xhttp.ContentType), "multipart/form-data") &&
|
||||||
r.Method == http.MethodPost
|
r.Method == http.MethodPost
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
|
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
|
||||||
func isRequestSignStreamingV4(r *http.Request) bool {
|
func isRequestSignStreamingV4(r *http.Request) bool {
|
||||||
return r.Header.Get("x-amz-content-sha256") == streamingContentSHA256 &&
|
return r.Header.Get(xhttp.AmzContentSha256) == streamingContentSHA256 &&
|
||||||
r.Method == http.MethodPut
|
r.Method == http.MethodPut
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,9 +110,9 @@ func getRequestAuthType(r *http.Request) authType {
|
|||||||
return authTypeJWT
|
return authTypeJWT
|
||||||
} else if isRequestPostPolicySignatureV4(r) {
|
} else if isRequestPostPolicySignatureV4(r) {
|
||||||
return authTypePostPolicy
|
return authTypePostPolicy
|
||||||
} else if _, ok := r.URL.Query()["Action"]; ok {
|
} else if _, ok := r.URL.Query()[xhttp.Action]; ok {
|
||||||
return authTypeSTS
|
return authTypeSTS
|
||||||
} else if _, ok := r.Header["Authorization"]; !ok {
|
} else if _, ok := r.Header[xhttp.Authorization]; !ok {
|
||||||
return authTypeAnonymous
|
return authTypeAnonymous
|
||||||
}
|
}
|
||||||
return authTypeUnknown
|
return authTypeUnknown
|
||||||
@@ -121,7 +122,7 @@ func getRequestAuthType(r *http.Request) authType {
|
|||||||
// It does not accept presigned or JWT or anonymous requests.
|
// It does not accept presigned or JWT or anonymous requests.
|
||||||
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, region string) APIErrorCode {
|
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, region string) APIErrorCode {
|
||||||
s3Err := ErrAccessDenied
|
s3Err := ErrAccessDenied
|
||||||
if _, ok := r.Header["X-Amz-Content-Sha256"]; ok &&
|
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
|
||||||
getRequestAuthType(r) == authTypeSigned && !skipContentSha256Cksum(r) {
|
getRequestAuthType(r) == authTypeSigned && !skipContentSha256Cksum(r) {
|
||||||
// We only support admin credentials to access admin APIs.
|
// We only support admin credentials to access admin APIs.
|
||||||
|
|
||||||
@@ -148,11 +149,11 @@ func checkAdminRequestAuthType(ctx context.Context, r *http.Request, region stri
|
|||||||
|
|
||||||
// Fetch the security token set by the client.
|
// Fetch the security token set by the client.
|
||||||
func getSessionToken(r *http.Request) (token string) {
|
func getSessionToken(r *http.Request) (token string) {
|
||||||
token = r.Header.Get("X-Amz-Security-Token")
|
token = r.Header.Get(xhttp.AmzSecurityToken)
|
||||||
if token != "" {
|
if token != "" {
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
return r.URL.Query().Get("X-Amz-Security-Token")
|
return r.URL.Query().Get(xhttp.AmzSecurityToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch claims in the security token returned by the client, doesn't return
|
// Fetch claims in the security token returned by the client, doesn't return
|
||||||
@@ -200,6 +201,39 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
|
|||||||
if _, ok = v.(string); !ok {
|
if _, ok = v.(string); !ok {
|
||||||
return nil, errInvalidAccessKeyID
|
return nil, errInvalidAccessKeyID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalPolicyOPA == nil {
|
||||||
|
// If OPA is not set, session token should
|
||||||
|
// have a policy and its mandatory, reject
|
||||||
|
// requests without policy claim.
|
||||||
|
p, pok := claims[iampolicy.PolicyName]
|
||||||
|
if !pok {
|
||||||
|
return nil, errAuthentication
|
||||||
|
}
|
||||||
|
if _, pok = p.(string); !pok {
|
||||||
|
return nil, errAuthentication
|
||||||
|
}
|
||||||
|
sp, spok := claims[iampolicy.SessionPolicyName]
|
||||||
|
// Sub policy is optional, if not set return success.
|
||||||
|
if !spok {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
// Sub policy is set but its not a string, reject such requests
|
||||||
|
spStr, spok := sp.(string)
|
||||||
|
if !spok {
|
||||||
|
return nil, errAuthentication
|
||||||
|
}
|
||||||
|
// Looks like subpolicy is set and is a string, if set then its
|
||||||
|
// base64 encoded, decode it. Decoding fails reject such requests.
|
||||||
|
spBytes, err := base64.StdEncoding.DecodeString(spStr)
|
||||||
|
if err != nil {
|
||||||
|
// Base64 decoding fails, we should log to indicate
|
||||||
|
// something is malforming the request sent by client.
|
||||||
|
logger.LogIf(context.Background(), err)
|
||||||
|
return nil, errAuthentication
|
||||||
|
}
|
||||||
|
claims[iampolicy.SessionPolicyName] = string(spBytes)
|
||||||
|
}
|
||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,8 +371,8 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
contentMD5, contentSHA256 []byte
|
contentMD5, contentSHA256 []byte
|
||||||
)
|
)
|
||||||
// Extract 'Content-Md5' if present.
|
// Extract 'Content-Md5' if present.
|
||||||
if _, ok := r.Header["Content-Md5"]; ok {
|
if _, ok := r.Header[xhttp.ContentMD5]; ok {
|
||||||
contentMD5, err = base64.StdEncoding.Strict().DecodeString(r.Header.Get("Content-Md5"))
|
contentMD5, err = base64.StdEncoding.Strict().DecodeString(r.Header.Get(xhttp.ContentMD5))
|
||||||
if err != nil || len(contentMD5) == 0 {
|
if err != nil || len(contentMD5) == 0 {
|
||||||
return ErrInvalidDigest
|
return ErrInvalidDigest
|
||||||
}
|
}
|
||||||
@@ -347,14 +381,14 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
|
// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
|
||||||
// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
|
// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
|
||||||
if skipSHA256 := skipContentSha256Cksum(r); !skipSHA256 && isRequestPresignedSignatureV4(r) {
|
if skipSHA256 := skipContentSha256Cksum(r); !skipSHA256 && isRequestPresignedSignatureV4(r) {
|
||||||
if sha256Sum, ok := r.URL.Query()["X-Amz-Content-Sha256"]; ok && len(sha256Sum) > 0 {
|
if sha256Sum, ok := r.URL.Query()[xhttp.AmzContentSha256]; ok && len(sha256Sum) > 0 {
|
||||||
contentSHA256, err = hex.DecodeString(sha256Sum[0])
|
contentSHA256, err = hex.DecodeString(sha256Sum[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrContentSHA256Mismatch
|
return ErrContentSHA256Mismatch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if _, ok := r.Header["X-Amz-Content-Sha256"]; !skipSHA256 && ok {
|
} else if _, ok := r.Header[xhttp.AmzContentSha256]; !skipSHA256 && ok {
|
||||||
contentSHA256, err = hex.DecodeString(r.Header.Get("X-Amz-Content-Sha256"))
|
contentSHA256, err = hex.DecodeString(r.Header.Get(xhttp.AmzContentSha256))
|
||||||
if err != nil || len(contentSHA256) == 0 {
|
if err != nil || len(contentSHA256) == 0 {
|
||||||
return ErrContentSHA256Mismatch
|
return ErrContentSHA256Mismatch
|
||||||
}
|
}
|
||||||
@@ -362,7 +396,8 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
|
|
||||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||||
// The verification happens implicit during reading.
|
// The verification happens implicit during reading.
|
||||||
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5), hex.EncodeToString(contentSHA256), -1, globalCLIContext.StrictS3Compat)
|
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5),
|
||||||
|
hex.EncodeToString(contentSHA256), -1, globalCLIContext.StrictS3Compat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// healTask represents what to heal along with options
|
||||||
|
// path: '/' => Heal disk formats along with metadata
|
||||||
|
// path: 'bucket/' or '/bucket/' => Heal bucket
|
||||||
|
// path: 'bucket/object' => Heal object
|
||||||
|
type healTask struct {
|
||||||
|
path string
|
||||||
|
opts madmin.HealOpts
|
||||||
|
// Healing response will be sent here
|
||||||
|
responseCh chan healResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// healResult represents a healing result with a possible error
|
||||||
|
type healResult struct {
|
||||||
|
result madmin.HealResultItem
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// healRoutine receives heal tasks, to heal buckets, objects and format.json
|
||||||
|
type healRoutine struct {
|
||||||
|
tasks chan healTask
|
||||||
|
doneCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new task in the tasks queue
|
||||||
|
func (h *healRoutine) queueHealTask(task healTask) {
|
||||||
|
h.tasks <- task
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for heal requests and process them
|
||||||
|
func (h *healRoutine) run() {
|
||||||
|
ctx := context.Background()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case task, ok := <-h.tasks:
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if globalHTTPServer != nil {
|
||||||
|
// Wait at max 1 minute for an inprogress request
|
||||||
|
// before proceeding to heal
|
||||||
|
waitCount := 60
|
||||||
|
// Any requests in progress, delay the heal.
|
||||||
|
for globalHTTPServer.GetRequestCount() > 2 && waitCount > 0 {
|
||||||
|
waitCount--
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res madmin.HealResultItem
|
||||||
|
var err error
|
||||||
|
bucket, object := urlPath2BucketObjectName(task.path)
|
||||||
|
switch {
|
||||||
|
case bucket == "" && object == "":
|
||||||
|
res, err = bgHealDiskFormat(ctx, task.opts)
|
||||||
|
case bucket != "" && object == "":
|
||||||
|
res, err = bgHealBucket(ctx, bucket, task.opts)
|
||||||
|
case bucket != "" && object != "":
|
||||||
|
res, err = bgHealObject(ctx, bucket, object, task.opts)
|
||||||
|
}
|
||||||
|
task.responseCh <- healResult{result: res, err: err}
|
||||||
|
case <-h.doneCh:
|
||||||
|
return
|
||||||
|
case <-GlobalServiceDoneCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initHealRoutine() *healRoutine {
|
||||||
|
return &healRoutine{
|
||||||
|
tasks: make(chan healTask),
|
||||||
|
doneCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func initBackgroundHealing() {
|
||||||
|
healBg := initHealRoutine()
|
||||||
|
go healBg.run()
|
||||||
|
|
||||||
|
globalBackgroundHealing = healBg
|
||||||
|
}
|
||||||
|
|
||||||
|
// bgHealDiskFormat - heals format.json, return value indicates if a
|
||||||
|
// failure error occurred.
|
||||||
|
func bgHealDiskFormat(ctx context.Context, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return madmin.HealResultItem{}, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := objectAPI.HealFormat(ctx, opts.DryRun)
|
||||||
|
|
||||||
|
// return any error, ignore error returned when disks have
|
||||||
|
// already healed.
|
||||||
|
if err != nil && err != errNoHealRequired {
|
||||||
|
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 {
|
||||||
|
for _, nerr := range globalNotificationSys.ReloadFormat(opts.DryRun) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bghealBucket - traverses and heals given bucket
|
||||||
|
func bgHealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return madmin.HealResultItem{}, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectAPI.HealBucket(ctx, bucket, opts.DryRun, opts.Remove)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bgHealObject - heal the given object and record result
|
||||||
|
func bgHealObject(ctx context.Context, bucket, object string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return madmin.HealResultItem{}, errServerNotInitialized
|
||||||
|
}
|
||||||
|
return objectAPI.HealObject(ctx, bucket, object, opts.DryRun, opts.Remove, opts.ScanMode)
|
||||||
|
}
|
||||||
@@ -131,7 +131,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
b.h.Write(buf)
|
b.h.Write(buf)
|
||||||
|
|
||||||
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
||||||
err = hashMismatchError{hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))}
|
err = HashMismatchError{hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))}
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(context.Background(), err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,33 +155,3 @@ func bitrotWriterSum(w io.Writer) []byte {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if a file has bitrot error.
|
|
||||||
func bitrotCheckFile(disk StorageAPI, volume string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) (err error) {
|
|
||||||
if algo != HighwayHash256S {
|
|
||||||
buf := []byte{}
|
|
||||||
// For whole-file bitrot we don't need to read the entire file as the bitrot verify happens on the server side even if we read 0-bytes.
|
|
||||||
_, err = disk.ReadFile(volume, filePath, 0, buf, NewBitrotVerifier(algo, sum))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf := make([]byte, shardSize)
|
|
||||||
r := newStreamingBitrotReader(disk, volume, filePath, tillOffset, algo, shardSize)
|
|
||||||
defer closeBitrotReaders([]io.ReaderAt{r})
|
|
||||||
var offset int64
|
|
||||||
for {
|
|
||||||
if offset == tillOffset {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var n int
|
|
||||||
tmpBuf := buf
|
|
||||||
if int64(len(tmpBuf)) > (tillOffset - offset) {
|
|
||||||
tmpBuf = tmpBuf[:(tillOffset - offset)]
|
|
||||||
}
|
|
||||||
n, err = r.ReadAt(tmpBuf, offset)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
offset += int64(n)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -46,13 +46,6 @@ func validateListObjectsArgs(prefix, marker, delimiter, encodingType string, max
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MinIO special conditions for ListObjects.
|
|
||||||
|
|
||||||
// Verify if delimiter is anything other than '/', which we do not support.
|
|
||||||
if delimiter != "" && delimiter != "/" {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
// Success.
|
|
||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -30,8 +30,9 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/dns"
|
"github.com/minio/minio/pkg/dns"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
@@ -445,7 +446,8 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to add Location information here only for bucket
|
// Make sure to add Location information here only for bucket
|
||||||
w.Header().Set("Location", getObjectLocation(r, globalDomainNames, bucket, ""))
|
w.Header().Set(xhttp.Location,
|
||||||
|
getObjectLocation(r, globalDomainNames, bucket, ""))
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
return
|
return
|
||||||
@@ -466,7 +468,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to add Location information here only for bucket
|
// Make sure to add Location information here only for bucket
|
||||||
w.Header().Set("Location", path.Clean(r.URL.Path)) // Clean any trailing slashes.
|
w.Header().Set(xhttp.Location, path.Clean(r.URL.Path)) // Clean any trailing slashes.
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
@@ -498,6 +500,9 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
bucket := mux.Vars(r)["bucket"]
|
bucket := mux.Vars(r)["bucket"]
|
||||||
|
|
||||||
|
// To detect if the client has disconnected.
|
||||||
|
r.Body = &detectDisconnect{r.Body, r.Context().Done()}
|
||||||
|
|
||||||
// Require Content-Length to be set in the request
|
// Require Content-Length to be set in the request
|
||||||
size := r.ContentLength
|
size := r.ContentLength
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
@@ -532,7 +537,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all tmp files creating during multipart upload
|
// Remove all tmp files created during multipart upload
|
||||||
defer form.RemoveAll()
|
defer form.RemoveAll()
|
||||||
|
|
||||||
// Extract all form fields
|
// Extract all form fields
|
||||||
@@ -678,8 +683,8 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
location := getObjectLocation(r, globalDomainNames, bucket, object)
|
location := getObjectLocation(r, globalDomainNames, bucket, object)
|
||||||
w.Header()["ETag"] = []string{`"` + objInfo.ETag + `"`}
|
w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
|
||||||
w.Header().Set("Location", location)
|
w.Header().Set(xhttp.Location, location)
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
defer sendEvent(eventArgs{
|
defer sendEvent(eventArgs{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/event/target"
|
"github.com/minio/minio/pkg/event/target"
|
||||||
@@ -246,6 +247,8 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||||
|
|
||||||
target, err := target.NewHTTPClientTarget(*host, w)
|
target, err := target.NewHTTPClientTarget(*host, w)
|
||||||
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))
|
||||||
|
|||||||
+1
-8
@@ -29,7 +29,7 @@ import (
|
|||||||
etcd "github.com/coreos/etcd/clientv3"
|
etcd "github.com/coreos/etcd/clientv3"
|
||||||
dns2 "github.com/miekg/dns"
|
dns2 "github.com/miekg/dns"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/cmd/logger/target/console"
|
"github.com/minio/minio/cmd/logger/target/console"
|
||||||
"github.com/minio/minio/cmd/logger/target/http"
|
"github.com/minio/minio/cmd/logger/target/http"
|
||||||
@@ -211,13 +211,6 @@ func handleCommonEnvVars() {
|
|||||||
globalIsBrowserEnabled = bool(browserFlag)
|
globalIsBrowserEnabled = bool(browserFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
traceFile := os.Getenv("MINIO_HTTP_TRACE")
|
|
||||||
if traceFile != "" {
|
|
||||||
var err error
|
|
||||||
globalHTTPTraceFile, err = os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
|
|
||||||
logger.FatalIf(err, "error opening file %s", traceFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
etcdEndpointsEnv, ok := os.LookupEnv("MINIO_ETCD_ENDPOINTS")
|
etcdEndpointsEnv, ok := os.LookupEnv("MINIO_ETCD_ENDPOINTS")
|
||||||
if ok {
|
if ok {
|
||||||
etcdEndpoints := strings.Split(etcdEndpointsEnv, ",")
|
etcdEndpoints := strings.Split(etcdEndpointsEnv, ",")
|
||||||
|
|||||||
+17
-52
@@ -21,7 +21,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
etcd "github.com/coreos/etcd/clientv3"
|
etcd "github.com/coreos/etcd/clientv3"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -53,8 +52,19 @@ func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteConfigEtcd(ctx context.Context, client *etcd.Client, configFile string) error {
|
func deleteConfigEtcd(ctx context.Context, client *etcd.Client, configFile string) error {
|
||||||
_, err := client.Delete(ctx, configFile)
|
timeoutCtx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
|
||||||
return err
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := client.Delete(timeoutCtx, configFile)
|
||||||
|
if err != nil {
|
||||||
|
if err == context.DeadlineExceeded {
|
||||||
|
return fmt.Errorf("etcd setup is unreachable, please check your endpoints %s",
|
||||||
|
client.Endpoints())
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected error %s returned by etcd setup, please check your endpoints %s",
|
||||||
|
err, client.Endpoints())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
|
func deleteConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
|
||||||
@@ -89,9 +99,11 @@ func readConfigEtcd(ctx context.Context, client *etcd.Client, configFile string)
|
|||||||
resp, err := client.Get(timeoutCtx, configFile)
|
resp, err := client.Get(timeoutCtx, configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == context.DeadlineExceeded {
|
if err == context.DeadlineExceeded {
|
||||||
return nil, fmt.Errorf("etcd setup is unreachable, please check your endpoints %s", client.Endpoints())
|
return nil, fmt.Errorf("etcd setup is unreachable, please check your endpoints %s",
|
||||||
|
client.Endpoints())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected error %s returned by etcd setup, please check your endpoints %s", err, client.Endpoints())
|
return nil, fmt.Errorf("unexpected error %s returned by etcd setup, please check your endpoints %s",
|
||||||
|
err, client.Endpoints())
|
||||||
}
|
}
|
||||||
if resp.Count == 0 {
|
if resp.Count == 0 {
|
||||||
return nil, errConfigNotFound
|
return nil, errConfigNotFound
|
||||||
@@ -104,54 +116,7 @@ func readConfigEtcd(ctx context.Context, client *etcd.Client, configFile string)
|
|||||||
return nil, errConfigNotFound
|
return nil, errConfigNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// watchConfigEtcd - watches for changes on `configFile` on etcd and loads them.
|
|
||||||
func watchConfigEtcd(objAPI ObjectLayer, configFile string, loadCfgFn func(ObjectLayer) error) {
|
|
||||||
for {
|
|
||||||
watchCh := globalEtcdClient.Watch(context.Background(), iamConfigPrefix)
|
|
||||||
select {
|
|
||||||
case <-GlobalServiceDoneCh:
|
|
||||||
return
|
|
||||||
case watchResp, ok := <-watchCh:
|
|
||||||
if !ok {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := watchResp.Err(); err != nil {
|
|
||||||
logger.LogIf(context.Background(), err)
|
|
||||||
// log and retry.
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, event := range watchResp.Events {
|
|
||||||
if event.IsModify() || event.IsCreate() {
|
|
||||||
loadCfgFn(objAPI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkConfigEtcd(ctx context.Context, client *etcd.Client, configFile string) error {
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
|
|
||||||
defer cancel()
|
|
||||||
resp, err := client.Get(timeoutCtx, configFile)
|
|
||||||
if err != nil {
|
|
||||||
if err == context.DeadlineExceeded {
|
|
||||||
return fmt.Errorf("etcd setup is unreachable, please check your endpoints %s", client.Endpoints())
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected error %s returned by etcd setup, please check your endpoints %s", err, client.Endpoints())
|
|
||||||
}
|
|
||||||
if resp.Count == 0 {
|
|
||||||
return errConfigNotFound
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
|
func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
|
||||||
if globalEtcdClient != nil {
|
|
||||||
return checkConfigEtcd(ctx, globalEtcdClient, configFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil {
|
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil {
|
||||||
// Treat object not found as config not found.
|
// Treat object not found as config not found.
|
||||||
if isErrObjectNotFound(err) {
|
if isErrObjectNotFound(err) {
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ func (s *serverConfig) TestNotificationTargets() error {
|
|||||||
if !v.Enable {
|
if !v.Enable {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t, err := target.NewElasticsearchTarget(k, v)
|
t, err := target.NewElasticsearchTarget(k, v, GlobalServiceDoneCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("elasticsearch(%s): %s", k, err.Error())
|
return fmt.Errorf("elasticsearch(%s): %s", k, err.Error())
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ func (s *serverConfig) TestNotificationTargets() error {
|
|||||||
if !v.Enable {
|
if !v.Enable {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t, err := target.NewKafkaTarget(k, v)
|
t, err := target.NewKafkaTarget(k, v, GlobalServiceDoneCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("kafka(%s): %s", k, err.Error())
|
return fmt.Errorf("kafka(%s): %s", k, err.Error())
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ func (s *serverConfig) TestNotificationTargets() error {
|
|||||||
if !v.Enable {
|
if !v.Enable {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t, err := target.NewNSQTarget(k, v)
|
t, err := target.NewNSQTarget(k, v, GlobalServiceDoneCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("nsq(%s): %s", k, err.Error())
|
return fmt.Errorf("nsq(%s): %s", k, err.Error())
|
||||||
}
|
}
|
||||||
@@ -651,7 +651,7 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
|
|||||||
|
|
||||||
for id, args := range config.Notify.Elasticsearch {
|
for id, args := range config.Notify.Elasticsearch {
|
||||||
if args.Enable {
|
if args.Enable {
|
||||||
newTarget, err := target.NewElasticsearchTarget(id, args)
|
newTarget, err := target.NewElasticsearchTarget(id, args, GlobalServiceDoneCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(context.Background(), err)
|
||||||
continue
|
continue
|
||||||
@@ -667,7 +667,7 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
|
|||||||
|
|
||||||
for id, args := range config.Notify.Kafka {
|
for id, args := range config.Notify.Kafka {
|
||||||
if args.Enable {
|
if args.Enable {
|
||||||
newTarget, err := target.NewKafkaTarget(id, args)
|
newTarget, err := target.NewKafkaTarget(id, args, GlobalServiceDoneCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(context.Background(), err)
|
||||||
continue
|
continue
|
||||||
@@ -724,7 +724,7 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
|
|||||||
|
|
||||||
for id, args := range config.Notify.NSQ {
|
for id, args := range config.Notify.NSQ {
|
||||||
if args.Enable {
|
if args.Enable {
|
||||||
newTarget, err := target.NewNSQTarget(id, args)
|
newTarget, err := target.NewNSQTarget(id, args, GlobalServiceDoneCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(context.Background(), err)
|
||||||
continue
|
continue
|
||||||
@@ -767,7 +767,7 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
|
|||||||
for id, args := range config.Notify.Webhook {
|
for id, args := range config.Notify.Webhook {
|
||||||
if args.Enable {
|
if args.Enable {
|
||||||
args.RootCAs = globalRootCAs
|
args.RootCAs = globalRootCAs
|
||||||
newTarget := target.NewWebhookTarget(id, args)
|
newTarget := target.NewWebhookTarget(id, args, GlobalServiceDoneCh)
|
||||||
if err := targetList.Add(newTarget); err != nil {
|
if err := targetList.Add(newTarget); err != nil {
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(context.Background(), err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -200,10 +200,10 @@ func TestValidateConfig(t *testing.T) {
|
|||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "postgresql": { "1": { "enable": true, "connectionString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "postgresql": { "1": { "enable": true, "connectionString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false},
|
||||||
|
|
||||||
// Test 16 - Test Kafka
|
// Test 16 - Test Kafka
|
||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "kafka": { "1": { "enable": true, "brokers": null, "topic": "" } }}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "kafka": { "1": { "enable": true, "brokers": null, "topic": "", "queueDir": "", "queueLimit": 0 } }}}`, false},
|
||||||
|
|
||||||
// Test 17 - Test Webhook
|
// Test 17 - Test Webhook
|
||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "webhook": { "1": { "enable": true, "endpoint": "" } }}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "webhook": { "1": { "enable": true, "endpoint": "", "queueDir": "", "queueLimit": 0} }}}`, false},
|
||||||
|
|
||||||
// Test 18 - Test MySQL
|
// Test 18 - Test MySQL
|
||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mysql": { "1": { "enable": true, "dsnString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mysql": { "1": { "enable": true, "dsnString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false},
|
||||||
@@ -224,7 +224,7 @@ func TestValidateConfig(t *testing.T) {
|
|||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "invalid", "url": "example.com", "index": "myindex" } }}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "invalid", "url": "example.com", "index": "myindex" } }}}`, false},
|
||||||
|
|
||||||
// Test 24 - Test valid Format for ElasticSearch
|
// Test 24 - Test valid Format for ElasticSearch
|
||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "namespace", "url": "example.com", "index": "myindex" } }}}`, true},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "namespace", "url": "example.com", "index": "myindex", "queueDir": "", "queueLimit": 0 } }}}`, true},
|
||||||
|
|
||||||
// Test 25 - Test Format for Redis
|
// Test 25 - Test Format for Redis
|
||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "format": "invalid", "address": "example.com:80", "password": "xxx", "key": "key1" } }}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "format": "invalid", "address": "example.com:80", "password": "xxx", "key": "key1" } }}}`, false},
|
||||||
@@ -236,7 +236,7 @@ func TestValidateConfig(t *testing.T) {
|
|||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mqtt": { "1": { "enable": true, "broker": "", "topic": "", "qos": 0, "username": "", "password": "", "queueDir": "", "queueLimit": 0}}}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mqtt": { "1": { "enable": true, "broker": "", "topic": "", "qos": 0, "username": "", "password": "", "queueDir": "", "queueLimit": 0}}}}`, false},
|
||||||
|
|
||||||
// Test 28 - Test NSQ
|
// Test 28 - Test NSQ
|
||||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "nsq": { "1": { "enable": true, "nsqdAddress": "", "topic": ""} }}}`, false},
|
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "nsq": { "1": { "enable": true, "nsqdAddress": "", "topic": "", "queueDir": "", "queueLimit": 0} }}}`, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
|
|||||||
+24
-15
@@ -29,7 +29,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/event/target"
|
"github.com/minio/minio/pkg/event/target"
|
||||||
"github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/iam/validator"
|
"github.com/minio/minio/pkg/iam/validator"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"github.com/minio/minio/pkg/quick"
|
"github.com/minio/minio/pkg/quick"
|
||||||
@@ -2413,6 +2413,7 @@ func migrateV27ToV28() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json'
|
// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json'
|
||||||
|
// if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json'
|
||||||
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
||||||
// Construct path to config.json for the given bucket.
|
// Construct path to config.json for the given bucket.
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
@@ -2423,10 +2424,14 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Rename config.json to config.json.deprecated only upon
|
|
||||||
// success of this function.
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
os.Rename(getConfigFile(), getConfigFile()+".deprecated")
|
if globalEtcdClient != nil {
|
||||||
|
deleteConfigEtcd(context.Background(), globalEtcdClient, configFile)
|
||||||
|
} else {
|
||||||
|
// Rename config.json to config.json.deprecated only upon
|
||||||
|
// success of this function.
|
||||||
|
os.Rename(getConfigFile(), getConfigFile()+".deprecated")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -2435,7 +2440,7 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
|||||||
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
|
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
|
||||||
// and configFile, take a transaction lock to avoid data race between readConfig()
|
// and configFile, take a transaction lock to avoid data race between readConfig()
|
||||||
// and saveConfig().
|
// and saveConfig().
|
||||||
objLock := globalNSMutex.NewNSLock(minioMetaBucket, transactionConfigFile)
|
objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigFile)
|
||||||
if err = objLock.GetLock(globalOperationTimeout); err != nil {
|
if err = objLock.GetLock(globalOperationTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2446,20 +2451,24 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
|||||||
return err
|
return err
|
||||||
} // if errConfigNotFound proceed to migrate..
|
} // if errConfigNotFound proceed to migrate..
|
||||||
|
|
||||||
|
var configFiles = []string{
|
||||||
|
getConfigFile(),
|
||||||
|
getConfigFile() + ".deprecated",
|
||||||
|
configFile,
|
||||||
|
}
|
||||||
var config = &serverConfig{}
|
var config = &serverConfig{}
|
||||||
if _, err = Load(getConfigFile(), config); err != nil {
|
for _, cfgFile := range configFiles {
|
||||||
if !os.IsNotExist(err) {
|
if _, err = Load(cfgFile, config); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Read from deprecate file as well if necessary.
|
|
||||||
if _, err = Load(getConfigFile()+".deprecated", config); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If all else fails simply initialize the server config.
|
continue
|
||||||
return newSrvConfig(objAPI)
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Initialize the server config, if no config exists.
|
||||||
|
return newSrvConfig(objAPI)
|
||||||
}
|
}
|
||||||
return saveServerConfig(context.Background(), objAPI, config)
|
return saveServerConfig(context.Background(), objAPI, config)
|
||||||
}
|
}
|
||||||
@@ -2483,7 +2492,7 @@ func migrateMinioSysConfig(objAPI ObjectLayer) error {
|
|||||||
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
|
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
|
||||||
// and configFile, take a transaction lock to avoid data race between readConfig()
|
// and configFile, take a transaction lock to avoid data race between readConfig()
|
||||||
// and saveConfig().
|
// and saveConfig().
|
||||||
objLock := globalNSMutex.NewNSLock(minioMetaBucket, transactionConfigFile)
|
objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigFile)
|
||||||
if err := objLock.GetLock(globalOperationTimeout); err != nil {
|
if err := objLock.GetLock(globalOperationTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/event/target"
|
"github.com/minio/minio/pkg/event/target"
|
||||||
"github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/iam/validator"
|
"github.com/minio/minio/pkg/iam/validator"
|
||||||
"github.com/minio/minio/pkg/quick"
|
"github.com/minio/minio/pkg/quick"
|
||||||
)
|
)
|
||||||
@@ -884,7 +884,7 @@ type serverConfigV32 struct {
|
|||||||
} `json:"policy"`
|
} `json:"policy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit with MQTT.
|
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in all notification targets.
|
||||||
type serverConfigV33 struct {
|
type serverConfigV33 struct {
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
quick.Config `json:"-"` // ignore interfaces
|
||||||
|
|
||||||
|
|||||||
+17
-48
@@ -49,10 +49,6 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config *serverCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
if globalEtcdClient != nil {
|
|
||||||
return saveConfigEtcd(ctx, globalEtcdClient, configFile, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a backup of the current config
|
// Create a backup of the current config
|
||||||
oldData, err := readConfig(ctx, objAPI, configFile)
|
oldData, err := readConfig(ctx, objAPI, configFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -71,15 +67,8 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config *serverCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readServerConfig(ctx context.Context, objAPI ObjectLayer) (*serverConfig, error) {
|
func readServerConfig(ctx context.Context, objAPI ObjectLayer) (*serverConfig, error) {
|
||||||
var configData []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
if globalEtcdClient != nil {
|
configData, err := readConfig(ctx, objAPI, configFile)
|
||||||
configData, err = readConfigEtcd(ctx, globalEtcdClient, configFile)
|
|
||||||
} else {
|
|
||||||
configData, err = readConfig(ctx, objAPI, configFile)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -152,45 +141,25 @@ func initConfig(objAPI ObjectLayer) error {
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
if isFile(getConfigFile()) {
|
||||||
|
if err := migrateConfig(); err != nil {
|
||||||
if globalEtcdClient != nil {
|
|
||||||
if err := checkConfigEtcd(context.Background(), globalEtcdClient, getConfigFile()); err != nil {
|
|
||||||
if err == errConfigNotFound {
|
|
||||||
// Migrates all configs at old location.
|
|
||||||
if err = migrateConfig(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Migrates etcd ${HOME}/.minio/config.json to '/config/config.json'
|
|
||||||
if err = migrateConfigToMinioSys(objAPI); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch config for changes and reloads them.
|
|
||||||
go watchConfigEtcd(objAPI, configFile, loadConfig)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if isFile(getConfigFile()) {
|
|
||||||
if err := migrateConfig(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
|
|
||||||
// to '<export_path>/.minio.sys/config/config.json'
|
|
||||||
// ignore if the file doesn't exist.
|
|
||||||
if err := migrateConfigToMinioSys(objAPI); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrates backend '<export_path>/.minio.sys/config/config.json' to latest version.
|
|
||||||
if err := migrateMinioSysConfig(objAPI); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
|
||||||
|
// to '<export_path>/.minio.sys/config/config.json'
|
||||||
|
// ignore if the file doesn't exist.
|
||||||
|
// If etcd is set then migrates /config/config.json
|
||||||
|
// to '<export_path>/.minio.sys/config/config.json'
|
||||||
|
if err := migrateConfigToMinioSys(objAPI); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrates backend '<export_path>/.minio.sys/config/config.json' to latest version.
|
||||||
|
if err := migrateMinioSysConfig(objAPI); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return loadConfig(objAPI)
|
return loadConfig(objAPI)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -125,6 +126,25 @@ func (s3KMS) IsRequested(h http.Header) bool {
|
|||||||
return false
|
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{}
|
||||||
|
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
|
// SSEC represents AWS SSE-C. It provides functionality to handle
|
||||||
// SSE-C requests.
|
// SSE-C requests.
|
||||||
|
|||||||
@@ -54,6 +54,56 @@ func TestKMSIsRequested(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var kmsParseHTTPTests = []struct {
|
||||||
|
Header http.Header
|
||||||
|
ShouldFail bool
|
||||||
|
}{
|
||||||
|
{Header: http.Header{}, ShouldFail: true}, // 0
|
||||||
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"aws:kms"}}, ShouldFail: false}, // 1
|
||||||
|
{Header: http.Header{
|
||||||
|
"X-Amz-Server-Side-Encryption": []string{"aws:kms"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"},
|
||||||
|
}, ShouldFail: false}, // 2
|
||||||
|
{Header: http.Header{
|
||||||
|
"X-Amz-Server-Side-Encryption": []string{"aws:kms"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Context": []string{"{}"},
|
||||||
|
}, ShouldFail: false}, // 3
|
||||||
|
{Header: http.Header{
|
||||||
|
"X-Amz-Server-Side-Encryption": []string{"aws:kms"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"},
|
||||||
|
}, ShouldFail: false}, // 4
|
||||||
|
{Header: http.Header{
|
||||||
|
"X-Amz-Server-Side-Encryption": []string{"aws:kms"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"},
|
||||||
|
}, ShouldFail: false}, // 5
|
||||||
|
{Header: http.Header{
|
||||||
|
"X-Amz-Server-Side-Encryption": []string{"AES256"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"},
|
||||||
|
}, ShouldFail: true}, // 6
|
||||||
|
{Header: http.Header{
|
||||||
|
"X-Amz-Server-Side-Encryption": []string{"aws:kms"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"},
|
||||||
|
"X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\""}, // invalid JSON
|
||||||
|
}, ShouldFail: true}, // 7
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKMSParseHTTP(t *testing.T) {
|
||||||
|
for i, test := range kmsParseHTTPTests {
|
||||||
|
_, _, err := S3KMS.ParseHTTP(test.Header)
|
||||||
|
if err == nil && test.ShouldFail {
|
||||||
|
t.Errorf("Test %d: should fail but succeeded", i)
|
||||||
|
}
|
||||||
|
if err != nil && !test.ShouldFail {
|
||||||
|
t.Errorf("Test %d: should pass but failed with: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var s3IsRequestedTests = []struct {
|
var s3IsRequestedTests = []struct {
|
||||||
Header http.Header
|
Header http.Header
|
||||||
Expected bool
|
Expected bool
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bgHealingUUID = "0000-0000-0000-0000"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBgHealSequence creates a background healing sequence
|
||||||
|
// operation which crawls all objects and heal them.
|
||||||
|
func newBgHealSequence(numDisks int) *healSequence {
|
||||||
|
|
||||||
|
reqInfo := &logger.ReqInfo{API: "BackgroundHeal"}
|
||||||
|
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
||||||
|
|
||||||
|
hs := madmin.HealOpts{
|
||||||
|
// Remove objects that do not have read-quorum
|
||||||
|
Remove: true,
|
||||||
|
ScanMode: madmin.HealNormalScan,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &healSequence{
|
||||||
|
sourceCh: make(chan string),
|
||||||
|
startTime: UTCNow(),
|
||||||
|
clientToken: bgHealingUUID,
|
||||||
|
settings: hs,
|
||||||
|
currentStatus: healSequenceStatus{
|
||||||
|
Summary: healNotStartedStatus,
|
||||||
|
HealSettings: hs,
|
||||||
|
NumDisks: numDisks,
|
||||||
|
updateLock: &sync.RWMutex{},
|
||||||
|
},
|
||||||
|
traverseAndHealDoneCh: make(chan error),
|
||||||
|
stopSignalCh: make(chan struct{}),
|
||||||
|
ctx: ctx,
|
||||||
|
reportProgress: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalBackgroundHealStatus() madmin.BgHealState {
|
||||||
|
backgroundSequence, ok := globalSweepHealState.getHealSequenceByToken(bgHealingUUID)
|
||||||
|
if !ok {
|
||||||
|
return madmin.BgHealState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return madmin.BgHealState{
|
||||||
|
ScannedItemsCount: backgroundSequence.scannedItemsCount,
|
||||||
|
LastHealActivity: backgroundSequence.lastHealActivity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDailyHeal() {
|
||||||
|
go startDailyHeal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func startDailyHeal() {
|
||||||
|
var objAPI ObjectLayer
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
|
// Wait until the object API is ready
|
||||||
|
for {
|
||||||
|
objAPI = newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find number of disks in the setup
|
||||||
|
info := objAPI.StorageInfo(ctx)
|
||||||
|
numDisks := info.Backend.OnlineDisks + info.Backend.OfflineDisks
|
||||||
|
|
||||||
|
nh := newBgHealSequence(numDisks)
|
||||||
|
globalSweepHealState.LaunchNewHealSequence(nh)
|
||||||
|
|
||||||
|
registerDailySweepListener(nh.sourceCh)
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The list of modules listening for the daily listing of all objects
|
||||||
|
// such as the daily heal ops, disk usage and bucket lifecycle management.
|
||||||
|
var globalDailySweepListeners = make([]chan string, 0)
|
||||||
|
var globalDailySweepListenersMu = sync.Mutex{}
|
||||||
|
|
||||||
|
// Add a new listener to the daily objects listing
|
||||||
|
func registerDailySweepListener(ch chan string) {
|
||||||
|
globalDailySweepListenersMu.Lock()
|
||||||
|
defer globalDailySweepListenersMu.Unlock()
|
||||||
|
|
||||||
|
globalDailySweepListeners = append(globalDailySweepListeners, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe copy of globalDailySweepListeners content
|
||||||
|
func copyDailySweepListeners() []chan string {
|
||||||
|
globalDailySweepListenersMu.Lock()
|
||||||
|
defer globalDailySweepListenersMu.Unlock()
|
||||||
|
|
||||||
|
var listenersCopy = make([]chan string, len(globalDailySweepListeners))
|
||||||
|
copy(listenersCopy, globalDailySweepListeners)
|
||||||
|
|
||||||
|
return listenersCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// sweepRound will list all objects, having read quorum or not and
|
||||||
|
// feeds to all listeners, such as the background healing
|
||||||
|
func sweepRound(ctx context.Context, objAPI ObjectLayer) error {
|
||||||
|
zeroDuration := time.Millisecond
|
||||||
|
zeroDynamicTimeout := newDynamicTimeout(zeroDuration, zeroDuration)
|
||||||
|
|
||||||
|
// General lock so we avoid parallel daily sweep by different instances.
|
||||||
|
sweepLock := globalNSMutex.NewNSLock(ctx, "system", "daily-sweep")
|
||||||
|
if err := sweepLock.GetLock(zeroDynamicTimeout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sweepLock.Unlock()
|
||||||
|
|
||||||
|
buckets, err := objAPI.ListBuckets(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all objects, having read quorum or not in all buckets
|
||||||
|
// and send them to all the registered sweep listeners
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
// Send bucket names to all listeners
|
||||||
|
for _, l := range copyDailySweepListeners() {
|
||||||
|
l <- bucket.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
marker := ""
|
||||||
|
for {
|
||||||
|
res, err := objAPI.ListObjectsHeal(ctx, bucket.Name, "", marker, "", 1000)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, obj := range res.Objects {
|
||||||
|
for _, l := range copyDailySweepListeners() {
|
||||||
|
l <- pathJoin(bucket.Name, obj.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !res.IsTruncated {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
marker = res.NextMarker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDailySweeper creates a go-routine which will list all
|
||||||
|
// objects in all buckets in a daily basis
|
||||||
|
func initDailySweeper() {
|
||||||
|
go dailySweeper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all objects in all buckets in a daily basis
|
||||||
|
func dailySweeper() {
|
||||||
|
var lastSweepTime time.Time
|
||||||
|
var objAPI ObjectLayer
|
||||||
|
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
|
// Wait until the object layer is ready
|
||||||
|
for {
|
||||||
|
objAPI = newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a sweep round each month
|
||||||
|
for {
|
||||||
|
if time.Since(lastSweepTime) < 30*24*time.Hour {
|
||||||
|
time.Sleep(time.Hour)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sweepRound(ctx, objAPI)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
// Unable to hold a lock means there is another
|
||||||
|
// instance doing the sweep round
|
||||||
|
case OperationTimedOut:
|
||||||
|
lastSweepTime = time.Now()
|
||||||
|
default:
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastSweepTime = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -303,7 +303,7 @@ func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object
|
|||||||
data := r.Reader
|
data := r.Reader
|
||||||
fs := cfs.FSObjects
|
fs := cfs.FSObjects
|
||||||
// Lock the object.
|
// Lock the object.
|
||||||
objectLock := fs.nsMutex.NewNSLock(bucket, object)
|
objectLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err := objectLock.GetLock(globalObjectTimeout); err != nil {
|
if err := objectLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
return objInfo, err
|
return objInfo, err
|
||||||
}
|
}
|
||||||
@@ -489,7 +489,7 @@ func (cfs *cacheFSObjects) NewMultipartUpload(ctx context.Context, bucket, objec
|
|||||||
// moveBucketToTrash clears cacheFSObjects of bucket contents and moves it to trash folder.
|
// moveBucketToTrash clears cacheFSObjects of bucket contents and moves it to trash folder.
|
||||||
func (cfs *cacheFSObjects) moveBucketToTrash(ctx context.Context, bucket string) (err error) {
|
func (cfs *cacheFSObjects) moveBucketToTrash(ctx context.Context, bucket string) (err error) {
|
||||||
fs := cfs.FSObjects
|
fs := cfs.FSObjects
|
||||||
bucketLock := fs.nsMutex.NewNSLock(bucket, "")
|
bucketLock := fs.nsMutex.NewNSLock(ctx, bucket, "")
|
||||||
if err = bucketLock.GetLock(globalObjectTimeout); err != nil {
|
if err = bucketLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -435,7 +435,7 @@ func (c cacheObjects) listCacheObjects(ctx context.Context, bucket, prefix, mark
|
|||||||
if delimiter == slashSeparator {
|
if delimiter == slashSeparator {
|
||||||
recursive = false
|
recursive = false
|
||||||
}
|
}
|
||||||
walkResultCh, endWalkCh := c.listPool.Release(listParams{bucket, recursive, marker, prefix})
|
walkResultCh, endWalkCh := c.listPool.Release(listParams{bucket, recursive, marker, prefix, false})
|
||||||
if walkResultCh == nil {
|
if walkResultCh == nil {
|
||||||
endWalkCh = make(chan struct{})
|
endWalkCh = make(chan struct{})
|
||||||
|
|
||||||
@@ -494,7 +494,7 @@ func (c cacheObjects) listCacheObjects(ctx context.Context, bucket, prefix, mark
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
params := listParams{bucket, recursive, nextMarker, prefix}
|
params := listParams{bucket, recursive, nextMarker, prefix, false}
|
||||||
if !eof {
|
if !eof {
|
||||||
c.listPool.Set(params, walkResultCh, endWalkCh)
|
c.listPool.Set(params, walkResultCh, endWalkCh)
|
||||||
}
|
}
|
||||||
@@ -945,7 +945,10 @@ func checkAtimeSupport(dir string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err = io.Copy(ioutil.Discard, file); err != io.EOF {
|
// add a sleep to ensure atime change is detected
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
if _, err = io.Copy(ioutil.Discard, file); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-12
@@ -124,17 +124,10 @@ func TestGetCacheFSMaxUse(t *testing.T) {
|
|||||||
|
|
||||||
// test wildcard patterns for excluding entries from cache
|
// test wildcard patterns for excluding entries from cache
|
||||||
func TestCacheExclusion(t *testing.T) {
|
func TestCacheExclusion(t *testing.T) {
|
||||||
fsDirs, err := getRandomDisks(1)
|
cobjects := &cacheObjects{
|
||||||
if err != nil {
|
cache: nil,
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
cconfig := CacheConfig{Expiry: 30, Drives: fsDirs}
|
|
||||||
cobjects, err := newServerCacheObjects(cconfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
cobj := cobjects.(*cacheObjects)
|
|
||||||
GlobalServiceDoneCh <- struct{}{}
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucketName string
|
bucketName string
|
||||||
objectName string
|
objectName string
|
||||||
@@ -155,8 +148,8 @@ func TestCacheExclusion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
cobj.exclude = []string{testCase.excludePattern}
|
cobjects.exclude = []string{testCase.excludePattern}
|
||||||
if cobj.isCacheExclude(testCase.bucketName, testCase.objectName) != testCase.expectedResult {
|
if cobjects.isCacheExclude(testCase.bucketName, testCase.objectName) != testCase.expectedResult {
|
||||||
t.Fatal("Cache exclusion test failed for case ", i)
|
t.Fatal("Cache exclusion test failed for case ", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-1
@@ -29,7 +29,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/encrypt"
|
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/ioutil"
|
"github.com/minio/minio/pkg/ioutil"
|
||||||
@@ -1238,6 +1238,17 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada
|
|||||||
opts.UserDefined = metadata
|
opts.UserDefined = metadata
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if crypto.S3KMS.IsRequested(r.Header) {
|
||||||
|
keyID, context, err := crypto.S3KMS.ParseHTTP(r.Header)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectOptions{}, err
|
||||||
|
}
|
||||||
|
sseKms, err := encrypt.NewSSEKMS(keyID, context)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectOptions{}, err
|
||||||
|
}
|
||||||
|
return ObjectOptions{ServerSideEncryption: sseKms, UserDefined: metadata}, nil
|
||||||
|
}
|
||||||
// default case of passing encryption headers and UserDefined metadata to backend
|
// default case of passing encryption headers and UserDefined metadata to backend
|
||||||
return getDefaultOpts(r.Header, false, metadata)
|
return getDefaultOpts(r.Header, false, metadata)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio-go/pkg/encrypt"
|
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/pkg/ellipses"
|
"github.com/minio/minio/pkg/ellipses"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -213,12 +213,12 @@ func parseEndpointSet(args ...string) (ep endpointSet, err error) {
|
|||||||
return ep, nil
|
return ep, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses all ellipses input arguments, expands them into corresponding
|
// GetAllSets - parses all ellipses input arguments, expands them into
|
||||||
// list of endpoints chunked evenly in accordance with a specific
|
// corresponding list of endpoints chunked evenly in accordance with a
|
||||||
// set size.
|
// specific set size.
|
||||||
// For example: {1...64} is divided into 4 sets each of size 16.
|
// For example: {1...64} is divided into 4 sets each of size 16.
|
||||||
// This applies to even distributed setup syntax as well.
|
// This applies to even distributed setup syntax as well.
|
||||||
func getAllSets(args ...string) ([][]string, error) {
|
func GetAllSets(args ...string) ([][]string, error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return nil, errInvalidArgument
|
return nil, errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ func getAllSets(args ...string) ([][]string, error) {
|
|||||||
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
|
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
|
||||||
// both ellipses and without ellipses transparently.
|
// both ellipses and without ellipses transparently.
|
||||||
func createServerEndpoints(serverAddr string, args ...string) (string, EndpointList, SetupType, int, int, error) {
|
func createServerEndpoints(serverAddr string, args ...string) (string, EndpointList, SetupType, int, int, error) {
|
||||||
setArgs, err := getAllSets(args...)
|
setArgs, err := GetAllSets(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serverAddr, nil, -1, 0, 0, err
|
return serverAddr, nil, -1, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-8
@@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -31,7 +32,7 @@ import (
|
|||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/cpu"
|
"github.com/minio/minio/pkg/cpu"
|
||||||
"github.com/minio/minio/pkg/disk"
|
"github.com/minio/minio/pkg/disk"
|
||||||
@@ -288,7 +289,7 @@ func (endpoints EndpointList) UpdateIsLocal() error {
|
|||||||
|
|
||||||
// localEndpointsMemUsage - returns ServerMemUsageInfo for only the
|
// localEndpointsMemUsage - returns ServerMemUsageInfo for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func localEndpointsMemUsage(endpoints EndpointList) ServerMemUsageInfo {
|
func localEndpointsMemUsage(endpoints EndpointList, r *http.Request) ServerMemUsageInfo {
|
||||||
var memUsages []mem.Usage
|
var memUsages []mem.Usage
|
||||||
var historicUsages []mem.Usage
|
var historicUsages []mem.Usage
|
||||||
scratchSpace := map[string]bool{}
|
scratchSpace := map[string]bool{}
|
||||||
@@ -303,8 +304,12 @@ func localEndpointsMemUsage(endpoints EndpointList) ServerMemUsageInfo {
|
|||||||
scratchSpace[endpoint.Host] = true
|
scratchSpace[endpoint.Host] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addr := r.Host
|
||||||
|
if globalIsDistXL {
|
||||||
|
addr = GetLocalPeer(endpoints)
|
||||||
|
}
|
||||||
return ServerMemUsageInfo{
|
return ServerMemUsageInfo{
|
||||||
Addr: GetLocalPeer(endpoints),
|
Addr: addr,
|
||||||
Usage: memUsages,
|
Usage: memUsages,
|
||||||
HistoricUsage: historicUsages,
|
HistoricUsage: historicUsages,
|
||||||
}
|
}
|
||||||
@@ -312,7 +317,7 @@ func localEndpointsMemUsage(endpoints EndpointList) ServerMemUsageInfo {
|
|||||||
|
|
||||||
// localEndpointsCPULoad - returns ServerCPULoadInfo for only the
|
// localEndpointsCPULoad - returns ServerCPULoadInfo for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func localEndpointsCPULoad(endpoints EndpointList) ServerCPULoadInfo {
|
func localEndpointsCPULoad(endpoints EndpointList, r *http.Request) ServerCPULoadInfo {
|
||||||
var cpuLoads []cpu.Load
|
var cpuLoads []cpu.Load
|
||||||
var historicLoads []cpu.Load
|
var historicLoads []cpu.Load
|
||||||
scratchSpace := map[string]bool{}
|
scratchSpace := map[string]bool{}
|
||||||
@@ -327,8 +332,12 @@ func localEndpointsCPULoad(endpoints EndpointList) ServerCPULoadInfo {
|
|||||||
scratchSpace[endpoint.Host] = true
|
scratchSpace[endpoint.Host] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addr := r.Host
|
||||||
|
if globalIsDistXL {
|
||||||
|
addr = GetLocalPeer(endpoints)
|
||||||
|
}
|
||||||
return ServerCPULoadInfo{
|
return ServerCPULoadInfo{
|
||||||
Addr: GetLocalPeer(endpoints),
|
Addr: addr,
|
||||||
Load: cpuLoads,
|
Load: cpuLoads,
|
||||||
HistoricLoad: historicLoads,
|
HistoricLoad: historicLoads,
|
||||||
}
|
}
|
||||||
@@ -336,7 +345,7 @@ func localEndpointsCPULoad(endpoints EndpointList) ServerCPULoadInfo {
|
|||||||
|
|
||||||
// localEndpointsDrivePerf - returns ServerDrivesPerfInfo for only the
|
// localEndpointsDrivePerf - returns ServerDrivesPerfInfo for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func localEndpointsDrivePerf(endpoints EndpointList) ServerDrivesPerfInfo {
|
func localEndpointsDrivePerf(endpoints EndpointList, r *http.Request) ServerDrivesPerfInfo {
|
||||||
var dps []disk.Performance
|
var dps []disk.Performance
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
@@ -351,9 +360,12 @@ func localEndpointsDrivePerf(endpoints EndpointList) ServerDrivesPerfInfo {
|
|||||||
dps = append(dps, dp)
|
dps = append(dps, dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addr := r.Host
|
||||||
|
if globalIsDistXL {
|
||||||
|
addr = GetLocalPeer(endpoints)
|
||||||
|
}
|
||||||
return ServerDrivesPerfInfo{
|
return ServerDrivesPerfInfo{
|
||||||
Addr: GetLocalPeer(endpoints),
|
Addr: addr,
|
||||||
Perf: dps,
|
Perf: dps,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -77,10 +77,7 @@ func (e *Erasure) DecodeDataBlocks(data [][]byte) error {
|
|||||||
if !needsReconstruction {
|
if !needsReconstruction {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := e.encoder.ReconstructData(data); err != nil {
|
return e.encoder.ReconstructData(data)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeDataAndParityBlocks decodes the given erasure-coded data and verifies it.
|
// DecodeDataAndParityBlocks decodes the given erasure-coded data and verifies it.
|
||||||
|
|||||||
+1
-1
@@ -491,7 +491,7 @@ func formatXLGetDeploymentID(refFormat *formatXLV3, formats []*formatXLV3) (stri
|
|||||||
func formatXLFixDeploymentID(ctx context.Context, endpoints EndpointList, storageDisks []StorageAPI, refFormat *formatXLV3) (err error) {
|
func formatXLFixDeploymentID(ctx context.Context, endpoints EndpointList, storageDisks []StorageAPI, refFormat *formatXLV3) (err error) {
|
||||||
// Acquire lock on format.json
|
// Acquire lock on format.json
|
||||||
mutex := newNSLock(globalIsDistXL)
|
mutex := newNSLock(globalIsDistXL)
|
||||||
formatLock := mutex.NewNSLock(minioMetaBucket, formatConfigFile)
|
formatLock := mutex.NewNSLock(ctx, minioMetaBucket, formatConfigFile)
|
||||||
if err = formatLock.GetLock(globalHealingTimeout); err != nil {
|
if err = formatLock.GetLock(globalHealingTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hold write lock on the object.
|
// Hold write lock on the object.
|
||||||
destLock := fs.nsMutex.NewNSLock(bucket, object)
|
destLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err = destLock.GetLock(globalObjectTimeout); err != nil {
|
if err = destLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-17
@@ -30,7 +30,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/lock"
|
"github.com/minio/minio/pkg/lock"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
@@ -286,7 +286,7 @@ func (fs *FSObjects) statBucketDir(ctx context.Context, bucket string) (os.FileI
|
|||||||
// MakeBucketWithLocation - create a new bucket, returns if it
|
// MakeBucketWithLocation - create a new bucket, returns if it
|
||||||
// already exists.
|
// already exists.
|
||||||
func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error {
|
func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error {
|
||||||
bucketLock := fs.nsMutex.NewNSLock(bucket, "")
|
bucketLock := fs.nsMutex.NewNSLock(ctx, bucket, "")
|
||||||
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
|
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket, locatio
|
|||||||
|
|
||||||
// GetBucketInfo - fetch bucket metadata info.
|
// GetBucketInfo - fetch bucket metadata info.
|
||||||
func (fs *FSObjects) GetBucketInfo(ctx context.Context, bucket string) (bi BucketInfo, e error) {
|
func (fs *FSObjects) GetBucketInfo(ctx context.Context, bucket string) (bi BucketInfo, e error) {
|
||||||
bucketLock := fs.nsMutex.NewNSLock(bucket, "")
|
bucketLock := fs.nsMutex.NewNSLock(ctx, bucket, "")
|
||||||
if e := bucketLock.GetRLock(globalObjectTimeout); e != nil {
|
if e := bucketLock.GetRLock(globalObjectTimeout); e != nil {
|
||||||
return bi, e
|
return bi, e
|
||||||
}
|
}
|
||||||
@@ -372,7 +372,7 @@ func (fs *FSObjects) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
|
|||||||
// DeleteBucket - delete a bucket and all the metadata associated
|
// DeleteBucket - delete a bucket and all the metadata associated
|
||||||
// with the bucket including pending multipart, object metadata.
|
// with the bucket including pending multipart, object metadata.
|
||||||
func (fs *FSObjects) DeleteBucket(ctx context.Context, bucket string) error {
|
func (fs *FSObjects) DeleteBucket(ctx context.Context, bucket string) error {
|
||||||
bucketLock := fs.nsMutex.NewNSLock(bucket, "")
|
bucketLock := fs.nsMutex.NewNSLock(ctx, bucket, "")
|
||||||
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
|
if err := bucketLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return err
|
return err
|
||||||
@@ -408,7 +408,7 @@ func (fs *FSObjects) DeleteBucket(ctx context.Context, bucket string) error {
|
|||||||
func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, e error) {
|
func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, e error) {
|
||||||
cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
|
cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
|
||||||
if !cpSrcDstSame {
|
if !cpSrcDstSame {
|
||||||
objectDWLock := fs.nsMutex.NewNSLock(dstBucket, dstObject)
|
objectDWLock := fs.nsMutex.NewNSLock(ctx, dstBucket, dstObject)
|
||||||
if err := objectDWLock.GetLock(globalObjectTimeout); err != nil {
|
if err := objectDWLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
@@ -431,8 +431,9 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu
|
|||||||
|
|
||||||
// Save objects' metadata in `fs.json`.
|
// Save objects' metadata in `fs.json`.
|
||||||
fsMeta := newFSMetaV1()
|
fsMeta := newFSMetaV1()
|
||||||
if _, err = fsMeta.ReadFrom(ctx, wlk); err != nil && err != io.EOF {
|
if _, err = fsMeta.ReadFrom(ctx, wlk); err != nil {
|
||||||
return oi, toObjectErr(err, srcBucket, srcObject)
|
// For any error to read fsMeta, set default ETag and proceed.
|
||||||
|
fsMeta = fs.defaultFsJSON(srcObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
fsMeta.Meta = srcInfo.UserDefined
|
fsMeta.Meta = srcInfo.UserDefined
|
||||||
@@ -479,7 +480,7 @@ func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string,
|
|||||||
|
|
||||||
if lockType != noLock {
|
if lockType != noLock {
|
||||||
// Lock the object before reading.
|
// Lock the object before reading.
|
||||||
lock := fs.nsMutex.NewNSLock(bucket, object)
|
lock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
switch lockType {
|
switch lockType {
|
||||||
case writeLock:
|
case writeLock:
|
||||||
if err = lock.GetLock(globalObjectTimeout); err != nil {
|
if err = lock.GetLock(globalObjectTimeout); err != nil {
|
||||||
@@ -566,7 +567,7 @@ func (fs *FSObjects) GetObject(ctx context.Context, bucket, object string, offse
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock the object before reading.
|
// Lock the object before reading.
|
||||||
objectLock := fs.nsMutex.NewNSLock(bucket, object)
|
objectLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
|
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return err
|
return err
|
||||||
@@ -710,10 +711,7 @@ func (fs *FSObjects) getObjectInfo(ctx context.Context, bucket, object string) (
|
|||||||
_, rerr := fsMeta.ReadFrom(ctx, rlk.LockedFile)
|
_, rerr := fsMeta.ReadFrom(ctx, rlk.LockedFile)
|
||||||
fs.rwPool.Close(fsMetaPath)
|
fs.rwPool.Close(fsMetaPath)
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
if rerr != io.EOF {
|
// For any error to read fsMeta, set default ETag and proceed.
|
||||||
return oi, rerr
|
|
||||||
}
|
|
||||||
// Set Default ETag, if fs.json is empty
|
|
||||||
fsMeta = fs.defaultFsJSON(object)
|
fsMeta = fs.defaultFsJSON(object)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -741,7 +739,7 @@ func (fs *FSObjects) getObjectInfo(ctx context.Context, bucket, object string) (
|
|||||||
// getObjectInfoWithLock - reads object metadata and replies back ObjectInfo.
|
// getObjectInfoWithLock - reads object metadata and replies back ObjectInfo.
|
||||||
func (fs *FSObjects) getObjectInfoWithLock(ctx context.Context, bucket, object string) (oi ObjectInfo, e error) {
|
func (fs *FSObjects) getObjectInfoWithLock(ctx context.Context, bucket, object string) (oi ObjectInfo, e error) {
|
||||||
// Lock the object before reading.
|
// Lock the object before reading.
|
||||||
objectLock := fs.nsMutex.NewNSLock(bucket, object)
|
objectLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
|
if err := objectLock.GetRLock(globalObjectTimeout); err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
@@ -766,7 +764,7 @@ func (fs *FSObjects) getObjectInfoWithLock(ctx context.Context, bucket, object s
|
|||||||
func (fs *FSObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (oi ObjectInfo, e error) {
|
func (fs *FSObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (oi ObjectInfo, e error) {
|
||||||
oi, err := fs.getObjectInfoWithLock(ctx, bucket, object)
|
oi, err := fs.getObjectInfoWithLock(ctx, bucket, object)
|
||||||
if err == errCorruptedFormat || err == io.EOF {
|
if err == errCorruptedFormat || err == io.EOF {
|
||||||
objectLock := fs.nsMutex.NewNSLock(bucket, object)
|
objectLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err = objectLock.GetLock(globalObjectTimeout); err != nil {
|
if err = objectLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
return oi, toObjectErr(err, bucket, object)
|
return oi, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
@@ -812,7 +810,7 @@ func (fs *FSObjects) PutObject(ctx context.Context, bucket string, object string
|
|||||||
return ObjectInfo{}, err
|
return ObjectInfo{}, err
|
||||||
}
|
}
|
||||||
// Lock the object.
|
// Lock the object.
|
||||||
objectLock := fs.nsMutex.NewNSLock(bucket, object)
|
objectLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err := objectLock.GetLock(globalObjectTimeout); err != nil {
|
if err := objectLock.GetLock(globalObjectTimeout); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return objInfo, err
|
return objInfo, err
|
||||||
@@ -967,7 +965,7 @@ func (fs *FSObjects) DeleteObjects(ctx context.Context, bucket string, objects [
|
|||||||
// and there are no rollbacks supported.
|
// and there are no rollbacks supported.
|
||||||
func (fs *FSObjects) DeleteObject(ctx context.Context, bucket, object string) error {
|
func (fs *FSObjects) DeleteObject(ctx context.Context, bucket, object string) error {
|
||||||
// Acquire a write lock before deleting the object.
|
// Acquire a write lock before deleting the object.
|
||||||
objectLock := fs.nsMutex.NewNSLock(bucket, object)
|
objectLock := fs.nsMutex.NewNSLock(ctx, bucket, object)
|
||||||
if err := objectLock.GetLock(globalOperationTimeout); err != nil {
|
if err := objectLock.GetLock(globalOperationTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1151,6 +1149,12 @@ func (fs *FSObjects) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error)
|
|||||||
return []BucketInfo{}, NotImplemented{}
|
return []BucketInfo{}, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListObjectsHeal - list all objects to be healed. Valid only for XL
|
||||||
|
func (fs *FSObjects) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
|
||||||
|
logger.LogIf(ctx, NotImplemented{})
|
||||||
|
return ListObjectsInfo{}, NotImplemented{}
|
||||||
|
}
|
||||||
|
|
||||||
// SetBucketPolicy sets policy on bucket
|
// SetBucketPolicy sets policy on bucket
|
||||||
func (fs *FSObjects) SetBucketPolicy(ctx context.Context, bucket string, policy *policy.Policy) error {
|
func (fs *FSObjects) SetBucketPolicy(ctx context.Context, bucket string, policy *policy.Policy) error {
|
||||||
return savePolicyConfig(ctx, fs, bucket, policy)
|
return savePolicyConfig(ctx, fs, bucket, policy)
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go/v6"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -166,7 +167,7 @@ func FromMinioClientListMultipartsInfo(lmur minio.ListMultipartUploadsResult) Li
|
|||||||
// FromMinioClientObjectInfo converts minio ObjectInfo to gateway ObjectInfo
|
// FromMinioClientObjectInfo converts minio ObjectInfo to gateway ObjectInfo
|
||||||
func FromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
func FromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
||||||
userDefined := FromMinioClientMetadata(oi.Metadata)
|
userDefined := FromMinioClientMetadata(oi.Metadata)
|
||||||
userDefined["Content-Type"] = oi.ContentType
|
userDefined[xhttp.ContentType] = oi.ContentType
|
||||||
|
|
||||||
return ObjectInfo{
|
return ObjectInfo{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
@@ -176,7 +177,7 @@ func FromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
|||||||
ETag: canonicalizeETag(oi.ETag),
|
ETag: canonicalizeETag(oi.ETag),
|
||||||
UserDefined: userDefined,
|
UserDefined: userDefined,
|
||||||
ContentType: oi.ContentType,
|
ContentType: oi.ContentType,
|
||||||
ContentEncoding: oi.Metadata.Get("Content-Encoding"),
|
ContentEncoding: oi.Metadata.Get(xhttp.ContentEncoding),
|
||||||
StorageClass: oi.StorageClass,
|
StorageClass: oi.StorageClass,
|
||||||
Expires: oi.Expires,
|
Expires: oi.Expires,
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-4
@@ -41,14 +41,14 @@ var (
|
|||||||
gatewayCmd = cli.Command{
|
gatewayCmd = cli.Command{
|
||||||
Name: "gateway",
|
Name: "gateway",
|
||||||
Usage: "start object storage gateway",
|
Usage: "start object storage gateway",
|
||||||
Flags: append(serverFlags, globalFlags...),
|
Flags: append(ServerFlags, GlobalFlags...),
|
||||||
HideHelpCommand: true,
|
HideHelpCommand: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterGatewayCommand registers a new command for gateway.
|
// RegisterGatewayCommand registers a new command for gateway.
|
||||||
func RegisterGatewayCommand(cmd cli.Command) error {
|
func RegisterGatewayCommand(cmd cli.Command) error {
|
||||||
cmd.Flags = append(append(cmd.Flags, append(cmd.Flags, serverFlags...)...), globalFlags...)
|
cmd.Flags = append(append(cmd.Flags, ServerFlags...), GlobalFlags...)
|
||||||
gatewayCmd.Subcommands = append(gatewayCmd.Subcommands, cmd)
|
gatewayCmd.Subcommands = append(gatewayCmd.Subcommands, cmd)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
// to IPv6 address ie minio will start listening on IPv6 address whereas another
|
// to IPv6 address ie minio will start listening on IPv6 address whereas another
|
||||||
// (non-)minio process is listening on IPv4 of given port.
|
// (non-)minio process is listening on IPv4 of given port.
|
||||||
// To avoid this error situation we check for port availability.
|
// To avoid this error situation we check for port availability.
|
||||||
logger.FatalIf(checkPortAvailability(globalMinioPort), "Unable to start the gateway")
|
logger.FatalIf(checkPortAvailability(globalMinioHost, globalMinioPort), "Unable to start the gateway")
|
||||||
|
|
||||||
// Check and load TLS certificates.
|
// Check and load TLS certificates.
|
||||||
var err error
|
var err error
|
||||||
@@ -148,6 +148,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
|
|
||||||
initNSLock(false) // Enable local namespace lock.
|
initNSLock(false) // Enable local namespace lock.
|
||||||
|
|
||||||
|
// Set when gateway is enabled
|
||||||
|
globalIsGateway = true
|
||||||
|
|
||||||
router := mux.NewRouter().SkipClean(true)
|
router := mux.NewRouter().SkipClean(true)
|
||||||
|
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
@@ -175,9 +178,10 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
|
|
||||||
// Currently only NAS and S3 gateway support encryption headers.
|
// Currently only NAS and S3 gateway support encryption headers.
|
||||||
encryptionEnabled := gatewayName == "s3" || gatewayName == "nas"
|
encryptionEnabled := gatewayName == "s3" || gatewayName == "nas"
|
||||||
|
allowSSEKMS := gatewayName == "s3" // Only S3 can support SSE-KMS (as pass-through)
|
||||||
|
|
||||||
// Add API router.
|
// Add API router.
|
||||||
registerAPIRouter(router, encryptionEnabled)
|
registerAPIRouter(router, encryptionEnabled, allowSSEKMS)
|
||||||
|
|
||||||
var getCert certs.GetCertificateFunc
|
var getCert certs.GetCertificateFunc
|
||||||
if globalTLSCerts != nil {
|
if globalTLSCerts != nil {
|
||||||
@@ -221,6 +225,11 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
logger.FatalIf(err, "Unable to initialize gateway backend")
|
logger.FatalIf(err, "Unable to initialize gateway backend")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate existing buckets to the etcd backend
|
||||||
|
if globalDNSConfig != nil {
|
||||||
|
initFederatorBackend(newObject)
|
||||||
|
}
|
||||||
|
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
// Create a new config system.
|
// Create a new config system.
|
||||||
globalConfigSys = NewConfigSys()
|
globalConfigSys = NewConfigSys()
|
||||||
@@ -234,6 +243,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
|
|
||||||
// This is only to uniquely identify each gateway deployments.
|
// This is only to uniquely identify each gateway deployments.
|
||||||
globalDeploymentID = os.Getenv("MINIO_GATEWAY_DEPLOYMENT_ID")
|
globalDeploymentID = os.Getenv("MINIO_GATEWAY_DEPLOYMENT_ID")
|
||||||
|
logger.SetDeploymentID(globalDeploymentID)
|
||||||
|
|
||||||
var cacheConfig = globalServerConfig.GetCacheConfig()
|
var cacheConfig = globalServerConfig.GetCacheConfig()
|
||||||
if len(cacheConfig.Drives) > 0 {
|
if len(cacheConfig.Drives) > 0 {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -34,6 +35,36 @@ func TestRegisterGatewayCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test running a registered gateway command with a flag
|
||||||
|
func TestRunRegisteredGatewayCommand(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
flagName := "test-flag"
|
||||||
|
flagValue := "foo"
|
||||||
|
|
||||||
|
cmd := cli.Command{
|
||||||
|
Name: "test-run-with-flag",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: flagName},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if actual := ctx.String(flagName); actual != flagValue {
|
||||||
|
t.Errorf("value of %s expects %s, but got %s", flagName, flagValue, actual)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = RegisterGatewayCommand(cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RegisterGatewayCommand got unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = newApp("minio").Run(
|
||||||
|
[]string{"minio", "gateway", cmd.Name, fmt.Sprintf("--%s", flagName), flagValue}); err != nil {
|
||||||
|
t.Errorf("running registered gateway command got unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test parseGatewayEndpoint
|
// Test parseGatewayEndpoint
|
||||||
func TestParseGatewayEndpoint(t *testing.T) {
|
func TestParseGatewayEndpoint(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
|||||||
@@ -101,6 +101,11 @@ func (a GatewayUnsupported) ListBucketsHeal(ctx context.Context) (buckets []Buck
|
|||||||
return nil, NotImplemented{}
|
return nil, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListObjectsHeal - Not implemented stub
|
||||||
|
func (a GatewayUnsupported) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
|
||||||
|
return ListObjectsInfo{}, NotImplemented{}
|
||||||
|
}
|
||||||
|
|
||||||
// HealObject - Not implemented stub
|
// HealObject - Not implemented stub
|
||||||
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool, scanMode madmin.HealScanMode) (h madmin.HealResultItem, e error) {
|
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool, scanMode madmin.HealScanMode) (h madmin.HealResultItem, e error) {
|
||||||
return h, NotImplemented{}
|
return h, NotImplemented{}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import (
|
|||||||
"github.com/Azure/go-autorest/autorest/azure"
|
"github.com/Azure/go-autorest/autorest/azure"
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
miniogopolicy "github.com/minio/minio-go/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||||
"github.com/minio/minio/cmd"
|
"github.com/minio/minio/cmd"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
@@ -111,7 +111,7 @@ EXAMPLES:
|
|||||||
|
|
||||||
minio.RegisterGatewayCommand(cli.Command{
|
minio.RegisterGatewayCommand(cli.Command{
|
||||||
Name: azureBackend,
|
Name: azureBackend,
|
||||||
Usage: "Microsoft Azure Blob Storage.",
|
Usage: "Microsoft Azure Blob Storage",
|
||||||
Action: azureGatewayMain,
|
Action: azureGatewayMain,
|
||||||
CustomHelpTemplate: azureGatewayTemplate,
|
CustomHelpTemplate: azureGatewayTemplate,
|
||||||
HideHelpCommand: true,
|
HideHelpCommand: true,
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
b2 "github.com/minio/blazer/base"
|
b2 "github.com/kurin/blazer/base"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
miniogopolicy "github.com/minio/minio-go/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
h2 "github.com/minio/minio/pkg/hash"
|
h2 "github.com/minio/minio/pkg/hash"
|
||||||
@@ -147,6 +147,29 @@ func b2ToObjectError(err error, params ...string) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code, msgCode, msg := b2.MsgCode(err)
|
||||||
|
if code == 0 {
|
||||||
|
// We don't interpret non B2 errors. B2 errors have statusCode
|
||||||
|
// to help us convert them to S3 object errors.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
objErr := b2MsgCodeToObjectError(code, msgCode, msg, params...)
|
||||||
|
if objErr == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return objErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func b2MsgCodeToObjectError(code int, msgCode string, msg string, params ...string) error {
|
||||||
|
// Following code is a non-exhaustive check to convert
|
||||||
|
// B2 errors into S3 compatible errors.
|
||||||
|
//
|
||||||
|
// For a more complete information - https://www.backblaze.com/b2/docs/
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
bucket := ""
|
bucket := ""
|
||||||
object := ""
|
object := ""
|
||||||
uploadID := ""
|
uploadID := ""
|
||||||
@@ -160,18 +183,7 @@ func b2ToObjectError(err error, params ...string) error {
|
|||||||
uploadID = params[2]
|
uploadID = params[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Following code is a non-exhaustive check to convert
|
switch msgCode {
|
||||||
// B2 errors into S3 compatible errors.
|
|
||||||
//
|
|
||||||
// For a more complete information - https://www.backblaze.com/b2/docs/
|
|
||||||
statusCode, code, msg := b2.Code(err)
|
|
||||||
if statusCode == 0 {
|
|
||||||
// We don't interpret non B2 errors. B2 errors have statusCode
|
|
||||||
// to help us convert them to S3 object errors.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch code {
|
|
||||||
case "duplicate_bucket_name":
|
case "duplicate_bucket_name":
|
||||||
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
|
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||||
case "bad_request":
|
case "bad_request":
|
||||||
@@ -343,7 +355,7 @@ func (l *b2Objects) ListObjects(ctx context.Context, bucket string, prefix strin
|
|||||||
Name: file.Name,
|
Name: file.Name,
|
||||||
ModTime: file.Timestamp,
|
ModTime: file.Timestamp,
|
||||||
Size: file.Size,
|
Size: file.Size,
|
||||||
ETag: minio.ToS3ETag(file.Info.ID),
|
ETag: minio.ToS3ETag(file.ID),
|
||||||
ContentType: file.Info.ContentType,
|
ContentType: file.Info.ContentType,
|
||||||
UserDefined: file.Info.Info,
|
UserDefined: file.Info.Info,
|
||||||
})
|
})
|
||||||
@@ -386,7 +398,7 @@ func (l *b2Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuat
|
|||||||
Name: file.Name,
|
Name: file.Name,
|
||||||
ModTime: file.Timestamp,
|
ModTime: file.Timestamp,
|
||||||
Size: file.Size,
|
Size: file.Size,
|
||||||
ETag: minio.ToS3ETag(file.Info.ID),
|
ETag: minio.ToS3ETag(file.ID),
|
||||||
ContentType: file.Info.ContentType,
|
ContentType: file.Info.ContentType,
|
||||||
UserDefined: file.Info.Info,
|
UserDefined: file.Info.Info,
|
||||||
})
|
})
|
||||||
@@ -462,7 +474,7 @@ func (l *b2Objects) GetObjectInfo(ctx context.Context, bucket string, object str
|
|||||||
return minio.ObjectInfo{
|
return minio.ObjectInfo{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Name: object,
|
Name: object,
|
||||||
ETag: minio.ToS3ETag(fi.ID),
|
ETag: minio.ToS3ETag(f.ID),
|
||||||
Size: fi.Size,
|
Size: fi.Size,
|
||||||
ModTime: fi.Timestamp,
|
ModTime: fi.Timestamp,
|
||||||
ContentType: fi.ContentType,
|
ContentType: fi.ContentType,
|
||||||
@@ -569,7 +581,7 @@ func (l *b2Objects) PutObject(ctx context.Context, bucket string, object string,
|
|||||||
return minio.ObjectInfo{
|
return minio.ObjectInfo{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Name: object,
|
Name: object,
|
||||||
ETag: minio.ToS3ETag(fi.ID),
|
ETag: minio.ToS3ETag(f.ID),
|
||||||
Size: fi.Size,
|
Size: fi.Size,
|
||||||
ModTime: fi.Timestamp,
|
ModTime: fi.Timestamp,
|
||||||
ContentType: fi.ContentType,
|
ContentType: fi.ContentType,
|
||||||
@@ -617,7 +629,7 @@ func (l *b2Objects) ListMultipartUploads(ctx context.Context, bucket string, pre
|
|||||||
if maxUploads > 100 {
|
if maxUploads > 100 {
|
||||||
maxUploads = 100
|
maxUploads = 100
|
||||||
}
|
}
|
||||||
largeFiles, nextMarker, err := bkt.ListUnfinishedLargeFiles(l.ctx, uploadIDMarker, maxUploads)
|
largeFiles, nextMarker, err := bkt.ListUnfinishedLargeFiles(l.ctx, maxUploads, uploadIDMarker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return lmi, b2ToObjectError(err, bucket)
|
return lmi, b2ToObjectError(err, bucket)
|
||||||
@@ -677,7 +689,7 @@ func (l *b2Objects) PutObjectPart(ctx context.Context, bucket string, object str
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr := newB2Reader(data, data.Size())
|
hr := newB2Reader(data, data.Size())
|
||||||
sha1, err := fc.UploadPart(l.ctx, hr, sha1AtEOF, int(hr.Size()), partID)
|
_, err = fc.UploadPart(l.ctx, hr, sha1AtEOF, int(hr.Size()), partID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return pi, b2ToObjectError(err, bucket, object, uploadID)
|
return pi, b2ToObjectError(err, bucket, object, uploadID)
|
||||||
@@ -686,7 +698,7 @@ func (l *b2Objects) PutObjectPart(ctx context.Context, bucket string, object str
|
|||||||
return minio.PartInfo{
|
return minio.PartInfo{
|
||||||
PartNumber: partID,
|
PartNumber: partID,
|
||||||
LastModified: minio.UTCNow(),
|
LastModified: minio.UTCNow(),
|
||||||
ETag: minio.ToS3ETag(sha1),
|
ETag: minio.ToS3ETag(fmt.Sprintf("%x", hr.sha1Hash.Sum(nil))),
|
||||||
Size: data.Size(),
|
Size: data.Size(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
b2 "github.com/minio/blazer/base"
|
|
||||||
|
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,71 +39,6 @@ func TestB2ObjectError(t *testing.T) {
|
|||||||
{
|
{
|
||||||
[]string{}, fmt.Errorf("Non B2 Error"), fmt.Errorf("Non B2 Error"),
|
[]string{}, fmt.Errorf("Non B2 Error"), fmt.Errorf("Non B2 Error"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
[]string{"bucket"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "duplicate_bucket_name",
|
|
||||||
}, minio.BucketAlreadyOwnedByYou{
|
|
||||||
Bucket: "bucket",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "bad_request",
|
|
||||||
}, minio.BucketNotFound{
|
|
||||||
Bucket: "bucket",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket", "object"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "bad_request",
|
|
||||||
}, minio.ObjectNameInvalid{
|
|
||||||
Bucket: "bucket",
|
|
||||||
Object: "object",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "bad_bucket_id",
|
|
||||||
}, minio.BucketNotFound{Bucket: "bucket"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket", "object"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "file_not_present",
|
|
||||||
}, minio.ObjectNotFound{
|
|
||||||
Bucket: "bucket",
|
|
||||||
Object: "object",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket", "object"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "not_found",
|
|
||||||
}, minio.ObjectNotFound{
|
|
||||||
Bucket: "bucket",
|
|
||||||
Object: "object",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Code: "cannot_delete_non_empty_bucket",
|
|
||||||
}, minio.BucketNotEmpty{
|
|
||||||
Bucket: "bucket",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"bucket", "object", "uploadID"}, b2.Error{
|
|
||||||
StatusCode: 1,
|
|
||||||
Message: "No active upload for",
|
|
||||||
}, minio.InvalidUploadID{
|
|
||||||
UploadID: "uploadID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
@@ -117,3 +50,97 @@ func TestB2ObjectError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test b2 msg code to object error.
|
||||||
|
func TestB2MsgCodeToObjectError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
params []string
|
||||||
|
code int
|
||||||
|
msgCode string
|
||||||
|
msg string
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]string{"bucket"},
|
||||||
|
1,
|
||||||
|
"duplicate_bucket_name",
|
||||||
|
"",
|
||||||
|
minio.BucketAlreadyOwnedByYou{
|
||||||
|
Bucket: "bucket",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket"},
|
||||||
|
1,
|
||||||
|
"bad_request",
|
||||||
|
"",
|
||||||
|
minio.BucketNotFound{
|
||||||
|
Bucket: "bucket",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket", "object"},
|
||||||
|
1,
|
||||||
|
"bad_request",
|
||||||
|
"",
|
||||||
|
minio.ObjectNameInvalid{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket"},
|
||||||
|
1,
|
||||||
|
"bad_bucket_id",
|
||||||
|
"",
|
||||||
|
minio.BucketNotFound{Bucket: "bucket"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket", "object"},
|
||||||
|
1,
|
||||||
|
"file_not_present",
|
||||||
|
"",
|
||||||
|
minio.ObjectNotFound{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket", "object"},
|
||||||
|
1,
|
||||||
|
"not_found",
|
||||||
|
"",
|
||||||
|
minio.ObjectNotFound{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket"},
|
||||||
|
1,
|
||||||
|
"cannot_delete_non_empty_bucket",
|
||||||
|
"",
|
||||||
|
minio.BucketNotEmpty{
|
||||||
|
Bucket: "bucket",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"bucket", "object", "uploadID"},
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"No active upload for",
|
||||||
|
minio.InvalidUploadID{
|
||||||
|
UploadID: "uploadID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
actualErr := b2MsgCodeToObjectError(testCase.code, testCase.msgCode, testCase.msg, testCase.params...)
|
||||||
|
if actualErr != nil {
|
||||||
|
if actualErr.Error() != testCase.expectedErr.Error() {
|
||||||
|
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedErr, actualErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import (
|
|||||||
"cloud.google.com/go/storage"
|
"cloud.google.com/go/storage"
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
miniogopolicy "github.com/minio/minio-go/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||||
"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/policy"
|
"github.com/minio/minio/pkg/policy"
|
||||||
@@ -395,10 +395,6 @@ func (l *gcsGateway) CleanupGCSMinioSysTmp(ctx context.Context) {
|
|||||||
for {
|
for {
|
||||||
attrs, err := it.Next()
|
attrs, err := it.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != iterator.Done {
|
|
||||||
ctx := logger.SetReqInfo(context.Background(), &logger.ReqInfo{})
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
l.CleanupGCSMinioSysTmpBucket(ctx, attrs.Name)
|
l.CleanupGCSMinioSysTmpBucket(ctx, attrs.Name)
|
||||||
@@ -461,7 +457,6 @@ func (l *gcsGateway) ListBuckets(ctx context.Context) (buckets []minio.BucketInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ierr != nil {
|
if ierr != nil {
|
||||||
logger.LogIf(ctx, ierr)
|
|
||||||
return buckets, gcsToObjectError(ierr)
|
return buckets, gcsToObjectError(ierr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"cloud.google.com/go/storage"
|
"cloud.google.com/go/storage"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
miniogo "github.com/minio/minio-go"
|
miniogo "github.com/minio/minio-go/v6"
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package hdfs
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/hdfs/v3"
|
"github.com/minio/hdfs/v3"
|
||||||
"github.com/minio/minio-go/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import (
|
|||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
|
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
miniogopolicy "github.com/minio/minio-go/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||||
"github.com/minio/minio-go/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
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"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/encrypt"
|
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -442,7 +442,8 @@ func (l *s3EncObjects) PutObject(ctx context.Context, bucket string, object stri
|
|||||||
// Decide if sse options needed to be passed to backend
|
// Decide if sse options needed to be passed to backend
|
||||||
if opts.ServerSideEncryption != nil &&
|
if opts.ServerSideEncryption != nil &&
|
||||||
((minio.GlobalGatewaySSE.SSEC() && opts.ServerSideEncryption.Type() == encrypt.SSEC) ||
|
((minio.GlobalGatewaySSE.SSEC() && opts.ServerSideEncryption.Type() == encrypt.SSEC) ||
|
||||||
(minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3)) {
|
(minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3) ||
|
||||||
|
opts.ServerSideEncryption.Type() == encrypt.KMS) {
|
||||||
sseOpts = opts.ServerSideEncryption
|
sseOpts = opts.ServerSideEncryption
|
||||||
}
|
}
|
||||||
if opts.ServerSideEncryption == nil {
|
if opts.ServerSideEncryption == nil {
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
miniogo "github.com/minio/minio-go"
|
miniogo "github.com/minio/minio-go/v6"
|
||||||
"github.com/minio/minio-go/pkg/credentials"
|
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/encrypt"
|
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||||
"github.com/minio/minio-go/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
"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/policy"
|
"github.com/minio/minio/pkg/policy"
|
||||||
@@ -77,37 +77,37 @@ ENVIRONMENT VARIABLES:
|
|||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Start minio gateway server for AWS S3 backend.
|
1. Start minio gateway server for AWS S3 backend.
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}accesskey
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}accesskey
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}secretkey
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}secretkey
|
||||||
{{.Prompt}} {{.HelpName}}
|
{{.Prompt}} {{.HelpName}}
|
||||||
|
|
||||||
2. Start minio gateway server for S3 backend on custom endpoint.
|
2. Start minio gateway server for S3 backend on custom endpoint.
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||||
{{.Prompt}} {{.HelpName}} https://play.min.io:9000
|
{{.Prompt}} {{.HelpName}} https://play.min.io:9000
|
||||||
|
|
||||||
3. Start minio gateway server for AWS S3 backend logging all requests to http endpoint.
|
3. Start minio gateway server for AWS S3 backend logging all requests to http endpoint.
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}"http://localhost:8000/"
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_LOGGER_HTTP_ENDPOINT{{.AssignmentOperator}}"http://localhost:8000/"
|
||||||
{{.Prompt}} {{.HelpName}} https://play.min.io:9000
|
{{.Prompt}} {{.HelpName}} https://play.min.io:9000
|
||||||
|
|
||||||
4. Start minio gateway server for AWS S3 backend with edge caching enabled.
|
4. Start minio gateway server for AWS S3 backend with edge caching enabled.
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}accesskey
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}accesskey
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}secretkey
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}secretkey
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}"/mnt/drive1;/mnt/drive2;/mnt/drive3;/mnt/drive4"
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_DRIVES{{.AssignmentOperator}}"/mnt/drive1;/mnt/drive2;/mnt/drive3;/mnt/drive4"
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}"bucket1/*;*.png"
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_EXCLUDE{{.AssignmentOperator}}"bucket1/*;*.png"
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}40
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_EXPIRY{{.AssignmentOperator}}40
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}80
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_MAXUSE{{.AssignmentOperator}}80
|
||||||
{{.Prompt}} {{.HelpName}}
|
{{.Prompt}} {{.HelpName}}
|
||||||
|
|
||||||
4. Start minio gateway server for AWS S3 backend using AWS environment variables.
|
4. Start minio gateway server for AWS S3 backend using AWS environment variables.
|
||||||
NOTE: The access and secret key in this case will authenticate with MinIO instead
|
NOTE: The access and secret key in this case will authenticate with MinIO instead
|
||||||
of AWS and AWS envs will be used to authenticate to AWS S3.
|
of AWS and AWS envs will be used to authenticate to AWS S3.
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}aws_access_key
|
{{.Prompt}} {{.EnvVarSetCommand}} AWS_ACCESS_KEY_ID{{.AssignmentOperator}}aws_access_key
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}aws_secret_key
|
{{.Prompt}} {{.EnvVarSetCommand}} AWS_SECRET_ACCESS_KEY{{.AssignmentOperator}}aws_secret_key
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}accesskey
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}accesskey
|
||||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}secretkey
|
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}secretkey
|
||||||
{{.Prompt}} {{.HelpName}}
|
{{.Prompt}} {{.HelpName}}
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -169,14 +169,6 @@ func randString(n int, src rand.Source, prefix string) string {
|
|||||||
return prefix + string(b[0:30-len(prefix)])
|
return prefix + string(b[0:30-len(prefix)])
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAmazonS3Endpoint(urlStr string) bool {
|
|
||||||
u, err := url.Parse(urlStr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return s3utils.IsAmazonEndpoint(*u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chains all credential types, in the following order:
|
// Chains all credential types, in the following order:
|
||||||
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
|
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
|
||||||
// - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
|
// - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
|
||||||
@@ -210,6 +202,11 @@ func newS3(urlStr string) (*miniogo.Core, error) {
|
|||||||
urlStr = "https://s3.amazonaws.com"
|
urlStr = "https://s3.amazonaws.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Override default params if the host is provided
|
// Override default params if the host is provided
|
||||||
endpoint, secure, err := minio.ParseGatewayEndpoint(urlStr)
|
endpoint, secure, err := minio.ParseGatewayEndpoint(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -217,15 +214,23 @@ func newS3(urlStr string) (*miniogo.Core, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var creds *credentials.Credentials
|
var creds *credentials.Credentials
|
||||||
if isAmazonS3Endpoint(urlStr) {
|
if s3utils.IsAmazonEndpoint(*u) {
|
||||||
// If we see an Amazon S3 endpoint, then we use more ways to fetch backend credentials.
|
// If we see an Amazon S3 endpoint, then we use more ways to fetch backend credentials.
|
||||||
// Specifically IAM style rotating credentials are only supported with AWS S3 endpoint.
|
// Specifically IAM style rotating credentials are only supported with AWS S3 endpoint.
|
||||||
creds = credentials.NewChainCredentials(defaultAWSCredProviders)
|
creds = credentials.NewChainCredentials(defaultAWSCredProviders)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
creds = credentials.NewChainCredentials(defaultProviders)
|
creds = credentials.NewChainCredentials(defaultProviders)
|
||||||
}
|
}
|
||||||
|
|
||||||
clnt, err := miniogo.NewWithCredentials(endpoint, creds, secure, "")
|
options := miniogo.Options{
|
||||||
|
Creds: creds,
|
||||||
|
Secure: secure,
|
||||||
|
Region: s3utils.GetRegionFromURL(*u),
|
||||||
|
BucketLookup: miniogo.BucketLookupAuto,
|
||||||
|
}
|
||||||
|
|
||||||
|
clnt, err := miniogo.NewWithOptions(endpoint, &options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -234,9 +239,12 @@ func newS3(urlStr string) (*miniogo.Core, error) {
|
|||||||
clnt.SetCustomTransport(minio.NewCustomHTTPTransport())
|
clnt.SetCustomTransport(minio.NewCustomHTTPTransport())
|
||||||
|
|
||||||
probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-bucket-sign-")
|
probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-bucket-sign-")
|
||||||
|
|
||||||
// Check if the provided keys are valid.
|
// Check if the provided keys are valid.
|
||||||
if _, err = clnt.BucketExists(probeBucketName); err != nil {
|
if _, err = clnt.BucketExists(probeBucketName); err != nil {
|
||||||
return nil, err
|
if miniogo.ToErrorResponse(err).Code != "AccessDenied" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &miniogo.Core{Client: clnt}, nil
|
return &miniogo.Core{Client: clnt}, nil
|
||||||
@@ -313,7 +321,19 @@ func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket, location
|
|||||||
func (l *s3Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, e error) {
|
func (l *s3Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, e error) {
|
||||||
buckets, err := l.Client.ListBuckets()
|
buckets, err := l.Client.ListBuckets()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bi, minio.ErrorRespToObjectError(err, bucket)
|
// Listbuckets may be disallowed, proceed to check if
|
||||||
|
// bucket indeed exists, if yes return success.
|
||||||
|
var ok bool
|
||||||
|
if ok, err = l.Client.BucketExists(bucket); err != nil {
|
||||||
|
return bi, minio.ErrorRespToObjectError(err, bucket)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return bi, minio.BucketNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
return minio.BucketInfo{
|
||||||
|
Name: bi.Name,
|
||||||
|
Created: time.Now().UTC(),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bi := range buckets {
|
for _, bi := range buckets {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
miniogo "github.com/minio/minio-go"
|
miniogo "github.com/minio/minio-go/v6"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
|
|
||||||
minio "github.com/minio/minio/cmd"
|
minio "github.com/minio/minio/cmd"
|
||||||
|
|||||||
+27
-15
@@ -24,10 +24,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/dns"
|
"github.com/minio/minio/pkg/dns"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
@@ -261,10 +262,10 @@ func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if hasSuffix(r.URL.Path, ".js") || r.URL.Path == minioReservedBucketPath+"/favicon.ico" {
|
if hasSuffix(r.URL.Path, ".js") || r.URL.Path == minioReservedBucketPath+"/favicon.ico" {
|
||||||
// For assets set cache expiry of one year. For each release, the name
|
// For assets set cache expiry of one year. For each release, the name
|
||||||
// of the asset name will change and hence it can not be served from cache.
|
// of the asset name will change and hence it can not be served from cache.
|
||||||
w.Header().Set("Cache-Control", "max-age=31536000")
|
w.Header().Set(xhttp.CacheControl, "max-age=31536000")
|
||||||
} else {
|
} else {
|
||||||
// For non asset requests we serve index.html which will never be cached.
|
// For non asset requests we serve index.html which will never be cached.
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set(xhttp.CacheControl, "no-store")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +295,7 @@ func (h minioReservedBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
|
|||||||
default:
|
default:
|
||||||
// For all other requests reject access to reserved
|
// For all other requests reject access to reserved
|
||||||
// buckets
|
// buckets
|
||||||
bucketName, _ := urlPath2BucketObjectName(r.URL.Path)
|
bucketName, _ := request2BucketObjectName(r)
|
||||||
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
||||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@@ -379,6 +380,20 @@ type resourceHandler struct {
|
|||||||
|
|
||||||
// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
|
// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
|
||||||
func setCorsHandler(h http.Handler) http.Handler {
|
func setCorsHandler(h http.Handler) http.Handler {
|
||||||
|
commonS3Headers := []string{
|
||||||
|
xhttp.Date,
|
||||||
|
xhttp.ETag,
|
||||||
|
xhttp.ServerInfo,
|
||||||
|
xhttp.Connection,
|
||||||
|
xhttp.AcceptRanges,
|
||||||
|
xhttp.ContentRange,
|
||||||
|
xhttp.ContentEncoding,
|
||||||
|
xhttp.ContentLength,
|
||||||
|
xhttp.ContentType,
|
||||||
|
"X-Amz*",
|
||||||
|
"x-amz*",
|
||||||
|
"*",
|
||||||
|
}
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
@@ -391,8 +406,8 @@ func setCorsHandler(h http.Handler) http.Handler {
|
|||||||
http.MethodOptions,
|
http.MethodOptions,
|
||||||
http.MethodPatch,
|
http.MethodPatch,
|
||||||
},
|
},
|
||||||
AllowedHeaders: []string{"*"},
|
AllowedHeaders: commonS3Headers,
|
||||||
ExposedHeaders: []string{"*"},
|
ExposedHeaders: commonS3Headers,
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -480,7 +495,7 @@ var notimplementedObjectResourceNames = map[string]bool{
|
|||||||
|
|
||||||
// Resource handler ServeHTTP() wrapper
|
// Resource handler ServeHTTP() wrapper
|
||||||
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
bucketName, objectName := urlPath2BucketObjectName(r.URL.Path)
|
bucketName, objectName := request2BucketObjectName(r)
|
||||||
|
|
||||||
// If bucketName is present and not objectName check for bucket level resource queries.
|
// If bucketName is present and not objectName check for bucket level resource queries.
|
||||||
if bucketName != "" && objectName == "" {
|
if bucketName != "" && objectName == "" {
|
||||||
@@ -682,7 +697,8 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket, object := urlPath2BucketObjectName(r.URL.Path)
|
bucket, object := request2BucketObjectName(r)
|
||||||
|
|
||||||
// ListBucket requests should be handled at current endpoint as
|
// ListBucket requests should be handled at current endpoint as
|
||||||
// all buckets data can be fetched from here.
|
// all buckets data can be fetched from here.
|
||||||
if r.Method == http.MethodGet && bucket == "" && object == "" {
|
if r.Method == http.MethodGet && bucket == "" && object == "" {
|
||||||
@@ -735,7 +751,7 @@ func setBucketForwardingHandler(h http.Handler) http.Handler {
|
|||||||
return bucketForwardingHandler{fwd, h}
|
return bucketForwardingHandler{fwd, h}
|
||||||
}
|
}
|
||||||
|
|
||||||
// customHeaderHandler sets x-amz-request-id, x-minio-deployment-id header.
|
// customHeaderHandler sets x-amz-request-id header.
|
||||||
// Previously, this value was set right before a response was sent to
|
// Previously, this value was set right before a response was sent to
|
||||||
// the client. So, logger and Error response XML were not using this
|
// the client. So, logger and Error response XML were not using this
|
||||||
// value. This is set here so that this header can be logged as
|
// value. This is set here so that this header can be logged as
|
||||||
@@ -749,12 +765,8 @@ func addCustomHeaders(h http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s customHeaderHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s customHeaderHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// Set custom headers such as x-amz-request-id and x-minio-deployment-id
|
// Set custom headers such as x-amz-request-id for each request.
|
||||||
// for each request.
|
w.Header().Set(xhttp.AmzRequestID, mustGetRequestID(UTCNow()))
|
||||||
w.Header().Set(responseRequestIDKey, mustGetRequestID(UTCNow()))
|
|
||||||
if globalDeploymentID != "" {
|
|
||||||
w.Header().Set(responseDeploymentIDKey, globalDeploymentID)
|
|
||||||
}
|
|
||||||
s.handler.ServeHTTP(logger.NewResponseWriter(w), r)
|
s.handler.ServeHTTP(logger.NewResponseWriter(w), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-5
@@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
isatty "github.com/mattn/go-isatty"
|
isatty "github.com/mattn/go-isatty"
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
|
|
||||||
etcd "github.com/coreos/etcd/clientv3"
|
etcd "github.com/coreos/etcd/clientv3"
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/dns"
|
"github.com/minio/minio/pkg/dns"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/iam/validator"
|
"github.com/minio/minio/pkg/iam/validator"
|
||||||
|
"github.com/minio/minio/pkg/pubsub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// minio configuration related constants.
|
// minio configuration related constants.
|
||||||
@@ -82,8 +83,6 @@ const (
|
|||||||
// GlobalMultipartCleanupInterval - Cleanup interval when the stale multipart cleanup is initiated.
|
// GlobalMultipartCleanupInterval - Cleanup interval when the stale multipart cleanup is initiated.
|
||||||
GlobalMultipartCleanupInterval = time.Hour * 24 // 24 hrs.
|
GlobalMultipartCleanupInterval = time.Hour * 24 // 24 hrs.
|
||||||
|
|
||||||
// Refresh interval to update in-memory bucket policy cache.
|
|
||||||
globalRefreshBucketPolicyInterval = 5 * time.Minute
|
|
||||||
// Refresh interval to update in-memory iam config cache.
|
// Refresh interval to update in-memory iam config cache.
|
||||||
globalRefreshIAMInterval = 5 * time.Minute
|
globalRefreshIAMInterval = 5 * time.Minute
|
||||||
|
|
||||||
@@ -111,6 +110,9 @@ var (
|
|||||||
// Indicates if the running minio server is an erasure-code backend.
|
// Indicates if the running minio server is an erasure-code backend.
|
||||||
globalIsXL = false
|
globalIsXL = false
|
||||||
|
|
||||||
|
// Indicates if the running minio is in gateway mode.
|
||||||
|
globalIsGateway = false
|
||||||
|
|
||||||
// This flag is set to 'true' by default
|
// This flag is set to 'true' by default
|
||||||
globalIsBrowserEnabled = true
|
globalIsBrowserEnabled = true
|
||||||
|
|
||||||
@@ -158,8 +160,9 @@ var (
|
|||||||
globalHTTPServerErrorCh = make(chan error)
|
globalHTTPServerErrorCh = make(chan error)
|
||||||
globalOSSignalCh = make(chan os.Signal, 1)
|
globalOSSignalCh = make(chan os.Signal, 1)
|
||||||
|
|
||||||
// File to log HTTP request/response headers and body.
|
// global Trace system to send HTTP request/response logs to
|
||||||
globalHTTPTraceFile *os.File
|
// registered listeners
|
||||||
|
globalHTTPTrace = pubsub.New()
|
||||||
|
|
||||||
globalEndpoints EndpointList
|
globalEndpoints EndpointList
|
||||||
|
|
||||||
@@ -259,6 +262,11 @@ var (
|
|||||||
// GlobalGatewaySSE sse options
|
// GlobalGatewaySSE sse options
|
||||||
GlobalGatewaySSE gatewaySSE
|
GlobalGatewaySSE gatewaySSE
|
||||||
|
|
||||||
|
// The always present healing routine ready to heal objects
|
||||||
|
globalBackgroundHealing *healRoutine
|
||||||
|
globalAllHealState *allHealState
|
||||||
|
globalSweepHealState *allHealState
|
||||||
|
|
||||||
// Add new variable global values here.
|
// Add new variable global values here.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+32
-9
@@ -27,10 +27,11 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
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/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
httptracer "github.com/minio/minio/pkg/handlers"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parses location constraint from the incoming reader.
|
// Parses location constraint from the incoming reader.
|
||||||
@@ -220,8 +221,8 @@ func extractReqParams(r *http.Request) map[string]string {
|
|||||||
func extractRespElements(w http.ResponseWriter) map[string]string {
|
func extractRespElements(w http.ResponseWriter) map[string]string {
|
||||||
|
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"requestId": w.Header().Get(responseRequestIDKey),
|
"requestId": w.Header().Get(xhttp.AmzRequestID),
|
||||||
"content-length": w.Header().Get("Content-Length"),
|
"content-length": w.Header().Get(xhttp.ContentLength),
|
||||||
// Add more fields here.
|
// Add more fields here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,18 +327,26 @@ func extractPostPolicyFormValues(ctx context.Context, form *multipart.Form) (fil
|
|||||||
|
|
||||||
// Log headers and body.
|
// Log headers and body.
|
||||||
func httpTraceAll(f http.HandlerFunc) http.HandlerFunc {
|
func httpTraceAll(f http.HandlerFunc) http.HandlerFunc {
|
||||||
if globalHTTPTraceFile == nil {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
return f
|
if !globalHTTPTrace.HasSubscribers() {
|
||||||
|
f.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trace := Trace(f, true, w, r)
|
||||||
|
globalHTTPTrace.Publish(trace)
|
||||||
}
|
}
|
||||||
return httptracer.TraceReqHandlerFunc(f, globalHTTPTraceFile, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log only the headers.
|
// Log only the headers.
|
||||||
func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc {
|
func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc {
|
||||||
if globalHTTPTraceFile == nil {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
return f
|
if !globalHTTPTrace.HasSubscribers() {
|
||||||
|
f.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trace := Trace(f, false, w, r)
|
||||||
|
globalHTTPTrace.Publish(trace)
|
||||||
}
|
}
|
||||||
return httptracer.TraceReqHandlerFunc(f, globalHTTPTraceFile, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns "/bucketName/objectName" for path-style or virtual-host-style requests.
|
// Returns "/bucketName/objectName" for path-style or virtual-host-style requests.
|
||||||
@@ -376,3 +385,17 @@ func notFoundHandlerJSON(w http.ResponseWriter, r *http.Request) {
|
|||||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gets host name for current node
|
||||||
|
func getHostName(r *http.Request) (hostName string, err error) {
|
||||||
|
var thisAddr *xnet.Host
|
||||||
|
hostName = r.Host
|
||||||
|
if globalIsDistXL {
|
||||||
|
thisAddr, err = xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hostName = thisAddr.String()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if s.Backend.Type == Unknown {
|
if s.Backend.Type == Unknown {
|
||||||
// ListBuckets to confirm gateway backend is up
|
// ListBuckets to confirm gateway backend is up
|
||||||
if _, err := objLayer.ListBuckets(ctx); err != nil {
|
if _, err := objLayer.ListBuckets(ctx); err != nil {
|
||||||
|
logger.LogOnceIf(ctx, err, struct{}{})
|
||||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,20 +14,26 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package handlers
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
trace "github.com/minio/minio/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var traceBodyPlaceHolder = []byte("<BODY>")
|
||||||
|
|
||||||
// recordRequest - records the first recLen bytes
|
// recordRequest - records the first recLen bytes
|
||||||
// of a given io.Reader
|
// of a given io.Reader
|
||||||
type recordRequest struct {
|
type recordRequest struct {
|
||||||
@@ -52,7 +58,12 @@ func (r *recordRequest) Read(p []byte) (n int, err error) {
|
|||||||
|
|
||||||
// Return the bytes that were recorded.
|
// Return the bytes that were recorded.
|
||||||
func (r *recordRequest) Data() []byte {
|
func (r *recordRequest) Data() []byte {
|
||||||
return r.buf.Bytes()
|
// If body logging is enabled then we return the actual body
|
||||||
|
if r.logBody {
|
||||||
|
return r.buf.Bytes()
|
||||||
|
}
|
||||||
|
// ... otherwise we return <BODY> placeholder
|
||||||
|
return traceBodyPlaceHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordResponseWriter - records the first recLen bytes
|
// recordResponseWriter - records the first recLen bytes
|
||||||
@@ -109,92 +120,84 @@ func (r *recordResponseWriter) Flush() {
|
|||||||
r.ResponseWriter.(http.Flusher).Flush()
|
r.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return response headers.
|
|
||||||
func (r *recordResponseWriter) Headers() []byte {
|
|
||||||
return r.headers.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return response body.
|
// Return response body.
|
||||||
func (r *recordResponseWriter) Body() []byte {
|
func (r *recordResponseWriter) Body() []byte {
|
||||||
return r.body.Bytes()
|
// If there was an error response or body logging is enabled
|
||||||
}
|
// then we return the body contents
|
||||||
|
if r.statusCode >= 400 || r.logBody {
|
||||||
// TraceReqHandlerFunc logs request/response headers and body.
|
return r.body.Bytes()
|
||||||
func TraceReqHandlerFunc(f http.HandlerFunc, output io.Writer, logBody bool) http.HandlerFunc {
|
|
||||||
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
|
||||||
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
|
|
||||||
bodyPlaceHolder := []byte("<BODY>")
|
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
const timeFormat = "2006-01-02 15:04:05 -0700"
|
|
||||||
var reqBodyRecorder *recordRequest
|
|
||||||
|
|
||||||
// Generate short random request ID
|
|
||||||
reqID := fmt.Sprintf("%f", float64(time.Now().UnixNano())/1e10)
|
|
||||||
|
|
||||||
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
|
|
||||||
r.Body = ioutil.NopCloser(reqBodyRecorder)
|
|
||||||
|
|
||||||
// Setup a http response body recorder
|
|
||||||
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
|
|
||||||
|
|
||||||
b := bytes.NewBuffer(nil)
|
|
||||||
fmt.Fprintf(b, "[REQUEST %s] [%s] [%s]\n", name, reqID, time.Now().Format(timeFormat))
|
|
||||||
|
|
||||||
f(respBodyRecorder, r)
|
|
||||||
|
|
||||||
// Build request log and write it to log file
|
|
||||||
fmt.Fprintf(b, "%s %s", r.Method, r.URL.Path)
|
|
||||||
if r.URL.RawQuery != "" {
|
|
||||||
fmt.Fprintf(b, "?%s", r.URL.RawQuery)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(b, "Host: %s\n", r.Host)
|
|
||||||
for k, v := range r.Header {
|
|
||||||
fmt.Fprintf(b, "%s: %s\n", k, v[0])
|
|
||||||
}
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
if logBody {
|
|
||||||
bodyContents := reqBodyRecorder.Data()
|
|
||||||
if bodyContents != nil {
|
|
||||||
// If body logging is disabled then we print <BODY> as a placeholder
|
|
||||||
// for the actual body.
|
|
||||||
b.Write(bodyContents)
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.Write(bodyPlaceHolder)
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
|
|
||||||
// Build response log and write it to log file
|
|
||||||
fmt.Fprintf(b, "[RESPONSE] [%s] [%s]\n", reqID, time.Now().Format(timeFormat))
|
|
||||||
|
|
||||||
b.Write(respBodyRecorder.Headers())
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
|
|
||||||
// recordResponseWriter{} is configured to record only
|
|
||||||
// responses with http code != 200 & != 206, we don't
|
|
||||||
// have to check for logBody value here.
|
|
||||||
bodyContents := respBodyRecorder.Body()
|
|
||||||
if bodyContents != nil {
|
|
||||||
b.Write(bodyContents)
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
} else {
|
|
||||||
if !logBody {
|
|
||||||
// If there was no error response and body logging is disabled
|
|
||||||
// then we print <BODY> as a placeholder for the actual body.
|
|
||||||
b.Write(bodyPlaceHolder)
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
|
|
||||||
// Write the contents in one shot so that logs don't get interspersed.
|
|
||||||
output.Write(b.Bytes())
|
|
||||||
}
|
}
|
||||||
|
// ... otherwise we return the <BODY> place holder
|
||||||
|
return traceBodyPlaceHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOpName sanitizes the operation name for mc
|
||||||
|
func getOpName(name string) (op string) {
|
||||||
|
op = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
|
||||||
|
op = strings.TrimSuffix(op, "Handler-fm")
|
||||||
|
op = strings.Replace(op, "objectAPIHandlers", "s3", 1)
|
||||||
|
op = strings.Replace(op, "webAPIHandlers", "s3", 1)
|
||||||
|
op = strings.Replace(op, "adminAPIHandlers", "admin", 1)
|
||||||
|
op = strings.Replace(op, "(*storageRESTServer)", "internal", 1)
|
||||||
|
op = strings.Replace(op, "(*peerRESTServer)", "internal", 1)
|
||||||
|
op = strings.Replace(op, "(*lockRESTServer)", "internal", 1)
|
||||||
|
op = strings.Replace(op, "stsAPIHandlers", "sts", 1)
|
||||||
|
op = strings.Replace(op, "LivenessCheckHandler", "healthcheck", 1)
|
||||||
|
op = strings.Replace(op, "ReadinessCheckHandler", "healthcheck", 1)
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace gets trace of http request
|
||||||
|
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info {
|
||||||
|
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
|
||||||
|
|
||||||
|
var reqBodyRecorder *recordRequest
|
||||||
|
t := trace.Info{FuncName: name}
|
||||||
|
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
|
||||||
|
r.Body = ioutil.NopCloser(reqBodyRecorder)
|
||||||
|
t.NodeName = r.Host
|
||||||
|
if globalIsDistXL {
|
||||||
|
t.NodeName = GetLocalPeer(globalEndpoints)
|
||||||
|
}
|
||||||
|
// strip port from the host address
|
||||||
|
if host, _, err := net.SplitHostPort(t.NodeName); err == nil {
|
||||||
|
t.NodeName = host
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a http request body recorder
|
||||||
|
reqHeaders := cloneHeader(r.Header)
|
||||||
|
reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
|
||||||
|
reqHeaders.Set("Host", r.Host)
|
||||||
|
for _, enc := range r.TransferEncoding {
|
||||||
|
reqHeaders.Add("Transfer-Encoding", enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
rq := trace.RequestInfo{
|
||||||
|
Time: time.Now().UTC(),
|
||||||
|
Method: r.Method,
|
||||||
|
Path: r.URL.Path,
|
||||||
|
RawQuery: r.URL.RawQuery,
|
||||||
|
Client: r.RemoteAddr,
|
||||||
|
Headers: reqHeaders,
|
||||||
|
Body: reqBodyRecorder.Data(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a http response body recorder
|
||||||
|
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
|
||||||
|
f(respBodyRecorder, r)
|
||||||
|
|
||||||
|
rs := trace.ResponseInfo{
|
||||||
|
Time: time.Now().UTC(),
|
||||||
|
Headers: cloneHeader(respBodyRecorder.Header()),
|
||||||
|
StatusCode: respBodyRecorder.statusCode,
|
||||||
|
Body: respBodyRecorder.Body(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.StatusCode == 0 {
|
||||||
|
rs.StatusCode = http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
t.ReqInfo = rq
|
||||||
|
t.RespInfo = rs
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package http
|
||||||
|
|
||||||
|
// Standard S3 HTTP response constants
|
||||||
|
const (
|
||||||
|
LastModified = "Last-Modified"
|
||||||
|
Date = "Date"
|
||||||
|
ETag = "ETag"
|
||||||
|
ContentType = "Content-Type"
|
||||||
|
ContentMD5 = "Content-Md5"
|
||||||
|
ContentEncoding = "Content-Encoding"
|
||||||
|
Expires = "Expires"
|
||||||
|
ContentLength = "Content-Length"
|
||||||
|
ContentLanguage = "Content-Language"
|
||||||
|
ContentRange = "Content-Range"
|
||||||
|
Connection = "Connection"
|
||||||
|
AcceptRanges = "Accept-Ranges"
|
||||||
|
AmzBucketRegion = "X-Amz-Bucket-Region"
|
||||||
|
ServerInfo = "Server"
|
||||||
|
RetryAfter = "Retry-After"
|
||||||
|
Location = "Location"
|
||||||
|
CacheControl = "Cache-Control"
|
||||||
|
ContentDisposition = "Content-Disposition"
|
||||||
|
Authorization = "Authorization"
|
||||||
|
Action = "Action"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Standard S3 HTTP request constants
|
||||||
|
const (
|
||||||
|
IfModifiedSince = "If-Modified-Since"
|
||||||
|
IfUnmodifiedSince = "If-Unmodified-Since"
|
||||||
|
IfMatch = "If-Match"
|
||||||
|
IfNoneMatch = "If-None-Match"
|
||||||
|
|
||||||
|
// S3 extensions
|
||||||
|
AmzCopySourceIfModifiedSince = "x-amz-copy-source-if-modified-since"
|
||||||
|
AmzCopySourceIfUnmodifiedSince = "x-amz-copy-source-if-unmodified-since"
|
||||||
|
|
||||||
|
AmzCopySourceIfNoneMatch = "x-amz-copy-source-if-none-match"
|
||||||
|
AmzCopySourceIfMatch = "x-amz-copy-source-if-match"
|
||||||
|
|
||||||
|
AmzCopySource = "X-Amz-Copy-Source"
|
||||||
|
AmzCopySourceVersionID = "X-Amz-Copy-Source-Version-Id"
|
||||||
|
AmzCopySourceRange = "X-Amz-Copy-Source-Range"
|
||||||
|
|
||||||
|
// Signature V4 related contants.
|
||||||
|
AmzContentSha256 = "X-Amz-Content-Sha256"
|
||||||
|
AmzDate = "X-Amz-Date"
|
||||||
|
AmzAlgorithm = "X-Amz-Algorithm"
|
||||||
|
AmzExpires = "X-Amz-Expires"
|
||||||
|
AmzSignedHeaders = "X-Amz-SignedHeaders"
|
||||||
|
AmzSignature = "X-Amz-Signature"
|
||||||
|
AmzCredential = "X-Amz-Credential"
|
||||||
|
AmzSecurityToken = "X-Amz-Security-Token"
|
||||||
|
AmzDecodedContentLength = "X-Amz-Decoded-Content-Length"
|
||||||
|
|
||||||
|
// Signature v2 related constants
|
||||||
|
AmzSignatureV2 = "Signature"
|
||||||
|
AmzAccessKeyID = "AWSAccessKeyId"
|
||||||
|
|
||||||
|
// Response request id.
|
||||||
|
AmzRequestID = "x-amz-request-id"
|
||||||
|
|
||||||
|
// Deployment id.
|
||||||
|
MinioDeploymentID = "x-minio-deployment-id"
|
||||||
|
)
|
||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverPort uint32 = 60000
|
var serverPort uint32 = 60000
|
||||||
|
|||||||
+9
-11
@@ -26,9 +26,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
|
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/pkg/certs"
|
"github.com/minio/minio/pkg/certs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,9 +55,6 @@ const (
|
|||||||
|
|
||||||
// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
|
// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
|
||||||
DefaultMaxHeaderBytes = 1 * humanize.MiByte
|
DefaultMaxHeaderBytes = 1 * humanize.MiByte
|
||||||
|
|
||||||
// DefaultHTTP2MaxConcurrentStreams - default value for HTTP 2.0 maximum concurrent streams allowed.
|
|
||||||
DefaultHTTP2MaxConcurrentStreams = 1024
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
|
// Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
|
||||||
@@ -133,11 +129,6 @@ func (srv *Server) Start() (err error) {
|
|||||||
|
|
||||||
// Start servicing with listener.
|
// Start servicing with listener.
|
||||||
if tlsConfig != nil {
|
if tlsConfig != nil {
|
||||||
if err = http2.ConfigureServer(&srv.Server, &http2.Server{
|
|
||||||
MaxConcurrentStreams: DefaultHTTP2MaxConcurrentStreams,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
|
return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
|
||||||
}
|
}
|
||||||
return srv.Server.Serve(listener)
|
return srv.Server.Serve(listener)
|
||||||
@@ -211,7 +202,14 @@ func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificat
|
|||||||
CipherSuites: defaultCipherSuites,
|
CipherSuites: defaultCipherSuites,
|
||||||
CurvePreferences: secureCurves,
|
CurvePreferences: secureCurves,
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
NextProtos: []string{"h2", "http/1.1"},
|
// Do not edit the next line, protos priority is kept
|
||||||
|
// on purpose in this manner for HTTP 2.0, we would
|
||||||
|
// still like HTTP 2.0 clients to negotiate connection
|
||||||
|
// to server if needed but by default HTTP 1.1 is
|
||||||
|
// expected. We need to change this in future
|
||||||
|
// when we wish to go back to HTTP 2.0 as default
|
||||||
|
// priority for HTTP protocol negotiation.
|
||||||
|
NextProtos: []string{"http/1.1", "h2"},
|
||||||
}
|
}
|
||||||
tlsConfig.GetCertificate = getCert
|
tlsConfig.GetCertificate = getCert
|
||||||
}
|
}
|
||||||
|
|||||||
+420
-171
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"path"
|
"path"
|
||||||
@@ -25,7 +26,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
etcd "github.com/coreos/etcd/clientv3"
|
etcd "github.com/coreos/etcd/clientv3"
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||||
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
@@ -60,6 +62,45 @@ type IAMSys struct {
|
|||||||
iamCannedPolicyMap map[string]iampolicy.Policy
|
iamCannedPolicyMap map[string]iampolicy.Policy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadPolicy - reloads a specific canned policy from backend disks or etcd.
|
||||||
|
func (sys *IAMSys) LoadPolicy(objAPI ObjectLayer, policyName string) error {
|
||||||
|
if objAPI == nil {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
prefix := iamConfigPoliciesPrefix
|
||||||
|
if globalEtcdClient == nil {
|
||||||
|
return reloadPolicy(context.Background(), objAPI, prefix, policyName, sys.iamCannedPolicyMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When etcd is set, we use watch APIs so this code is not needed.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUser - reloads a specific user from backend disks or etcd.
|
||||||
|
func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, temp bool) error {
|
||||||
|
if objAPI == nil {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
prefix := iamConfigUsersPrefix
|
||||||
|
if temp {
|
||||||
|
prefix = iamConfigSTSPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalEtcdClient == nil {
|
||||||
|
return reloadUser(context.Background(), objAPI, prefix, accessKey, sys.iamUsersMap, sys.iamPolicyMap)
|
||||||
|
}
|
||||||
|
// When etcd is set, we use watch APIs so this code is not needed.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Load - loads iam subsystem
|
// Load - loads iam subsystem
|
||||||
func (sys *IAMSys) Load(objAPI ObjectLayer) error {
|
func (sys *IAMSys) Load(objAPI ObjectLayer) error {
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
@@ -68,6 +109,105 @@ func (sys *IAMSys) Load(objAPI ObjectLayer) error {
|
|||||||
return sys.refresh(objAPI)
|
return sys.refresh(objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
|
||||||
|
eventCreate := event.IsModify() || event.IsCreate()
|
||||||
|
eventDelete := event.Type == etcd.EventTypeDelete
|
||||||
|
usersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigUsersPrefix)
|
||||||
|
stsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigSTSPrefix)
|
||||||
|
policyPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPoliciesPrefix)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(),
|
||||||
|
defaultContextTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case eventCreate:
|
||||||
|
switch {
|
||||||
|
case usersPrefix:
|
||||||
|
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigUsersPrefix))
|
||||||
|
reloadEtcdUser(ctx, iamConfigUsersPrefix, accessKey,
|
||||||
|
sys.iamUsersMap, sys.iamPolicyMap)
|
||||||
|
case stsPrefix:
|
||||||
|
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigSTSPrefix))
|
||||||
|
reloadEtcdUser(ctx, iamConfigSTSPrefix, accessKey,
|
||||||
|
sys.iamUsersMap, sys.iamPolicyMap)
|
||||||
|
case policyPrefix:
|
||||||
|
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigPoliciesPrefix))
|
||||||
|
reloadEtcdPolicy(ctx, iamConfigPoliciesPrefix,
|
||||||
|
policyName, sys.iamCannedPolicyMap)
|
||||||
|
}
|
||||||
|
case eventDelete:
|
||||||
|
switch {
|
||||||
|
case usersPrefix:
|
||||||
|
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigUsersPrefix))
|
||||||
|
delete(sys.iamUsersMap, accessKey)
|
||||||
|
delete(sys.iamPolicyMap, accessKey)
|
||||||
|
case stsPrefix:
|
||||||
|
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigSTSPrefix))
|
||||||
|
delete(sys.iamUsersMap, accessKey)
|
||||||
|
delete(sys.iamPolicyMap, accessKey)
|
||||||
|
case policyPrefix:
|
||||||
|
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigPoliciesPrefix))
|
||||||
|
delete(sys.iamCannedPolicyMap, policyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch etcd entries for IAM
|
||||||
|
func (sys *IAMSys) watchIAMEtcd() {
|
||||||
|
watchEtcd := func() {
|
||||||
|
// Refresh IAMSys with etcd watch.
|
||||||
|
for {
|
||||||
|
watchCh := globalEtcdClient.Watch(context.Background(),
|
||||||
|
iamConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
|
||||||
|
select {
|
||||||
|
case <-GlobalServiceDoneCh:
|
||||||
|
return
|
||||||
|
case watchResp, ok := <-watchCh:
|
||||||
|
if !ok {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := watchResp.Err(); err != nil {
|
||||||
|
logger.LogIf(context.Background(), err)
|
||||||
|
// log and retry.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, event := range watchResp.Events {
|
||||||
|
sys.Lock()
|
||||||
|
sys.reloadFromEvent(event)
|
||||||
|
sys.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go watchEtcd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *IAMSys) watchIAMDisk(objAPI ObjectLayer) {
|
||||||
|
watchDisk := func() {
|
||||||
|
ticker := time.NewTicker(globalRefreshIAMInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-GlobalServiceDoneCh:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
sys.refresh(objAPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Refresh IAMSys in background.
|
||||||
|
go watchDisk()
|
||||||
|
}
|
||||||
|
|
||||||
// Init - initializes config system from iam.json
|
// Init - initializes config system from iam.json
|
||||||
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -75,50 +215,9 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
defer func() {
|
defer sys.watchIAMEtcd()
|
||||||
go func() {
|
|
||||||
// Refresh IAMSys with etcd watch.
|
|
||||||
for {
|
|
||||||
watchCh := globalEtcdClient.Watch(context.Background(), iamConfigPrefix)
|
|
||||||
select {
|
|
||||||
case <-GlobalServiceDoneCh:
|
|
||||||
return
|
|
||||||
case watchResp, ok := <-watchCh:
|
|
||||||
if !ok {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := watchResp.Err(); err != nil {
|
|
||||||
logger.LogIf(context.Background(), err)
|
|
||||||
// log and retry.
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, event := range watchResp.Events {
|
|
||||||
if event.IsModify() || event.IsCreate() || event.Type == etcd.EventTypeDelete {
|
|
||||||
sys.refreshEtcd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}()
|
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer sys.watchIAMDisk(objAPI)
|
||||||
// Refresh IAMSys in background.
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(globalRefreshIAMInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-GlobalServiceDoneCh:
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
sys.refresh(objAPI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
@@ -147,8 +246,8 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCannedPolicy - deletes a canned policy.
|
// DeletePolicy - deletes a canned policy from backend or etcd.
|
||||||
func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
|
func (sys *IAMSys) DeletePolicy(policyName string) error {
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
@@ -159,11 +258,17 @@ func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
configFile := pathJoin(iamConfigPoliciesPrefix, policyName, iamPolicyFile)
|
pFile := pathJoin(iamConfigPoliciesPrefix, policyName, iamPolicyFile)
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
err = deleteConfigEtcd(context.Background(), globalEtcdClient, configFile)
|
err = deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
|
||||||
} else {
|
} else {
|
||||||
err = deleteConfig(context.Background(), objectAPI, configFile)
|
err = deleteConfig(context.Background(), objectAPI, pFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case ObjectNotFound:
|
||||||
|
// Ignore error if policy is already deleted.
|
||||||
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
@@ -173,8 +278,8 @@ func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListCannedPolicies - lists all canned policies.
|
// ListPolicies - lists all canned policies.
|
||||||
func (sys *IAMSys) ListCannedPolicies() (map[string][]byte, error) {
|
func (sys *IAMSys) ListPolicies() (map[string][]byte, error) {
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
@@ -196,8 +301,8 @@ func (sys *IAMSys) ListCannedPolicies() (map[string][]byte, error) {
|
|||||||
return cannedPolicyMap, nil
|
return cannedPolicyMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCannedPolicy - sets a new canned policy.
|
// SetPolicy - sets a new canned policy.
|
||||||
func (sys *IAMSys) SetCannedPolicy(policyName string, p iampolicy.Policy) error {
|
func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
@@ -278,11 +383,11 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
|||||||
pFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile)
|
pFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile)
|
||||||
iFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
|
iFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
// It is okay to ingnore errors when deleting policy.json for the user.
|
// It is okay to ignore errors when deleting policy.json for the user.
|
||||||
_ = deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
|
deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
|
||||||
err = deleteConfigEtcd(context.Background(), globalEtcdClient, iFile)
|
err = deleteConfigEtcd(context.Background(), globalEtcdClient, iFile)
|
||||||
} else {
|
} else {
|
||||||
// It is okay to ingnore errors when deleting policy.json for the user.
|
// It is okay to ignore errors when deleting policy.json for the user.
|
||||||
_ = deleteConfig(context.Background(), objectAPI, pFile)
|
_ = deleteConfig(context.Background(), objectAPI, pFile)
|
||||||
err = deleteConfig(context.Background(), objectAPI, iFile)
|
err = deleteConfig(context.Background(), objectAPI, iFile)
|
||||||
}
|
}
|
||||||
@@ -290,7 +395,8 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
|||||||
//
|
//
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
err = errNoSuchUser
|
// ignore if user is already deleted.
|
||||||
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
@@ -490,6 +596,51 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserSecretKey - sets user secret key
|
||||||
|
func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
cred, ok := sys.iamUsersMap[accessKey]
|
||||||
|
if !ok {
|
||||||
|
return errNoSuchUser
|
||||||
|
}
|
||||||
|
|
||||||
|
uinfo := madmin.UserInfo{
|
||||||
|
SecretKey: secretKey,
|
||||||
|
Status: madmin.AccountStatus(cred.Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
|
||||||
|
data, err := json.Marshal(uinfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalEtcdClient != nil {
|
||||||
|
err = saveConfigEtcd(context.Background(), globalEtcdClient, configFile, data)
|
||||||
|
} else {
|
||||||
|
err = saveConfig(context.Background(), objectAPI, configFile, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.iamUsersMap[accessKey] = auth.Credentials{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
Status: string(uinfo.Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUser - get user credentials
|
// GetUser - get user credentials
|
||||||
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
@@ -499,16 +650,87 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
|||||||
return cred, ok && cred.IsValid()
|
return cred, ok && cred.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowedSTS is meant for STS based temporary credentials,
|
||||||
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
// which implements claims validation and verification other than
|
||||||
|
// applying policies.
|
||||||
|
func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
|
||||||
|
pname, ok := args.Claims[iampolicy.PolicyName]
|
||||||
|
if !ok {
|
||||||
|
// When claims are set, it should have a "policy" field.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pnameStr, ok := pname.(string)
|
||||||
|
if !ok {
|
||||||
|
// When claims has "policy" field, it should be string.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
defer sys.RUnlock()
|
||||||
|
|
||||||
|
// If policy is available for given user, check the policy.
|
||||||
|
name, ok := sys.iamPolicyMap[args.AccountName]
|
||||||
|
if !ok {
|
||||||
|
// No policy available reject.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pnameStr != name {
|
||||||
|
// When claims has a policy, it should match the
|
||||||
|
// policy of args.AccountName which server remembers.
|
||||||
|
// if not reject such requests.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check if we have a sessionPolicy.
|
||||||
|
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
|
||||||
|
if !ok {
|
||||||
|
// Sub policy not set, this is most common since subPolicy
|
||||||
|
// is optional, use the top level policy only.
|
||||||
|
p, ok := sys.iamCannedPolicyMap[pnameStr]
|
||||||
|
return ok && p.IsAllowed(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
spolicyStr, ok := spolicy.(string)
|
||||||
|
if !ok {
|
||||||
|
// Sub policy if set, should be a string reject
|
||||||
|
// malformed/malicious requests.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if policy is parseable.
|
||||||
|
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||||
|
if err != nil {
|
||||||
|
// Log any error in input session policy config.
|
||||||
|
logger.LogIf(context.Background(), err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy without Version string value reject it.
|
||||||
|
if subPolicy.Version == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub policy is set and valid.
|
||||||
|
p, ok := sys.iamCannedPolicyMap[pnameStr]
|
||||||
|
return ok && p.IsAllowed(args) && subPolicy.IsAllowed(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
|
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
||||||
// If opa is configured, use OPA always.
|
// If opa is configured, use OPA always.
|
||||||
if globalPolicyOPA != nil {
|
if globalPolicyOPA != nil {
|
||||||
return globalPolicyOPA.IsAllowed(args)
|
return globalPolicyOPA.IsAllowed(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With claims set, we should do STS related checks and validation.
|
||||||
|
if len(args.Claims) > 0 {
|
||||||
|
return sys.IsAllowedSTS(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
|
||||||
// If policy is available for given user, check the policy.
|
// If policy is available for given user, check the policy.
|
||||||
if name, found := sys.iamPolicyMap[args.AccountName]; found {
|
if name, found := sys.iamPolicyMap[args.AccountName]; found {
|
||||||
p, ok := sys.iamCannedPolicyMap[name]
|
p, ok := sys.iamCannedPolicyMap[name]
|
||||||
@@ -521,6 +743,25 @@ func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
|||||||
|
|
||||||
var defaultContextTimeout = 30 * time.Second
|
var defaultContextTimeout = 30 * time.Second
|
||||||
|
|
||||||
|
func etcdKvsToSet(prefix string, kvs []*mvccpb.KeyValue) set.StringSet {
|
||||||
|
users := set.NewStringSet()
|
||||||
|
for _, kv := range kvs {
|
||||||
|
// Extract user by stripping off the `prefix` value as suffix,
|
||||||
|
// then strip off the remaining basename to obtain the prefix
|
||||||
|
// value, usually in the following form.
|
||||||
|
//
|
||||||
|
// key := "config/iam/users/newuser/identity.json"
|
||||||
|
// prefix := "config/iam/users/"
|
||||||
|
// v := trim(trim(key, prefix), base(key)) == "newuser"
|
||||||
|
//
|
||||||
|
user := path.Clean(strings.TrimSuffix(strings.TrimPrefix(string(kv.Key), prefix), path.Base(string(kv.Key))))
|
||||||
|
if !users.Contains(user) {
|
||||||
|
users.Add(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
// Similar to reloadUsers but updates users, policies maps from etcd server,
|
// Similar to reloadUsers but updates users, policies maps from etcd server,
|
||||||
func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
|
func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
@@ -534,61 +775,32 @@ func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policy
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
users := set.NewStringSet()
|
users := etcdKvsToSet(prefix, r.Kvs)
|
||||||
for _, kv := range r.Kvs {
|
|
||||||
// Extract user by stripping off the `prefix` value as suffix,
|
|
||||||
// then strip off the remaining basename to obtain the prefix
|
|
||||||
// value, usually in the following form.
|
|
||||||
//
|
|
||||||
// key := "config/iam/users/newuser/identity.json"
|
|
||||||
// prefix := "config/iam/users/"
|
|
||||||
// v := trim(trim(key, prefix), base(key)) == "newuser"
|
|
||||||
//
|
|
||||||
user := path.Clean(strings.TrimSuffix(strings.TrimPrefix(string(kv.Key), prefix), path.Base(string(kv.Key))))
|
|
||||||
if !users.Contains(user) {
|
|
||||||
users.Add(user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload config and policies for all users.
|
// Reload config and policies for all users.
|
||||||
for _, user := range users.ToSlice() {
|
for _, user := range users.ToSlice() {
|
||||||
idFile := pathJoin(prefix, user, iamIdentityFile)
|
if err = reloadEtcdUser(ctx, prefix, user, usersMap, policyMap); err != nil {
|
||||||
pFile := pathJoin(prefix, user, iamPolicyFile)
|
return err
|
||||||
cdata, cerr := readConfigEtcd(ctx, globalEtcdClient, idFile)
|
|
||||||
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
|
|
||||||
if cerr != nil && cerr != errConfigNotFound {
|
|
||||||
return cerr
|
|
||||||
}
|
|
||||||
if perr != nil && perr != errConfigNotFound {
|
|
||||||
return perr
|
|
||||||
}
|
|
||||||
if cerr == errConfigNotFound && perr == errConfigNotFound {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cerr == nil {
|
|
||||||
var cred auth.Credentials
|
|
||||||
if err = json.Unmarshal(cdata, &cred); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cred.AccessKey = user
|
|
||||||
if cred.IsExpired() {
|
|
||||||
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
|
|
||||||
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
usersMap[cred.AccessKey] = cred
|
|
||||||
}
|
|
||||||
if perr == nil {
|
|
||||||
var policyName string
|
|
||||||
if err = json.Unmarshal(pdata, &policyName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
policyMap[user] = policyName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reloadEtcdPolicy(ctx context.Context, prefix string, policyName string,
|
||||||
|
cannedPolicyMap map[string]iampolicy.Policy) error {
|
||||||
|
pFile := pathJoin(prefix, policyName, iamPolicyFile)
|
||||||
|
pdata, err := readConfigEtcd(ctx, globalEtcdClient, pFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var p iampolicy.Policy
|
||||||
|
if err = json.Unmarshal(pdata, &p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cannedPolicyMap[policyName] = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func reloadEtcdPolicies(prefix string, cannedPolicyMap map[string]iampolicy.Policy) error {
|
func reloadEtcdPolicies(prefix string, cannedPolicyMap map[string]iampolicy.Policy) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -601,38 +813,32 @@ func reloadEtcdPolicies(prefix string, cannedPolicyMap map[string]iampolicy.Poli
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
policies := set.NewStringSet()
|
policies := etcdKvsToSet(prefix, r.Kvs)
|
||||||
for _, kv := range r.Kvs {
|
|
||||||
// Extract policy by stripping off the `prefix` value as suffix,
|
|
||||||
// then strip off the remaining basename to obtain the prefix
|
|
||||||
// value, usually in the following form.
|
|
||||||
//
|
|
||||||
// key := "config/iam/policies/newpolicy/identity.json"
|
|
||||||
// prefix := "config/iam/policies/"
|
|
||||||
// v := trim(trim(key, prefix), base(key)) == "newpolicy"
|
|
||||||
//
|
|
||||||
policyName := path.Clean(strings.TrimSuffix(strings.TrimPrefix(string(kv.Key), prefix), path.Base(string(kv.Key))))
|
|
||||||
if !policies.Contains(policyName) {
|
|
||||||
policies.Add(policyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload config and policies for all policys.
|
// Reload config and policies for all policys.
|
||||||
for _, policyName := range policies.ToSlice() {
|
for _, policyName := range policies.ToSlice() {
|
||||||
pFile := pathJoin(prefix, policyName, iamPolicyFile)
|
if err = reloadEtcdPolicy(ctx, prefix, policyName, cannedPolicyMap); err != nil {
|
||||||
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
|
|
||||||
if perr != nil {
|
|
||||||
return perr
|
|
||||||
}
|
|
||||||
var p iampolicy.Policy
|
|
||||||
if err = json.Unmarshal(pdata, &p); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cannedPolicyMap[policyName] = p
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reloadPolicy(ctx context.Context, objectAPI ObjectLayer, prefix string,
|
||||||
|
policyName string, cannedPolicyMap map[string]iampolicy.Policy) error {
|
||||||
|
pFile := pathJoin(prefix, policyName, iamPolicyFile)
|
||||||
|
pdata, err := readConfig(context.Background(), objectAPI, pFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var p iampolicy.Policy
|
||||||
|
if err = json.Unmarshal(pdata, &p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cannedPolicyMap[policyName] = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[string]iampolicy.Policy) error {
|
func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[string]iampolicy.Policy) error {
|
||||||
marker := ""
|
marker := ""
|
||||||
for {
|
for {
|
||||||
@@ -644,16 +850,10 @@ func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[st
|
|||||||
}
|
}
|
||||||
marker = lo.NextMarker
|
marker = lo.NextMarker
|
||||||
for _, prefix := range lo.Prefixes {
|
for _, prefix := range lo.Prefixes {
|
||||||
pFile := pathJoin(prefix, iamPolicyFile)
|
if err = reloadPolicy(context.Background(), objectAPI, iamConfigPoliciesPrefix,
|
||||||
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
|
path.Base(prefix), cannedPolicyMap); err != nil {
|
||||||
if perr != nil {
|
|
||||||
return perr
|
|
||||||
}
|
|
||||||
var p iampolicy.Policy
|
|
||||||
if err = json.Unmarshal(pdata, &p); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cannedPolicyMap[path.Base(prefix)] = p
|
|
||||||
}
|
}
|
||||||
if !lo.IsTruncated {
|
if !lo.IsTruncated {
|
||||||
break
|
break
|
||||||
@@ -663,6 +863,86 @@ func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[st
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reloadEtcdUser(ctx context.Context, prefix string, accessKey string,
|
||||||
|
usersMap map[string]auth.Credentials, policyMap map[string]string) error {
|
||||||
|
idFile := pathJoin(prefix, accessKey, iamIdentityFile)
|
||||||
|
pFile := pathJoin(prefix, accessKey, iamPolicyFile)
|
||||||
|
cdata, cerr := readConfigEtcd(ctx, globalEtcdClient, idFile)
|
||||||
|
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
|
||||||
|
if cerr != nil && cerr != errConfigNotFound {
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
if perr != nil && perr != errConfigNotFound {
|
||||||
|
return perr
|
||||||
|
}
|
||||||
|
if cerr == errConfigNotFound && perr == errConfigNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cerr == nil {
|
||||||
|
var cred auth.Credentials
|
||||||
|
if err := json.Unmarshal(cdata, &cred); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cred.AccessKey = path.Base(accessKey)
|
||||||
|
if cred.IsExpired() {
|
||||||
|
// Delete expired identity.
|
||||||
|
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
|
||||||
|
// Delete expired identity policy.
|
||||||
|
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
usersMap[cred.AccessKey] = cred
|
||||||
|
}
|
||||||
|
if perr == nil {
|
||||||
|
var policyName string
|
||||||
|
if err := json.Unmarshal(pdata, &policyName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
policyMap[path.Base(accessKey)] = policyName
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadUser(ctx context.Context, objectAPI ObjectLayer, prefix string, accessKey string,
|
||||||
|
usersMap map[string]auth.Credentials, policyMap map[string]string) error {
|
||||||
|
idFile := pathJoin(prefix, accessKey, iamIdentityFile)
|
||||||
|
pFile := pathJoin(prefix, accessKey, iamPolicyFile)
|
||||||
|
cdata, cerr := readConfig(ctx, objectAPI, idFile)
|
||||||
|
pdata, perr := readConfig(ctx, objectAPI, pFile)
|
||||||
|
if cerr != nil && cerr != errConfigNotFound {
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
if perr != nil && perr != errConfigNotFound {
|
||||||
|
return perr
|
||||||
|
}
|
||||||
|
if cerr == errConfigNotFound && perr == errConfigNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cerr == nil {
|
||||||
|
var cred auth.Credentials
|
||||||
|
if err := json.Unmarshal(cdata, &cred); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cred.AccessKey = path.Base(accessKey)
|
||||||
|
if cred.IsExpired() {
|
||||||
|
// Delete expired identity.
|
||||||
|
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
|
||||||
|
// Delete expired identity policy.
|
||||||
|
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
usersMap[cred.AccessKey] = cred
|
||||||
|
}
|
||||||
|
if perr == nil {
|
||||||
|
var policyName string
|
||||||
|
if err := json.Unmarshal(pdata, &policyName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
policyMap[path.Base(accessKey)] = policyName
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// reloadUsers reads an updates users, policies from object layer into user and policy maps.
|
// reloadUsers reads an updates users, policies from object layer into user and policy maps.
|
||||||
func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
|
func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
|
||||||
marker := ""
|
marker := ""
|
||||||
@@ -675,40 +955,9 @@ func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.
|
|||||||
}
|
}
|
||||||
marker = lo.NextMarker
|
marker = lo.NextMarker
|
||||||
for _, prefix := range lo.Prefixes {
|
for _, prefix := range lo.Prefixes {
|
||||||
idFile := pathJoin(prefix, iamIdentityFile)
|
// Prefix is empty because prefix is already part of the List output.
|
||||||
pFile := pathJoin(prefix, iamPolicyFile)
|
if err = reloadUser(context.Background(), objectAPI, "", prefix, usersMap, policyMap); err != nil {
|
||||||
cdata, cerr := readConfig(context.Background(), objectAPI, idFile)
|
return err
|
||||||
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
|
|
||||||
if cerr != nil && cerr != errConfigNotFound {
|
|
||||||
return cerr
|
|
||||||
}
|
|
||||||
if perr != nil && perr != errConfigNotFound {
|
|
||||||
return perr
|
|
||||||
}
|
|
||||||
if cerr == errConfigNotFound && perr == errConfigNotFound {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cerr == nil {
|
|
||||||
var cred auth.Credentials
|
|
||||||
if err = json.Unmarshal(cdata, &cred); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cred.AccessKey = path.Base(prefix)
|
|
||||||
if cred.IsExpired() {
|
|
||||||
// Delete expired identity.
|
|
||||||
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
|
|
||||||
// Delete expired identity policy.
|
|
||||||
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
usersMap[cred.AccessKey] = cred
|
|
||||||
}
|
|
||||||
if perr == nil {
|
|
||||||
var policyName string
|
|
||||||
if err = json.Unmarshal(pdata, &policyName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
policyMap[path.Base(prefix)] = policyName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !lo.IsTruncated {
|
if !lo.IsTruncated {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ var (
|
|||||||
errChangeCredNotAllowed = errors.New("Changing access key and secret key not allowed")
|
errChangeCredNotAllowed = errors.New("Changing access key and secret key not allowed")
|
||||||
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
||||||
errNoAuthToken = errors.New("JWT token missing")
|
errNoAuthToken = errors.New("JWT token missing")
|
||||||
|
errIncorrectCreds = errors.New("Current access key or secret key is incorrect")
|
||||||
)
|
)
|
||||||
|
|
||||||
func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (string, error) {
|
func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (string, error) {
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/dsync"
|
"github.com/minio/dsync/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// lockRequesterInfo stores various info from the client for each lock that is requested.
|
// lockRequesterInfo stores various info from the client for each lock that is requested.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/minio/dsync"
|
"github.com/minio/dsync/v2"
|
||||||
"github.com/minio/minio/cmd/http"
|
"github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/cmd/rest"
|
"github.com/minio/minio/cmd/rest"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user