Compare commits

..

35 Commits

Author SHA1 Message Date
Kaan Kabalak 36c19f1d65 Check bucket & prefix after obj's for new bucket are fetched (#7838)
Fixes #7837
2019-06-27 12:13:18 -07:00
iliul d3f9f8be88 golint: fix redundant code logic (#7842)
Signed-off-by: Lei Liu <liul.stone@gmail.com>
2019-06-27 15:18:33 +05:30
Krishna Srinivas 183ec094c4 Simplify HTTP trace related code (#7833) 2019-06-26 22:41:12 -07:00
Harshavardhana c1d2b3d5c3 Handle HEAD/GET requests for virtual DNS requests (#7839)
r.URL.Path is empty when HEAD bucket with virtual
DNS requests come in since bucket is now part of
r.Host, we should use our domain names and fetch
the right bucket/object names.

This fixes an really old issue in our federation
setups.
2019-06-26 18:21:54 -07:00
Praveen raj Mani be72609d1f Expose version info in prometheus (#7812)
Fixes #7795
2019-06-26 10:36:54 -07:00
Anis Elleuch 48f2c98052 admin: Add Background heal status info API (#7774)
This API returns the information related to the self healing routine.

For the moment, it returns:
- The total number of objects that are scanned
- The last time when an item was scanned
2019-06-25 16:42:24 -07:00
Kanagaraj M 286c663495 list objects in browser ordered by last modified (#7805)
- return all objects in web-handlers listObjects response
- added local pagination to object list ui
- also fixed infinite loader and removed unused fields
2019-06-25 16:31:50 -07:00
Yao Zongyou 941fed8e4a s3Select: call Close on error to release the read lock (#7830) 2019-06-25 13:30:48 -07:00
Yao Zongyou 55092bede1 add timestamp compare support (#7832) 2019-06-25 11:05:37 -07:00
Yao Zongyou 90a3b830f4 fix typo and the string representation of the time.Time value (#7831) 2019-06-25 09:54:14 -07:00
Yao Zongyou 23b9df0694 Fix s3select TRIM function's nil pointer dereference bug (#7817) 2019-06-24 16:59:33 -07:00
Kanagaraj M 48cb271a46 include ip address while doing checkPortAvailability (#7818)
While checking for port availability, ip address should be included.
When a machine has multiple ip addresses, multiple minio instances
or some other applications can be run on same port but different
ip address.

Fixes #7685
2019-06-24 15:02:39 -07:00
Klaus Post 61229b38f7 Update compression README.md (#7823)
- Snappy is not and RLE compressor, it is LZ77 based.
- Add `xz` as a common file type.
- Add most common media container types.
- Never heard of `application/x-spoon`. Google turns up a blank as well.
- Change link to minio blog post on compression & encryption.
2019-06-23 13:52:58 +05:30
Harshavardhana 90ca73af13 Allow trace even if server is not initialized (#7822) 2019-06-21 16:47:51 -07:00
Daniel Valdivia a04b6561a0 Fix a typo on the comment for ListenBucketNotification (#7821) 2019-06-21 11:58:52 -07:00
Andreas Auernhammer 219d841496 remove encryption key section for certtool docs (#7820)
This commit removes the encryption key section from
the certool.exe docs because:
 - MinIO does not support any TLS cipher that encrypts
   something with the private key. We only support PFS
   ciphers.
 - The doc comment is not really accurate anyway.
2019-06-21 10:39:02 -07:00
dependabot[bot] baef49b4a2 Bump stringstream from 0.0.5 to 0.0.6 in /browser (#7816)
Bumps [stringstream](https://github.com/mhart/StringStream) from 0.0.5 to 0.0.6.
- [Release notes](https://github.com/mhart/StringStream/releases)
- [Commits](https://github.com/mhart/StringStream/compare/v0.0.5...v0.0.6)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-21 07:49:51 -07:00
Ashish Kumar Sinha 3022f60561 Stop duplicate entry in Notification.xml (#7690)
fixes #7671
2019-06-21 13:28:02 +05:30
poornas 680fdf6f90 Update doc link (#7814) 2019-06-20 16:49:21 -07:00
Harshavardhana 1af6e8cb72 Add support for session policies in STS APIs (#7747)
This PR adds support for adding session policies
for further restrictions on STS credentials, useful
in situations when applications want to generate
creds for multiple interested parties with different
set of policy restrictions.

This session policy is not mandatory, but optional.

Fixes #7732
2019-06-20 15:28:33 -07:00
Andreas Auernhammer 98d3913a1e enable SSE-KMS pass-through on S3 gateway (#7788)
This commit relaxes the restriction that the MinIO gateway
does not accept SSE-KMS headers. Now, the S3 gateway allows
SSE-KMS headers for PUT and MULTIPART PUT requests and forwards them
to the S3 gateway backend (AWS). This is considered SSE pass-through
mode.

Fixes #7753
2019-06-19 17:37:08 -07:00
Harshavardhana 35c38e4bd8 Add docs regarding trace (#7808) 2019-06-19 14:28:03 -07:00
Minio Trusted e43d3a075c Update yaml files to latest version RELEASE.2019-06-19T18-24-42Z 2019-06-19 18:31:37 +00:00
Harshavardhana 43e0ef4248 Update docs with new settings (#7803) 2019-06-18 17:52:27 -07:00
Harshavardhana cd7d5b59e5 Add DeleteUser() to generate events in etcd (#7804)
Fixes a regression introduced in 6d89435356

Fixes #7797
2019-06-18 15:44:23 -07:00
poornas 299ef9b188 Trace: Replace function name with API prefix (#7794)
This change is required for `Admin Trace`
2019-06-18 13:55:13 -07:00
Harshavardhana b30c436715 [notify] Make sure to return when quorum is missing (#7799)
Fixes a regression introduced in 510ec153b9
2019-06-18 09:23:33 -07:00
Nitish Tiwari 7001fe407f Check local address in healthcheck script to ensure correct MinIO port (#7787)
Fixes #7780
2019-06-17 00:12:36 -07:00
Minio Trusted 59f7266081 Update yaml files to latest version RELEASE.2019-06-15T23-07-18Z 2019-06-15 23:17:28 +00:00
Harshavardhana 99bf4d0c42 [sec] Match ${aws:username} exactly instead of prefix match (#7791)
This PR fixes a security issue where an IAM user based
on his policy is granted more privileges than restricted
by the users IAM policy.

This is due to an issue of prefix based Matcher() function
which was incorrectly matching prefix based on resource
prefixes instead of exact match.
2019-06-15 11:27:17 -07:00
Cody Maloney 7b8beecc81 Move lock to not surround pieces which don't use any internal members. (#7779)
Previously the read/write lock applied both for gateway use cases as
well the object store use case. Nothing from sys is touched or looked
at in the gateway usecase though, so we don't need to lock. Don't lock
to make the gateway policy getting a little more efficient, particularly
as where this is called from (checkRequestAuthType) is quite common.
2019-06-15 10:11:10 -07:00
Praveen raj Mani 510ec153b9 Refreshing notification system should not erase the rules-map of other buckets (#7758)
Fixes #7707
2019-06-15 03:14:27 -07:00
dependabot[bot] e29a37e95c Bump macaddress from 0.2.8 to 0.2.9 in /browser (#7781)
Bumps [macaddress](https://github.com/scravy/node-macaddress) from 0.2.8 to 0.2.9.
- [Release notes](https://github.com/scravy/node-macaddress/releases)
- [Commits](https://github.com/scravy/node-macaddress/compare/0.2.8...0.2.9)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-15 03:07:56 -07:00
Harshavardhana 4a4048fe27 Migrate minio etcd config to backend config (#7751)
etcd when used in federated setups, currently
mandates that all clusters should have same
config.json, which is too restrictive and makes
federation a restrictive environment.

This change makes it apparent that each cluster
needs to be independently managed if necessary
from `mc admin info` command line.

Each cluster with in federation can have their
own root credentials and as well as separate
regions. This way buckets get further restrictions
and allows for root creds to be not common
across clusters/data centers.

Existing data in etcd gets migrated to backend
on each clusters, upon start. Once done
users can change their config entries
independently.
2019-06-15 03:07:54 -07:00
Minio Trusted da2887f914 Update yaml files to latest version RELEASE.2019-06-13T01-41-13Z 2019-06-13 01:46:00 +00:00
87 changed files with 1700 additions and 992 deletions
+7
View File
@@ -31,3 +31,10 @@ export const SHARE_OBJECT_EXPIRY_MINUTES = 0
export const ACCESS_KEY_MIN_LENGTH = 3
export const SECRET_KEY_MIN_LENGTH = 8
export const SORT_BY_NAME = "name"
export const SORT_BY_SIZE = "size"
export const SORT_BY_LAST_MODIFIED = "last-modified"
export const SORT_ORDER_ASC = "asc"
export const SORT_ORDER_DESC = "desc"
+34 -17
View File
@@ -18,11 +18,19 @@ import React from "react"
import classNames from "classnames"
import { connect } from "react-redux"
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 = ({
sortNameOrder,
sortSizeOrder,
sortLastModifiedOrder,
sortedByName,
sortedBySize,
sortedByLastModified,
sortOrder,
sortObjects
}) => (
<div className="feb-container">
@@ -31,48 +39,54 @@ export const ObjectsHeader = ({
<div
className="fesl-item fesl-item-name"
id="sort-by-name"
onClick={() => sortObjects("name")}
onClick={() => sortObjects(SORT_BY_NAME)}
data-sort="name"
>
Name
<i
className={classNames({
"fesli-sort": true,
"fesli-sort--active": sortedByName,
fa: true,
"fa-sort-alpha-desc": sortNameOrder,
"fa-sort-alpha-asc": !sortNameOrder
"fa-sort-alpha-desc": sortedByName && sortOrder === SORT_ORDER_DESC,
"fa-sort-alpha-asc": sortedByName && sortOrder === SORT_ORDER_ASC
})}
/>
</div>
<div
className="fesl-item fesl-item-size"
id="sort-by-size"
onClick={() => sortObjects("size")}
onClick={() => sortObjects(SORT_BY_SIZE)}
data-sort="size"
>
Size
<i
className={classNames({
"fesli-sort": true,
"fesli-sort--active": sortedBySize,
fa: true,
"fa-sort-amount-desc": sortSizeOrder,
"fa-sort-amount-asc": !sortSizeOrder
"fa-sort-amount-desc":
sortedBySize && sortOrder === SORT_ORDER_DESC,
"fa-sort-amount-asc": sortedBySize && sortOrder === SORT_ORDER_ASC
})}
/>
</div>
<div
className="fesl-item fesl-item-modified"
id="sort-by-last-modified"
onClick={() => sortObjects("last-modified")}
onClick={() => sortObjects(SORT_BY_LAST_MODIFIED)}
data-sort="last-modified"
>
Last Modified
<i
className={classNames({
"fesli-sort": true,
"fesli-sort--active": sortedByLastModified,
fa: true,
"fa-sort-numeric-desc": sortLastModifiedOrder,
"fa-sort-numeric-asc": !sortLastModifiedOrder
"fa-sort-numeric-desc":
sortedByLastModified && sortOrder === SORT_ORDER_DESC,
"fa-sort-numeric-asc":
sortedByLastModified && sortOrder === SORT_ORDER_ASC
})}
/>
</div>
@@ -83,10 +97,10 @@ export const ObjectsHeader = ({
const mapStateToProps = state => {
return {
sortNameOrder: state.objects.sortBy == "name" && state.objects.sortOrder,
sortSizeOrder: state.objects.sortBy == "size" && state.objects.sortOrder,
sortLastModifiedOrder:
state.objects.sortBy == "last-modified" && state.objects.sortOrder
sortedByName: state.objects.sortBy == SORT_BY_NAME,
sortedBySize: state.objects.sortBy == SORT_BY_SIZE,
sortedByLastModified: state.objects.sortBy == SORT_BY_LAST_MODIFIED,
sortOrder: state.objects.sortOrder
}
}
@@ -96,4 +110,7 @@ const mapDispatchToProps = dispatch => {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ObjectsHeader)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ObjectsHeader)
+37 -23
View File
@@ -15,32 +15,52 @@
*/
import React from "react"
import classNames from "classnames"
import { connect } from "react-redux"
import InfiniteScroll from "react-infinite-scroller"
import * as actionsObjects from "./actions"
import ObjectsList from "./ObjectsList"
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() {
const { objects, isTruncated, currentBucket, loadObjects } = this.props
const { objects, listLoading } = this.props
const visibleObjects = objects.slice(0, this.state.page * 100)
return (
<div className="feb-container">
<div style={{ position: "relative" }}>
<InfiniteScroll
pageStart={0}
loadMore={() => loadObjects(true)}
hasMore={isTruncated}
loadMore={this.loadNextPage}
hasMore={objects.length > visibleObjects.length}
useWindow={true}
initialLoad={false}
>
<ObjectsList objects={objects} />
<ObjectsList objects={visibleObjects} />
</InfiniteScroll>
<div
className="text-center"
style={{ display: isTruncated && currentBucket ? "block" : "none" }}
>
<span>Loading...</span>
</div>
{listLoading && <div className="loading" />}
</div>
)
}
@@ -51,16 +71,10 @@ const mapStateToProps = state => {
currentBucket: state.buckets.currentBucket,
currentPrefix: state.objects.currentPrefix,
objects: state.objects.list,
isTruncated: state.objects.isTruncated
sortBy: state.objects.sortBy,
sortOrder: state.objects.sortOrder,
listLoading: state.objects.listLoading
}
}
const mapDispatchToProps = dispatch => {
return {
loadObjects: append => dispatch(actionsObjects.fetchObjects(append))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(
ObjectsListContainer
)
export default connect(mapStateToProps)(ObjectsListContainer)
@@ -17,6 +17,7 @@
import React from "react"
import { shallow } from "enzyme"
import { ObjectsHeader } from "../ObjectsHeader"
import { SORT_ORDER_ASC, SORT_ORDER_DESC } from "../../constants"
describe("ObjectsHeader", () => {
it("should render without crashing", () => {
@@ -24,44 +25,84 @@ describe("ObjectsHeader", () => {
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 wrapper = shallow(<ObjectsHeader sortObjects={sortObjects} />)
const wrapper = shallow(
<ObjectsHeader
sortObjects={sortObjects}
sortedByName={true}
sortOrder={SORT_ORDER_ASC}
/>
)
expect(
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-asc")
).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 wrapper = shallow(
<ObjectsHeader sortObjects={sortObjects} sortNameOrder={true} />
<ObjectsHeader
sortObjects={sortObjects}
sortedByName={true}
sortOrder={SORT_ORDER_DESC}
/>
)
expect(
wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-desc")
).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 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(
wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-desc")
).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 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(
wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-desc")
@@ -20,14 +20,13 @@ import { ObjectsListContainer } from "../ObjectsListContainer"
describe("ObjectsList", () => {
it("should render without crashing", () => {
shallow(<ObjectsListContainer loadObjects={jest.fn()} />)
shallow(<ObjectsListContainer objects={[]} />)
})
it("should render ObjectsList with objects", () => {
const wrapper = shallow(
<ObjectsListContainer
objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
loadObjects={jest.fn()}
/>
)
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(
<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 * as actionsObjects from "../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"
jest.mock("../../web", () => ({
@@ -37,8 +43,6 @@ jest.mock("../../web", () => ({
} else {
return Promise.resolve({
objects: [{ name: "test1" }, { name: "test2" }],
istruncated: false,
nextmarker: "test2",
writable: false
})
}
@@ -77,17 +81,11 @@ describe("Objects actions", () => {
const expectedActions = [
{
type: "objects/SET_LIST",
objects: [{ name: "test1" }, { name: "test2" }],
isTruncated: false,
marker: "test2"
objects: [{ name: "test1" }, { name: "test2" }]
}
]
store.dispatch(
actionsObjects.setList(
[{ name: "test1" }, { name: "test2" }],
"test2",
false
)
actionsObjects.setList([{ name: "test1" }, { name: "test2" }])
)
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
@@ -98,10 +96,10 @@ describe("Objects actions", () => {
const expectedActions = [
{
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()
expect(actions).toEqual(expectedActions)
})
@@ -111,10 +109,10 @@ describe("Objects actions", () => {
const expectedActions = [
{
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()
expect(actions).toEqual(expectedActions)
})
@@ -126,23 +124,26 @@ describe("Objects actions", () => {
})
const expectedActions = [
{
type: "objects/SET_LIST",
objects: [{ name: "test1" }, { name: "test2" }],
marker: "test2",
isTruncated: false
type: "objects/RESET_LIST"
},
{ listLoading: true, type: "objects/SET_LIST_LOADING" },
{
type: "objects/SET_SORT_BY",
sortBy: ""
sortBy: SORT_BY_LAST_MODIFIED
},
{
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",
prefixWritable: false
}
},
{ listLoading: false, type: "objects/SET_LIST_LOADING" }
]
return store.dispatch(actionsObjects.fetchObjects()).then(() => {
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", () => {
const store = mockStore({
buckets: { currentBucket: "test-deny" },
objects: { currentPrefix: "" }
})
const expectedActions = [
{
type: "objects/RESET_LIST"
},
{ listLoading: true, type: "objects/SET_LIST_LOADING" },
{
type: "alert/SET",
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(() => {
const actions = store.getActions()
@@ -213,28 +196,24 @@ describe("Objects actions", () => {
objects: {
list: [],
sortBy: "",
sortOrder: false,
isTruncated: false,
marker: ""
sortOrder: SORT_ORDER_ASC
}
})
const expectedActions = [
{
type: "objects/SET_SORT_BY",
sortBy: "name"
sortBy: SORT_BY_NAME
},
{
type: "objects/SET_SORT_ORDER",
sortOrder: true
sortOrder: SORT_ORDER_ASC
},
{
type: "objects/SET_LIST",
objects: [],
isTruncated: false,
marker: ""
objects: []
}
]
store.dispatch(actionsObjects.sortObjects("name"))
store.dispatch(actionsObjects.sortObjects(SORT_BY_NAME))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
@@ -246,6 +225,10 @@ describe("Objects actions", () => {
})
const expectedActions = [
{ type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" },
{
type: "objects/RESET_LIST"
},
{ listLoading: true, type: "objects/SET_LIST_LOADING" },
{ type: "objects/CHECKED_LIST_RESET" }
]
store.dispatch(actionsObjects.selectPrefix("abc/"))
@@ -16,17 +16,17 @@
import reducer from "../reducer"
import * as actions from "../actions"
import { SORT_ORDER_ASC, SORT_BY_NAME } from "../../constants"
describe("objects reducer", () => {
it("should return the initial state", () => {
const initialState = reducer(undefined, {})
expect(initialState).toEqual({
list: [],
listLoading: false,
sortBy: "",
sortOrder: false,
sortOrder: SORT_ORDER_ASC,
currentPrefix: "",
marker: "",
isTruncated: false,
prefixWritable: false,
shareObject: {
show: false,
@@ -40,37 +40,9 @@ describe("objects reducer", () => {
it("should handle SET_LIST", () => {
const newState = reducer(undefined, {
type: actions.SET_LIST,
objects: [{ name: "obj1" }, { name: "obj2" }],
marker: "obj2",
isTruncated: false
objects: [{ 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", () => {
@@ -98,30 +70,28 @@ describe("objects reducer", () => {
it("should handle SET_SORT_BY", () => {
const newState = reducer(undefined, {
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", () => {
const newState = reducer(undefined, {
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", () => {
const newState = reducer(
{ currentPrefix: "test1/", marker: "abc", isTruncated: true },
{ currentPrefix: "test1/" },
{
type: actions.SET_CURRENT_PREFIX,
prefix: "test2/"
}
)
expect(newState.currentPrefix).toEqual("test2/")
expect(newState.marker).toEqual("")
expect(newState.isTruncated).toBeFalsy()
})
it("should handle SET_PREFIX_WRITABLE", () => {
+92 -58
View File
@@ -16,15 +16,26 @@
import web from "../web"
import history from "../history"
import { sortObjectsByName, sortObjectsBySize, sortObjectsByDate } from "../utils"
import {
sortObjectsByName,
sortObjectsBySize,
sortObjectsByDate
} from "../utils"
import { getCurrentBucket } from "../buckets/selectors"
import { getCurrentPrefix, getCheckedList } from "./selectors"
import * as alertActions from "../alert/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 RESET_LIST = "object/RESET_LIST"
export const RESET_LIST = "objects/RESET_LIST"
export const APPEND_LIST = "objects/APPEND_LIST"
export const REMOVE = "objects/REMOVE"
export const SET_SORT_BY = "objects/SET_SORT_BY"
@@ -35,53 +46,63 @@ export const SET_SHARE_OBJECT = "objects/SET_SHARE_OBJECT"
export const CHECKED_LIST_ADD = "objects/CHECKED_LIST_ADD"
export const CHECKED_LIST_REMOVE = "objects/CHECKED_LIST_REMOVE"
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,
objects,
marker,
isTruncated
objects
})
export const resetList = () => ({
type: RESET_LIST
})
export const appendList = (objects, marker, isTruncated) => ({
type: APPEND_LIST,
objects,
marker,
isTruncated
export const setListLoading = listLoading => ({
type: SET_LIST_LOADING,
listLoading
})
export const fetchObjects = append => {
export const fetchObjects = () => {
return function(dispatch, getState) {
const {buckets: {currentBucket}, objects: {currentPrefix, marker}} = getState()
dispatch(resetList())
const {
buckets: { currentBucket },
objects: { currentPrefix }
} = getState()
if (currentBucket) {
dispatch(setListLoading(true))
return web
.ListObjects({
bucketName: currentBucket,
prefix: currentPrefix,
marker: append ? marker : ""
prefix: currentPrefix
})
.then(res => {
let objects = []
if (res.objects) {
objects = res.objects.map(object => {
return {
...object,
name: object.name.replace(currentPrefix, "")
}
})
// 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 = []
if (res.objects) {
objects = res.objects.map(object => {
return {
...object,
name: object.name.replace(currentPrefix, "")
}
})
}
const sortBy = SORT_BY_LAST_MODIFIED
const sortOrder = SORT_ORDER_DESC
dispatch(setSortBy(sortBy))
dispatch(setSortOrder(sortOrder))
const sortedList = sortObjectsList(objects, sortBy, sortOrder)
dispatch(setList(sortedList))
dispatch(setPrefixWritable(res.writable))
dispatch(setListLoading(false))
}
if (append) {
dispatch(appendList(objects, res.nextmarker, res.istruncated))
} else {
dispatch(setList(objects, res.nextmarker, res.istruncated))
dispatch(setSortBy(""))
dispatch(setSortOrder(false))
}
dispatch(setPrefixWritable(res.writable))
})
.catch(err => {
if (web.LoggedIn()) {
@@ -96,6 +117,7 @@ export const fetchObjects = append => {
} else {
history.push("/login")
}
dispatch(setListLoading(false))
})
}
}
@@ -103,26 +125,27 @@ export const fetchObjects = append => {
export const sortObjects = sortBy => {
return function(dispatch, getState) {
const {objects} = getState()
const sortOrder = objects.sortBy == sortBy ? !objects.sortOrder : true
const { objects } = getState()
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(setSortOrder(sortOrder))
let list
switch (sortBy) {
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 sortedList = sortObjectsList(objects.list, sortBy, sortOrder)
dispatch(setList(sortedList))
}
}
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 {
dispatch(showShareObject(object, `${location.host}` + '/' + `${currentBucket}` + '/' + encodeURI(objectName)))
dispatch(
showShareObject(
object,
`${location.host}` +
"/" +
`${currentBucket}` +
"/" +
encodeURI(objectName)
)
)
dispatch(
alertActions.set({
type: "success",
@@ -322,13 +354,14 @@ export const downloadCheckedObjects = () => {
}${minioBrowserPrefix}/zip?token=${res.token}`
downloadZip(requestUrl, req, dispatch)
})
.catch(err => dispatch(
alertActions.set({
type: "danger",
message: err.message
})
.catch(err =>
dispatch(
alertActions.set({
type: "danger",
message: err.message
})
)
)
)
}
}
}
@@ -351,7 +384,8 @@ const downloadZip = (url, req, dispatch) => {
var separator = req.prefix.length > 1 ? "-" : ""
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()
window.URL.revokeObjectURL(blobUrl)
+8 -16
View File
@@ -15,6 +15,7 @@
*/
import * as actionsObjects from "./actions"
import { SORT_ORDER_ASC } from "../constants"
const removeObject = (list, objectToRemove, lookup) => {
const idx = list.findIndex(object => lookup(object) === objectToRemove)
@@ -27,11 +28,10 @@ const removeObject = (list, objectToRemove, lookup) => {
export default (
state = {
list: [],
listLoading: false,
sortBy: "",
sortOrder: false,
sortOrder: SORT_ORDER_ASC,
currentPrefix: "",
marker: "",
isTruncated: false,
prefixWritable: false,
shareObject: {
show: false,
@@ -46,23 +46,17 @@ export default (
case actionsObjects.SET_LIST:
return {
...state,
list: action.objects,
marker: action.marker,
isTruncated: action.isTruncated
list: action.objects
}
case actionsObjects.RESET_LIST:
return {
...state,
list: [],
marker: "",
isTruncated: false
list: []
}
case actionsObjects.APPEND_LIST:
case actionsObjects.SET_LIST_LOADING:
return {
...state,
list: [...state.list, ...action.objects],
marker: action.marker,
isTruncated: action.isTruncated
listLoading: action.listLoading
}
case actionsObjects.REMOVE:
return {
@@ -82,9 +76,7 @@ export default (
case actionsObjects.SET_CURRENT_PREFIX:
return {
...state,
currentPrefix: action.prefix,
marker: "",
isTruncated: false
currentPrefix: action.prefix
}
case actionsObjects.SET_PREFIX_WRITABLE:
return {
+4 -4
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { minioBrowserPrefix } from "./constants.js"
import { minioBrowserPrefix, SORT_ORDER_DESC } from "./constants.js"
export const sortObjectsByName = (objects, order) => {
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
return 0
})
if (order) {
if (order === SORT_ORDER_DESC) {
folders = folders.reverse()
files = files.reverse()
}
@@ -40,7 +40,7 @@ export const sortObjectsBySize = (objects, order) => {
let folders = objects.filter(object => object.name.endsWith("/"))
let files = objects.filter(object => !object.name.endsWith("/"))
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]
}
@@ -51,7 +51,7 @@ export const sortObjectsByDate = (objects, order) => {
(a, b) =>
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]
}
+3
View File
@@ -43,6 +43,9 @@ header.fesl-row {
color: @dark-gray;
font-size: 14px;
}
& > .fesli-sort--active {
.opacity(0.5);
}
&:hover:not(.fesl-item-actions) {
background: lighten(@text-muted-color, 22%);
+37
View File
@@ -113,4 +113,41 @@
margin: 0;
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);
}
}
+7 -1
View File
@@ -100,4 +100,10 @@
List
--------------------------*/
@list-row-selected-bg: #fbf2bf;
@list-row-even-bg: #fafafa;
@list-row-even-bg: #fafafa;
/*--------------------------
Loading
---------------------------*/
@loading-track-bg: #eeeeee;
@loading-point-bg: #00303f;
+35 -52
View File
File diff suppressed because one or more lines are too long
+162 -119
View File
@@ -366,6 +366,11 @@ array-equal@^1.0.0:
version "1.0.0"
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:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@@ -398,6 +403,15 @@ array-unique@^0.3.2:
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
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:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -508,24 +522,25 @@ aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
babel-cli@^6.14.0:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.24.1.tgz#207cd705bba61489b2ea41b5312341cf6aca2283"
babel-cli@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1"
integrity sha1-UCq1SHTX24itALiHoGODzgPQAvE=
dependencies:
babel-core "^6.24.1"
babel-polyfill "^6.23.0"
babel-register "^6.24.1"
babel-runtime "^6.22.0"
commander "^2.8.1"
convert-source-map "^1.1.0"
babel-core "^6.26.0"
babel-polyfill "^6.26.0"
babel-register "^6.26.0"
babel-runtime "^6.26.0"
commander "^2.11.0"
convert-source-map "^1.5.0"
fs-readdir-recursive "^1.0.0"
glob "^7.0.0"
lodash "^4.2.0"
output-file-sync "^1.1.0"
path-is-absolute "^1.0.0"
glob "^7.1.2"
lodash "^4.17.4"
output-file-sync "^1.1.2"
path-is-absolute "^1.0.1"
slash "^1.0.0"
source-map "^0.5.0"
v8flags "^2.0.10"
source-map "^0.5.6"
v8flags "^2.1.1"
optionalDependencies:
chokidar "^1.6.1"
@@ -569,29 +584,30 @@ babel-core@^6.0.0, babel-core@^6.26.0:
slash "^1.0.0"
source-map "^0.5.6"
babel-core@^6.14.0, babel-core@^6.24.1:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729"
babel-core@^6.26.3:
version "6.26.3"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
dependencies:
babel-code-frame "^6.22.0"
babel-generator "^6.25.0"
babel-code-frame "^6.26.0"
babel-generator "^6.26.0"
babel-helpers "^6.24.1"
babel-messages "^6.23.0"
babel-register "^6.24.1"
babel-runtime "^6.22.0"
babel-template "^6.25.0"
babel-traverse "^6.25.0"
babel-types "^6.25.0"
babylon "^6.17.2"
convert-source-map "^1.1.0"
debug "^2.1.1"
json5 "^0.5.0"
lodash "^4.2.0"
minimatch "^3.0.2"
path-is-absolute "^1.0.0"
private "^0.1.6"
babel-register "^6.26.0"
babel-runtime "^6.26.0"
babel-template "^6.26.0"
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
convert-source-map "^1.5.1"
debug "^2.6.9"
json5 "^0.5.1"
lodash "^4.17.4"
minimatch "^3.0.4"
path-is-absolute "^1.0.1"
private "^0.1.8"
slash "^1.0.0"
source-map "^0.5.0"
source-map "^0.5.7"
babel-generator@^6.18.0, babel-generator@^6.26.0:
version "6.26.0"
@@ -606,19 +622,6 @@ babel-generator@^6.18.0, babel-generator@^6.26.0:
source-map "^0.5.6"
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:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.24.1.tgz#0ad7917e33c8d751e646daca4e77cc19377d2cbc"
@@ -985,6 +988,15 @@ babel-polyfill@6.23.0, babel-polyfill@^6.23.0:
core-js "^2.4.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:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
@@ -1038,18 +1050,6 @@ babel-preset-react@^6.11.1:
babel-plugin-transform-react-jsx-source "^6.22.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:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
@@ -1086,7 +1086,7 @@ babel-template@^6.16.0, babel-template@^6.26.0:
babylon "^6.18.0"
lodash "^4.17.4"
babel-template@^6.24.1, babel-template@^6.25.0:
babel-template@^6.24.1:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071"
dependencies:
@@ -1771,17 +1771,17 @@ commander@2.17.x, commander@~2.17.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
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"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe"
dependencies:
graceful-readlink ">= 1.0.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==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -1867,14 +1867,17 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
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:
version "1.5.1"
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:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -2144,7 +2147,7 @@ date-now@^0.1.4:
version "0.1.4"
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"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
@@ -2161,7 +2164,7 @@ debug@^0.7.4:
version "0.7.4"
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"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
dependencies:
@@ -2601,26 +2604,32 @@ enzyme-to-json@^3.3.0:
dependencies:
lodash "^4.17.4"
enzyme@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479"
enzyme@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6"
integrity sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==
dependencies:
array.prototype.flat "^1.2.1"
cheerio "^1.0.0-rc.2"
function.prototype.name "^1.0.3"
has "^1.0.1"
function.prototype.name "^1.1.0"
has "^1.0.3"
html-element-map "^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-regex "^1.0.4"
is-string "^1.0.4"
is-subset "^0.1.1"
lodash "^4.17.4"
object-inspect "^1.5.0"
lodash.escape "^4.0.1"
lodash.isequal "^4.5.0"
object-inspect "^1.6.0"
object-is "^1.0.1"
object.assign "^4.1.0"
object.entries "^1.0.4"
object.values "^1.0.4"
raf "^3.4.0"
rst-selector-parser "^2.2.3"
string.prototype.trim "^1.1.2"
errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
@@ -2636,6 +2645,18 @@ error-ex@^1.2.0:
dependencies:
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:
version "1.10.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
@@ -2655,7 +2676,7 @@ es-abstract@^1.6.1:
is-callable "^1.1.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"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
@@ -3347,18 +3368,19 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
function-bind@^1.1.1:
function-bind@^1.0.2, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
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"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
integrity sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==
dependencies:
define-properties "^1.1.2"
function-bind "^1.1.1"
@@ -3445,9 +3467,10 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob@^7.0.0, glob@^7.1.1, glob@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
glob@^7.0.3, glob@^7.0.5:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -3456,10 +3479,9 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.3, glob@^7.0.5:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
glob@^7.1.1, glob@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -3650,7 +3672,7 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
has@^1.0.1:
has@^1.0.1, has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
@@ -3764,6 +3786,13 @@ html-comment-regex@^1.1.0:
version "1.1.1"
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:
version "1.0.2"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
@@ -5093,6 +5122,11 @@ lodash.deburr@^3.0.0:
dependencies:
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:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@@ -5112,6 +5146,11 @@ lodash.isequal@^3.0:
lodash._baseisequal "^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:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@@ -5200,8 +5239,9 @@ lru-cache@^5.1.1:
yallist "^3.0.2"
macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
version "0.2.9"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.9.tgz#3579b8b9acd5b96b4553abf0f394185a86813cb3"
integrity sha512-k4F1JUof6cQXxNFzx3thLby4oJzXTXQueAOOts944Vqizn+Rjc2QNFenT9FJSLU1CH3PmrHRSyZs2E+Cqw+P2w==
make-dir@^1.0.0:
version "1.3.0"
@@ -5502,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:
minimist "0.0.8"
moment@^2.15.1:
version "2.18.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
moment@^2.24.0:
version "2.24.0"
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":
version "1.0.0"
@@ -5858,9 +5899,10 @@ object-inspect@^1.1.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.2.2.tgz#c82115e4fcc888aea14d64c22e4f17f6a70d5e5a"
object-inspect@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.5.0.tgz#9d876c11e40f485c79215670281b767488f9bfe3"
object-inspect@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
object-is@^1.0.1:
version "1.0.1"
@@ -6054,9 +6096,10 @@ osenv@^0.1.4:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
output-file-sync@^1.1.0:
output-file-sync@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76"
integrity sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=
dependencies:
graceful-fs "^4.1.4"
mkdirp "^0.5.1"
@@ -6630,7 +6673,7 @@ private@^0.1.6:
version "0.1.7"
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"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -7141,7 +7184,7 @@ regenerate@^1.2.1:
version "1.3.2"
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"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
@@ -7750,12 +7793,6 @@ source-map-support@^0.4.15:
dependencies:
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:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76"
@@ -7775,7 +7812,7 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.6:
source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -7969,6 +8006,15 @@ string-width@^2.0.0:
is-fullwidth-code-point "^2.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:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
@@ -7995,15 +8041,11 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringstream@~0.0.4:
stringstream@~0.0.4, stringstream@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
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:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -8546,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"
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
v8flags@^2.0.10:
v8flags@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
integrity sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=
dependencies:
user-home "^1.1.1"
+80 -7
View File
@@ -43,6 +43,7 @@ import (
"github.com/minio/minio/pkg/mem"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/quick"
trace "github.com/minio/minio/pkg/trace"
)
const (
@@ -785,6 +786,49 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
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
// Get config.json of this minio setup.
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
@@ -940,6 +984,12 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
accessKey := vars["accessKey"]
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to delete user.
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
if nerr.Err != nil {
@@ -1426,10 +1476,14 @@ func (a adminAPIHandlers) SetConfigKeysHandler(w http.ResponseWriter, r *http.Re
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "HTTPTrace")
trcAll := r.URL.Query().Get("all") == "true"
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
// 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
@@ -1438,14 +1492,33 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
doneCh := make(chan struct{})
defer close(doneCh)
traceCh := globalTrace.Trace(doneCh, trcAll)
// 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 := w.Write(entry); err != nil {
return
}
if _, err := w.Write([]byte("\n")); err != nil {
if err := enc.Encode(entry); err != nil {
return
}
w.(http.Flusher).Flush()
+28 -3
View File
@@ -131,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
// argument returns if a heal sequence actually exists.
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
@@ -335,6 +348,12 @@ type healSequence struct {
// the last result index sent to client
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
ctx context.Context
}
@@ -552,17 +571,20 @@ func (h *healSequence) queueHealTask(path string, healType madmin.HealItemType)
}
func (h *healSequence) healItemsFromSourceCh() error {
h.lastHealActivity = UTCNow()
// Start healing the config prefix.
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
return err
logger.LogIf(h.ctx, err)
}
// Start healing the bucket config prefix.
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
return err
logger.LogIf(h.ctx, err)
}
for path := range h.sourceCh {
var itemType madmin.HealItemType
switch {
case path == "/":
@@ -574,8 +596,11 @@ func (h *healSequence) healItemsFromSourceCh() error {
}
if err := h.queueHealTask(path, itemType); err != nil {
return err
logger.LogIf(h.ctx, err)
}
h.scannedItemsCount++
h.lastHealActivity = UTCNow()
}
return nil
+2
View File
@@ -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}/{prefix:.*}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
adminV1Router.Methods(http.MethodPost).Path("/background-heal/status").HandlerFunc(httpTraceAll(adminAPI.BackgroundHealStatusHandler))
/// Health operations
}
+6 -1
View File
@@ -28,10 +28,12 @@ type objectAPIHandlers struct {
CacheAPI func() CacheObjectLayer
// Returns true of handlers should interpret encryption.
EncryptionEnabled func() bool
// Returns true if handlers allow SSE-KMS encryption headers.
AllowSSEKMS func() bool
}
// registerAPIRouter - registers S3 compatible APIs.
func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) {
// Initialize API.
api := objectAPIHandlers{
ObjectAPI: newObjectLayerFn,
@@ -39,6 +41,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
EncryptionEnabled: func() bool {
return encryptionEnabled
},
AllowSSEKMS: func() bool {
return allowSSEKMS
},
}
// API Router
+33
View File
@@ -200,6 +200,39 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
if _, ok = v.(string); !ok {
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
}
+17 -52
View File
@@ -21,7 +21,6 @@ import (
"context"
"errors"
"fmt"
"time"
etcd "github.com/coreos/etcd/clientv3"
"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 {
_, err := client.Delete(ctx, configFile)
return err
timeoutCtx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
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 {
@@ -89,9 +99,11 @@ func readConfigEtcd(ctx context.Context, client *etcd.Client, configFile string)
resp, err := client.Get(timeoutCtx, configFile)
if err != nil {
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 {
return nil, errConfigNotFound
@@ -104,54 +116,7 @@ func readConfigEtcd(ctx context.Context, client *etcd.Client, configFile string)
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 {
if globalEtcdClient != nil {
return checkConfigEtcd(ctx, globalEtcdClient, configFile)
}
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil {
// Treat object not found as config not found.
if isErrObjectNotFound(err) {
+22 -13
View File
@@ -29,7 +29,7 @@ import (
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/event"
"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"
xnet "github.com/minio/minio/pkg/net"
"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'
// if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json'
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
// Construct path to config.json for the given bucket.
configFile := path.Join(minioConfigPrefix, minioConfigFile)
@@ -2423,10 +2424,14 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
}
defer func() {
// Rename config.json to config.json.deprecated only upon
// success of this function.
if err == nil {
os.Rename(getConfigFile(), getConfigFile()+".deprecated")
if globalEtcdClient != nil {
deleteConfigEtcd(context.Background(), globalEtcdClient, configFile)
} else {
// Rename config.json to config.json.deprecated only upon
// success of this function.
os.Rename(getConfigFile(), getConfigFile()+".deprecated")
}
}
}()
@@ -2446,20 +2451,24 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
return err
} // if errConfigNotFound proceed to migrate..
var configFiles = []string{
getConfigFile(),
getConfigFile() + ".deprecated",
configFile,
}
var config = &serverConfig{}
if _, err = Load(getConfigFile(), config); err != nil {
if !os.IsNotExist(err) {
return err
}
// Read from deprecate file as well if necessary.
if _, err = Load(getConfigFile()+".deprecated", config); err != nil {
for _, cfgFile := range configFiles {
if _, err = Load(cfgFile, config); err != nil {
if !os.IsNotExist(err) {
return err
}
// If all else fails simply initialize the server config.
return newSrvConfig(objAPI)
continue
}
break
}
if os.IsNotExist(err) {
// Initialize the server config, if no config exists.
return newSrvConfig(objAPI)
}
return saveServerConfig(context.Background(), objAPI, config)
}
+17 -48
View File
@@ -49,10 +49,6 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config *serverCon
}
configFile := path.Join(minioConfigPrefix, minioConfigFile)
if globalEtcdClient != nil {
return saveConfigEtcd(ctx, globalEtcdClient, configFile, data)
}
// Create a backup of the current config
oldData, err := readConfig(ctx, objAPI, configFile)
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) {
var configData []byte
var err error
configFile := path.Join(minioConfigPrefix, minioConfigFile)
if globalEtcdClient != nil {
configData, err = readConfigEtcd(ctx, globalEtcdClient, configFile)
} else {
configData, err = readConfig(ctx, objAPI, configFile)
}
configData, err := readConfig(ctx, objAPI, configFile)
if err != nil {
return nil, err
}
@@ -152,45 +141,25 @@ func initConfig(objAPI ObjectLayer) error {
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 err := migrateConfig(); err != nil {
return err
}
}
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
// to '<export_path>/.minio.sys/config/config.json'
// ignore if the file doesn't exist.
if err := migrateConfigToMinioSys(objAPI); err != nil {
return err
}
// Migrates backend '<export_path>/.minio.sys/config/config.json' to latest version.
if err := migrateMinioSysConfig(objAPI); err != nil {
if isFile(getConfigFile()) {
if err := migrateConfig(); err != nil {
return err
}
}
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
// to '<export_path>/.minio.sys/config/config.json'
// ignore if the file doesn't exist.
// If etcd is set then migrates /config/config.json
// to '<export_path>/.minio.sys/config/config.json'
if err := migrateConfigToMinioSys(objAPI); err != nil {
return err
}
// Migrates backend '<export_path>/.minio.sys/config/config.json' to latest version.
if err := migrateMinioSysConfig(objAPI); err != nil {
return err
}
return loadConfig(objAPI)
}
+20
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/json"
"net/http"
"strings"
)
@@ -125,6 +126,25 @@ func (s3KMS) IsRequested(h http.Header) bool {
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 (
// SSEC represents AWS SSE-C. It provides functionality to handle
// SSE-C requests.
+50
View File
@@ -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 {
Header http.Header
Expected bool
+12
View File
@@ -60,6 +60,18 @@ func newBgHealSequence(numDisks int) *healSequence {
}
}
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()
}
+11
View File
@@ -1238,6 +1238,17 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada
opts.UserDefined = metadata
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
return getDefaultOpts(r.Header, false, metadata)
}
+1 -4
View File
@@ -77,10 +77,7 @@ func (e *Erasure) DecodeDataBlocks(data [][]byte) error {
if !needsReconstruction {
return nil
}
if err := e.encoder.ReconstructData(data); err != nil {
return err
}
return nil
return e.encoder.ReconstructData(data)
}
// DecodeDataAndParityBlocks decodes the given erasure-coded data and verifies it.
+3 -5
View File
@@ -121,7 +121,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// to IPv6 address ie minio will start listening on IPv6 address whereas another
// (non-)minio process is listening on IPv4 of given port.
// 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.
var err error
@@ -158,9 +158,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
registerSTSRouter(router)
}
// initialize globalTrace system
globalTrace = NewTraceSys(context.Background(), globalEndpoints)
enableConfigOps := globalEtcdClient != nil && gatewayName == "nas"
enableIAMOps := globalEtcdClient != nil
@@ -181,9 +178,10 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// Currently only NAS and S3 gateway support encryption headers.
encryptionEnabled := gatewayName == "s3" || gatewayName == "nas"
allowSSEKMS := gatewayName == "s3" // Only S3 can support SSE-KMS (as pass-through)
// Add API router.
registerAPIRouter(router, encryptionEnabled)
registerAPIRouter(router, encryptionEnabled, allowSSEKMS)
var getCert certs.GetCertificateFunc
if globalTLSCerts != nil {
+2 -1
View File
@@ -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
if opts.ServerSideEncryption != nil &&
((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
}
if opts.ServerSideEncryption == nil {
+4 -3
View File
@@ -294,7 +294,7 @@ func (h minioReservedBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
default:
// For all other requests reject access to reserved
// buckets
bucketName, _ := urlPath2BucketObjectName(r.URL.Path)
bucketName, _ := request2BucketObjectName(r)
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL, guessIsBrowserReq(r))
return
@@ -494,7 +494,7 @@ var notimplementedObjectResourceNames = map[string]bool{
// Resource handler ServeHTTP() wrapper
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 != "" && objectName == "" {
@@ -696,7 +696,8 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
return
}
bucket, object := urlPath2BucketObjectName(r.URL.Path)
bucket, object := request2BucketObjectName(r)
// ListBucket requests should be handled at current endpoint as
// all buckets data can be fetched from here.
if r.Method == http.MethodGet && bucket == "" && object == "" {
+2 -1
View File
@@ -35,6 +35,7 @@ import (
"github.com/minio/minio/pkg/dns"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/iam/validator"
"github.com/minio/minio/pkg/pubsub"
)
// minio configuration related constants.
@@ -161,7 +162,7 @@ var (
// global Trace system to send HTTP request/response logs to
// registered listeners
globalTrace *HTTPTraceSys
globalHTTPTrace = pubsub.New()
globalEndpoints EndpointList
+4 -4
View File
@@ -326,24 +326,24 @@ func extractPostPolicyFormValues(ctx context.Context, form *multipart.Form) (fil
// Log headers and body.
func httpTraceAll(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !globalTrace.HasTraceListeners() {
if !globalHTTPTrace.HasSubscribers() {
f.ServeHTTP(w, r)
return
}
trace := Trace(f, true, w, r)
globalTrace.Publish(trace)
globalHTTPTrace.Publish(trace)
}
}
// Log only the headers.
func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !globalTrace.HasTraceListeners() {
if !globalHTTPTrace.HasSubscribers() {
f.ServeHTTP(w, r)
return
}
trace := Trace(f, false, w, r)
globalTrace.Publish(trace)
globalHTTPTrace.Publish(trace)
}
}
+18 -4
View File
@@ -118,12 +118,26 @@ func (r *recordResponseWriter) Body() []byte {
return r.body.Bytes()
}
// getOpName sanitizes the operation name for mc
func getOpName(name string) (op string) {
op = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
op = strings.TrimSuffix(op, "Handler-fm")
op = strings.Replace(op, "objectAPIHandlers", "s3", 1)
op = strings.Replace(op, "webAPIHandlers", "s3", 1)
op = strings.Replace(op, "adminAPIHandlers", "admin", 1)
op = strings.Replace(op, "(*storageRESTServer)", "internal", 1)
op = strings.Replace(op, "(*peerRESTServer)", "internal", 1)
op = strings.Replace(op, "(*lockRESTServer)", "internal", 1)
op = strings.Replace(op, "stsAPIHandlers", "sts", 1)
op = strings.Replace(op, "LivenessCheckHandler", "healthcheck", 1)
op = strings.Replace(op, "ReadinessCheckHandler", "healthcheck", 1)
return op
}
// Trace gets trace of http request
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info {
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
name = strings.TrimSuffix(name, "Handler-fm")
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
bodyPlaceHolder := []byte("<BODY>")
var reqBodyRecorder *recordRequest
@@ -136,7 +150,7 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
if err == nil {
t.NodeName = host.Name
}
rq := trace.RequestInfo{Time: time.Now().UTC(), Method: r.Method, Path: r.URL.Path, RawQuery: r.URL.RawQuery}
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)
rq.Headers.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
rq.Headers.Set("Host", r.Host)
-115
View File
@@ -1,115 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"bytes"
"context"
"encoding/json"
"strings"
"sync"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/pubsub"
"github.com/minio/minio/pkg/trace"
)
//HTTPTraceSys holds global trace state
type HTTPTraceSys struct {
peers []*peerRESTClient
pubsub *pubsub.PubSub
}
// NewTraceSys - creates new HTTPTraceSys with all nodes subscribed to
// the trace pub sub system
func NewTraceSys(ctx context.Context, endpoints EndpointList) *HTTPTraceSys {
remoteHosts := getRemoteHosts(endpoints)
remoteClients, err := getRestClients(remoteHosts)
if err != nil {
logger.FatalIf(err, "Unable to start httptrace sub system")
}
ps := pubsub.New()
return &HTTPTraceSys{
remoteClients, ps,
}
}
// HasTraceListeners returns true if trace listeners are registered
// for this node or peers
func (sys *HTTPTraceSys) HasTraceListeners() bool {
return sys != nil && sys.pubsub.HasSubscribers()
}
// Publish - publishes trace message to the http trace pubsub system
func (sys *HTTPTraceSys) Publish(traceMsg trace.Info) {
sys.pubsub.Publish(traceMsg)
}
// Trace writes http trace to writer
func (sys *HTTPTraceSys) Trace(doneCh chan struct{}, trcAll bool) chan []byte {
traceCh := make(chan []byte)
go func() {
defer close(traceCh)
var wg = &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
buf := &bytes.Buffer{}
ch := sys.pubsub.Subscribe()
defer sys.pubsub.Unsubscribe(ch)
for {
select {
case entry := <-ch:
trcInfo := entry.(trace.Info)
path := strings.TrimPrefix(trcInfo.ReqInfo.Path, "/")
// omit inter-node traffic if trcAll is false
if !trcAll && strings.HasPrefix(path, minioReservedBucket) {
continue
}
buf.Reset()
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(trcInfo); err != nil {
continue
}
traceCh <- buf.Bytes()
case <-doneCh:
return
}
}
}()
for _, peer := range sys.peers {
wg.Add(1)
go func(peer *peerRESTClient) {
defer wg.Done()
ch, err := peer.Trace(doneCh, trcAll)
if err != nil {
return
}
for entry := range ch {
traceCh <- entry
}
}(peer)
}
wg.Wait()
}()
return traceCh
}
+74 -2
View File
@@ -17,6 +17,7 @@
package cmd
import (
"bytes"
"context"
"encoding/json"
"path"
@@ -649,16 +650,87 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
return cred, ok && cred.IsValid()
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
// IsAllowedSTS is meant for STS based temporary credentials,
// 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()
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 globalPolicyOPA != nil {
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 name, found := sys.iamPolicyMap[args.AccountName]; found {
p, ok := sys.iamCannedPolicyMap[name]
+21 -1
View File
@@ -34,11 +34,25 @@ var (
},
[]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() {
prometheus.MustRegister(httpRequestsDuration)
prometheus.MustRegister(newMinioCollector())
prometheus.MustRegister(minioVersionInfo)
}
// 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.
func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
// Expose MinIO's version information
minioVersionInfo.WithLabelValues(Version, CommitID).Add(1)
// Always expose network stats
// Network Sent/Received Bytes
@@ -184,7 +201,10 @@ func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
func metricsHandler() http.Handler {
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)
err = registry.Register(newMinioCollector())
+3 -3
View File
@@ -190,10 +190,10 @@ func isHostIP(ipAddress string) bool {
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.
// 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.
isAddrInUseErr := func(err error) (b bool) {
if opErr, ok := err.(*net.OpError); ok {
@@ -209,7 +209,7 @@ func checkPortAvailability(port string) (err error) {
network := []string{"tcp", "tcp4", "tcp6"}
for _, n := range network {
l, err := net.Listen(n, net.JoinHostPort("", port))
l, err := net.Listen(n, net.JoinHostPort(host, port))
if err == nil {
// As we are able to listen on this network, the port is not in use.
// Close the listener and continue check other networks.
+5 -3
View File
@@ -206,11 +206,13 @@ func TestCheckPortAvailability(t *testing.T) {
defer listener.Close()
testCases := []struct {
host string
port string
expectedErr error
}{
{port, fmt.Errorf("listen tcp :%v: bind: address already in use", port)},
{getFreePort(), nil},
{"", port, fmt.Errorf("listen tcp :%v: bind: address already in use", port)},
{"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 {
@@ -219,7 +221,7 @@ func TestCheckPortAvailability(t *testing.T) {
continue
}
err := checkPortAvailability(testCase.port)
err := checkPortAvailability(testCase.host, testCase.port)
if testCase.expectedErr == nil {
if err != nil {
t.Fatalf("error: expected = <nil>, got = %v", err)
+22
View File
@@ -34,6 +34,7 @@ import (
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
)
@@ -230,6 +231,24 @@ func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
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.
func (sys *NotificationSys) StartProfiling(profiler string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
@@ -656,6 +675,9 @@ func (sys *NotificationSys) refresh(objAPI ObjectLayer) error {
ctx := logger.SetReqInfo(context.Background(), &logger.ReqInfo{BucketName: bucket.Name})
config, err := readNotificationConfig(ctx, objAPI, bucket.Name)
if err != nil && err != errNoSuchNotifications {
if _, ok := err.(*event.ErrARNNotFound); ok {
continue
}
return err
}
if err == errNoSuchNotifications {
+4 -4
View File
@@ -1052,7 +1052,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
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
return
}
@@ -1178,7 +1178,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
}
// 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)
}
@@ -1315,7 +1315,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
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
return
}
@@ -1333,7 +1333,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
}
// 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)
}
+58 -38
View File
@@ -17,7 +17,6 @@
package cmd
import (
"bufio"
"bytes"
"context"
"crypto/tls"
@@ -31,8 +30,10 @@ import (
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/rest"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
trace "github.com/minio/minio/pkg/trace"
)
// client to talk to peer Nodes.
@@ -212,7 +213,7 @@ func (client *peerRESTClient) ReloadFormat(dryRun bool) error {
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,
pattern string, targetID event.TargetID, addr xnet.Host) error {
args := listenBucketNotificationReq{
@@ -422,52 +423,71 @@ func (client *peerRESTClient) SignalService(sig serviceSignal) error {
return nil
}
// Trace - send http trace request to peer nodes
func (client *peerRESTClient) Trace(doneCh chan struct{}, trcAll bool) (chan []byte, error) {
ch := make(chan []byte)
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() {
cleanupFn := func(cancel context.CancelFunc, ch chan []byte, respBody io.ReadCloser) {
close(ch)
if cancel != nil {
cancel()
}
http.DrainBody(respBody)
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 {
values := make(url.Values)
values.Set(peerRESTTraceAll, strconv.FormatBool(trcAll))
// get cancellation context to properly unsubscribe peers
ctx, cancel := context.WithCancel(context.Background())
respBody, err := client.callWithContext(ctx, peerRESTMethodTrace, values, nil, -1)
if err != nil {
//retry
time.Sleep(5 * time.Second)
select {
case <-doneCh:
cleanupFn(cancel, ch, respBody)
return
default:
}
continue
}
bio := bufio.NewScanner(respBody)
go func() {
<-doneCh
cancel()
}()
// Unmarshal each line, returns marshaled values.
for bio.Scan() {
ch <- bio.Bytes()
}
client.doTrace(traceCh, doneCh, trcAll)
select {
case <-doneCh:
cleanupFn(cancel, ch, respBody)
return
default:
// There was error in the REST request, retry after sometime as probably the peer is down.
time.Sleep(5 * time.Second)
}
}
}()
return ch, nil
}
func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
+1
View File
@@ -26,6 +26,7 @@ const (
peerRESTMethodDrivePerfInfo = "driveperfinfo"
peerRESTMethodDeleteBucket = "deletebucket"
peerRESTMethodSignalService = "signalservice"
peerRESTMethodBackgroundHealStatus = "backgroundhealstatus"
peerRESTMethodGetLocks = "getlocks"
peerRESTMethodBucketPolicyRemove = "removebucketpolicy"
peerRESTMethodLoadUser = "loaduser"
+33 -18
View File
@@ -19,7 +19,6 @@ package cmd
import (
"context"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -679,36 +678,51 @@ func (s *peerRESTServer) TraceHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Connection", "close")
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
ch := globalTrace.pubsub.Subscribe()
defer globalTrace.pubsub.Unsubscribe(ch)
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
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:
trcInfo := entry.(trace.Info)
path := strings.TrimPrefix(trcInfo.ReqInfo.Path, "/")
// omit inter-node traffic if trcAll is false
if !trcAll && strings.HasPrefix(path, minioReservedBucket) {
continue
}
if err := enc.Encode(trcInfo); err != nil {
return
}
if _, err := w.Write([]byte("\n")); err != nil {
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) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(err.Error()))
@@ -755,6 +769,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
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))
}
+3 -3
View File
@@ -85,9 +85,6 @@ func (sys *PolicySys) Remove(bucketName string) {
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (sys *PolicySys) IsAllowed(args policy.Args) bool {
sys.RLock()
defer sys.RUnlock()
if globalIsGateway {
// When gateway is enabled, no cached value
// is used to validate bucket policies.
@@ -99,6 +96,9 @@ func (sys *PolicySys) IsAllowed(args policy.Args) bool {
}
}
} else {
sys.RLock()
defer sys.RUnlock()
// If policy is available for given bucket, check the policy.
if p, found := sys.bucketPolicyMap[args.BucketName]; found {
return p.IsAllowed(args)
+3 -2
View File
@@ -119,8 +119,9 @@ func configureServerHandler(endpoints EndpointList) (http.Handler, error) {
}
}
// Add API router, additionally all server mode support encryption.
registerAPIRouter(router, true)
// Add API router, additionally all server mode support encryption
// but don't allow SSE-KMS.
registerAPIRouter(router, true, false)
// Register rest of the handlers.
return registerHandlers(router, globalHandlers...), nil
+1 -4
View File
@@ -176,7 +176,7 @@ func serverHandleCmdArgs(ctx *cli.Context) {
// to IPv6 address ie minio will start listening on IPv6 address whereas another
// (non-)minio process is listening on IPv4 of given port.
// 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)
globalIsDistXL = (setupType == DistXLSetupType)
@@ -294,9 +294,6 @@ func serverMain(ctx *cli.Context) {
globalSweepHealState = initHealState()
}
// initialize globalTrace system
globalTrace = NewTraceSys(context.Background(), globalEndpoints)
// Configure server.
var handler http.Handler
handler, err = configureServerHandler(globalEndpoints)
+59 -12
View File
@@ -17,13 +17,16 @@
package cmd
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/iam/validator"
"github.com/minio/minio/pkg/wildcard"
)
@@ -124,11 +127,6 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
return
}
if r.Form.Get("Policy") != "" {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
if r.Form.Get("Version") != stsAPIVersion {
logger.LogIf(ctx, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSMissingParameter))
@@ -147,6 +145,29 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
ctx = newContext(r, w, action)
defer logger.AuditLog(w, r, action, nil)
sessionPolicyStr := r.Form.Get("Policy")
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
// The plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters.
if len(sessionPolicyStr) > 2048 {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
if len(sessionPolicyStr) > 0 {
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
if err != nil {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
// Version in policy must not be empty
if sessionPolicy.Version == "" {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
}
var err error
m := make(map[string]interface{})
m["exp"], err = validator.GetDefaultExpiration(r.Form.Get("DurationSeconds"))
@@ -171,7 +192,11 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
// This policy is the policy associated with the user
// requesting for temporary credentials. The temporary
// credentials will inherit the same policy requirements.
m["policy"] = policyName
m[iampolicy.PolicyName] = policyName
if len(sessionPolicyStr) > 0 {
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
}
secret := globalServerConfig.GetCredential().SecretKey
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
@@ -216,11 +241,6 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
return
}
if r.Form.Get("Policy") != "" {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
if r.Form.Get("Version") != stsAPIVersion {
logger.LogIf(ctx, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSMissingParameter))
@@ -276,6 +296,29 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
return
}
sessionPolicyStr := r.Form.Get("Policy")
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
// The plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters.
if len(sessionPolicyStr) > 2048 {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
if len(sessionPolicyStr) > 0 {
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
if err != nil {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
// Version in policy must not be empty
if sessionPolicy.Version == "" {
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
return
}
}
secret := globalServerConfig.GetCredential().SecretKey
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
if err != nil {
@@ -289,7 +332,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
// be set and configured on your identity provider as part of
// JWT custom claims.
var policyName string
if v, ok := m["policy"]; ok {
if v, ok := m[iampolicy.PolicyName]; ok {
policyName, _ = v.(string)
}
@@ -298,6 +341,10 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
subFromToken, _ = v.(string)
}
if len(sessionPolicyStr) > 0 {
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
}
// Set the newly generated credentials.
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
logger.LogIf(ctx, err)
+2 -2
View File
@@ -2135,7 +2135,7 @@ func registerBucketLevelFunc(bucket *mux.Router, api objectAPIHandlers, apiFunct
func registerAPIFunctions(muxRouter *mux.Router, objLayer ObjectLayer, apiFunctions ...string) {
if len(apiFunctions) == 0 {
// Register all api endpoints by default.
registerAPIRouter(muxRouter, true)
registerAPIRouter(muxRouter, true, false)
return
}
// API Router.
@@ -2176,7 +2176,7 @@ func initTestAPIEndPoints(objLayer ObjectLayer, apiFunctions []string) http.Hand
registerAPIFunctions(muxRouter, objLayer, apiFunctions...)
return muxRouter
}
registerAPIRouter(muxRouter, true)
registerAPIRouter(muxRouter, true, false)
return muxRouter
}
+12
View File
@@ -71,8 +71,20 @@ func cloneHeader(h http.Header) http.Header {
return h2
}
func request2BucketObjectName(r *http.Request) (bucketName, objectName string) {
path, err := getResource(r.URL.Path, r.Host, globalDomainNames)
if err != nil {
logger.CriticalIf(context.Background(), err)
}
return urlPath2BucketObjectName(path)
}
// Convert url path into bucket and object name.
func urlPath2BucketObjectName(path string) (bucketName, objectName string) {
if path == "" || path == slashSeparator {
return "", ""
}
// Trim any preceding slash separator.
urlPath := strings.TrimPrefix(path, slashSeparator)
+66 -50
View File
@@ -401,11 +401,9 @@ type ListObjectsArgs struct {
// ListObjectsRep - list objects response.
type ListObjectsRep struct {
Objects []WebObjectInfo `json:"objects"`
NextMarker string `json:"nextmarker"`
IsTruncated bool `json:"istruncated"`
Writable bool `json:"writable"` // Used by client to show "upload file" button.
UIVersion string `json:"uiVersion"`
Objects []WebObjectInfo `json:"objects"`
Writable bool `json:"writable"` // Used by client to show "upload file" button.
UIVersion string `json:"uiVersion"`
}
// WebObjectInfo container for list objects metadata.
@@ -448,26 +446,36 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
if err != nil {
return toJSONError(ctx, err, args.BucketName)
}
result, err := core.ListObjects(args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000)
if err != nil {
return toJSONError(ctx, err, args.BucketName)
nextMarker := ""
// Fetch all the objects
for {
result, err := core.ListObjects(args.BucketName, args.Prefix, nextMarker, slashSeparator, 1000)
if err != nil {
return toJSONError(ctx, err, args.BucketName)
}
for _, obj := range result.Contents {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: obj.Key,
LastModified: obj.LastModified,
Size: obj.Size,
ContentType: obj.ContentType,
})
}
for _, p := range result.CommonPrefixes {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: p.Prefix,
})
}
nextMarker = result.NextMarker
// Return when there are no more objects
if !result.IsTruncated {
return nil
}
}
reply.NextMarker = result.NextMarker
reply.IsTruncated = result.IsTruncated
for _, obj := range result.Contents {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: obj.Key,
LastModified: obj.LastModified,
Size: obj.Size,
ContentType: obj.ContentType,
})
}
for _, p := range result.CommonPrefixes {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: p.Prefix,
})
}
return nil
}
claims, owner, authErr := webRequestAuthenticate(r)
@@ -551,35 +559,43 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
return toJSONError(ctx, errInvalidBucketName)
}
lo, err := listObjects(ctx, args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000)
if err != nil {
return &json2.Error{Message: err.Error()}
}
for i := range lo.Objects {
if crypto.IsEncrypted(lo.Objects[i].UserDefined) {
lo.Objects[i].Size, err = lo.Objects[i].DecryptedSize()
if err != nil {
return toJSONError(ctx, err)
nextMarker := ""
// Fetch all the objects
for {
lo, err := listObjects(ctx, args.BucketName, args.Prefix, nextMarker, slashSeparator, 1000)
if err != nil {
return &json2.Error{Message: err.Error()}
}
for i := range lo.Objects {
if crypto.IsEncrypted(lo.Objects[i].UserDefined) {
lo.Objects[i].Size, err = lo.Objects[i].DecryptedSize()
if err != nil {
return toJSONError(ctx, err)
}
}
}
}
reply.NextMarker = lo.NextMarker
reply.IsTruncated = lo.IsTruncated
for _, obj := range lo.Objects {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: obj.Name,
LastModified: obj.ModTime,
Size: obj.Size,
ContentType: obj.ContentType,
})
}
for _, prefix := range lo.Prefixes {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: prefix,
})
}
return nil
for _, obj := range lo.Objects {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: obj.Name,
LastModified: obj.ModTime,
Size: obj.Size,
ContentType: obj.ContentType,
})
}
for _, prefix := range lo.Prefixes {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: prefix,
})
}
nextMarker = lo.NextMarker
// Return when there are no more objects
if !lo.IsTruncated {
return nil
}
}
}
// RemoveObjectArgs - args to remove an object, JSON will look like.
+7 -8
View File
@@ -39,6 +39,7 @@ const (
healthPath = "/minio/health/live"
timeout = time.Duration(30 * time.Second)
tcp = "tcp"
anyIP = ":::"
)
// returns container boot time by finding
@@ -69,17 +70,15 @@ func findEndpoint() (string, error) {
scanner := bufio.NewScanner(stdout)
scanner.Split(bufio.ScanLines)
// MinIO works on TCP and it is supposed to be
// the only process listening on a port inside
// container. So we take the first row of netstat
// output (that has tcp) and assume that is the
// MinIO server port.
// the only process listening on a port on any IP address
// (on :::) inside container.
// Since MinIO is running as non-root user, we can
// no longer depend on the PID/Program name column
// not depend on the PID/Program name column
// of netstat output
for scanner.Scan() {
if strings.Contains(scanner.Text(), tcp) {
line := scanner.Text()
newLine := strings.Replace(line, ":::", "127.0.0.1:", 1)
line := scanner.Text()
if strings.Contains(line, tcp) && strings.Contains(line, anyIP) {
newLine := strings.Replace(line, anyIP, "127.0.0.1:", 1)
fields := strings.Fields(newLine)
// index 3 in the row has the Local address
// find the last index of ":" - address will
+14 -1
View File
@@ -40,7 +40,20 @@ Add the following optimal entries for _core-site.xml_ to configure _s3a_ with **
* _fs.s3a.access.key=minio_ (Access Key to access MinIO instance, this is obtained after the deployment on k8s)
* _fs.s3a.secret.key=minio123_ (Secret Key to access MinIO instance, this is obtained after the deployment on k8s)
* _fs.s3a.endpoint=`http://minio-address/`_
* _fs.s3a.path.style.acces=true_
* _fs.s3a.multipart.size=128M_
* _fs.s3a.fast.upload=true_
* _fs.s3a.fast.upload.buffer=bytebuffer_
* _fs.s3a.path.style.access=true_
* _fs.s3a.block.size=256M_
* _fs.s3a.commiter.name=magic_
* _fs.s3a.committer.magic.enabled=true_
* _fs.s3a.committer.threads=16_
* _fs.s3a.connection.maximum=32_
* _fs.s3a.fast.upload.active.blocks=8_
* _fs.s3a.max.total.tasks=16_
* _fs.s3a.threads.core=32_
* _fs.s3a.threads.max=32_
* _mapreduce.outputcommitter.factory.scheme.s3a=org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory_
![s3a-config](https://github.com/minio/minio/blob/master/docs/bigdata/images/image4.png?raw=true "custom core-site s3a")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 82 KiB

+8 -3
View File
@@ -44,7 +44,7 @@ export MINIO_COMPRESS_MIMETYPES="application/pdf"
### 3. Note
- Already compressed objects are not fit for compression since they do not have compressible patterns. Such objects do not produce efficient [`Run-length encoding (RLE)`](https://en.wikipedia.org/wiki/Run-length_encoding) which is a fitness factor for a lossless data compression. Below is a list of common files and content-types which are not suitable for compression.
- Already compressed objects are not fit for compression since they do not have compressible patterns. Such objects do not produce efficient [`LZ compression`](https://en.wikipedia.org/wiki/LZ77_and_LZ78) which is a fitness factor for a lossless data compression. Below is a list of common files and content-types which are not suitable for compression.
- Extensions
@@ -53,6 +53,10 @@ export MINIO_COMPRESS_MIMETYPES="application/pdf"
| `rar` | (WinRAR)
| `zip` | (ZIP)
| `7z` | (7-Zip)
| `xz` | (LZMA)
| `mp4` | (MP4)
| `mkv` | (MKV media)
| `mov` | (MOV)
- Content-Types
@@ -61,10 +65,11 @@ export MINIO_COMPRESS_MIMETYPES="application/pdf"
| `application/zip` |
| `application/x-gzip` |
| `application/zip` |
| `application/x-bz2` |
| `application/x-compress` |
| `application/x-spoon` |
| `application/x-xz` |
- MinIO does not support encryption with compression because compression and encryption together enables room for side channel attacks like [`CRIME and BREACH`](https://en.wikipedia.org/wiki/CRIME)
- MinIO does not support encryption with compression because compression and encryption together potentially enables room for side channel attacks like [`CRIME and BREACH`](https://blog.minio.io/c-e-compression-encryption-cb6b7f04a369)
- MinIO does not support compression for Gateway (Azure/GCS/NAS) implementations.
+81 -4
View File
@@ -174,16 +174,93 @@ minio server /data
```
### HTTP Trace
By default, MinIO disables the feature to log HTTP trace. You may enable this feature by setting `MINIO_HTTP_TRACE` environment variable.
HTTP tracing can be enabled by using [`mc admin trace`](https://github.com/minio/mc/blob/master/docs/minio-admin-complete-guide.md#command-trace---display-minio-server-http-trace) command.
Example:
```sh
export MINIO_HTTP_TRACE=/var/log/minio.log
minio server /data
```
Default trace is succinct only to indicate the API operations being called and the HTTP response status.
```sh
mc admin trace myminio
17:21:45.729309964 objectAPIHandlers.GetBucketLocation localhost:9000/vk-photos/?location= 200 OK
17:21:45.738167329 objectAPIHandlers.HeadBucket localhost:9000/vk-photos/ 200 OK
17:21:45.747676811 objectAPIHandlers.ListObjectsV1 localhost:9000/vk-photos/?delimiter=%2F&max-keys=1000&prefix= 200 OK
```
To trace entire HTTP request
```sh
mc admin trace --verbose myminio
127.0.0.1 [REQUEST objectAPIHandlers.GetBucketLocation] [17:23:21.404025835]
127.0.0.1 GET /yyy/?location=
127.0.0.1 Host: localhost:9000
127.0.0.1 Content-Length: 0
127.0.0.1 User-Agent: MinIO (linux; amd64) minio-go/v6.0.29 mc/2019-06-15T10:29:41Z
127.0.0.1 X-Amz-Content-Sha256: UNSIGNED-PAYLOAD
127.0.0.1 X-Amz-Date: 20190619T172321Z
127.0.0.1 Authorization: AWS4-HMAC-SHA256 Credential=Q3AM3UQ867SPQQA43P2F/20190619/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=8e53d8574db3424aa00dd73637575512b250c923edcad3cbf58a727305205709
127.0.0.1
127.0.0.1 [RESPONSE] [17:23:21.404780651]
127.0.0.1 200 OK
127.0.0.1 X-Amz-Request-Id: 15A9A965FF7A7546
127.0.0.1 X-Minio-Deployment-Id: 41e39f4a-3b66-415b-9ddf-025d76a58668
127.0.0.1 X-Xss-Protection: 1; mode=block
127.0.0.1 Accept-Ranges: bytes
127.0.0.1 Server: MinIO/DEVELOPMENT.2019-06-18T17-17-02Z
127.0.0.1 Content-Type: application/xml
127.0.0.1 Vary: Origin
127.0.0.1 X-Amz-Bucket-Region: us-east-1
127.0.0.1 Content-Length: 137
127.0.0.1 Content-Security-Policy: block-all-mixed-content
127.0.0.1 <?xml version="1.0" encoding="UTF-8"?>
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">us-east-1</LocationConstraint>127.0.0.1
127.0.0.1 [REQUEST objectAPIHandlers.HeadBucket] [17:23:21.412985428]
127.0.0.1 HEAD /yyy/
127.0.0.1 Host: localhost:9000
127.0.0.1 User-Agent: MinIO (linux; amd64) minio-go/v6.0.29 mc/2019-06-15T10:29:41Z
127.0.0.1 X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
127.0.0.1 X-Amz-Date: 20190619T172321Z
127.0.0.1 Authorization: AWS4-HMAC-SHA256 Credential=Q3AM3UQ867SPQQA43P2F/20190619/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=e0a02a62d39994d0206586f01dd2ab3a4aea74e60da9ff4d427629f705c62c02
127.0.0.1 Content-Length: 0
127.0.0.1
127.0.0.1 [RESPONSE] [17:23:21.413457159]
127.0.0.1 200 OK
127.0.0.1 Vary: Origin
127.0.0.1 Accept-Ranges: bytes
127.0.0.1 Content-Length: 0
127.0.0.1 X-Amz-Bucket-Region: us-east-1
127.0.0.1 X-Amz-Request-Id: 15A9A9660005982D
127.0.0.1 X-Minio-Deployment-Id: 41e39f4a-3b66-415b-9ddf-025d76a58668
127.0.0.1 X-Xss-Protection: 1; mode=block
127.0.0.1 Content-Security-Policy: block-all-mixed-content
127.0.0.1 Server: MinIO/DEVELOPMENT.2019-06-18T17-17-02Z
127.0.0.1
127.0.0.1 [REQUEST objectAPIHandlers.ListObjectsV1] [17:23:21.423153668]
127.0.0.1 GET /yyy/?delimiter=%2F&max-keys=1000&prefix=
127.0.0.1 Host: localhost:9000
127.0.0.1 Content-Length: 0
127.0.0.1 User-Agent: MinIO (linux; amd64) minio-go/v6.0.29 mc/2019-06-15T10:29:41Z
127.0.0.1 X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
127.0.0.1 X-Amz-Date: 20190619T172321Z
127.0.0.1 Authorization: AWS4-HMAC-SHA256 Credential=Q3AM3UQ867SPQQA43P2F/20190619/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=46ee3d2fc5085432b94bc3205076abd8166ffa3e35c639f84e9684c7c6a181c9
127.0.0.1
127.0.0.1 [RESPONSE] [17:23:21.424260967]
127.0.0.1 200 OK
127.0.0.1 Content-Security-Policy: block-all-mixed-content
127.0.0.1 Content-Type: application/xml
127.0.0.1 Server: MinIO/DEVELOPMENT.2019-06-18T17-17-02Z
127.0.0.1 X-Amz-Bucket-Region: us-east-1
127.0.0.1 X-Minio-Deployment-Id: 41e39f4a-3b66-415b-9ddf-025d76a58668
127.0.0.1 Accept-Ranges: bytes
127.0.0.1 Content-Length: 253
127.0.0.1 Vary: Origin
127.0.0.1 X-Amz-Request-Id: 15A9A966009F94A6
127.0.0.1 X-Xss-Protection: 1; mode=block
127.0.0.1 <?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Name>yyy</Name><Prefix></Prefix><Marker></Marker><MaxKeys>1000</MaxKeys><Delimiter>/</Delimiter><IsTruncated>false</IsTruncated></ListBucketResult>
```
## Explore Further
* [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide)
+2 -2
View File
@@ -9,8 +9,8 @@ Install MinIO - [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quicksta
### 2. Run MinIO in federated mode
Bucket lookup from DNS federation requires two dependencies
- etcd (for config, bucket SRV records)
- CoreDNS (for DNS management based on populated bucket SRV records, optional)
- etcd (for bucket DNS service records)
- CoreDNS (for DNS management based on populated bucket DNS service records, optional)
## Architecture
@@ -5,7 +5,7 @@ version: '2'
# 9001 through 9004.
services:
minio1:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
volumes:
- data1:/data
ports:
@@ -15,7 +15,7 @@ services:
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio2:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
volumes:
- data2:/data
ports:
@@ -25,7 +25,7 @@ services:
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio3:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
volumes:
- data3:/data
ports:
@@ -35,7 +35,7 @@ services:
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio4:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
volumes:
- data4:/data
ports:
@@ -2,7 +2,7 @@ version: '3.1'
services:
minio1:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -24,7 +24,7 @@ services:
- access_key
minio2:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -46,7 +46,7 @@ services:
- access_key
minio3:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -68,7 +68,7 @@ services:
- access_key
minio4:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio4
volumes:
- minio4-data:/export
@@ -2,7 +2,7 @@ version: '3'
services:
minio1:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -24,7 +24,7 @@ services:
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
minio2:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -46,7 +46,7 @@ services:
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
minio3:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -68,7 +68,7 @@ services:
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
minio4:
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
hostname: minio4
volumes:
- minio4-data:/export
+1 -1
View File
@@ -18,7 +18,7 @@ Kubernetes concepts like Deployments and StatefulSets provide perfect platform t
## Prerequisites
To run this example, you need Kubernetes version >=1.4 cluster installed and running, and that you have installed the [`kubectl`](https://kubernetes.io/docs/tasks/kubectl/install/) command line tool in your path. Please see the [getting started guides](https://kubernetes.io/docs/getting-started-guides/) for installation instructions for your platform.
To run this example, you need Kubernetes version >=1.4 cluster installed and running, and that you have installed the [`kubectl`](https://kubernetes.io/docs/tasks/kubectl/install/) command line tool in your path. Please see the [getting started guides](https://kubernetes.io/docs/setup/) for installation instructions for your platform.
<a name="minio-standalone-server-deployment"></a>
@@ -30,7 +30,7 @@ spec:
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
args:
- server
@@ -21,7 +21,7 @@ spec:
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
args:
- server
- http://minio-0.minio.default.svc.cluster.local/data
@@ -21,7 +21,7 @@ spec:
containers:
- name: minio
# Pulls the default Minio image from Docker Hub
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
args:
- gateway
- gcs
@@ -29,7 +29,7 @@ spec:
- name: data
mountPath: "/data"
# Pulls the lastest Minio image from Docker Hub
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
image: minio/minio:RELEASE.2019-06-19T18-24-42Z
args:
- server
- /data
+19 -10
View File
@@ -10,19 +10,28 @@ The temporary security credentials returned by this API consists of an access ke
#### DurationSeconds
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 12 hours. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
| Params | Value |
| :-- | :-- |
| *Type* | *Integer* |
| Params | Value |
| :-- | :-- |
| *Type* | *Integer* |
| *Valid Range* | *Minimum value of 900. Maximum value of 43200.* |
| *Required* | *No* |
| *Required* | *No* |
#### Policy
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Valid Range* | *Minimum length of 1. Maximum length of 2048.* |
| *Required* | *No* |
#### Version
Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Required* | *Yes* |
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Required* | *Yes* |
#### AUTHPARAMS
Indicates STS API Authorization information. If you are familiar with AWS Signature V4 Authorization header, this STS API supports signature V4 authorization as mentioned [here](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
@@ -35,7 +44,7 @@ XML error response for this API is similar to [AWS STS AssumeRole](https://docs.
#### Sample Request
```
http://minio:9000/?Action=AssumeRole&DurationSeconds=3600&Version=2011-06-15&AUTHPARAMS
http://minio:9000/?Action=AssumeRole&DurationSeconds=3600&Version=2011-06-15&Policy={"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}&AUTHPARAMS
```
#### Sample Response
@@ -82,7 +91,7 @@ aws_secret_access_key = foo12345
> NOTE: In the following commands `--role-arn` and `--role-session-name` are not meaningful for MinIO and can be set to any value satisfying the command line requirements.
```
$ aws --profile foobar --endpoint-url http://localhost:9000 sts assume-role --role-arn arn:xxx:xxx:xxx:xxx --role-session-name anything
$ aws --profile foobar --endpoint-url http://localhost:9000 sts assume-role --policy '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}' --role-arn arn:xxx:xxx:xxx:xxxx --role-session-name anything
{
"AssumedRoleUser": {
"Arn": ""
+21 -13
View File
@@ -9,29 +9,37 @@ By default, the temporary security credentials created by AssumeRoleWithClientGr
#### DurationSeconds
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 12 hours. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
| Params | Value |
| :-- | :-- |
| *Type* | *Integer* |
| Params | Value |
| :-- | :-- |
| *Type* | *Integer* |
| *Valid Range* | *Minimum value of 900. Maximum value of 43200.* |
| *Required* | *No* |
| *Required* | *No* |
#### Policy
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Valid Range* | *Minimum length of 1. Maximum length of 2048.* |
| *Required* | *No* |
#### Token
The OAuth 2.0 access token that is provided by the identity provider. Application must get this token by authenticating the application using client credential grants before the application makes an AssumeRoleWithClientGrants call.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Length Constraints* | *Minimum length of 4. Maximum length of 2048.* |
| *Required* | *Yes* |
| *Required* | *Yes* |
#### Version
Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Required* | *Yes* |
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Required* | *Yes* |
#### Response Elements
XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_ResponseElements)
+21 -12
View File
@@ -7,28 +7,37 @@ By default, the temporary security credentials created by AssumeRoleWithWebIdent
#### DurationSeconds
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 12 hours. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
| Params | Value |
| :-- | :-- |
| *Type* | *Integer* |
| Params | Value |
| :-- | :-- |
| *Type* | *Integer* |
| *Valid Range* | *Minimum value of 900. Maximum value of 43200.* |
| *Required* | *No* |
| *Required* | *No* |
#### Policy
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Valid Range* | *Minimum length of 1. Maximum length of 2048.* |
| *Required* | *No* |
#### WebIdentityToken
The OAuth 2.0 access token that is provided by the web identity provider. Application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Length Constraints* | *Minimum length of 4. Maximum length of 2048.* |
| *Required* | *Yes* |
| *Required* | *Yes* |
#### Version
Indicates STS API version information, the only supported value is '2011-06-15'. This value is borrowed from AWS STS API documentation for compatibility reasons.
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Required* | *Yes* |
| Params | Value |
| :-- | :-- |
| *Type* | *String* |
| *Required* | *Yes* |
#### Response Elements
XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_ResponseElements)
-5
View File
@@ -237,11 +237,6 @@ ip_address = "127.0.0.1"
# Whether this certificate will be used for a TLS server
tls_www_server
# Whether this certificate will be used to encrypt data (needed
# in TLS RSA cipher suites). Note that it is preferred to use different
# keys for encryption and signing.
encryption_key
```
Run `certtool.exe` and specify the configuration file to generate a certificate:
+6 -3
View File
@@ -21,6 +21,7 @@ require (
github.com/djherbis/atime v1.0.0
github.com/dnaeon/go-vcr v1.0.1 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/eapache/go-resiliency v1.2.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.1.2-0.20190322152051-20337d8c3947
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/fatih/color v1.7.0
@@ -88,9 +89,11 @@ require (
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
go.etcd.io/bbolt v1.3.3 // indirect
go.uber.org/atomic v1.3.2
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b // indirect
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f
golang.org/x/tools v0.0.0-20190619181801-b76e30ffa0aa // indirect
google.golang.org/api v0.4.0
gopkg.in/Shopify/sarama.v1 v1.20.0
gopkg.in/olivere/elastic.v5 v5.0.80
+22 -6
View File
@@ -128,6 +128,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
github.com/eapache/go-resiliency v0.0.0-20160104191539-b86b1ec0dd42/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
@@ -433,8 +435,6 @@ github.com/minio/minio-go v0.0.0-20190327203652-5325257a208f h1:u+iNxfkLrfyWp7Kx
github.com/minio/minio-go v0.0.0-20190327203652-5325257a208f/go.mod h1:/haSOWG8hQNx2+JOfLJ9GKp61EAmgPwRVw/Sac0NzaM=
github.com/minio/minio-go/v6 v6.0.26 h1:nHLr1A+sJBv/sQu6zc5BrHLFAStCXxloC+jmZp4FtW0=
github.com/minio/minio-go/v6 v6.0.26/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/minio-go/v6 v6.0.28 h1:RIsoxMaljP2euGbu2r0gBC0UNn70l2gzHjifU6DhE0c=
github.com/minio/minio-go/v6 v6.0.28/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/minio-go/v6 v6.0.29 h1:p4YPxK1beY13reFJjCE5QwCnXUMT9D5sV5wl0BSy5Xo=
github.com/minio/minio-go/v6 v6.0.29/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/parquet-go v0.0.0-20190318185229-9d767baf1679 h1:OMKaN/82sBHUZPvjYNBFituHExa1OGY63eACDGtetKs=
@@ -679,8 +679,10 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -689,6 +691,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -711,8 +715,12 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00=
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -755,6 +763,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190614160838-b47fdc937951 h1:ZUgGZ7PSkne6oY+VgAvayrB16owfm9/DKAtgWubzgzU=
golang.org/x/sys v0.0.0-20190614160838-b47fdc937951/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f h1:dHNZYIYdq2QuU6w73vZ/DzesPbVlZVYZTtTZmrnsbQ8=
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -775,6 +787,10 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190318200714-bb1270c20edf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190619181801-b76e30ffa0aa h1:OKbX3LR+DrC3Oj0rhniAcIbi43SoQW6ennRcN9MTxpE=
golang.org/x/tools v0.0.0-20190619181801-b76e30ffa0aa/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.0.0-20180603000442-8e296ef26005/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20180916000451-19ff8768a5c0/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+4
View File
@@ -222,6 +222,10 @@ func (conf *Config) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if len(parsedConfig.QueueList) > 0 {
for i, q1 := range parsedConfig.QueueList[:len(parsedConfig.QueueList)-1] {
for _, q2 := range parsedConfig.QueueList[i+1:] {
// Removes the region from ARN if server region is not set
if q2.ARN.region != "" && q1.ARN.region == "" {
q2.ARN.region = ""
}
if reflect.DeepEqual(q1, q2) {
return &ErrDuplicateQueueConfiguration{q1}
}
+1 -4
View File
@@ -43,10 +43,7 @@ func setUpStore(directory string, limit uint16) (Store, error) {
// Tear down store
func tearDownStore() error {
if err := os.RemoveAll(queueDir); err != nil {
return err
}
return nil
return os.RemoveAll(queueDir)
}
// TestQueueStorePut - tests for store.Put
+6
View File
@@ -20,6 +20,12 @@ import (
"github.com/minio/minio/pkg/policy"
)
// Policy claim constants
const (
PolicyName = "policy"
SessionPolicyName = "sessionPolicy"
)
// ReadWrite - provides full access to all buckets and all objects
var ReadWrite = Policy{
Version: DefaultVersion,
+2 -1
View File
@@ -19,6 +19,7 @@ package iampolicy
import (
"encoding/json"
"fmt"
"path"
"strings"
"github.com/minio/minio/pkg/policy/condition"
@@ -56,7 +57,7 @@ func (r Resource) Match(resource string, conditionValues map[string][]string) bo
pattern = strings.Replace(pattern, key.VarName(), rvalues[0], -1)
}
}
if strings.HasPrefix(resource, pattern) {
if path.Clean(resource) == pattern {
return true
}
return wildcard.Match(pattern, resource)
+9 -6
View File
@@ -18,6 +18,7 @@ package iampolicy
import (
"encoding/json"
"fmt"
"reflect"
"testing"
)
@@ -120,15 +121,17 @@ func TestResourceMatch(t *testing.T) {
{NewResource("*", "*"), "mybucket", false},
{NewResource("mybucket", "*"), "mybucket10/myobject", false},
{NewResource("mybucket?0", "/2010/photos/*"), "mybucket0/2010/photos/1.jpg", false},
{NewResource("mybucket", ""), "mybucket/myobject", true},
{NewResource("mybucket", ""), "mybucket/myobject", false},
}
for i, testCase := range testCases {
result := testCase.resource.Match(testCase.objectName, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
testCase := testCase
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
result := testCase.resource.Match(testCase.objectName, nil)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
})
}
}
+9 -6
View File
@@ -18,6 +18,7 @@ package iampolicy
import (
"encoding/json"
"fmt"
"reflect"
"testing"
)
@@ -174,16 +175,18 @@ func TestResourceSetMatch(t *testing.T) {
{NewResourceSet(NewResource("", "*")), "mybucket/myobject", false},
{NewResourceSet(NewResource("*", "*")), "mybucket", false},
{NewResourceSet(NewResource("mybucket", "*")), "mybucket10/myobject", false},
{NewResourceSet(NewResource("mybucket", "")), "mybucket/myobject", true},
{NewResourceSet(NewResource("mybucket", "")), "mybucket/myobject", false},
{NewResourceSet(), "mybucket/myobject", false},
}
for i, testCase := range testCases {
result := testCase.resourceSet.Match(testCase.resource, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
testCase := testCase
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
result := testCase.resourceSet.Match(testCase.resource, nil)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
})
}
}
+7 -23
View File
@@ -17,9 +17,7 @@
package madmin
import (
"bufio"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
@@ -35,7 +33,7 @@ type TraceInfo struct {
// Trace - listen on http trace notifications.
func (adm AdminClient) Trace(allTrace bool, doneCh <-chan struct{}) <-chan TraceInfo {
traceInfoCh := make(chan TraceInfo, 1)
traceInfoCh := make(chan TraceInfo)
// Only success, start a routine to start reading line by line.
go func(traceInfoCh chan<- TraceInfo) {
defer close(traceInfoCh)
@@ -58,30 +56,16 @@ func (adm AdminClient) Trace(allTrace bool, doneCh <-chan struct{}) <-chan Trace
return
}
// Initialize a new bufio scanner, to read line by line.
bio := bufio.NewScanner(resp.Body)
// Close the response body.
defer resp.Body.Close()
// Unmarshal each line, returns marshaled values.
for bio.Scan() {
var traceRec trace.Info
if err = json.Unmarshal(bio.Bytes(), &traceRec); err != nil {
continue
dec := json.NewDecoder(resp.Body)
for {
var info trace.Info
if err = dec.Decode(&info); err != nil {
break
}
select {
case <-doneCh:
return
case traceInfoCh <- TraceInfo{Trace: traceRec}:
}
}
// Look for any underlying errors.
if err = bio.Err(); err != nil {
// For an unexpected connection drop from server, we close the body
// and re-connect.
if err == io.ErrUnexpectedEOF {
resp.Body.Close()
case traceInfoCh <- TraceInfo{Trace: info}:
}
}
}
+45
View File
@@ -0,0 +1,45 @@
// +build ignore
/*
* 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 main
import (
"log"
"github.com/minio/minio/pkg/madmin"
)
func main() {
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
// New returns an MinIO Admin client object.
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
if err != nil {
log.Fatalln(err)
}
healStatusResult, err := madmClnt.BackgroundHealStatus()
if err != nil {
log.Fatalln(err)
}
log.Printf("Heal status result: %+v\n", healStatusResult)
}
+34
View File
@@ -269,3 +269,37 @@ func (adm *AdminClient) Heal(bucket, prefix string, healOpts HealOpts,
}
return healStart, healTaskStatus, nil
}
// BgHealState represents the status of the background heal
type BgHealState struct {
ScannedItemsCount int64
LastHealActivity time.Time
}
// BackgroundHealStatus returns the background heal status of the
// current server or cluster.
func (adm *AdminClient) BackgroundHealStatus() (BgHealState, error) {
// Execute POST request to background heal status api
resp, err := adm.executeMethod("POST", requestData{relPath: "/v1/background-heal/status"})
if err != nil {
return BgHealState{}, err
}
defer closeResponse(resp)
if resp.StatusCode != http.StatusOK {
return BgHealState{}, httpRespToErrorResponse(resp)
}
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return BgHealState{}, err
}
var healState BgHealState
err = json.Unmarshal(respBytes, &healState)
if err != nil {
return BgHealState{}, err
}
return healState, nil
}
+41 -40
View File
@@ -20,64 +20,65 @@ import (
"sync"
)
// Sub - subscriber entity.
type Sub struct {
ch chan interface{}
filter func(entry interface{}) bool
}
// PubSub holds publishers and subscribers
type PubSub struct {
subs []chan interface{}
pub chan interface{}
mutex sync.Mutex
subs []*Sub
sync.RWMutex
}
// process item to subscribers.
func (ps *PubSub) process() {
for item := range ps.pub {
ps.mutex.Lock()
for _, sub := range ps.subs {
go func(s chan interface{}) {
s <- item
}(sub)
}
ps.mutex.Unlock()
}
}
// Publish message to pubsub system
// Publish message to the subscribers.
// Note that publish is always nob-blocking send so that we don't block on slow receivers.
// Hence receivers should use buffered channel so as not to miss the published events.
func (ps *PubSub) Publish(item interface{}) {
ps.pub <- item
ps.RLock()
defer ps.RUnlock()
for _, sub := range ps.subs {
if sub.filter(item) {
select {
case sub.ch <- item:
default:
}
}
}
}
// Subscribe - Adds a subscriber to pubsub system
func (ps *PubSub) Subscribe() chan interface{} {
ps.mutex.Lock()
defer ps.mutex.Unlock()
ch := make(chan interface{})
ps.subs = append(ps.subs, ch)
return ch
}
func (ps *PubSub) Subscribe(subCh chan interface{}, doneCh chan struct{}, filter func(entry interface{}) bool) {
ps.Lock()
defer ps.Unlock()
// Unsubscribe removes current subscriber
func (ps *PubSub) Unsubscribe(ch chan interface{}) {
ps.mutex.Lock()
defer ps.mutex.Unlock()
sub := &Sub{subCh, filter}
ps.subs = append(ps.subs, sub)
for i, sub := range ps.subs {
if sub == ch {
close(ch)
ps.subs = append(ps.subs[:i], ps.subs[i+1:]...)
go func() {
<-doneCh
ps.Lock()
defer ps.Unlock()
for i, s := range ps.subs {
if s == sub {
ps.subs = append(ps.subs[:i], ps.subs[i+1:]...)
}
}
}
}()
}
// HasSubscribers returns true if pubsub system has subscribers
func (ps *PubSub) HasSubscribers() bool {
ps.mutex.Lock()
defer ps.mutex.Unlock()
ps.RLock()
defer ps.RUnlock()
return len(ps.subs) > 0
}
// New inits a PubSub system
func New() *PubSub {
ps := &PubSub{}
ps.pub = make(chan interface{})
go ps.process()
return ps
return &PubSub{}
}
+35 -11
View File
@@ -19,12 +19,19 @@ package pubsub
import (
"fmt"
"testing"
"time"
)
func TestSubscribe(t *testing.T) {
ps := New()
ps.Subscribe()
ps.Subscribe()
ch1 := make(chan interface{}, 1)
ch2 := make(chan interface{}, 1)
doneCh := make(chan struct{})
defer close(doneCh)
ps.Subscribe(ch1, doneCh, nil)
ps.Subscribe(ch2, doneCh, nil)
ps.Lock()
defer ps.Unlock()
if len(ps.subs) != 2 {
t.Errorf("expected 2 subscribers")
}
@@ -32,20 +39,33 @@ func TestSubscribe(t *testing.T) {
func TestUnsubscribe(t *testing.T) {
ps := New()
c1 := ps.Subscribe()
ps.Subscribe()
ps.Unsubscribe(c1)
ch1 := make(chan interface{}, 1)
ch2 := make(chan interface{}, 1)
doneCh1 := make(chan struct{})
doneCh2 := make(chan struct{})
ps.Subscribe(ch1, doneCh1, nil)
ps.Subscribe(ch2, doneCh2, nil)
close(doneCh1)
// Allow for the above statement to take effect.
time.Sleep(100 * time.Millisecond)
ps.Lock()
if len(ps.subs) != 1 {
t.Errorf("expected 1 subscriber")
}
ps.Unlock()
close(doneCh2)
}
func TestPubSub(t *testing.T) {
ps := New()
c1 := ps.Subscribe()
ch1 := make(chan interface{}, 1)
doneCh1 := make(chan struct{})
defer close(doneCh1)
ps.Subscribe(ch1, doneCh1, func(entry interface{}) bool { return true })
val := "hello"
ps.Publish(val)
msg := <-c1
msg := <-ch1
if msg != "hello" {
t.Errorf(fmt.Sprintf("expected %s , found %s", val, msg))
}
@@ -53,13 +73,17 @@ func TestPubSub(t *testing.T) {
func TestMultiPubSub(t *testing.T) {
ps := New()
c1 := ps.Subscribe()
c2 := ps.Subscribe()
ch1 := make(chan interface{}, 1)
ch2 := make(chan interface{}, 1)
doneCh := make(chan struct{})
defer close(doneCh)
ps.Subscribe(ch1, doneCh, func(entry interface{}) bool { return true })
ps.Subscribe(ch2, doneCh, func(entry interface{}) bool { return true })
val := "hello"
ps.Publish(val)
msg1 := <-c1
msg2 := <-c2
msg1 := <-ch1
msg2 := <-ch2
if msg1 != "hello" && msg2 != "hello" {
t.Errorf(fmt.Sprintf("expected both subscribers to have%s , found %s and %s", val, msg1, msg2))
}
+3
View File
@@ -272,11 +272,13 @@ func (s3Select *S3Select) Open(getReader func(offset, length int64) (io.ReadClos
s3Select.progressReader, err = newProgressReader(rc, s3Select.Input.CompressionType)
if err != nil {
rc.Close()
return err
}
s3Select.recordReader, err = csv.NewReader(s3Select.progressReader, &s3Select.Input.CSVArgs)
if err != nil {
rc.Close()
return err
}
@@ -289,6 +291,7 @@ func (s3Select *S3Select) Open(getReader func(offset, length int64) (io.ReadClos
s3Select.progressReader, err = newProgressReader(rc, s3Select.Input.CompressionType)
if err != nil {
rc.Close()
return err
}
+9
View File
@@ -274,6 +274,15 @@ func (e *FuncExpr) analyze(s *Select) (result qProp) {
}
return result
case sqlFnTrim:
if e.Trim.TrimChars != nil {
result.combine(e.Trim.TrimChars.analyze(s))
}
if e.Trim.TrimFrom != nil {
result.combine(e.Trim.TrimFrom.analyze(s))
}
return result
case sqlFnSubstring:
errVal := fmt.Errorf("Invalid argument(s) to %s", string(funcName))
result.combine(e.Substring.Expr.analyze(s))
+13 -8
View File
@@ -337,20 +337,25 @@ func handleSQLSubstring(r Record, e *SubstringFunc) (val *Value, err error) {
}
func handleSQLTrim(r Record, e *TrimFunc) (res *Value, err error) {
charsV, cerr := e.TrimChars.evalNode(r)
if cerr != nil {
return nil, cerr
}
inferTypeAsString(charsV)
chars, ok := charsV.ToString()
if !ok {
return nil, errNonStringTrimArg
chars := ""
ok := false
if e.TrimChars != nil {
charsV, cerr := e.TrimChars.evalNode(r)
if cerr != nil {
return nil, cerr
}
inferTypeAsString(charsV)
chars, ok = charsV.ToString()
if !ok {
return nil, errNonStringTrimArg
}
}
fromV, ferr := e.TrimFrom.evalNode(r)
if ferr != nil {
return nil, ferr
}
inferTypeAsString(fromV)
from, ok := fromV.ToString()
if !ok {
return nil, errNonStringTrimArg
+27 -2
View File
@@ -97,7 +97,7 @@ func (v *Value) Repr() string {
case typeBool, typeInt, typeFloat:
return fmt.Sprintf("%v:%s", v.value, v.GetTypeString())
case typeTimestamp:
return fmt.Sprintf("%s:TIMESTAMP", v.value.(*time.Time))
return fmt.Sprintf("%s:TIMESTAMP", v.value.(time.Time))
case typeString:
return fmt.Sprintf("\"%s\":%s", v.value.(string), v.GetTypeString())
case typeBytes:
@@ -335,11 +335,17 @@ func (v *Value) compareOp(op string, a *Value) (res bool, err error) {
}
boolV, ok1b := v.ToBool()
boolA, ok2b := v.ToBool()
boolA, ok2b := a.ToBool()
if ok1b && ok2b {
return boolCompare(op, boolV, boolA)
}
timestampV, ok1t := v.ToTimestamp()
timestampA, ok2t := a.ToTimestamp()
if ok1t && ok2t {
return timestampCompare(op, timestampV, timestampA), nil
}
return false, errCmpMismatchedTypes
}
@@ -721,6 +727,25 @@ func boolCompare(op string, left, right bool) (bool, error) {
}
}
func timestampCompare(op string, left, right time.Time) bool {
switch op {
case opLt:
return left.Before(right)
case opLte:
return left.Before(right) || left.Equal(right)
case opGt:
return left.After(right)
case opGte:
return left.After(right) || left.Equal(right)
case opEq:
return left.Equal(right)
case opIneq:
return !left.Equal(right)
}
// This case does not happen
return false
}
func isValidArithOperator(op string) bool {
switch op {
case opPlus:
+1
View File
@@ -38,6 +38,7 @@ type RequestInfo struct {
RawQuery string `json:"rawquery,omitempty"`
Headers http.Header `json:"headers,omitempty"`
Body []byte `json:"body,omitempty"`
Client string `json:"client"`
}
// ResponseInfo represents trace of http request