mirror of
https://github.com/pgsty/minio.git
synced 2026-08-01 16:40:43 +03:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36c19f1d65 | |||
| d3f9f8be88 | |||
| 183ec094c4 | |||
| c1d2b3d5c3 | |||
| be72609d1f | |||
| 48f2c98052 | |||
| 286c663495 | |||
| 941fed8e4a | |||
| 55092bede1 | |||
| 90a3b830f4 | |||
| 23b9df0694 | |||
| 48cb271a46 | |||
| 61229b38f7 | |||
| 90ca73af13 | |||
| a04b6561a0 | |||
| 219d841496 | |||
| baef49b4a2 | |||
| 3022f60561 | |||
| 680fdf6f90 | |||
| 1af6e8cb72 | |||
| 98d3913a1e | |||
| 35c38e4bd8 | |||
| e43d3a075c | |||
| 43e0ef4248 | |||
| cd7d5b59e5 | |||
| 299ef9b188 | |||
| b30c436715 | |||
| 7001fe407f | |||
| 59f7266081 | |||
| 99bf4d0c42 | |||
| 7b8beecc81 | |||
| 510ec153b9 | |||
| e29a37e95c | |||
| 4a4048fe27 | |||
| da2887f914 |
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because one or more lines are too long
+162
-119
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
peerRESTMethodDrivePerfInfo = "driveperfinfo"
|
||||
peerRESTMethodDeleteBucket = "deletebucket"
|
||||
peerRESTMethodSignalService = "signalservice"
|
||||
peerRESTMethodBackgroundHealStatus = "backgroundhealstatus"
|
||||
peerRESTMethodGetLocks = "getlocks"
|
||||
peerRESTMethodBucketPolicyRemove = "removebucketpolicy"
|
||||
peerRESTMethodLoadUser = "loaduser"
|
||||
|
||||
+33
-18
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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_
|
||||
|
||||

|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 82 KiB |
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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}:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user