mirror of
https://github.com/pgsty/minio.git
synced 2026-11-13 02:55:42 +03:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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.
|
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ stage/
|
|||||||
.sia_temp/
|
.sia_temp/
|
||||||
config.json
|
config.json
|
||||||
healthcheck
|
healthcheck
|
||||||
|
check-user
|
||||||
|
|||||||
+4
-2
@@ -10,7 +10,8 @@ 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 && \
|
||||||
|
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/check-user check-user.go
|
||||||
|
|
||||||
FROM alpine:3.9
|
FROM alpine:3.9
|
||||||
|
|
||||||
@@ -22,10 +23,11 @@ EXPOSE 9000
|
|||||||
|
|
||||||
COPY --from=0 /go/bin/minio /usr/bin/minio
|
COPY --from=0 /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
||||||
|
COPY --from=0 /usr/bin/check-user /usr/bin/check-user
|
||||||
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"]
|
||||||
|
|||||||
+4
-3
@@ -2,7 +2,7 @@ FROM alpine:3.9
|
|||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck dockerscripts/check-user /usr/bin/
|
||||||
COPY minio /usr/bin/
|
COPY minio /usr/bin/
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
@@ -10,11 +10,12 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_SECRET_KEY_FILE=secret_key
|
MINIO_SECRET_KEY_FILE=secret_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 && \
|
||||||
chmod +x /usr/bin/healthcheck
|
chmod +x /usr/bin/healthcheck && \
|
||||||
|
chmod +x /usr/bin/check-user
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -7,13 +7,15 @@ 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 && \
|
||||||
|
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/check-user check-user.go
|
||||||
|
|
||||||
FROM alpine:3.9
|
FROM alpine:3.9
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
||||||
|
COPY --from=0 /usr/bin/check-user /usr/bin/check-user
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE off
|
||||||
@@ -21,12 +23,13 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_SECRET_KEY_FILE=secret_key
|
MINIO_SECRET_KEY_FILE=secret_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 && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
chmod +x /usr/bin/healthcheck
|
chmod +x /usr/bin/healthcheck && \
|
||||||
|
chmod +x /usr/bin/check-user
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -67,6 +73,7 @@ build: checks
|
|||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null
|
||||||
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/healthcheck $(PWD)/dockerscripts/healthcheck.go 1>/dev/null
|
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/healthcheck $(PWD)/dockerscripts/healthcheck.go 1>/dev/null
|
||||||
|
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/check-user $(PWD)/dockerscripts/check-user.go 1>/dev/null
|
||||||
|
|
||||||
docker: build
|
docker: build
|
||||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
@@ -31,3 +31,10 @@ export const SHARE_OBJECT_EXPIRY_MINUTES = 0
|
|||||||
|
|
||||||
export const ACCESS_KEY_MIN_LENGTH = 3
|
export const ACCESS_KEY_MIN_LENGTH = 3
|
||||||
export const SECRET_KEY_MIN_LENGTH = 8
|
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,36 +46,43 @@ 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 => {
|
||||||
|
// we need to check if the bucket name and prefix are the same as
|
||||||
|
// when the request was made before updating the displayed objects
|
||||||
|
if (
|
||||||
|
currentBucket === getCurrentBucket(getState()) &&
|
||||||
|
currentPrefix === getCurrentPrefix(getState())
|
||||||
|
) {
|
||||||
let objects = []
|
let objects = []
|
||||||
if (res.objects) {
|
if (res.objects) {
|
||||||
objects = res.objects.map(object => {
|
objects = res.objects.map(object => {
|
||||||
@@ -74,14 +92,17 @@ export const fetchObjects = append => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (append) {
|
|
||||||
dispatch(appendList(objects, res.nextmarker, res.istruncated))
|
const sortBy = SORT_BY_LAST_MODIFIED
|
||||||
} else {
|
const sortOrder = SORT_ORDER_DESC
|
||||||
dispatch(setList(objects, res.nextmarker, res.istruncated))
|
dispatch(setSortBy(sortBy))
|
||||||
dispatch(setSortBy(""))
|
dispatch(setSortOrder(sortOrder))
|
||||||
dispatch(setSortOrder(false))
|
const sortedList = sortObjectsList(objects, sortBy, sortOrder)
|
||||||
}
|
dispatch(setList(sortedList))
|
||||||
|
|
||||||
dispatch(setPrefixWritable(res.writable))
|
dispatch(setPrefixWritable(res.writable))
|
||||||
|
dispatch(setListLoading(false))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,25 +126,26 @@ 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":
|
|
||||||
list = sortObjectsBySize(objects.list, sortOrder)
|
|
||||||
break
|
|
||||||
case "last-modified":
|
|
||||||
list = sortObjectsByDate(objects.list, sortOrder)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
list = objects.list
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
dispatch(setList(list, objects.marker, objects.isTruncated))
|
}
|
||||||
|
|
||||||
|
const sortObjectsList = (list, sortBy, sortOrder) => {
|
||||||
|
switch (sortBy) {
|
||||||
|
case SORT_BY_NAME:
|
||||||
|
return sortObjectsByName(list, sortOrder)
|
||||||
|
case SORT_BY_SIZE:
|
||||||
|
return sortObjectsBySize(list, sortOrder)
|
||||||
|
case SORT_BY_LAST_MODIFIED:
|
||||||
|
return sortObjectsByDate(list, sortOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,7 +354,8 @@ 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 =>
|
||||||
|
dispatch(
|
||||||
alertActions.set({
|
alertActions.set({
|
||||||
type: "danger",
|
type: "danger",
|
||||||
message: err.message
|
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 {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* 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("/"))
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ 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) files = files.reverse()
|
if (order === SORT_ORDER_DESC) files = files.reverse()
|
||||||
return [...folders, ...files]
|
return [...folders, ...files]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export const sortObjectsByDate = (objects, order) => {
|
|||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
|
new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
|
||||||
)
|
)
|
||||||
if (order) files = files.reverse()
|
if (order === SORT_ORDER_DESC) files = files.reverse()
|
||||||
return [...folders, ...files]
|
return [...folders, ...files]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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%);
|
||||||
|
|||||||
@@ -114,3 +114,40 @@
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,3 +101,9 @@
|
|||||||
--------------------------*/
|
--------------------------*/
|
||||||
@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
+14259
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",
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
+27
-38
File diff suppressed because one or more lines are too long
+192
-211
@@ -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==
|
||||||
@@ -3373,18 +3368,19 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
|
|||||||
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,9 +3467,10 @@ 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.2"
|
version "7.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||||
|
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
fs.realpath "^1.0.0"
|
fs.realpath "^1.0.0"
|
||||||
inflight "^1.0.4"
|
inflight "^1.0.4"
|
||||||
@@ -3482,10 +3479,9 @@ 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.1, glob@^7.1.2:
|
||||||
version "7.1.3"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||||
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
fs.realpath "^1.0.0"
|
fs.realpath "^1.0.0"
|
||||||
inflight "^1.0.4"
|
inflight "^1.0.4"
|
||||||
@@ -3578,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"
|
||||||
@@ -3675,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==
|
||||||
@@ -3789,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"
|
||||||
@@ -4953,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"
|
||||||
@@ -5122,6 +5122,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"
|
||||||
@@ -5141,6 +5146,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"
|
||||||
@@ -5189,10 +5199,6 @@ loglevel@^1.4.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"
|
||||||
@@ -5233,8 +5239,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"
|
||||||
@@ -5454,7 +5461,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=
|
||||||
@@ -5469,6 +5476,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"
|
||||||
@@ -5530,9 +5542,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"
|
||||||
@@ -5630,10 +5643,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"
|
||||||
@@ -5861,8 +5874,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"
|
||||||
@@ -5885,9 +5899,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"
|
||||||
@@ -6019,6 +6034,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"
|
||||||
@@ -6080,9 +6096,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"
|
||||||
@@ -6656,7 +6673,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"
|
||||||
|
|
||||||
@@ -7167,7 +7184,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"
|
||||||
|
|
||||||
@@ -7400,12 +7417,6 @@ 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:
|
|
||||||
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.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
|
rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, 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"
|
||||||
@@ -7782,12 +7793,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"
|
||||||
@@ -7807,17 +7812,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=
|
||||||
@@ -7825,6 +7820,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"
|
||||||
@@ -8010,6 +8006,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"
|
||||||
@@ -8036,15 +8041,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"
|
||||||
@@ -8408,18 +8409,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"
|
||||||
@@ -8592,9 +8588,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"
|
||||||
|
|
||||||
@@ -8855,17 +8852,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"
|
||||||
@@ -9019,12 +9009,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"
|
|
||||||
|
|||||||
+134
-19
@@ -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 (
|
||||||
@@ -681,7 +683,7 @@ 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, string(mimeJSON))
|
||||||
// Set 200 OK status
|
// Set 200 OK status
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
}
|
}
|
||||||
@@ -701,20 +703,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 +787,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 +985,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 +1061,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 +1120,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 +1138,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 +1170,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 +1226,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 +1269,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 +1470,63 @@ 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().Add(xhttp.Connection, "close")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -194,7 +194,9 @@ var (
|
|||||||
"webhook": {
|
"webhook": {
|
||||||
"1": {
|
"1": {
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"endpoint": ""
|
"endpoint": "",
|
||||||
|
"queueDir": "",
|
||||||
|
"queueLimit": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -268,7 +270,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()
|
||||||
|
|
||||||
|
|||||||
+136
-89
@@ -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
|
||||||
}
|
}
|
||||||
@@ -351,6 +371,7 @@ func newHealSequence(bucket, objPrefix, clientAddr string,
|
|||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
objPrefix: objPrefix,
|
objPrefix: objPrefix,
|
||||||
path: pathJoin(bucket, objPrefix),
|
path: pathJoin(bucket, objPrefix),
|
||||||
|
reportProgress: true,
|
||||||
startTime: UTCNow(),
|
startTime: UTCNow(),
|
||||||
clientToken: mustGetUUID(),
|
clientToken: mustGetUUID(),
|
||||||
clientAddress: clientAddr,
|
clientAddress: clientAddr,
|
||||||
@@ -484,7 +505,11 @@ func (h *healSequence) healSequenceStart() {
|
|||||||
h.currentStatus.StartTime = UTCNow()
|
h.currentStatus.StartTime = UTCNow()
|
||||||
h.currentStatus.updateLock.Unlock()
|
h.currentStatus.updateLock.Unlock()
|
||||||
|
|
||||||
|
if h.sourceCh == nil {
|
||||||
go h.traverseAndHeal()
|
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"`
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
@@ -1735,6 +1736,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,
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+15
-12
@@ -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 {
|
||||||
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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio-go/v6/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 {
|
||||||
@@ -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{
|
||||||
|
|||||||
@@ -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(), configFile)
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ func TestValidateConfig(t *testing.T) {
|
|||||||
{`{"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},
|
{`{"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},
|
||||||
|
|||||||
+21
-12
@@ -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,11 +2424,15 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
if globalEtcdClient != nil {
|
||||||
|
deleteConfigEtcd(context.Background(), globalEtcdClient, configFile)
|
||||||
|
} else {
|
||||||
// Rename config.json to config.json.deprecated only upon
|
// Rename config.json to config.json.deprecated only upon
|
||||||
// success of this function.
|
// success of this function.
|
||||||
if err == nil {
|
|
||||||
os.Rename(getConfigFile(), getConfigFile()+".deprecated")
|
os.Rename(getConfigFile(), getConfigFile()+".deprecated")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
transactionConfigFile := configFile + ".transaction"
|
transactionConfigFile := configFile + ".transaction"
|
||||||
@@ -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,21 +2451,25 @@ 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 _, err = Load(cfgFile, config); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Read from deprecate file as well if necessary.
|
continue
|
||||||
if _, err = Load(getConfigFile()+".deprecated", config); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
// If all else fails simply initialize the server config.
|
break
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Initialize the server config, if no config exists.
|
||||||
return newSrvConfig(objAPI)
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 in MQTT and kafka.
|
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in notification targets.
|
||||||
type serverConfigV33 struct {
|
type serverConfigV33 struct {
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
quick.Config `json:"-"` // ignore interfaces
|
||||||
|
|
||||||
|
|||||||
+4
-35
@@ -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,36 +141,17 @@ func initConfig(objAPI ObjectLayer) error {
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
|
||||||
|
|
||||||
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 isFile(getConfigFile()) {
|
||||||
if err := migrateConfig(); err != nil {
|
if err := migrateConfig(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
|
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
|
||||||
// to '<export_path>/.minio.sys/config/config.json'
|
// to '<export_path>/.minio.sys/config/config.json'
|
||||||
// ignore if the file doesn't exist.
|
// 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 {
|
if err := migrateConfigToMinioSys(objAPI); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -190,7 +160,6 @@ func initConfig(objAPI ObjectLayer) error {
|
|||||||
if err := migrateMinioSysConfig(objAPI); err != nil {
|
if err := migrateMinioSysConfig(objAPI); err != nil {
|
||||||
return err
|
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 24 hours
|
||||||
|
for {
|
||||||
|
if time.Since(lastSweepTime) < 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
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-16
@@ -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,6 +21,7 @@ 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"
|
||||||
|
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -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 {
|
||||||
@@ -239,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 {
|
||||||
@@ -304,9 +309,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
printGatewayStartupMessage(getAPIEndpoints(), gatewayName)
|
printGatewayStartupMessage(getAPIEndpoints(), gatewayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set when gateway is enabled
|
|
||||||
globalIsGateway = true
|
|
||||||
|
|
||||||
// Set uptime time after object layer has initialized.
|
// Set uptime time after object layer has initialized.
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
|
|||||||
@@ -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{}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,10 +239,13 @@ 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 {
|
||||||
|
if miniogo.ToErrorResponse(err).Code != "AccessDenied" {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &miniogo.Core{Client: clnt}, nil
|
return &miniogo.Core{Client: clnt}, nil
|
||||||
}
|
}
|
||||||
@@ -313,8 +321,20 @@ 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 {
|
||||||
|
// 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)
|
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 {
|
||||||
if bi.Name != bucket {
|
if bi.Name != bucket {
|
||||||
|
|||||||
+19
-21
@@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
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
|
||||||
@@ -380,15 +381,15 @@ 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{
|
commonS3Headers := []string{
|
||||||
"Date",
|
xhttp.Date,
|
||||||
"ETag",
|
xhttp.ETag,
|
||||||
"Server",
|
xhttp.ServerInfo,
|
||||||
"Connection",
|
xhttp.Connection,
|
||||||
"Accept-Ranges",
|
xhttp.AcceptRanges,
|
||||||
"Content-Range",
|
xhttp.ContentRange,
|
||||||
"Content-Encoding",
|
xhttp.ContentEncoding,
|
||||||
"Content-Length",
|
xhttp.ContentLength,
|
||||||
"Content-Type",
|
xhttp.ContentType,
|
||||||
"X-Amz*",
|
"X-Amz*",
|
||||||
"x-amz*",
|
"x-amz*",
|
||||||
"*",
|
"*",
|
||||||
@@ -494,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 == "" {
|
||||||
@@ -696,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 == "" {
|
||||||
@@ -749,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
|
||||||
@@ -763,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -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.
|
||||||
@@ -159,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
|
||||||
|
|
||||||
@@ -260,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.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+17
-9
@@ -27,10 +27,10 @@ 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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parses location constraint from the incoming reader.
|
// Parses location constraint from the incoming reader.
|
||||||
@@ -220,8 +220,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 +326,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.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package handlers
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -24,8 +24,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
|
trace "github.com/minio/minio/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// recordRequest - records the first recLen bytes
|
// recordRequest - records the first recLen bytes
|
||||||
@@ -109,92 +113,78 @@ 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()
|
return r.body.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceReqHandlerFunc logs request/response headers and body.
|
// getOpName sanitizes the operation name for mc
|
||||||
func TraceReqHandlerFunc(f http.HandlerFunc, output io.Writer, logBody bool) http.HandlerFunc {
|
func getOpName(name string) (op string) {
|
||||||
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
op = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
|
||||||
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
|
op = strings.TrimSuffix(op, "Handler-fm")
|
||||||
bodyPlaceHolder := []byte("<BODY>")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
// Trace gets trace of http request
|
||||||
const timeFormat = "2006-01-02 15:04:05 -0700"
|
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info {
|
||||||
|
|
||||||
|
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
|
||||||
|
|
||||||
|
bodyPlaceHolder := []byte("<BODY>")
|
||||||
var reqBodyRecorder *recordRequest
|
var reqBodyRecorder *recordRequest
|
||||||
|
|
||||||
// Generate short random request ID
|
t := trace.Info{FuncName: name}
|
||||||
reqID := fmt.Sprintf("%f", float64(time.Now().UnixNano())/1e10)
|
|
||||||
|
|
||||||
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
|
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
|
||||||
r.Body = ioutil.NopCloser(reqBodyRecorder)
|
r.Body = ioutil.NopCloser(reqBodyRecorder)
|
||||||
|
|
||||||
// Setup a http response body recorder
|
host, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
||||||
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
|
if err == nil {
|
||||||
|
t.NodeName = host.Name
|
||||||
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")
|
rq := trace.RequestInfo{Time: time.Now().UTC(), Method: r.Method, Path: r.URL.Path, RawQuery: r.URL.RawQuery, Client: r.RemoteAddr}
|
||||||
|
rq.Headers = cloneHeader(r.Header)
|
||||||
fmt.Fprintf(b, "Host: %s\n", r.Host)
|
rq.Headers.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
|
||||||
for k, v := range r.Header {
|
rq.Headers.Set("Host", r.Host)
|
||||||
fmt.Fprintf(b, "%s: %s\n", k, v[0])
|
for _, enc := range r.TransferEncoding {
|
||||||
|
rq.Headers.Add("Transfer-Encoding", enc)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
if logBody {
|
if logBody {
|
||||||
bodyContents := reqBodyRecorder.Data()
|
|
||||||
if bodyContents != nil {
|
|
||||||
// If body logging is disabled then we print <BODY> as a placeholder
|
// If body logging is disabled then we print <BODY> as a placeholder
|
||||||
// for the actual body.
|
// for the actual body.
|
||||||
b.Write(bodyContents)
|
rq.Body = reqBodyRecorder.Data()
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
b.Write(bodyPlaceHolder)
|
rq.Body = bodyPlaceHolder
|
||||||
fmt.Fprintf(b, "\n")
|
|
||||||
}
|
}
|
||||||
|
// Setup a http response body recorder
|
||||||
|
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
|
||||||
|
f(respBodyRecorder, r)
|
||||||
|
|
||||||
fmt.Fprintf(b, "\n")
|
rs := trace.ResponseInfo{Time: time.Now().UTC()}
|
||||||
|
rs.Headers = cloneHeader(respBodyRecorder.Header())
|
||||||
// Build response log and write it to log file
|
rs.StatusCode = respBodyRecorder.statusCode
|
||||||
fmt.Fprintf(b, "[RESPONSE] [%s] [%s]\n", reqID, time.Now().Format(timeFormat))
|
if rs.StatusCode == 0 {
|
||||||
|
rs.StatusCode = http.StatusOK
|
||||||
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()
|
bodyContents := respBodyRecorder.Body()
|
||||||
if bodyContents != nil {
|
if bodyContents != nil {
|
||||||
b.Write(bodyContents)
|
rs.Body = bodyContents
|
||||||
fmt.Fprintf(b, "\n")
|
}
|
||||||
} else {
|
|
||||||
if !logBody {
|
if !logBody {
|
||||||
// If there was no error response and body logging is disabled
|
// If there was no error response and body logging is disabled
|
||||||
// then we print <BODY> as a placeholder for the actual body.
|
// then we print <BODY> as a placeholder for the actual body.
|
||||||
b.Write(bodyPlaceHolder)
|
rs.Body = 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())
|
|
||||||
}
|
}
|
||||||
|
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"
|
||||||
|
)
|
||||||
+338
-135
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"path"
|
"path"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
etcd "github.com/coreos/etcd/clientv3"
|
etcd "github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||||
"github.com/minio/minio-go/v6/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/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
@@ -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,19 +109,63 @@ func (sys *IAMSys) Load(objAPI ObjectLayer) error {
|
|||||||
return sys.refresh(objAPI)
|
return sys.refresh(objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init - initializes config system from iam.json
|
func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
|
||||||
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
eventCreate := event.IsModify() || event.IsCreate()
|
||||||
if objAPI == nil {
|
eventDelete := event.Type == etcd.EventTypeDelete
|
||||||
return errInvalidArgument
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalEtcdClient != nil {
|
// Watch etcd entries for IAM
|
||||||
defer func() {
|
func (sys *IAMSys) watchIAMEtcd() {
|
||||||
go func() {
|
watchEtcd := func() {
|
||||||
// Refresh IAMSys with etcd watch.
|
// Refresh IAMSys with etcd watch.
|
||||||
for {
|
for {
|
||||||
watchCh := globalEtcdClient.Watch(context.Background(),
|
watchCh := globalEtcdClient.Watch(context.Background(),
|
||||||
iamConfigPrefix, etcd.WithPrefix())
|
iamConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
|
||||||
select {
|
select {
|
||||||
case <-GlobalServiceDoneCh:
|
case <-GlobalServiceDoneCh:
|
||||||
return
|
return
|
||||||
@@ -96,18 +181,18 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, event := range watchResp.Events {
|
for _, event := range watchResp.Events {
|
||||||
if event.IsModify() || event.IsCreate() || event.Type == etcd.EventTypeDelete {
|
sys.Lock()
|
||||||
sys.refreshEtcd()
|
sys.reloadFromEvent(event)
|
||||||
|
sys.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
go watchEtcd()
|
||||||
}()
|
}
|
||||||
} else {
|
|
||||||
defer func() {
|
func (sys *IAMSys) watchIAMDisk(objAPI ObjectLayer) {
|
||||||
// Refresh IAMSys in background.
|
watchDisk := func() {
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(globalRefreshIAMInterval)
|
ticker := time.NewTicker(globalRefreshIAMInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
@@ -118,8 +203,21 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
|||||||
sys.refresh(objAPI)
|
sys.refresh(objAPI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
}()
|
// Refresh IAMSys in background.
|
||||||
|
go watchDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init - initializes config system from iam.json
|
||||||
|
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
||||||
|
if objAPI == nil {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalEtcdClient != nil {
|
||||||
|
defer sys.watchIAMEtcd()
|
||||||
|
} else {
|
||||||
|
defer sys.watchIAMDisk(objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
@@ -148,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
|
||||||
@@ -160,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()
|
||||||
@@ -174,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
|
||||||
@@ -197,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
|
||||||
@@ -279,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)
|
||||||
}
|
}
|
||||||
@@ -291,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()
|
||||||
@@ -545,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]
|
||||||
@@ -567,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)
|
||||||
@@ -580,58 +775,29 @@ 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)
|
|
||||||
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
|
return err
|
||||||
}
|
}
|
||||||
cred.AccessKey = user
|
|
||||||
if cred.IsExpired() {
|
|
||||||
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
|
|
||||||
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
usersMap[cred.AccessKey] = cred
|
return nil
|
||||||
}
|
}
|
||||||
if perr == nil {
|
|
||||||
var policyName string
|
func reloadEtcdPolicy(ctx context.Context, prefix string, policyName string,
|
||||||
if err = json.Unmarshal(pdata, &policyName); err != nil {
|
cannedPolicyMap map[string]iampolicy.Policy) error {
|
||||||
|
pFile := pathJoin(prefix, policyName, iamPolicyFile)
|
||||||
|
pdata, err := readConfigEtcd(ctx, globalEtcdClient, pFile)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
policyMap[user] = policyName
|
var p iampolicy.Policy
|
||||||
}
|
if err = json.Unmarshal(pdata, &p); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
cannedPolicyMap[policyName] = p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,35 +813,29 @@ 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() {
|
||||||
|
if err = reloadEtcdPolicy(ctx, prefix, policyName, cannedPolicyMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadPolicy(ctx context.Context, objectAPI ObjectLayer, prefix string,
|
||||||
|
policyName string, cannedPolicyMap map[string]iampolicy.Policy) error {
|
||||||
pFile := pathJoin(prefix, policyName, iamPolicyFile)
|
pFile := pathJoin(prefix, policyName, iamPolicyFile)
|
||||||
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
|
pdata, err := readConfig(context.Background(), objectAPI, pFile)
|
||||||
if perr != nil {
|
if err != nil {
|
||||||
return perr
|
return err
|
||||||
}
|
}
|
||||||
var p iampolicy.Policy
|
var p iampolicy.Policy
|
||||||
if err = json.Unmarshal(pdata, &p); err != nil {
|
if err = json.Unmarshal(pdata, &p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cannedPolicyMap[policyName] = p
|
cannedPolicyMap[policyName] = p
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,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
|
||||||
@@ -709,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 := ""
|
||||||
@@ -721,41 +955,10 @@ 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)
|
|
||||||
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
|
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 {
|
||||||
break
|
break
|
||||||
|
|||||||
+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"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/dsync"
|
"github.com/minio/dsync/v2"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/dsync"
|
"github.com/minio/dsync/v2"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -63,6 +63,6 @@ func AuditLog(w http.ResponseWriter, r *http.Request, api string, reqClaims map[
|
|||||||
}
|
}
|
||||||
// Send audit logs only to http targets.
|
// Send audit logs only to http targets.
|
||||||
for _, t := range AuditTargets {
|
for _, t := range AuditTargets {
|
||||||
_ = t.Send(audit.ToEntry(w, r, api, statusCode, reqClaims))
|
_ = t.Send(audit.ToEntry(w, r, api, statusCode, reqClaims, globalDeploymentID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -55,6 +55,8 @@ const (
|
|||||||
|
|
||||||
var trimStrings []string
|
var trimStrings []string
|
||||||
|
|
||||||
|
var globalDeploymentID string
|
||||||
|
|
||||||
// TimeFormat - logging time format.
|
// TimeFormat - logging time format.
|
||||||
const TimeFormat string = "15:04:05 MST 01/02/2006"
|
const TimeFormat string = "15:04:05 MST 01/02/2006"
|
||||||
|
|
||||||
@@ -154,6 +156,11 @@ func uniqueEntries(paths []string) []string {
|
|||||||
return m.ToSlice()
|
return m.ToSlice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDeploymentID -- Deployment Id from the main package is set here
|
||||||
|
func SetDeploymentID(deploymentID string) {
|
||||||
|
globalDeploymentID = deploymentID
|
||||||
|
}
|
||||||
|
|
||||||
// Init sets the trimStrings to possible GOPATHs
|
// Init sets the trimStrings to possible GOPATHs
|
||||||
// and GOROOT directories. Also append github.com/minio/minio
|
// and GOROOT directories. Also append github.com/minio/minio
|
||||||
// This is done to clean up the filename, when stack trace is
|
// This is done to clean up the filename, when stack trace is
|
||||||
@@ -317,7 +324,9 @@ func logIf(ctx context.Context, err error) {
|
|||||||
|
|
||||||
// Get the cause for the Error
|
// Get the cause for the Error
|
||||||
message := err.Error()
|
message := err.Error()
|
||||||
|
if req.DeploymentID == "" {
|
||||||
|
req.DeploymentID = globalDeploymentID
|
||||||
|
}
|
||||||
entry := log.Entry{
|
entry := log.Entry{
|
||||||
DeploymentID: req.DeploymentID,
|
DeploymentID: req.DeploymentID,
|
||||||
Level: ErrorLvl.String(),
|
Level: ErrorLvl.String(),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ type Entry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToEntry - constructs an audit entry object.
|
// ToEntry - constructs an audit entry object.
|
||||||
func ToEntry(w http.ResponseWriter, r *http.Request, api string, statusCode int, reqClaims map[string]interface{}) Entry {
|
func ToEntry(w http.ResponseWriter, r *http.Request, api string, statusCode int, reqClaims map[string]interface{}, deploymentID string) Entry {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
@@ -67,13 +68,13 @@ func ToEntry(w http.ResponseWriter, r *http.Request, api string, statusCode int,
|
|||||||
for k, v := range w.Header() {
|
for k, v := range w.Header() {
|
||||||
respHeader[k] = strings.Join(v, ",")
|
respHeader[k] = strings.Join(v, ",")
|
||||||
}
|
}
|
||||||
respHeader["Etag"] = strings.Trim(respHeader["Etag"], `"`)
|
respHeader[xhttp.ETag] = strings.Trim(respHeader[xhttp.ETag], `"`)
|
||||||
|
|
||||||
entry := Entry{
|
entry := Entry{
|
||||||
Version: Version,
|
Version: Version,
|
||||||
DeploymentID: w.Header().Get("x-minio-deployment-id"),
|
DeploymentID: deploymentID,
|
||||||
RemoteHost: handlers.GetSourceIP(r),
|
RemoteHost: handlers.GetSourceIP(r),
|
||||||
RequestID: w.Header().Get("x-amz-request-id"),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
UserAgent: r.UserAgent(),
|
UserAgent: r.UserAgent(),
|
||||||
Time: time.Now().UTC().Format(time.RFC3339Nano),
|
Time: time.Now().UTC().Format(time.RFC3339Nano),
|
||||||
ReqQuery: reqQuery,
|
ReqQuery: reqQuery,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
gohttp "net/http"
|
gohttp "net/http"
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
@@ -49,11 +50,11 @@ func (h *Target) startHTTPLogger() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := gohttp.NewRequest("POST", h.endpoint, bytes.NewBuffer(logJSON))
|
req, err := gohttp.NewRequest(http.MethodPost, h.endpoint, bytes.NewBuffer(logJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set(xhttp.ContentType, "application/json")
|
||||||
|
|
||||||
resp, err := h.client.Do(req)
|
resp, err := h.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+3
-3
@@ -27,8 +27,8 @@ import (
|
|||||||
"github.com/minio/minio/pkg/words"
|
"github.com/minio/minio/pkg/words"
|
||||||
)
|
)
|
||||||
|
|
||||||
// global flags for minio.
|
// GlobalFlags - global flags for minio.
|
||||||
var globalFlags = []cli.Flag{
|
var GlobalFlags = []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config-dir, C",
|
Name: "config-dir, C",
|
||||||
Value: defaultConfigDir.Get(),
|
Value: defaultConfigDir.Get(),
|
||||||
@@ -131,7 +131,7 @@ func newApp(name string) *cli.App {
|
|||||||
app.Version = Version
|
app.Version = Version
|
||||||
app.Usage = "Cloud Storage Server."
|
app.Usage = "Cloud Storage Server."
|
||||||
app.Description = `MinIO is an Amazon S3 compatible object storage server. Use it to store photos, videos, VMs, containers, log files, or any blob of data as objects.`
|
app.Description = `MinIO is an Amazon S3 compatible object storage server. Use it to store photos, videos, VMs, containers, log files, or any blob of data as objects.`
|
||||||
app.Flags = globalFlags
|
app.Flags = GlobalFlags
|
||||||
app.HideVersion = true // Hide `--version` flag, we already have `minio version`.
|
app.HideVersion = true // Hide `--version` flag, we already have `minio version`.
|
||||||
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
|
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
|
||||||
app.Commands = commands
|
app.Commands = commands
|
||||||
|
|||||||
+21
-1
@@ -34,11 +34,25 @@ var (
|
|||||||
},
|
},
|
||||||
[]string{"request_type"},
|
[]string{"request_type"},
|
||||||
)
|
)
|
||||||
|
minioVersionInfo = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Namespace: "minio",
|
||||||
|
Name: "version_info",
|
||||||
|
Help: "Version of current MinIO server instance",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
// current version
|
||||||
|
"version",
|
||||||
|
// commit-id of the current version
|
||||||
|
"commit",
|
||||||
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(httpRequestsDuration)
|
prometheus.MustRegister(httpRequestsDuration)
|
||||||
prometheus.MustRegister(newMinioCollector())
|
prometheus.MustRegister(newMinioCollector())
|
||||||
|
prometheus.MustRegister(minioVersionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newMinioCollector describes the collector
|
// newMinioCollector describes the collector
|
||||||
@@ -64,6 +78,9 @@ func (c *minioCollector) Describe(ch chan<- *prometheus.Desc) {
|
|||||||
// Collect is called by the Prometheus registry when collecting metrics.
|
// Collect is called by the Prometheus registry when collecting metrics.
|
||||||
func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
|
func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
|
||||||
|
// Expose MinIO's version information
|
||||||
|
minioVersionInfo.WithLabelValues(Version, CommitID).Add(1)
|
||||||
|
|
||||||
// Always expose network stats
|
// Always expose network stats
|
||||||
|
|
||||||
// Network Sent/Received Bytes
|
// Network Sent/Received Bytes
|
||||||
@@ -184,7 +201,10 @@ func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
|
|||||||
func metricsHandler() http.Handler {
|
func metricsHandler() http.Handler {
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
err := registry.Register(httpRequestsDuration)
|
err := registry.Register(minioVersionInfo)
|
||||||
|
logger.LogIf(context.Background(), err)
|
||||||
|
|
||||||
|
err = registry.Register(httpRequestsDuration)
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(context.Background(), err)
|
||||||
|
|
||||||
err = registry.Register(newMinioCollector())
|
err = registry.Register(newMinioCollector())
|
||||||
|
|||||||
+12
-11
@@ -27,7 +27,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/dsync"
|
"github.com/minio/dsync/v2"
|
||||||
"github.com/minio/lsync"
|
"github.com/minio/lsync"
|
||||||
"github.com/minio/minio-go/v6/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -127,7 +127,7 @@ type nsLockMap struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock the namespace resource.
|
// Lock the namespace resource.
|
||||||
func (n *nsLockMap) lock(volume, path string, lockSource, opsID string, readLock bool, timeout time.Duration) (locked bool) {
|
func (n *nsLockMap) lock(ctx context.Context, volume, path string, lockSource, opsID string, readLock bool, timeout time.Duration) (locked bool) {
|
||||||
var nsLk *nsLock
|
var nsLk *nsLock
|
||||||
|
|
||||||
n.lockMapMutex.Lock()
|
n.lockMapMutex.Lock()
|
||||||
@@ -135,7 +135,7 @@ func (n *nsLockMap) lock(volume, path string, lockSource, opsID string, readLock
|
|||||||
nsLk, found := n.lockMap[param]
|
nsLk, found := n.lockMap[param]
|
||||||
if !found {
|
if !found {
|
||||||
n.lockMap[param] = &nsLock{
|
n.lockMap[param] = &nsLock{
|
||||||
LRWMutex: &lsync.LRWMutex{},
|
LRWMutex: lsync.NewLRWMutex(ctx),
|
||||||
ref: 1,
|
ref: 1,
|
||||||
}
|
}
|
||||||
nsLk = n.lockMap[param]
|
nsLk = n.lockMap[param]
|
||||||
@@ -199,7 +199,7 @@ func (n *nsLockMap) Lock(volume, path, opsID string, timeout time.Duration) (loc
|
|||||||
readLock := false // This is a write lock.
|
readLock := false // This is a write lock.
|
||||||
|
|
||||||
lockSource := getSource() // Useful for debugging
|
lockSource := getSource() // Useful for debugging
|
||||||
return n.lock(volume, path, lockSource, opsID, readLock, timeout)
|
return n.lock(context.Background(), volume, path, lockSource, opsID, readLock, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock - unlocks any previously acquired write locks.
|
// Unlock - unlocks any previously acquired write locks.
|
||||||
@@ -213,7 +213,7 @@ func (n *nsLockMap) RLock(volume, path, opsID string, timeout time.Duration) (lo
|
|||||||
readLock := true
|
readLock := true
|
||||||
|
|
||||||
lockSource := getSource() // Useful for debugging
|
lockSource := getSource() // Useful for debugging
|
||||||
return n.lock(volume, path, lockSource, opsID, readLock, timeout)
|
return n.lock(context.Background(), volume, path, lockSource, opsID, readLock, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RUnlock - unlocks any previously acquired read locks.
|
// RUnlock - unlocks any previously acquired read locks.
|
||||||
@@ -241,7 +241,7 @@ func (n *nsLockMap) ForceUnlock(volume, path string) {
|
|||||||
// are blocking can now proceed as normal and any new locks will also
|
// are blocking can now proceed as normal and any new locks will also
|
||||||
// participate normally.
|
// participate normally.
|
||||||
if n.isDistXL { // For distributed mode, broadcast ForceUnlock message.
|
if n.isDistXL { // For distributed mode, broadcast ForceUnlock message.
|
||||||
dsync.NewDRWMutex(pathJoin(volume, path), globalDsync).ForceUnlock()
|
dsync.NewDRWMutex(context.Background(), pathJoin(volume, path), globalDsync).ForceUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove lock from the map.
|
// Remove lock from the map.
|
||||||
@@ -291,6 +291,7 @@ func (di *distLockInstance) RUnlock() {
|
|||||||
|
|
||||||
// localLockInstance - frontend/top-level interface for namespace locks.
|
// localLockInstance - frontend/top-level interface for namespace locks.
|
||||||
type localLockInstance struct {
|
type localLockInstance struct {
|
||||||
|
ctx context.Context
|
||||||
ns *nsLockMap
|
ns *nsLockMap
|
||||||
volume, path, opsID string
|
volume, path, opsID string
|
||||||
}
|
}
|
||||||
@@ -298,12 +299,12 @@ type localLockInstance struct {
|
|||||||
// NewNSLock - returns a lock instance for a given volume and
|
// NewNSLock - returns a lock instance for a given volume and
|
||||||
// path. The returned lockInstance object encapsulates the nsLockMap,
|
// path. The returned lockInstance object encapsulates the nsLockMap,
|
||||||
// volume, path and operation ID.
|
// volume, path and operation ID.
|
||||||
func (n *nsLockMap) NewNSLock(volume, path string) RWLocker {
|
func (n *nsLockMap) NewNSLock(ctx context.Context, volume, path string) RWLocker {
|
||||||
opsID := mustGetUUID()
|
opsID := mustGetUUID()
|
||||||
if n.isDistXL {
|
if n.isDistXL {
|
||||||
return &distLockInstance{dsync.NewDRWMutex(pathJoin(volume, path), globalDsync), volume, path, opsID}
|
return &distLockInstance{dsync.NewDRWMutex(ctx, pathJoin(volume, path), globalDsync), volume, path, opsID}
|
||||||
}
|
}
|
||||||
return &localLockInstance{n, volume, path, opsID}
|
return &localLockInstance{ctx, n, volume, path, opsID}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock - block until write lock is taken or timeout has occurred.
|
// Lock - block until write lock is taken or timeout has occurred.
|
||||||
@@ -311,7 +312,7 @@ func (li *localLockInstance) GetLock(timeout *dynamicTimeout) (timedOutErr error
|
|||||||
lockSource := getSource()
|
lockSource := getSource()
|
||||||
start := UTCNow()
|
start := UTCNow()
|
||||||
readLock := false
|
readLock := false
|
||||||
if !li.ns.lock(li.volume, li.path, lockSource, li.opsID, readLock, timeout.Timeout()) {
|
if !li.ns.lock(li.ctx, li.volume, li.path, lockSource, li.opsID, readLock, timeout.Timeout()) {
|
||||||
timeout.LogFailure()
|
timeout.LogFailure()
|
||||||
return OperationTimedOut{Path: li.path}
|
return OperationTimedOut{Path: li.path}
|
||||||
}
|
}
|
||||||
@@ -330,7 +331,7 @@ func (li *localLockInstance) GetRLock(timeout *dynamicTimeout) (timedOutErr erro
|
|||||||
lockSource := getSource()
|
lockSource := getSource()
|
||||||
start := UTCNow()
|
start := UTCNow()
|
||||||
readLock := true
|
readLock := true
|
||||||
if !li.ns.lock(li.volume, li.path, lockSource, li.opsID, readLock, timeout.Timeout()) {
|
if !li.ns.lock(li.ctx, li.volume, li.path, lockSource, li.opsID, readLock, timeout.Timeout()) {
|
||||||
timeout.LogFailure()
|
timeout.LogFailure()
|
||||||
return OperationTimedOut{Path: li.path}
|
return OperationTimedOut{Path: li.path}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -31,7 +32,7 @@ func TestGetSource(t *testing.T) {
|
|||||||
currentSource := func() string { return getSource() }
|
currentSource := func() string { return getSource() }
|
||||||
gotSource := currentSource()
|
gotSource := currentSource()
|
||||||
// Hard coded line number, 32, in the "expectedSource" value
|
// Hard coded line number, 32, in the "expectedSource" value
|
||||||
expectedSource := "[namespace-lock_test.go:32:TestGetSource()]"
|
expectedSource := "[namespace-lock_test.go:33:TestGetSource()]"
|
||||||
if gotSource != expectedSource {
|
if gotSource != expectedSource {
|
||||||
t.Errorf("expected : %s, got : %s", expectedSource, gotSource)
|
t.Errorf("expected : %s, got : %s", expectedSource, gotSource)
|
||||||
}
|
}
|
||||||
@@ -204,7 +205,7 @@ func TestNamespaceForceUnlockTest(t *testing.T) {
|
|||||||
isDistXL := false
|
isDistXL := false
|
||||||
initNSLock(isDistXL)
|
initNSLock(isDistXL)
|
||||||
// Create lock.
|
// Create lock.
|
||||||
lock := globalNSMutex.NewNSLock("bucket", "object")
|
lock := globalNSMutex.NewNSLock(context.Background(), "bucket", "object")
|
||||||
if lock.GetLock(newDynamicTimeout(60*time.Second, time.Second)) != nil {
|
if lock.GetLock(newDynamicTimeout(60*time.Second, time.Second)) != nil {
|
||||||
t.Fatalf("Failed to get lock")
|
t.Fatalf("Failed to get lock")
|
||||||
}
|
}
|
||||||
@@ -215,7 +216,7 @@ func TestNamespaceForceUnlockTest(t *testing.T) {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Try to claim lock again.
|
// Try to claim lock again.
|
||||||
anotherLock := globalNSMutex.NewNSLock("bucket", "object")
|
anotherLock := globalNSMutex.NewNSLock(context.Background(), "bucket", "object")
|
||||||
if anotherLock.GetLock(newDynamicTimeout(60*time.Second, time.Second)) != nil {
|
if anotherLock.GetLock(newDynamicTimeout(60*time.Second, time.Second)) != nil {
|
||||||
t.Errorf("Failed to get lock")
|
t.Errorf("Failed to get lock")
|
||||||
return
|
return
|
||||||
|
|||||||
+3
-3
@@ -190,10 +190,10 @@ func isHostIP(ipAddress string) bool {
|
|||||||
return net.ParseIP(host) != nil
|
return net.ParseIP(host) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkPortAvailability - check if given port is already in use.
|
// checkPortAvailability - check if given host and port is already in use.
|
||||||
// Note: The check method tries to listen on given port and closes it.
|
// Note: The check method tries to listen on given port and closes it.
|
||||||
// It is possible to have a disconnected client in this tiny window of time.
|
// It is possible to have a disconnected client in this tiny window of time.
|
||||||
func checkPortAvailability(port string) (err error) {
|
func checkPortAvailability(host, port string) (err error) {
|
||||||
// Return true if err is "address already in use" error.
|
// Return true if err is "address already in use" error.
|
||||||
isAddrInUseErr := func(err error) (b bool) {
|
isAddrInUseErr := func(err error) (b bool) {
|
||||||
if opErr, ok := err.(*net.OpError); ok {
|
if opErr, ok := err.(*net.OpError); ok {
|
||||||
@@ -209,7 +209,7 @@ func checkPortAvailability(port string) (err error) {
|
|||||||
|
|
||||||
network := []string{"tcp", "tcp4", "tcp6"}
|
network := []string{"tcp", "tcp4", "tcp6"}
|
||||||
for _, n := range network {
|
for _, n := range network {
|
||||||
l, err := net.Listen(n, net.JoinHostPort("", port))
|
l, err := net.Listen(n, net.JoinHostPort(host, port))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// As we are able to listen on this network, the port is not in use.
|
// As we are able to listen on this network, the port is not in use.
|
||||||
// Close the listener and continue check other networks.
|
// Close the listener and continue check other networks.
|
||||||
|
|||||||
+5
-3
@@ -206,11 +206,13 @@ func TestCheckPortAvailability(t *testing.T) {
|
|||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
host string
|
||||||
port string
|
port string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{port, fmt.Errorf("listen tcp :%v: bind: address already in use", port)},
|
{"", port, fmt.Errorf("listen tcp :%v: bind: address already in use", port)},
|
||||||
{getFreePort(), nil},
|
{"127.0.0.1", port, fmt.Errorf("listen tcp 127.0.0.1:%v: bind: address already in use", port)},
|
||||||
|
{"", getFreePort(), nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
@@ -219,7 +221,7 @@ func TestCheckPortAvailability(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := checkPortAvailability(testCase.port)
|
err := checkPortAvailability(testCase.host, testCase.port)
|
||||||
if testCase.expectedErr == nil {
|
if testCase.expectedErr == nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: expected = <nil>, got = %v", err)
|
t.Fatalf("error: expected = <nil>, got = %v", err)
|
||||||
|
|||||||
+85
-3
@@ -34,6 +34,7 @@ import (
|
|||||||
"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/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"github.com/minio/minio/pkg/policy"
|
"github.com/minio/minio/pkg/policy"
|
||||||
)
|
)
|
||||||
@@ -157,6 +158,66 @@ func (sys *NotificationSys) ReloadFormat(dryRun bool) []NotificationPeerErr {
|
|||||||
return ng.Wait()
|
return ng.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePolicy - deletes policy across all peers.
|
||||||
|
func (sys *NotificationSys) DeletePolicy(policyName string) []NotificationPeerErr {
|
||||||
|
ng := WithNPeers(len(sys.peerClients))
|
||||||
|
for idx, client := range sys.peerClients {
|
||||||
|
if client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client := client
|
||||||
|
ng.Go(context.Background(), func() error {
|
||||||
|
return client.DeletePolicy(policyName)
|
||||||
|
}, idx, *client.host)
|
||||||
|
}
|
||||||
|
return ng.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPolicy - reloads a specific modified policy across all peers
|
||||||
|
func (sys *NotificationSys) LoadPolicy(policyName string) []NotificationPeerErr {
|
||||||
|
ng := WithNPeers(len(sys.peerClients))
|
||||||
|
for idx, client := range sys.peerClients {
|
||||||
|
if client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client := client
|
||||||
|
ng.Go(context.Background(), func() error {
|
||||||
|
return client.LoadPolicy(policyName)
|
||||||
|
}, idx, *client.host)
|
||||||
|
}
|
||||||
|
return ng.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser - deletes a specific user across all peers
|
||||||
|
func (sys *NotificationSys) DeleteUser(accessKey string) []NotificationPeerErr {
|
||||||
|
ng := WithNPeers(len(sys.peerClients))
|
||||||
|
for idx, client := range sys.peerClients {
|
||||||
|
if client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client := client
|
||||||
|
ng.Go(context.Background(), func() error {
|
||||||
|
return client.DeleteUser(accessKey)
|
||||||
|
}, idx, *client.host)
|
||||||
|
}
|
||||||
|
return ng.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUser - reloads a specific user across all peers
|
||||||
|
func (sys *NotificationSys) LoadUser(accessKey string, temp bool) []NotificationPeerErr {
|
||||||
|
ng := WithNPeers(len(sys.peerClients))
|
||||||
|
for idx, client := range sys.peerClients {
|
||||||
|
if client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client := client
|
||||||
|
ng.Go(context.Background(), func() error {
|
||||||
|
return client.LoadUser(accessKey, temp)
|
||||||
|
}, idx, *client.host)
|
||||||
|
}
|
||||||
|
return ng.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// LoadUsers - calls LoadUsers RPC call on all peers.
|
// LoadUsers - calls LoadUsers RPC call on all peers.
|
||||||
func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
|
func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
|
||||||
ng := WithNPeers(len(sys.peerClients))
|
ng := WithNPeers(len(sys.peerClients))
|
||||||
@@ -170,6 +231,24 @@ func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
|
|||||||
return ng.Wait()
|
return ng.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BackgroundHealStatus - returns background heal status of all peers
|
||||||
|
func (sys *NotificationSys) BackgroundHealStatus() []madmin.BgHealState {
|
||||||
|
states := make([]madmin.BgHealState, len(sys.peerClients))
|
||||||
|
for idx, client := range sys.peerClients {
|
||||||
|
if client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
st, err := client.BackgroundHealStatus()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(context.Background(), err)
|
||||||
|
} else {
|
||||||
|
states[idx] = st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return states
|
||||||
|
}
|
||||||
|
|
||||||
// StartProfiling - start profiling on remote peers, by initiating a remote RPC.
|
// StartProfiling - start profiling on remote peers, by initiating a remote RPC.
|
||||||
func (sys *NotificationSys) StartProfiling(profiler string) []NotificationPeerErr {
|
func (sys *NotificationSys) StartProfiling(profiler string) []NotificationPeerErr {
|
||||||
ng := WithNPeers(len(sys.peerClients))
|
ng := WithNPeers(len(sys.peerClients))
|
||||||
@@ -523,7 +602,7 @@ func (sys *NotificationSys) initListeners(ctx context.Context, objAPI ObjectLaye
|
|||||||
// 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(ctx, minioMetaBucket, transactionConfigFile)
|
||||||
if err := objLock.GetRLock(globalOperationTimeout); err != nil {
|
if err := objLock.GetRLock(globalOperationTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -596,6 +675,9 @@ func (sys *NotificationSys) refresh(objAPI ObjectLayer) error {
|
|||||||
ctx := logger.SetReqInfo(context.Background(), &logger.ReqInfo{BucketName: bucket.Name})
|
ctx := logger.SetReqInfo(context.Background(), &logger.ReqInfo{BucketName: bucket.Name})
|
||||||
config, err := readNotificationConfig(ctx, objAPI, bucket.Name)
|
config, err := readNotificationConfig(ctx, objAPI, bucket.Name)
|
||||||
if err != nil && err != errNoSuchNotifications {
|
if err != nil && err != errNoSuchNotifications {
|
||||||
|
if _, ok := err.(*event.ErrARNNotFound); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err == errNoSuchNotifications {
|
if err == errNoSuchNotifications {
|
||||||
@@ -976,7 +1058,7 @@ func SaveListener(objAPI ObjectLayer, bucketName string, eventNames []event.Name
|
|||||||
// 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(ctx, minioMetaBucket, transactionConfigFile)
|
||||||
if err := objLock.GetLock(globalOperationTimeout); err != nil {
|
if err := objLock.GetLock(globalOperationTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1027,7 +1109,7 @@ func RemoveListener(objAPI ObjectLayer, bucketName string, targetID event.Target
|
|||||||
// 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(ctx, minioMetaBucket, transactionConfigFile)
|
||||||
if err := objLock.GetLock(globalOperationTimeout); err != nil {
|
if err := objLock.GetLock(globalOperationTimeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
)
|
)
|
||||||
@@ -234,7 +236,94 @@ func removeListenerConfig(ctx context.Context, objAPI ObjectLayer, bucket string
|
|||||||
return objAPI.DeleteObject(ctx, minioMetaBucket, lcPath)
|
return objAPI.DeleteObject(ctx, minioMetaBucket, lcPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listObjectsNonSlash(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, delimiter string, maxKeys int, tpool *TreeWalkPool, listDir ListDirFunc, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) (loi ListObjectsInfo, err error) {
|
||||||
|
endWalkCh := make(chan struct{})
|
||||||
|
defer close(endWalkCh)
|
||||||
|
recursive := true
|
||||||
|
walkResultCh := startTreeWalk(ctx, bucket, prefix, "", recursive, listDir, endWalkCh)
|
||||||
|
|
||||||
|
var objInfos []ObjectInfo
|
||||||
|
var eof bool
|
||||||
|
var prevPrefix string
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(objInfos) == maxKeys {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result, ok := <-walkResultCh
|
||||||
|
if !ok {
|
||||||
|
eof = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var objInfo ObjectInfo
|
||||||
|
var err error
|
||||||
|
|
||||||
|
index := strings.Index(strings.TrimPrefix(result.entry, prefix), delimiter)
|
||||||
|
if index == -1 {
|
||||||
|
objInfo, err = getObjInfo(ctx, bucket, result.entry)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore errFileNotFound as the object might have got
|
||||||
|
// deleted in the interim period of listing and getObjectInfo(),
|
||||||
|
// ignore quorum error as it might be an entry from an outdated disk.
|
||||||
|
if IsErrIgnored(err, []error{
|
||||||
|
errFileNotFound,
|
||||||
|
errXLReadQuorum,
|
||||||
|
}...) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return loi, toObjectErr(err, bucket, prefix)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index = len(prefix) + index + len(delimiter)
|
||||||
|
currPrefix := result.entry[:index]
|
||||||
|
if currPrefix == prevPrefix {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prevPrefix = currPrefix
|
||||||
|
|
||||||
|
objInfo = ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: currPrefix,
|
||||||
|
IsDir: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if objInfo.Name <= marker {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfos = append(objInfos, objInfo)
|
||||||
|
if result.end {
|
||||||
|
eof = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ListObjectsInfo{}
|
||||||
|
for _, objInfo := range objInfos {
|
||||||
|
if objInfo.IsDir {
|
||||||
|
result.Prefixes = append(result.Prefixes, objInfo.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.Objects = append(result.Objects, objInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !eof {
|
||||||
|
result.IsTruncated = true
|
||||||
|
if len(objInfos) > 0 {
|
||||||
|
result.NextMarker = objInfos[len(objInfos)-1].Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, delimiter string, maxKeys int, tpool *TreeWalkPool, listDir ListDirFunc, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) (loi ListObjectsInfo, err error) {
|
func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, delimiter string, maxKeys int, tpool *TreeWalkPool, listDir ListDirFunc, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) (loi ListObjectsInfo, err error) {
|
||||||
|
if delimiter != slashSeparator && delimiter != "" {
|
||||||
|
return listObjectsNonSlash(ctx, obj, bucket, prefix, marker, delimiter, maxKeys, tpool, listDir, getObjInfo, getObjectInfoDirs...)
|
||||||
|
}
|
||||||
|
|
||||||
if err := checkListObjsArgs(ctx, bucket, prefix, marker, delimiter, obj); err != nil {
|
if err := checkListObjsArgs(ctx, bucket, prefix, marker, delimiter, obj); err != nil {
|
||||||
return loi, err
|
return loi, err
|
||||||
}
|
}
|
||||||
@@ -272,7 +361,7 @@ func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, d
|
|||||||
recursive = false
|
recursive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
walkResultCh, endWalkCh := tpool.Release(listParams{bucket, recursive, marker, prefix})
|
walkResultCh, endWalkCh := tpool.Release(listParams{bucket, recursive, marker, prefix, false})
|
||||||
if walkResultCh == nil {
|
if walkResultCh == nil {
|
||||||
endWalkCh = make(chan struct{})
|
endWalkCh = make(chan struct{})
|
||||||
walkResultCh = startTreeWalk(ctx, bucket, prefix, marker, recursive, listDir, endWalkCh)
|
walkResultCh = startTreeWalk(ctx, bucket, prefix, marker, recursive, listDir, endWalkCh)
|
||||||
@@ -333,7 +422,7 @@ func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save list routine for the next marker if we haven't reached EOF.
|
// Save list routine for the next marker if we haven't reached EOF.
|
||||||
params := listParams{bucket, recursive, nextMarker, prefix}
|
params := listParams{bucket, recursive, nextMarker, prefix, false}
|
||||||
if !eof {
|
if !eof {
|
||||||
tpool.Set(params, walkResultCh, endWalkCh)
|
tpool.Set(params, walkResultCh, endWalkCh)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,9 +90,11 @@ type ObjectLayer interface {
|
|||||||
HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error)
|
HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error)
|
||||||
HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error)
|
HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error)
|
||||||
HealObject(ctx context.Context, bucket, object string, dryRun, remove bool, scanMode madmin.HealScanMode) (madmin.HealResultItem, error)
|
HealObject(ctx context.Context, bucket, object string, dryRun, remove bool, scanMode madmin.HealScanMode) (madmin.HealResultItem, error)
|
||||||
ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error)
|
|
||||||
HealObjects(ctx context.Context, bucket, prefix string, healObjectFn func(string, string) error) error
|
HealObjects(ctx context.Context, bucket, prefix string, healObjectFn func(string, string) error) error
|
||||||
|
|
||||||
|
ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error)
|
||||||
|
ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error)
|
||||||
|
|
||||||
// Policy operations
|
// Policy operations
|
||||||
SetBucketPolicy(context.Context, string, *policy.Policy) error
|
SetBucketPolicy(context.Context, string, *policy.Policy) error
|
||||||
GetBucketPolicy(context.Context, string) (*policy.Policy, error)
|
GetBucketPolicy(context.Context, string) (*policy.Policy, error)
|
||||||
|
|||||||
@@ -471,10 +471,6 @@ func testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler) {
|
|||||||
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||||
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||||
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||||
// Valid, existing bucket, but sending invalid delimeter values (9-10).
|
|
||||||
// Empty string < "" > and forward slash < / > are the ony two valid arguments for delimeter.
|
|
||||||
{"test-bucket-list-object", "", "", "*", 0, ListObjectsInfo{}, fmt.Errorf("delimiter '%s' is not supported", "*"), false},
|
|
||||||
{"test-bucket-list-object", "", "", "-", 0, ListObjectsInfo{}, fmt.Errorf("delimiter '%s' is not supported", "-"), false},
|
|
||||||
// Testing for failure cases with both perfix and marker (11).
|
// Testing for failure cases with both perfix and marker (11).
|
||||||
// The prefix and marker combination to be valid it should satisfy strings.HasPrefix(marker, prefix).
|
// The prefix and marker combination to be valid it should satisfy strings.HasPrefix(marker, prefix).
|
||||||
{"test-bucket-list-object", "asia", "europe-object", "", 0, ListObjectsInfo{}, fmt.Errorf("Invalid combination of marker '%s' and prefix '%s'", "europe-object", "asia"), false},
|
{"test-bucket-list-object", "asia", "europe-object", "", 0, ListObjectsInfo{}, fmt.Errorf("Invalid combination of marker '%s' and prefix '%s'", "europe-object", "asia"), false},
|
||||||
|
|||||||
+17
-1
@@ -36,6 +36,7 @@ import (
|
|||||||
snappy "github.com/golang/snappy"
|
snappy "github.com/golang/snappy"
|
||||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
"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/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
@@ -346,7 +347,7 @@ func isCompressible(header http.Header, object string) bool {
|
|||||||
// Eliminate the non-compressible objects.
|
// Eliminate the non-compressible objects.
|
||||||
func excludeForCompression(header http.Header, object string) bool {
|
func excludeForCompression(header http.Header, object string) bool {
|
||||||
objStr := object
|
objStr := object
|
||||||
contentType := header.Get("Content-Type")
|
contentType := header.Get(xhttp.ContentType)
|
||||||
if globalIsCompressionEnabled {
|
if globalIsCompressionEnabled {
|
||||||
// We strictly disable compression for standard extensions/content-types (`compressed`).
|
// We strictly disable compression for standard extensions/content-types (`compressed`).
|
||||||
if hasStringSuffixInSlice(objStr, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, contentType) {
|
if hasStringSuffixInSlice(objStr, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, contentType) {
|
||||||
@@ -793,3 +794,18 @@ func (cr *snappyCompressReader) Read(p []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns error if the cancelCh has been closed (indicating that S3 client has disconnected)
|
||||||
|
type detectDisconnect struct {
|
||||||
|
io.ReadCloser
|
||||||
|
cancelCh <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *detectDisconnect) Read(p []byte) (int, error) {
|
||||||
|
select {
|
||||||
|
case <-d.cancelCh:
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
default:
|
||||||
|
return d.ReadCloser.Read(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
)
|
)
|
||||||
@@ -49,7 +50,7 @@ func checkCopyObjectPartPreconditions(ctx context.Context, w http.ResponseWriter
|
|||||||
// x-amz-copy-source-if-none-match
|
// x-amz-copy-source-if-none-match
|
||||||
func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo, encETag string) bool {
|
func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo, encETag string) bool {
|
||||||
// Return false for methods other than GET and HEAD.
|
// Return false for methods other than GET and HEAD.
|
||||||
if r.Method != "PUT" {
|
if r.Method != http.MethodPut {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if encETag == "" {
|
if encETag == "" {
|
||||||
@@ -68,15 +69,15 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
|||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
|
|
||||||
// set object-related metadata headers
|
// set object-related metadata headers
|
||||||
w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat))
|
w.Header().Set(xhttp.LastModified, objInfo.ModTime.UTC().Format(http.TimeFormat))
|
||||||
|
|
||||||
if objInfo.ETag != "" {
|
if objInfo.ETag != "" {
|
||||||
w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
|
w.Header()[xhttp.ETag] = []string{"\"" + objInfo.ETag + "\""}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// x-amz-copy-source-if-modified-since: Return the object only if it has been modified
|
// x-amz-copy-source-if-modified-since: Return the object only if it has been modified
|
||||||
// since the specified time otherwise return 412 (precondition failed).
|
// since the specified time otherwise return 412 (precondition failed).
|
||||||
ifModifiedSinceHeader := r.Header.Get("x-amz-copy-source-if-modified-since")
|
ifModifiedSinceHeader := r.Header.Get(xhttp.AmzCopySourceIfModifiedSince)
|
||||||
if ifModifiedSinceHeader != "" {
|
if ifModifiedSinceHeader != "" {
|
||||||
if givenTime, err := time.Parse(http.TimeFormat, ifModifiedSinceHeader); err == nil {
|
if givenTime, err := time.Parse(http.TimeFormat, ifModifiedSinceHeader); err == nil {
|
||||||
if !ifModifiedSince(objInfo.ModTime, givenTime) {
|
if !ifModifiedSince(objInfo.ModTime, givenTime) {
|
||||||
@@ -90,7 +91,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
|||||||
|
|
||||||
// x-amz-copy-source-if-unmodified-since : Return the object only if it has not been
|
// x-amz-copy-source-if-unmodified-since : Return the object only if it has not been
|
||||||
// modified since the specified time, otherwise return a 412 (precondition failed).
|
// modified since the specified time, otherwise return a 412 (precondition failed).
|
||||||
ifUnmodifiedSinceHeader := r.Header.Get("x-amz-copy-source-if-unmodified-since")
|
ifUnmodifiedSinceHeader := r.Header.Get(xhttp.AmzCopySourceIfUnmodifiedSince)
|
||||||
if ifUnmodifiedSinceHeader != "" {
|
if ifUnmodifiedSinceHeader != "" {
|
||||||
if givenTime, err := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); err == nil {
|
if givenTime, err := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); err == nil {
|
||||||
if ifModifiedSince(objInfo.ModTime, givenTime) {
|
if ifModifiedSince(objInfo.ModTime, givenTime) {
|
||||||
@@ -106,7 +107,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
|||||||
|
|
||||||
// x-amz-copy-source-if-match : Return the object only if its entity tag (ETag) is the
|
// x-amz-copy-source-if-match : Return the object only if its entity tag (ETag) is the
|
||||||
// same as the one specified; otherwise return a 412 (precondition failed).
|
// same as the one specified; otherwise return a 412 (precondition failed).
|
||||||
ifMatchETagHeader := r.Header.Get("x-amz-copy-source-if-match")
|
ifMatchETagHeader := r.Header.Get(xhttp.AmzCopySourceIfMatch)
|
||||||
if ifMatchETagHeader != "" {
|
if ifMatchETagHeader != "" {
|
||||||
etag := objInfo.ETag
|
etag := objInfo.ETag
|
||||||
if shouldDecryptEtag {
|
if shouldDecryptEtag {
|
||||||
@@ -122,7 +123,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
|||||||
|
|
||||||
// If-None-Match : Return the object only if its entity tag (ETag) is different from the
|
// If-None-Match : Return the object only if its entity tag (ETag) is different from the
|
||||||
// one specified otherwise, return a 304 (not modified).
|
// one specified otherwise, return a 304 (not modified).
|
||||||
ifNoneMatchETagHeader := r.Header.Get("x-amz-copy-source-if-none-match")
|
ifNoneMatchETagHeader := r.Header.Get(xhttp.AmzCopySourceIfNoneMatch)
|
||||||
if ifNoneMatchETagHeader != "" {
|
if ifNoneMatchETagHeader != "" {
|
||||||
etag := objInfo.ETag
|
etag := objInfo.ETag
|
||||||
if shouldDecryptEtag {
|
if shouldDecryptEtag {
|
||||||
@@ -147,7 +148,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
|||||||
// If-None-Match
|
// If-None-Match
|
||||||
func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo) bool {
|
func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo) bool {
|
||||||
// Return false for methods other than GET and HEAD.
|
// Return false for methods other than GET and HEAD.
|
||||||
if r.Method != "GET" && r.Method != "HEAD" {
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// If the object doesn't have a modtime (IsZero), or the modtime
|
// If the object doesn't have a modtime (IsZero), or the modtime
|
||||||
@@ -163,15 +164,15 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
|
|
||||||
// set object-related metadata headers
|
// set object-related metadata headers
|
||||||
w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat))
|
w.Header().Set(xhttp.LastModified, objInfo.ModTime.UTC().Format(http.TimeFormat))
|
||||||
|
|
||||||
if objInfo.ETag != "" {
|
if objInfo.ETag != "" {
|
||||||
w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
|
w.Header()[xhttp.ETag] = []string{"\"" + objInfo.ETag + "\""}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If-Modified-Since : Return the object only if it has been modified since the specified time,
|
// If-Modified-Since : Return the object only if it has been modified since the specified time,
|
||||||
// otherwise return a 304 (not modified).
|
// otherwise return a 304 (not modified).
|
||||||
ifModifiedSinceHeader := r.Header.Get("If-Modified-Since")
|
ifModifiedSinceHeader := r.Header.Get(xhttp.IfModifiedSince)
|
||||||
if ifModifiedSinceHeader != "" {
|
if ifModifiedSinceHeader != "" {
|
||||||
if givenTime, err := time.Parse(http.TimeFormat, ifModifiedSinceHeader); err == nil {
|
if givenTime, err := time.Parse(http.TimeFormat, ifModifiedSinceHeader); err == nil {
|
||||||
if !ifModifiedSince(objInfo.ModTime, givenTime) {
|
if !ifModifiedSince(objInfo.ModTime, givenTime) {
|
||||||
@@ -185,7 +186,7 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// If-Unmodified-Since : Return the object only if it has not been modified since the specified
|
// If-Unmodified-Since : Return the object only if it has not been modified since the specified
|
||||||
// time, otherwise return a 412 (precondition failed).
|
// time, otherwise return a 412 (precondition failed).
|
||||||
ifUnmodifiedSinceHeader := r.Header.Get("If-Unmodified-Since")
|
ifUnmodifiedSinceHeader := r.Header.Get(xhttp.IfUnmodifiedSince)
|
||||||
if ifUnmodifiedSinceHeader != "" {
|
if ifUnmodifiedSinceHeader != "" {
|
||||||
if givenTime, err := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); err == nil {
|
if givenTime, err := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); err == nil {
|
||||||
if ifModifiedSince(objInfo.ModTime, givenTime) {
|
if ifModifiedSince(objInfo.ModTime, givenTime) {
|
||||||
@@ -199,7 +200,7 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// If-Match : Return the object only if its entity tag (ETag) is the same as the one specified;
|
// If-Match : Return the object only if its entity tag (ETag) is the same as the one specified;
|
||||||
// otherwise return a 412 (precondition failed).
|
// otherwise return a 412 (precondition failed).
|
||||||
ifMatchETagHeader := r.Header.Get("If-Match")
|
ifMatchETagHeader := r.Header.Get(xhttp.IfMatch)
|
||||||
if ifMatchETagHeader != "" {
|
if ifMatchETagHeader != "" {
|
||||||
if !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
|
if !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
|
||||||
// If the object ETag does not match with the specified ETag.
|
// If the object ETag does not match with the specified ETag.
|
||||||
@@ -211,7 +212,7 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// If-None-Match : Return the object only if its entity tag (ETag) is different from the
|
// If-None-Match : Return the object only if its entity tag (ETag) is different from the
|
||||||
// one specified otherwise, return a 304 (not modified).
|
// one specified otherwise, return a 304 (not modified).
|
||||||
ifNoneMatchETagHeader := r.Header.Get("If-None-Match")
|
ifNoneMatchETagHeader := r.Header.Get(xhttp.IfNoneMatch)
|
||||||
if ifNoneMatchETagHeader != "" {
|
if ifNoneMatchETagHeader != "" {
|
||||||
if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) {
|
if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) {
|
||||||
// If the object ETag matches with the specified ETag.
|
// If the object ETag matches with the specified ETag.
|
||||||
|
|||||||
+31
-27
@@ -36,6 +36,7 @@ import (
|
|||||||
miniogo "github.com/minio/minio-go/v6"
|
miniogo "github.com/minio/minio-go/v6"
|
||||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||||
"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"
|
||||||
@@ -50,12 +51,12 @@ import (
|
|||||||
|
|
||||||
// supportedHeadGetReqParams - supported request parameters for GET and HEAD presigned request.
|
// supportedHeadGetReqParams - supported request parameters for GET and HEAD presigned request.
|
||||||
var supportedHeadGetReqParams = map[string]string{
|
var supportedHeadGetReqParams = map[string]string{
|
||||||
"response-expires": "Expires",
|
"response-expires": xhttp.Expires,
|
||||||
"response-content-type": "Content-Type",
|
"response-content-type": xhttp.ContentType,
|
||||||
"response-cache-control": "Cache-Control",
|
"response-cache-control": xhttp.CacheControl,
|
||||||
"response-content-encoding": "Content-Encoding",
|
"response-content-encoding": xhttp.ContentEncoding,
|
||||||
"response-content-language": "Content-Language",
|
"response-content-language": xhttp.ContentLanguage,
|
||||||
"response-content-disposition": "Content-Disposition",
|
"response-content-disposition": xhttp.ContentDisposition,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -165,8 +166,8 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
|||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Key: object,
|
Key: object,
|
||||||
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,
|
||||||
})
|
})
|
||||||
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
|
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
|
||||||
} else {
|
} else {
|
||||||
@@ -207,8 +208,8 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
|||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Key: object,
|
Key: object,
|
||||||
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,
|
||||||
})
|
})
|
||||||
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
|
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
|
||||||
} else {
|
} else {
|
||||||
@@ -650,7 +651,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
// TODO: Reject requests where body/payload is present, for now we don't even read it.
|
// TODO: Reject requests where body/payload is present, for now we don't even read it.
|
||||||
|
|
||||||
// Read escaped copy source path to check for parameters.
|
// Read escaped copy source path to check for parameters.
|
||||||
cpSrcPath := r.Header.Get("X-Amz-Copy-Source")
|
cpSrcPath := r.Header.Get(xhttp.AmzCopySource)
|
||||||
|
|
||||||
// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
|
// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
|
||||||
// Regardless of whether you have enabled versioning, each object in your bucket
|
// Regardless of whether you have enabled versioning, each object in your bucket
|
||||||
@@ -668,7 +669,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
// Note that url.Parse does the unescaping
|
// Note that url.Parse does the unescaping
|
||||||
cpSrcPath = u.Path
|
cpSrcPath = u.Path
|
||||||
}
|
}
|
||||||
if vid := r.Header.Get("X-Amz-Copy-Source-Version-Id"); vid != "" {
|
if vid := r.Header.Get(xhttp.AmzCopySourceVersionID); vid != "" {
|
||||||
// Check if versionId header was added, if yes then check if
|
// Check if versionId header was added, if yes then check if
|
||||||
// its non "null" value, we should error out since we do not support
|
// its non "null" value, we should error out since we do not support
|
||||||
// any versions other than "null".
|
// any versions other than "null".
|
||||||
@@ -1052,7 +1053,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if crypto.S3KMS.IsRequested(r.Header) {
|
if crypto.S3KMS.IsRequested(r.Header) && !api.AllowSSEKMS() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1064,6 +1065,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
|
// To detect if the client has disconnected.
|
||||||
|
r.Body = &detectDisconnect{r.Body, r.Context().Done()}
|
||||||
|
|
||||||
// X-Amz-Copy-Source shouldn't be set for this call.
|
// X-Amz-Copy-Source shouldn't be set for this call.
|
||||||
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
|
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
|
||||||
@@ -1178,7 +1182,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This request header needs to be set prior to setting ObjectOptions
|
// This request header needs to be set prior to setting ObjectOptions
|
||||||
if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) {
|
if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) {
|
||||||
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1268,7 +1272,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
} else if hasServerSideEncryptionHeader(r.Header) {
|
} else if hasServerSideEncryptionHeader(r.Header) {
|
||||||
etag = getDecryptedETag(r.Header, objInfo, false)
|
etag = getDecryptedETag(r.Header, objInfo, false)
|
||||||
}
|
}
|
||||||
w.Header()["ETag"] = []string{"\"" + etag + "\""}
|
w.Header()[xhttp.ETag] = []string{"\"" + etag + "\""}
|
||||||
|
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if crypto.IsEncrypted(objInfo.UserDefined) {
|
if crypto.IsEncrypted(objInfo.UserDefined) {
|
||||||
@@ -1315,7 +1319,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if crypto.S3KMS.IsRequested(r.Header) {
|
if crypto.S3KMS.IsRequested(r.Header) && !api.AllowSSEKMS() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1333,7 +1337,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This request header needs to be set prior to setting ObjectOptions
|
// This request header needs to be set prior to setting ObjectOptions
|
||||||
if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) {
|
if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) {
|
||||||
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1450,7 +1454,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read escaped copy source path to check for parameters.
|
// Read escaped copy source path to check for parameters.
|
||||||
cpSrcPath := r.Header.Get("X-Amz-Copy-Source")
|
cpSrcPath := r.Header.Get(xhttp.AmzCopySource)
|
||||||
|
|
||||||
// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
|
// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
|
||||||
// Regardless of whether you have enabled versioning, each object in your bucket
|
// Regardless of whether you have enabled versioning, each object in your bucket
|
||||||
@@ -1468,7 +1472,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
|||||||
// Note that url.Parse does the unescaping
|
// Note that url.Parse does the unescaping
|
||||||
cpSrcPath = u.Path
|
cpSrcPath = u.Path
|
||||||
}
|
}
|
||||||
if vid := r.Header.Get("X-Amz-Copy-Source-Version-Id"); vid != "" {
|
if vid := r.Header.Get(xhttp.AmzCopySourceVersionID); vid != "" {
|
||||||
// Check if X-Amz-Copy-Source-Version-Id header was added, if yes then check if
|
// Check if X-Amz-Copy-Source-Version-Id header was added, if yes then check if
|
||||||
// its non "null" value, we should error out since we do not support
|
// its non "null" value, we should error out since we do not support
|
||||||
// any versions other than "null".
|
// any versions other than "null".
|
||||||
@@ -1537,7 +1541,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
// Get request range.
|
// Get request range.
|
||||||
var rs *HTTPRangeSpec
|
var rs *HTTPRangeSpec
|
||||||
rangeHeader := r.Header.Get("x-amz-copy-source-range")
|
rangeHeader := r.Header.Get(xhttp.AmzCopySourceRange)
|
||||||
if rangeHeader != "" {
|
if rangeHeader != "" {
|
||||||
var parseRangeErr error
|
var parseRangeErr error
|
||||||
if rs, parseRangeErr = parseCopyPartRangeSpec(rangeHeader); parseRangeErr != nil {
|
if rs, parseRangeErr = parseCopyPartRangeSpec(rangeHeader); parseRangeErr != nil {
|
||||||
@@ -1729,7 +1733,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
|||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
// X-Amz-Copy-Source shouldn't be set for this call.
|
// X-Amz-Copy-Source shouldn't be set for this call.
|
||||||
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
|
if _, ok := r.Header[xhttp.AmzCopySource]; ok {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1747,7 +1751,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
|||||||
rAuthType := getRequestAuthType(r)
|
rAuthType := getRequestAuthType(r)
|
||||||
// For auth type streaming signature, we need to gather a different content length.
|
// For auth type streaming signature, we need to gather a different content length.
|
||||||
if rAuthType == authTypeStreamingSigned {
|
if rAuthType == authTypeStreamingSigned {
|
||||||
if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok {
|
if sizeStr, ok := r.Header[xhttp.AmzDecodedContentLength]; ok {
|
||||||
if sizeStr[0] == "" {
|
if sizeStr[0] == "" {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@@ -1949,7 +1953,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
|||||||
if isEncrypted {
|
if isEncrypted {
|
||||||
etag = tryDecryptETag(objectEncryptionKey, partInfo.ETag, crypto.SSEC.IsRequested(r.Header))
|
etag = tryDecryptETag(objectEncryptionKey, partInfo.ETag, crypto.SSEC.IsRequested(r.Header))
|
||||||
}
|
}
|
||||||
w.Header()["ETag"] = []string{"\"" + etag + "\""}
|
w.Header()[xhttp.ETag] = []string{"\"" + etag + "\""}
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
@@ -2264,15 +2268,15 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
|||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, _ := xml.Marshal(errorResponse)
|
encodedErrorResponse, _ := xml.Marshal(errorResponse)
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
w.Header().Set("Content-Type", string(mimeXML))
|
w.Header().Set(xhttp.ContentType, string(mimeXML))
|
||||||
w.Write(encodedErrorResponse)
|
w.Write(encodedErrorResponse)
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
@@ -2307,7 +2311,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set etag.
|
// Set etag.
|
||||||
w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
|
w.Header()[xhttp.ETag] = []string{"\"" + objInfo.ETag + "\""}
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodedSuccessResponse)
|
writeSuccessResponseXML(w, encodedSuccessResponse)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
* MinIO Cloud Storage, (C) 2018, 2019 MinIO, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|||||||
+134
-3
@@ -23,13 +23,17 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"github.com/minio/minio/pkg/policy"
|
"github.com/minio/minio/pkg/policy"
|
||||||
|
trace "github.com/minio/minio/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// client to talk to peer Nodes.
|
// client to talk to peer Nodes.
|
||||||
@@ -51,9 +55,16 @@ func (client *peerRESTClient) reConnect() error {
|
|||||||
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
|
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
|
||||||
// after verifying format.json
|
// after verifying format.json
|
||||||
func (client *peerRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
func (client *peerRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
||||||
|
return client.callWithContext(context.Background(), method, values, body, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
||||||
|
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
|
||||||
|
// after verifying format.json
|
||||||
|
func (client *peerRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
||||||
if !client.connected {
|
if !client.connected {
|
||||||
err := client.reConnect()
|
err := client.reConnect()
|
||||||
logger.LogIf(context.Background(), err)
|
logger.LogIf(ctx, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -63,7 +74,7 @@ func (client *peerRESTClient) call(method string, values url.Values, body io.Rea
|
|||||||
values = make(url.Values)
|
values = make(url.Values)
|
||||||
}
|
}
|
||||||
|
|
||||||
respBody, err = client.restClient.Call(method, values, body, length)
|
respBody, err = client.restClient.CallWithContext(ctx, method, values, body, length)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return respBody, nil
|
return respBody, nil
|
||||||
}
|
}
|
||||||
@@ -202,7 +213,7 @@ func (client *peerRESTClient) ReloadFormat(dryRun bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenBucketNotification - send listent bucket notification to peer nodes.
|
// ListenBucketNotification - send listen bucket notification to peer nodes.
|
||||||
func (client *peerRESTClient) ListenBucketNotification(bucket string, eventNames []event.Name,
|
func (client *peerRESTClient) ListenBucketNotification(bucket string, eventNames []event.Name,
|
||||||
pattern string, targetID event.TargetID, addr xnet.Host) error {
|
pattern string, targetID event.TargetID, addr xnet.Host) error {
|
||||||
args := listenBucketNotificationReq{
|
args := listenBucketNotificationReq{
|
||||||
@@ -337,6 +348,59 @@ func (client *peerRESTClient) PutBucketNotification(bucket string, rulesMap even
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePolicy - delete a specific canned policy.
|
||||||
|
func (client *peerRESTClient) DeletePolicy(policyName string) (err error) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTPolicy, policyName)
|
||||||
|
|
||||||
|
respBody, err := client.call(peerRESTMethodDeletePolicy, values, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPolicy - reload a specific canned policy.
|
||||||
|
func (client *peerRESTClient) LoadPolicy(policyName string) (err error) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTPolicy, policyName)
|
||||||
|
|
||||||
|
respBody, err := client.call(peerRESTMethodLoadPolicy, values, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser - delete a specific user.
|
||||||
|
func (client *peerRESTClient) DeleteUser(accessKey string) (err error) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTUser, accessKey)
|
||||||
|
|
||||||
|
respBody, err := client.call(peerRESTMethodDeleteUser, values, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUser - reload a specific user.
|
||||||
|
func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTUser, accessKey)
|
||||||
|
values.Set(peerRESTUserTemp, strconv.FormatBool(temp))
|
||||||
|
|
||||||
|
respBody, err := client.call(peerRESTMethodLoadUser, values, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadUsers - send load users command to peer nodes.
|
// LoadUsers - send load users command to peer nodes.
|
||||||
func (client *peerRESTClient) LoadUsers() (err error) {
|
func (client *peerRESTClient) LoadUsers() (err error) {
|
||||||
respBody, err := client.call(peerRESTMethodLoadUsers, nil, nil, -1)
|
respBody, err := client.call(peerRESTMethodLoadUsers, nil, nil, -1)
|
||||||
@@ -359,6 +423,73 @@ func (client *peerRESTClient) SignalService(sig serviceSignal) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client *peerRESTClient) BackgroundHealStatus() (madmin.BgHealState, error) {
|
||||||
|
respBody, err := client.call(peerRESTMethodBackgroundHealStatus, nil, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return madmin.BgHealState{}, err
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
|
||||||
|
state := madmin.BgHealState{}
|
||||||
|
err = gob.NewDecoder(respBody).Decode(&state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *peerRESTClient) doTrace(traceCh chan interface{}, doneCh chan struct{}, trcAll bool) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTTraceAll, strconv.FormatBool(trcAll))
|
||||||
|
|
||||||
|
// To cancel the REST request in case doneCh gets closed.
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
cancelCh := make(chan struct{})
|
||||||
|
defer close(cancelCh)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
case <-cancelCh:
|
||||||
|
// There was an error in the REST request.
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
respBody, err := client.callWithContext(ctx, peerRESTMethodTrace, values, nil, -1)
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := gob.NewDecoder(respBody)
|
||||||
|
for {
|
||||||
|
var info trace.Info
|
||||||
|
if err = dec.Decode(&info); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case traceCh <- info:
|
||||||
|
default:
|
||||||
|
// Do not block on slow receivers.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace - send http trace request to peer nodes
|
||||||
|
func (client *peerRESTClient) Trace(traceCh chan interface{}, doneCh chan struct{}, trcAll bool) {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
client.doTrace(traceCh, doneCh, trcAll)
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// There was error in the REST request, retry after sometime as probably the peer is down.
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
|
func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
|
||||||
var remoteHosts []*xnet.Host
|
var remoteHosts []*xnet.Host
|
||||||
for _, hostStr := range GetRemotePeers(endpoints) {
|
for _, hostStr := range GetRemotePeers(endpoints) {
|
||||||
|
|||||||
+11
-1
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
const peerRESTVersion = "v1"
|
const peerRESTVersion = "v2"
|
||||||
const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion
|
const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,8 +26,13 @@ const (
|
|||||||
peerRESTMethodDrivePerfInfo = "driveperfinfo"
|
peerRESTMethodDrivePerfInfo = "driveperfinfo"
|
||||||
peerRESTMethodDeleteBucket = "deletebucket"
|
peerRESTMethodDeleteBucket = "deletebucket"
|
||||||
peerRESTMethodSignalService = "signalservice"
|
peerRESTMethodSignalService = "signalservice"
|
||||||
|
peerRESTMethodBackgroundHealStatus = "backgroundhealstatus"
|
||||||
peerRESTMethodGetLocks = "getlocks"
|
peerRESTMethodGetLocks = "getlocks"
|
||||||
peerRESTMethodBucketPolicyRemove = "removebucketpolicy"
|
peerRESTMethodBucketPolicyRemove = "removebucketpolicy"
|
||||||
|
peerRESTMethodLoadUser = "loaduser"
|
||||||
|
peerRESTMethodDeleteUser = "deleteuser"
|
||||||
|
peerRESTMethodLoadPolicy = "loadpolicy"
|
||||||
|
peerRESTMethodDeletePolicy = "deletepolicy"
|
||||||
peerRESTMethodLoadUsers = "loadusers"
|
peerRESTMethodLoadUsers = "loadusers"
|
||||||
peerRESTMethodStartProfiling = "startprofiling"
|
peerRESTMethodStartProfiling = "startprofiling"
|
||||||
peerRESTMethodDownloadProfilingData = "downloadprofilingdata"
|
peerRESTMethodDownloadProfilingData = "downloadprofilingdata"
|
||||||
@@ -37,11 +42,16 @@ const (
|
|||||||
peerRESTMethodReloadFormat = "reloadformat"
|
peerRESTMethodReloadFormat = "reloadformat"
|
||||||
peerRESTMethodTargetExists = "targetexists"
|
peerRESTMethodTargetExists = "targetexists"
|
||||||
peerRESTMethodSendEvent = "sendevent"
|
peerRESTMethodSendEvent = "sendevent"
|
||||||
|
peerRESTMethodTrace = "trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
peerRESTBucket = "bucket"
|
peerRESTBucket = "bucket"
|
||||||
|
peerRESTUser = "user"
|
||||||
|
peerRESTUserTemp = "user-temp"
|
||||||
|
peerRESTPolicy = "policy"
|
||||||
peerRESTSignal = "signal"
|
peerRESTSignal = "signal"
|
||||||
peerRESTProfiler = "profiler"
|
peerRESTProfiler = "profiler"
|
||||||
peerRESTDryRun = "dry-run"
|
peerRESTDryRun = "dry-run"
|
||||||
|
peerRESTTraceAll = "all"
|
||||||
)
|
)
|
||||||
|
|||||||
+185
-1
@@ -23,14 +23,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"github.com/minio/minio/pkg/policy"
|
"github.com/minio/minio/pkg/policy"
|
||||||
|
trace "github.com/minio/minio/pkg/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To abstract a node over network.
|
// To abstract a node over network.
|
||||||
@@ -117,7 +120,125 @@ func (s *peerRESTServer) GetLocksHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadUsersHandler - returns server info.
|
// DeletePolicyHandler - deletes a policy on the server.
|
||||||
|
func (s *peerRESTServer) DeletePolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
s.writeErrorResponse(w, errServerNotInitialized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
policyName := vars[peerRESTPolicy]
|
||||||
|
if policyName == "" {
|
||||||
|
s.writeErrorResponse(w, errors.New("policyName is missing"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPolicyHandler - reloads a policy on the server.
|
||||||
|
func (s *peerRESTServer) LoadPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
s.writeErrorResponse(w, errServerNotInitialized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
policyName := vars[peerRESTPolicy]
|
||||||
|
if policyName == "" {
|
||||||
|
s.writeErrorResponse(w, errors.New("policyName is missing"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalIAMSys.LoadPolicy(objAPI, policyName); err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserHandler - deletes a user on the server.
|
||||||
|
func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
s.writeErrorResponse(w, errServerNotInitialized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
accessKey := vars[peerRESTUser]
|
||||||
|
if accessKey == "" {
|
||||||
|
s.writeErrorResponse(w, errors.New("username is missing"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUserHandler - reloads a user on the server.
|
||||||
|
func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
s.writeErrorResponse(w, errServerNotInitialized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
accessKey := vars[peerRESTUser]
|
||||||
|
if accessKey == "" {
|
||||||
|
s.writeErrorResponse(w, errors.New("username is missing"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
temp, err := strconv.ParseBool(vars[peerRESTUserTemp])
|
||||||
|
if err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = globalIAMSys.LoadUser(objAPI, accessKey, temp); err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUsersHandler - reloads all users and canned policies.
|
||||||
func (s *peerRESTServer) LoadUsersHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *peerRESTServer) LoadUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !s.IsValid(w, r) {
|
if !s.IsValid(w, r) {
|
||||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||||
@@ -547,6 +668,62 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TraceHandler sends http trace messages back to peer rest client
|
||||||
|
func (s *peerRESTServer) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trcAll := r.URL.Query().Get(peerRESTTraceAll) == "true"
|
||||||
|
|
||||||
|
w.Header().Set(xhttp.Connection, "close")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
|
||||||
|
filter := func(entry interface{}) bool {
|
||||||
|
if trcAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
trcInfo := entry.(trace.Info)
|
||||||
|
return !strings.HasPrefix(trcInfo.ReqInfo.Path, minioReservedBucketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
defer close(doneCh)
|
||||||
|
|
||||||
|
// Trace Publisher uses nonblocking publish and hence does not wait for slow subscribers.
|
||||||
|
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||||
|
ch := make(chan interface{}, 2000)
|
||||||
|
globalHTTPTrace.Subscribe(ch, doneCh, filter)
|
||||||
|
|
||||||
|
enc := gob.NewEncoder(w)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry := <-ch:
|
||||||
|
if err := enc.Encode(entry); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-r.Context().Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *peerRESTServer) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := newContext(r, w, "BackgroundHealStatus")
|
||||||
|
|
||||||
|
state := getLocalBackgroundHealStatus()
|
||||||
|
|
||||||
|
defer w.(http.Flusher).Flush()
|
||||||
|
logger.LogIf(ctx, gob.NewEncoder(w).Encode(state))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *peerRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
|
func (s *peerRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
@@ -576,6 +753,10 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
|||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||||
|
|
||||||
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
|
||||||
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
|
||||||
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...)
|
||||||
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser, peerRESTUserTemp)...)
|
||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUsers).HandlerFunc(httpTraceAll(server.LoadUsersHandler))
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUsers).HandlerFunc(httpTraceAll(server.LoadUsersHandler))
|
||||||
|
|
||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...)
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...)
|
||||||
@@ -588,5 +769,8 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
|||||||
|
|
||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodReloadFormat).HandlerFunc(httpTraceHdrs(server.ReloadFormatHandler)).Queries(restQueries(peerRESTDryRun)...)
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodReloadFormat).HandlerFunc(httpTraceHdrs(server.ReloadFormatHandler)).Queries(restQueries(peerRESTDryRun)...)
|
||||||
|
|
||||||
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodTrace).HandlerFunc(server.TraceHandler)
|
||||||
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBackgroundHealStatus).HandlerFunc(server.BackgroundHealStatusHandler)
|
||||||
|
|
||||||
router.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
|
router.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -85,9 +85,6 @@ func (sys *PolicySys) Remove(bucketName string) {
|
|||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
func (sys *PolicySys) IsAllowed(args policy.Args) bool {
|
func (sys *PolicySys) IsAllowed(args policy.Args) bool {
|
||||||
sys.RLock()
|
|
||||||
defer sys.RUnlock()
|
|
||||||
|
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
// When gateway is enabled, no cached value
|
// When gateway is enabled, no cached value
|
||||||
// is used to validate bucket policies.
|
// is used to validate bucket policies.
|
||||||
@@ -99,6 +96,9 @@ func (sys *PolicySys) IsAllowed(args policy.Args) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
|
||||||
// If policy is available for given bucket, check the policy.
|
// If policy is available for given bucket, check the policy.
|
||||||
if p, found := sys.bucketPolicyMap[args.BucketName]; found {
|
if p, found := sys.bucketPolicyMap[args.BucketName]; found {
|
||||||
return p.IsAllowed(args)
|
return p.IsAllowed(args)
|
||||||
@@ -114,7 +114,6 @@ func (sys *PolicySys) IsAllowed(args policy.Args) bool {
|
|||||||
func (sys *PolicySys) refresh(objAPI ObjectLayer) error {
|
func (sys *PolicySys) refresh(objAPI ObjectLayer) error {
|
||||||
buckets, err := objAPI.ListBuckets(context.Background())
|
buckets, err := objAPI.ListBuckets(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(context.Background(), err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sys.removeDeletedBuckets(buckets)
|
sys.removeDeletedBuckets(buckets)
|
||||||
|
|||||||
+9
-3
@@ -52,13 +52,13 @@ type Client struct {
|
|||||||
newAuthToken func() string
|
newAuthToken func() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call - make a REST call.
|
// CallWithContext - make a REST call with context.
|
||||||
func (c *Client) Call(method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
|
func (c *Client) CallWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
|
||||||
req, err := http.NewRequest(http.MethodPost, c.url.String()+"/"+method+"?"+values.Encode(), body)
|
req, err := http.NewRequest(http.MethodPost, c.url.String()+"/"+method+"?"+values.Encode(), body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &NetworkError{err}
|
return nil, &NetworkError{err}
|
||||||
}
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
req.Header.Set("Authorization", "Bearer "+c.newAuthToken())
|
req.Header.Set("Authorization", "Bearer "+c.newAuthToken())
|
||||||
req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
@@ -84,6 +84,12 @@ func (c *Client) Call(method string, values url.Values, body io.Reader, length i
|
|||||||
return resp.Body, nil
|
return resp.Body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call - make a REST call.
|
||||||
|
func (c *Client) Call(method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return c.CallWithContext(ctx, method, values, body, length)
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes all idle connections of the underlying http client
|
// Close closes all idle connections of the underlying http client
|
||||||
func (c *Client) Close() {
|
func (c *Client) Close() {
|
||||||
if c.httpIdleConnsCloser != nil {
|
if c.httpIdleConnsCloser != nil {
|
||||||
|
|||||||
+4
-3
@@ -48,7 +48,7 @@ func registerDistXLRouters(router *mux.Router, endpoints EndpointList) {
|
|||||||
|
|
||||||
// List of some generic handlers which are applied for all incoming requests.
|
// List of some generic handlers which are applied for all incoming requests.
|
||||||
var globalHandlers = []HandlerFunc{
|
var globalHandlers = []HandlerFunc{
|
||||||
// set x-amz-request-id, x-minio-deployment-id header.
|
// set x-amz-request-id header.
|
||||||
addCustomHeaders,
|
addCustomHeaders,
|
||||||
// set HTTP security headers such as Content-Security-Policy.
|
// set HTTP security headers such as Content-Security-Policy.
|
||||||
addSecurityHeaders,
|
addSecurityHeaders,
|
||||||
@@ -119,8 +119,9 @@ func configureServerHandler(endpoints EndpointList) (http.Handler, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add API router, additionally all server mode support encryption.
|
// Add API router, additionally all server mode support encryption
|
||||||
registerAPIRouter(router, true)
|
// but don't allow SSE-KMS.
|
||||||
|
registerAPIRouter(router, true, false)
|
||||||
|
|
||||||
// Register rest of the handlers.
|
// Register rest of the handlers.
|
||||||
return registerHandlers(router, globalHandlers...), nil
|
return registerHandlers(router, globalHandlers...), nil
|
||||||
|
|||||||
+16
-5
@@ -27,7 +27,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/dsync"
|
"github.com/minio/dsync/v2"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/certs"
|
"github.com/minio/minio/pkg/certs"
|
||||||
@@ -38,7 +38,8 @@ func init() {
|
|||||||
logger.RegisterUIError(fmtError)
|
logger.RegisterUIError(fmtError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverFlags = []cli.Flag{
|
// ServerFlags - server command specific flags
|
||||||
|
var ServerFlags = []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "address",
|
Name: "address",
|
||||||
Value: ":" + globalMinioDefaultPort,
|
Value: ":" + globalMinioDefaultPort,
|
||||||
@@ -49,7 +50,7 @@ var serverFlags = []cli.Flag{
|
|||||||
var serverCmd = cli.Command{
|
var serverCmd = cli.Command{
|
||||||
Name: "server",
|
Name: "server",
|
||||||
Usage: "start object storage server",
|
Usage: "start object storage server",
|
||||||
Flags: append(serverFlags, globalFlags...),
|
Flags: append(ServerFlags, GlobalFlags...),
|
||||||
Action: serverMain,
|
Action: serverMain,
|
||||||
CustomHelpTemplate: `NAME:
|
CustomHelpTemplate: `NAME:
|
||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
@@ -175,7 +176,7 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
|||||||
// 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 sutiation we check for port availability.
|
// To avoid this error sutiation we check for port availability.
|
||||||
logger.FatalIf(checkPortAvailability(globalMinioPort), "Unable to start the server")
|
logger.FatalIf(checkPortAvailability(globalMinioHost, globalMinioPort), "Unable to start the server")
|
||||||
|
|
||||||
globalIsXL = (setupType == XLSetupType)
|
globalIsXL = (setupType == XLSetupType)
|
||||||
globalIsDistXL = (setupType == DistXLSetupType)
|
globalIsDistXL = (setupType == DistXLSetupType)
|
||||||
@@ -287,8 +288,11 @@ func serverMain(ctx *cli.Context) {
|
|||||||
// Initialize name space lock.
|
// Initialize name space lock.
|
||||||
initNSLock(globalIsDistXL)
|
initNSLock(globalIsDistXL)
|
||||||
|
|
||||||
|
if globalIsXL {
|
||||||
// Init global heal state
|
// Init global heal state
|
||||||
initAllHealState(globalIsXL)
|
globalAllHealState = initHealState()
|
||||||
|
globalSweepHealState = initHealState()
|
||||||
|
}
|
||||||
|
|
||||||
// Configure server.
|
// Configure server.
|
||||||
var handler http.Handler
|
var handler http.Handler
|
||||||
@@ -310,6 +314,7 @@ func serverMain(ctx *cli.Context) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
newObject, err := newObjectLayer(globalEndpoints)
|
newObject, err := newObjectLayer(globalEndpoints)
|
||||||
|
logger.SetDeploymentID(globalDeploymentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Stop watching for any certificate changes.
|
// Stop watching for any certificate changes.
|
||||||
globalTLSCerts.Stop()
|
globalTLSCerts.Stop()
|
||||||
@@ -369,6 +374,12 @@ func serverMain(ctx *cli.Context) {
|
|||||||
logger.Fatal(errors.New("Invalid KMS configuration"), "auto-encryption is enabled but server does not support encryption")
|
logger.Fatal(errors.New("Invalid KMS configuration"), "auto-encryption is enabled but server does not support encryption")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalIsXL {
|
||||||
|
initBackgroundHealing()
|
||||||
|
initDailyHeal()
|
||||||
|
initDailySweeper()
|
||||||
|
}
|
||||||
|
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalObjectAPI = newObject
|
globalObjectAPI = newObject
|
||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user