From 756da8a1348254f81ecaed9d956f1c729ec05912 Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Fri, 17 May 2019 08:35:37 +0300 Subject: [PATCH 01/92] Fix: tighten revive exit code & make it happy (#17127) * Revive should fail the build * Fix the associated errors --- pkg/services/sqlstore/transactions.go | 10 +++++----- scripts/go/configs/revive.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/services/sqlstore/transactions.go b/pkg/services/sqlstore/transactions.go index 0ecb0938d4f7..a0f648043399 100644 --- a/pkg/services/sqlstore/transactions.go +++ b/pkg/services/sqlstore/transactions.go @@ -12,7 +12,7 @@ import ( // WithTransactionalDbSession calls the callback with an session within a transaction func (ss *SqlStore) WithTransactionalDbSession(ctx context.Context, callback dbTransactionFunc) error { - return inTransactionWithRetryCtx(ss.engine, ctx, callback, 0) + return inTransactionWithRetryCtx(ctx, ss.engine, callback, 0) } func (ss *SqlStore) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error { @@ -20,17 +20,17 @@ func (ss *SqlStore) InTransaction(ctx context.Context, fn func(ctx context.Conte } func (ss *SqlStore) inTransactionWithRetry(ctx context.Context, fn func(ctx context.Context) error, retry int) error { - return inTransactionWithRetryCtx(ss.engine, ctx, func(sess *DBSession) error { + return inTransactionWithRetryCtx(ctx, ss.engine, func(sess *DBSession) error { withValue := context.WithValue(ctx, ContextSessionName, sess) return fn(withValue) }, retry) } func inTransactionWithRetry(callback dbTransactionFunc, retry int) error { - return inTransactionWithRetryCtx(x, context.Background(), callback, retry) + return inTransactionWithRetryCtx(context.Background(), x, callback, retry) } -func inTransactionWithRetryCtx(engine *xorm.Engine, ctx context.Context, callback dbTransactionFunc, retry int) error { +func inTransactionWithRetryCtx(ctx context.Context, engine *xorm.Engine, callback dbTransactionFunc, retry int) error { sess, err := startSession(ctx, engine, true) if err != nil { return err @@ -73,5 +73,5 @@ func inTransaction(callback dbTransactionFunc) error { } func inTransactionCtx(ctx context.Context, callback dbTransactionFunc) error { - return inTransactionWithRetryCtx(x, ctx, callback, 0) + return inTransactionWithRetryCtx(ctx, x, callback, 0) } diff --git a/scripts/go/configs/revive.toml b/scripts/go/configs/revive.toml index 2d4410ee5489..a40486c31333 100644 --- a/scripts/go/configs/revive.toml +++ b/scripts/go/configs/revive.toml @@ -1,7 +1,7 @@ ignoreGeneratedHeader = false severity = "error" confidence = 0.8 -errorCode = 0 +errorCode = 1 [rule.context-as-argument] [rule.error-return] From 32cdab4cbfc1fe5e7fdd1d1b6344db3305a85298 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 17 May 2019 10:11:55 +0200 Subject: [PATCH 02/92] explore: make sure datasource is added to target (#17116) Fixes a regression introduced in #16959 which removed datasource property from changed query for angular query editors having the result of loading explore URL's without datasource loaded the default query in query editor and Explore. Ref #16808 --- public/app/features/explore/QueryEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 54927b8cc91b..5689f67ee13b 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -35,7 +35,7 @@ export default class QueryEditor extends PureComponent { const loader = getAngularLoader(); const template = ' '; - const target = { ...initialQuery }; + const target = { datasource: datasource.name, ...initialQuery }; const scopeProps = { ctrl: { datasource, From 1a80885180a38018d6b5f41cb462958fe519bd31 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 17 May 2019 12:45:11 +0200 Subject: [PATCH 03/92] explore: fix issues when loading and both graph/table are collapsed (#17113) Removes the functionality of being able to collapse/expand the logs container. When both graph and table are collapsed and you reload the page then the start page should not be displayed. When both graph and table are collapsed and you reload the page then the graph and table panels should be displayed. Fix so that reducer tests are run. On of the test used fit() instead of it() which had the consequence of only 1 reducer test was executed and the rest skipped. There was some failing tests that now is updated and now passes. Fixes #17098 --- .../app/features/explore/GraphContainer.tsx | 28 +++---- public/app/features/explore/LogsContainer.tsx | 15 +--- public/app/features/explore/Panel.tsx | 17 +++- .../app/features/explore/TableContainer.tsx | 8 +- .../app/features/explore/state/actionTypes.ts | 9 --- public/app/features/explore/state/actions.ts | 22 +---- .../features/explore/state/reducers.test.ts | 81 ++++++++----------- public/app/features/explore/state/reducers.ts | 5 +- .../app/features/explore/state/selectors.ts | 3 +- public/app/types/explore.ts | 4 - public/sass/pages/_explore.scss | 19 ++++- 11 files changed, 86 insertions(+), 125 deletions(-) diff --git a/public/app/features/explore/GraphContainer.tsx b/public/app/features/explore/GraphContainer.tsx index 7033473a33b3..0fba2ae6ded4 100644 --- a/public/app/features/explore/GraphContainer.tsx +++ b/public/app/features/explore/GraphContainer.tsx @@ -46,22 +46,20 @@ export class GraphContainer extends PureComponent { const graphHeight = showingGraph && showingTable ? 200 : 400; const timeRange = { from: range.from.valueOf(), to: range.to.valueOf() }; - if (!graphResult) { - return null; - } - return ( - - + + {graphResult && ( + + )} ); } diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 83cce42c7d19..7356fb15c178 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -7,7 +7,7 @@ import { ExploreId, ExploreItemState } from 'app/types/explore'; import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import { StoreState } from 'app/types'; -import { toggleLogs, changeDedupStrategy, changeTime } from './state/actions'; +import { changeDedupStrategy, changeTime } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes'; @@ -27,8 +27,6 @@ interface LogsContainerProps { timeZone: TimeZone; scanning?: boolean; scanRange?: RawTimeRange; - showingLogs: boolean; - toggleLogs: typeof toggleLogs; toggleLogLevelAction: typeof toggleLogLevelAction; changeDedupStrategy: typeof changeDedupStrategy; dedupStrategy: LogsDedupStrategy; @@ -48,10 +46,6 @@ export class LogsContainer extends PureComponent { changeTime(exploreId, range); }; - onClickLogsButton = () => { - this.props.toggleLogs(this.props.exploreId, this.props.showingLogs); - }; - handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => { this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); }; @@ -76,7 +70,6 @@ export class LogsContainer extends PureComponent { onStopScanning, range, timeZone, - showingLogs, scanning, scanRange, width, @@ -84,7 +77,7 @@ export class LogsContainer extends PureComponent { } = this.props; return ( - + void; + collapsible?: boolean; + onToggle?: (isOpen: boolean) => void; } export default class Panel extends PureComponent { - onClickToggle = () => this.props.onToggle(!this.props.isOpen); + onClickToggle = () => { + const { onToggle, isOpen } = this.props; + if (onToggle) { + onToggle(!isOpen); + } + }; render() { - const { isOpen, loading } = this.props; + const { isOpen, loading, collapsible } = this.props; + const panelClass = collapsible + ? 'explore-panel explore-panel--collapsible panel-container' + : 'explore-panel panel-container'; const iconClass = isOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'; const loaderClass = loading ? 'explore-panel__loader explore-panel__loader--active' : 'explore-panel__loader'; return ( -
+
diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index 78f190a05cb8..18ee70d8ee20 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -27,13 +27,9 @@ export class TableContainer extends PureComponent { render() { const { loading, onClickCell, showingTable, tableResult } = this.props; - if (!tableResult) { - return null; - } - return ( - - + + {tableResult &&
} ); } diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 225a672ae2e5..ff7fdcb55dec 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -204,10 +204,6 @@ export interface ToggleGraphPayload { exploreId: ExploreId; } -export interface ToggleLogsPayload { - exploreId: ExploreId; -} - export interface UpdateUIStatePayload extends Partial { exploreId: ExploreId; } @@ -412,11 +408,6 @@ export const toggleTableAction = actionCreatorFactory('explo */ export const toggleGraphAction = actionCreatorFactory('explore/TOGGLE_GRAPH').create(); -/** - * Expand/collapse the logs result viewer. When collapsed, log queries won't be run. - */ -export const toggleLogsAction = actionCreatorFactory('explore/TOGGLE_LOGS').create(); - /** * Updates datasource instance before datasouce loading has started */ diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 310f310e6710..c1157e01c6ae 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -72,10 +72,8 @@ import { splitOpenAction, addQueryRowAction, toggleGraphAction, - toggleLogsAction, toggleTableAction, ToggleGraphPayload, - ToggleLogsPayload, ToggleTablePayload, updateUIStateAction, runQueriesAction, @@ -517,7 +515,6 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false, replaceU const { datasourceInstance, queries, - showingLogs, showingGraph, showingTable, datasourceError, @@ -562,7 +559,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false, replaceU }) ); } - if ((ignoreUIState || showingLogs) && mode === ExploreMode.Logs) { + if (mode === ExploreMode.Logs) { dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' })); } @@ -700,7 +697,7 @@ export function stateSave(replaceUrl = false): ThunkResult { range: toRawTimeRange(left.range), ui: { showingGraph: left.showingGraph, - showingLogs: left.showingLogs, + showingLogs: true, showingTable: left.showingTable, dedupStrategy: left.dedupStrategy, }, @@ -713,7 +710,7 @@ export function stateSave(replaceUrl = false): ThunkResult { range: toRawTimeRange(right.range), ui: { showingGraph: right.showingGraph, - showingLogs: right.showingLogs, + showingLogs: true, showingTable: right.showingTable, dedupStrategy: right.dedupStrategy, }, @@ -731,10 +728,7 @@ export function stateSave(replaceUrl = false): ThunkResult { * queries won't be run */ const togglePanelActionCreator = ( - actionCreator: - | ActionCreator - | ActionCreator - | ActionCreator + actionCreator: ActionCreator | ActionCreator ) => (exploreId: ExploreId, isPanelVisible: boolean): ThunkResult => { return dispatch => { let uiFragmentStateUpdate: Partial; @@ -744,9 +738,6 @@ const togglePanelActionCreator = ( case toggleGraphAction.type: uiFragmentStateUpdate = { showingGraph: !isPanelVisible }; break; - case toggleLogsAction.type: - uiFragmentStateUpdate = { showingLogs: !isPanelVisible }; - break; case toggleTableAction.type: uiFragmentStateUpdate = { showingTable: !isPanelVisible }; break; @@ -766,11 +757,6 @@ const togglePanelActionCreator = ( */ export const toggleGraph = togglePanelActionCreator(toggleGraphAction); -/** - * Expand/collapse the logs result viewer. When collapsed, log queries won't be run. - */ -export const toggleLogs = togglePanelActionCreator(toggleLogsAction); - /** * Expand/collapse the table result viewer. When collapsed, table queries won't be run. */ diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index c5ee8dbb7798..5af71c29f8df 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -10,7 +10,6 @@ import { ExploreItemState, ExploreUrlState, ExploreState, - QueryTransaction, RangeScanner, ExploreMode, } from 'app/types/explore'; @@ -25,6 +24,7 @@ import { splitOpenAction, splitCloseAction, changeModeAction, + runQueriesAction, } from './actionTypes'; import { Reducer } from 'redux'; import { ActionOf } from 'app/core/redux/actionCreatorFactory'; @@ -36,7 +36,7 @@ import { DataSourceApi, DataQuery } from '@grafana/ui'; describe('Explore item reducer', () => { describe('scanning', () => { - test('should start scanning', () => { + it('should start scanning', () => { const scanner = jest.fn(); const initalState = { ...makeExploreItemState(), @@ -53,7 +53,7 @@ describe('Explore item reducer', () => { scanner, }); }); - test('should stop scanning', () => { + it('should stop scanning', () => { const scanner = jest.fn(); const initalState = { ...makeExploreItemState(), @@ -96,7 +96,6 @@ describe('Explore item reducer', () => { describe('when testDataSourceFailureAction is dispatched', () => { it('then it should set correct state', () => { const error = 'some error'; - const queryTransactions: QueryTransaction[] = []; const initalState: Partial = { datasourceError: null, graphResult: [], @@ -111,7 +110,6 @@ describe('Explore item reducer', () => { }; const expectedState = { datasourceError: error, - queryTransactions, graphResult: undefined as any[], tableResult: undefined as TableModel, logsResult: undefined as LogsModel, @@ -144,9 +142,9 @@ describe('Explore item reducer', () => { const StartPage = {}; const datasourceInstance = { meta: { - metrics: {}, - logs: {}, - tables: {}, + metrics: true, + logs: true, + tables: true, }, components: { ExploreStartPage: StartPage, @@ -175,6 +173,11 @@ describe('Explore item reducer', () => { queryKeys, supportedModes: [ExploreMode.Metrics, ExploreMode.Logs], mode: ExploreMode.Metrics, + graphIsLoading: false, + tableIsLoading: false, + logIsLoading: false, + latency: 0, + queryErrors: [], }; reducerTester() @@ -185,6 +188,28 @@ describe('Explore item reducer', () => { }); }); }); + + describe('run queries', () => { + describe('when runQueriesAction is dispatched', () => { + it('then it should set correct state', () => { + const initalState: Partial = { + showingStartPage: true, + }; + const expectedState = { + queryIntervals: { + interval: '1s', + intervalMs: 1000, + }, + showingStartPage: false, + }; + + reducerTester() + .givenReducer(itemReducer, initalState) + .whenActionIsDispatched(runQueriesAction({ exploreId: ExploreId.left })) + .thenStateShouldEqual(expectedState); + }); + }); + }); }); export const setup = (urlStateOverrides?: any) => { @@ -529,46 +554,8 @@ describe('Explore reducer', () => { }); }); - describe('and refreshInterval differs', () => { - it('then it should return update refreshInterval', () => { - const { initalState, serializedUrlState } = setup(); - const expectedState = { - ...initalState, - left: { - ...initalState.left, - update: { - ...initalState.left.update, - refreshInterval: true, - }, - }, - }; - const stateWithDifferentDataSource = { - ...initalState, - left: { - ...initalState.left, - urlState: { - ...initalState.left.urlState, - refreshInterval: '5s', - }, - }, - }; - - reducerTester() - .givenReducer(exploreReducer, stateWithDifferentDataSource) - .whenActionIsDispatched( - updateLocation({ - query: { - left: serializedUrlState, - }, - path: '/explore', - }) - ) - .thenStateShouldEqual(expectedState); - }); - }); - describe('and nothing differs', () => { - fit('then it should return update ui', () => { + it('then it should return update ui', () => { const { initalState, serializedUrlState } = setup(); const expectedState = { ...initalState }; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index f0847360cbe3..743294f0e74c 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -95,7 +95,6 @@ export const makeExploreItemState = (): ExploreItemState => ({ scanning: false, scanRange: null, showingGraph: true, - showingLogs: true, showingTable: true, graphIsLoading: false, logIsLoading: false, @@ -351,7 +350,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta logsResult: resultType === 'Logs' ? null : state.logsResult, latency: 0, queryErrors, - showingStartPage: false, graphIsLoading: resultType === 'Graph' ? false : state.graphIsLoading, logIsLoading: resultType === 'Logs' ? false : state.logIsLoading, tableIsLoading: resultType === 'Table' ? false : state.tableIsLoading, @@ -371,7 +369,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta graphIsLoading: resultType === 'Graph' ? true : state.graphIsLoading, logIsLoading: resultType === 'Logs' ? true : state.logIsLoading, tableIsLoading: resultType === 'Table' ? true : state.tableIsLoading, - showingStartPage: false, update: makeInitialUpdateState(), }; }, @@ -392,7 +389,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta graphIsLoading: false, logIsLoading: false, tableIsLoading: false, - showingStartPage: false, update: makeInitialUpdateState(), }; }, @@ -543,6 +539,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, queryIntervals, + showingStartPage: false, }; }, }) diff --git a/public/app/features/explore/state/selectors.ts b/public/app/features/explore/state/selectors.ts index fff52651646c..6925e706d4f4 100644 --- a/public/app/features/explore/state/selectors.ts +++ b/public/app/features/explore/state/selectors.ts @@ -3,10 +3,9 @@ import { ExploreItemState } from 'app/types'; import { filterLogLevels, dedupLogRows } from 'app/core/logs_model'; export const exploreItemUIStateSelector = (itemState: ExploreItemState) => { - const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState; + const { showingGraph, showingTable, showingStartPage, dedupStrategy } = itemState; return { showingGraph, - showingLogs, showingTable, showingStartPage, dedupStrategy, diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index a828cbf9d3ad..6f70ecaa25bb 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -204,10 +204,6 @@ export interface ExploreItemState { * True if graph result viewer is expanded. Query runs will contain graph queries. */ showingGraph: boolean; - /** - * True if logs result viewer is expanded. Query runs will contain logs queries. - */ - showingLogs: boolean; /** * True StartPage needs to be shown. Typically set to `false` once queries have been run. */ diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index d77992038327..26401522118b 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -164,7 +164,7 @@ .explore-panel__header { padding: $space-sm $space-md 0 $space-md; display: flex; - cursor: pointer; + cursor: inherit; transition: all 0.1s linear; } @@ -176,9 +176,20 @@ } .explore-panel__header-buttons { - margin-right: $space-sm; - font-size: $font-size-lg; - line-height: $font-size-h6; + display: none; +} + +.explore-panel--collapsible { + .explore-panel__header { + cursor: pointer; + } + + .explore-panel__header-buttons { + margin-right: $space-sm; + font-size: $font-size-lg; + line-height: $font-size-h6; + display: inherit; + } } .time-series-disclaimer { From 35f227de11fd2b3f542f920571ea93a7890ad11c Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Fri, 17 May 2019 14:57:26 +0300 Subject: [PATCH 04/92] Feature: LDAP refactoring (#16950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * incapsulates multipleldap logic under one module * abstracts users upsert and get logic * changes some of the text error messages and import sort sequence * heavily refactors the LDAP module – LDAP module now only deals with LDAP related behaviour * integrates affected auth_proxy module and their tests * refactoring of the auth_proxy logic --- go.mod | 1 + go.sum | 2 + pkg/extensions/main.go | 1 + pkg/login/auth.go | 15 +- pkg/login/auth_test.go | 32 +- pkg/login/ldap_login.go | 38 +- pkg/login/ldap_login_test.go | 106 ++-- pkg/middleware/auth_proxy.go | 6 +- pkg/middleware/auth_proxy/auth_proxy.go | 136 ++-- pkg/middleware/auth_proxy/auth_proxy_test.go | 92 ++- pkg/middleware/middleware.go | 3 +- pkg/services/ldap/hooks.go | 5 - pkg/services/ldap/ldap.go | 582 ++++++++--------- pkg/services/ldap/ldap_helpers_test.go | 140 +++++ pkg/services/ldap/ldap_login_test.go | 120 +++- pkg/services/ldap/ldap_test.go | 583 ++++-------------- pkg/services/ldap/settings.go | 6 +- pkg/services/ldap/test.go | 49 +- pkg/services/multildap/multildap.go | 204 ++++++ pkg/services/sqlstore/sqlstore.go | 10 +- pkg/services/user/user.go | 39 ++ pkg/setting/setting.go | 1 + .../brianvoe/gofakeit/BENCHMARKS.md | 134 ++++ .../brianvoe/gofakeit/CODE_OF_CONDUCT.md | 46 ++ .../brianvoe/gofakeit/CONTRIBUTING.md | 1 + .../github.com/brianvoe/gofakeit/LICENSE.txt | 20 + vendor/github.com/brianvoe/gofakeit/README.md | 254 ++++++++ vendor/github.com/brianvoe/gofakeit/TODO.txt | 3 + .../github.com/brianvoe/gofakeit/address.go | 131 ++++ vendor/github.com/brianvoe/gofakeit/beer.go | 45 ++ vendor/github.com/brianvoe/gofakeit/bool.go | 10 + vendor/github.com/brianvoe/gofakeit/color.go | 44 ++ .../github.com/brianvoe/gofakeit/company.go | 30 + .../github.com/brianvoe/gofakeit/contact.go | 40 ++ .../github.com/brianvoe/gofakeit/currency.go | 38 ++ .../brianvoe/gofakeit/data/address.go | 15 + .../github.com/brianvoe/gofakeit/data/beer.go | 10 + .../brianvoe/gofakeit/data/colors.go | 7 + .../brianvoe/gofakeit/data/company.go | 9 + .../brianvoe/gofakeit/data/computer.go | 8 + .../brianvoe/gofakeit/data/contact.go | 6 + .../brianvoe/gofakeit/data/currency.go | 7 + .../github.com/brianvoe/gofakeit/data/data.go | 28 + .../brianvoe/gofakeit/data/datetime.go | 9 + .../brianvoe/gofakeit/data/files.go | 7 + .../brianvoe/gofakeit/data/hacker.go | 20 + .../brianvoe/gofakeit/data/hipster.go | 6 + .../brianvoe/gofakeit/data/internet.go | 8 + .../github.com/brianvoe/gofakeit/data/job.go | 8 + .../brianvoe/gofakeit/data/log_level.go | 8 + .../brianvoe/gofakeit/data/lorem.go | 6 + .../brianvoe/gofakeit/data/payment.go | 20 + .../brianvoe/gofakeit/data/person.go | 9 + .../brianvoe/gofakeit/data/status_code.go | 7 + .../brianvoe/gofakeit/data/vehicle.go | 10 + .../github.com/brianvoe/gofakeit/datetime.go | 77 +++ vendor/github.com/brianvoe/gofakeit/doc.go | 10 + vendor/github.com/brianvoe/gofakeit/faker.go | 15 + vendor/github.com/brianvoe/gofakeit/file.go | 11 + .../github.com/brianvoe/gofakeit/generate.go | 41 ++ vendor/github.com/brianvoe/gofakeit/hacker.go | 35 ++ .../github.com/brianvoe/gofakeit/hipster.go | 20 + vendor/github.com/brianvoe/gofakeit/image.go | 8 + .../github.com/brianvoe/gofakeit/internet.go | 55 ++ vendor/github.com/brianvoe/gofakeit/job.go | 34 + .../github.com/brianvoe/gofakeit/log_level.go | 15 + vendor/github.com/brianvoe/gofakeit/logo.png | Bin 0 -> 36022 bytes vendor/github.com/brianvoe/gofakeit/misc.go | 132 ++++ vendor/github.com/brianvoe/gofakeit/name.go | 26 + vendor/github.com/brianvoe/gofakeit/number.go | 84 +++ .../github.com/brianvoe/gofakeit/password.go | 68 ++ .../github.com/brianvoe/gofakeit/payment.go | 81 +++ vendor/github.com/brianvoe/gofakeit/person.go | 45 ++ .../brianvoe/gofakeit/status_code.go | 11 + vendor/github.com/brianvoe/gofakeit/string.go | 48 ++ vendor/github.com/brianvoe/gofakeit/struct.go | 87 +++ vendor/github.com/brianvoe/gofakeit/unique.go | 34 + .../brianvoe/gofakeit/user_agent.go | 92 +++ .../github.com/brianvoe/gofakeit/vehicle.go | 55 ++ vendor/github.com/brianvoe/gofakeit/words.go | 100 +++ vendor/github.com/robfig/cron/README.md | 2 +- vendor/github.com/robfig/cron/doc.go | 2 +- vendor/modules.txt | 3 + 83 files changed, 3375 insertions(+), 991 deletions(-) delete mode 100644 pkg/services/ldap/hooks.go create mode 100644 pkg/services/ldap/ldap_helpers_test.go create mode 100644 pkg/services/multildap/multildap.go create mode 100644 pkg/services/user/user.go create mode 100644 vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md create mode 100644 vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md create mode 100644 vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md create mode 100644 vendor/github.com/brianvoe/gofakeit/LICENSE.txt create mode 100644 vendor/github.com/brianvoe/gofakeit/README.md create mode 100644 vendor/github.com/brianvoe/gofakeit/TODO.txt create mode 100644 vendor/github.com/brianvoe/gofakeit/address.go create mode 100644 vendor/github.com/brianvoe/gofakeit/beer.go create mode 100644 vendor/github.com/brianvoe/gofakeit/bool.go create mode 100644 vendor/github.com/brianvoe/gofakeit/color.go create mode 100644 vendor/github.com/brianvoe/gofakeit/company.go create mode 100644 vendor/github.com/brianvoe/gofakeit/contact.go create mode 100644 vendor/github.com/brianvoe/gofakeit/currency.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/address.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/beer.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/colors.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/company.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/computer.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/contact.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/currency.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/data.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/datetime.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/files.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/hacker.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/hipster.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/internet.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/job.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/log_level.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/lorem.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/payment.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/person.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/status_code.go create mode 100644 vendor/github.com/brianvoe/gofakeit/data/vehicle.go create mode 100644 vendor/github.com/brianvoe/gofakeit/datetime.go create mode 100644 vendor/github.com/brianvoe/gofakeit/doc.go create mode 100644 vendor/github.com/brianvoe/gofakeit/faker.go create mode 100644 vendor/github.com/brianvoe/gofakeit/file.go create mode 100644 vendor/github.com/brianvoe/gofakeit/generate.go create mode 100644 vendor/github.com/brianvoe/gofakeit/hacker.go create mode 100644 vendor/github.com/brianvoe/gofakeit/hipster.go create mode 100644 vendor/github.com/brianvoe/gofakeit/image.go create mode 100644 vendor/github.com/brianvoe/gofakeit/internet.go create mode 100644 vendor/github.com/brianvoe/gofakeit/job.go create mode 100644 vendor/github.com/brianvoe/gofakeit/log_level.go create mode 100644 vendor/github.com/brianvoe/gofakeit/logo.png create mode 100644 vendor/github.com/brianvoe/gofakeit/misc.go create mode 100644 vendor/github.com/brianvoe/gofakeit/name.go create mode 100644 vendor/github.com/brianvoe/gofakeit/number.go create mode 100644 vendor/github.com/brianvoe/gofakeit/password.go create mode 100644 vendor/github.com/brianvoe/gofakeit/payment.go create mode 100644 vendor/github.com/brianvoe/gofakeit/person.go create mode 100644 vendor/github.com/brianvoe/gofakeit/status_code.go create mode 100644 vendor/github.com/brianvoe/gofakeit/string.go create mode 100644 vendor/github.com/brianvoe/gofakeit/struct.go create mode 100644 vendor/github.com/brianvoe/gofakeit/unique.go create mode 100644 vendor/github.com/brianvoe/gofakeit/user_agent.go create mode 100644 vendor/github.com/brianvoe/gofakeit/vehicle.go create mode 100644 vendor/github.com/brianvoe/gofakeit/words.go diff --git a/go.mod b/go.mod index 106d8b726617..619f5183a5c7 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/aws/aws-sdk-go v1.18.5 github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 + github.com/brianvoe/gofakeit v3.17.0+incompatible github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/codegangsta/cli v1.20.0 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 55223ecbc74e..3c77812fbf7b 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/brianvoe/gofakeit v3.17.0+incompatible h1:C1+30+c0GtjgGDtRC+iePZeP1WMiwsWCELNJhmc7aIc= +github.com/brianvoe/gofakeit v3.17.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 6ee742a4d8e3..cbe9ec2b7b07 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -1,6 +1,7 @@ package extensions import ( + _ "github.com/brianvoe/gofakeit" _ "github.com/gobwas/glob" _ "github.com/robfig/cron" _ "gopkg.in/square/go-jose.v2" diff --git a/pkg/login/auth.go b/pkg/login/auth.go index 56f614d92dea..51eda933c6e1 100644 --- a/pkg/login/auth.go +++ b/pkg/login/auth.go @@ -4,8 +4,8 @@ import ( "errors" "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" - LDAP "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ldap" ) var ( @@ -25,7 +25,8 @@ func Init() { bus.AddHandler("auth", AuthenticateUser) } -func AuthenticateUser(query *m.LoginUserQuery) error { +// AuthenticateUser authenticates the user via username & password +func AuthenticateUser(query *models.LoginUserQuery) error { if err := validateLoginAttempts(query.Username); err != nil { return err } @@ -35,24 +36,24 @@ func AuthenticateUser(query *m.LoginUserQuery) error { } err := loginUsingGrafanaDB(query) - if err == nil || (err != m.ErrUserNotFound && err != ErrInvalidCredentials) { + if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials) { return err } ldapEnabled, ldapErr := loginUsingLdap(query) if ldapEnabled { - if ldapErr == nil || ldapErr != LDAP.ErrInvalidCredentials { + if ldapErr == nil || ldapErr != ldap.ErrInvalidCredentials { return ldapErr } err = ldapErr } - if err == ErrInvalidCredentials || err == LDAP.ErrInvalidCredentials { + if err == ErrInvalidCredentials || err == ldap.ErrInvalidCredentials { saveInvalidLoginAttempt(query) } - if err == m.ErrUserNotFound { + if err == models.ErrUserNotFound { return ErrInvalidCredentials } diff --git a/pkg/login/auth_test.go b/pkg/login/auth_test.go index 85ad3bc07dce..658560b0ce1d 100644 --- a/pkg/login/auth_test.go +++ b/pkg/login/auth_test.go @@ -6,8 +6,8 @@ import ( . "github.com/smartystreets/goconvey/convey" - m "github.com/grafana/grafana/pkg/models" - LDAP "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ldap" ) func TestAuthenticateUser(t *testing.T) { @@ -17,7 +17,7 @@ func TestAuthenticateUser(t *testing.T) { mockLoginUsingGrafanaDB(nil, sc) mockLoginUsingLdap(false, nil, sc) - loginQuery := m.LoginUserQuery{ + loginQuery := models.LoginUserQuery{ Username: "user", Password: "", } @@ -84,7 +84,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) - mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc) + mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) mockLoginUsingLdap(false, nil, sc) mockSaveInvalidLoginAttempt(sc) @@ -101,14 +101,14 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) - mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc) - mockLoginUsingLdap(true, LDAP.ErrInvalidCredentials, sc) + mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) + mockLoginUsingLdap(true, ldap.ErrInvalidCredentials, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) Convey("it should result in", func() { - So(err, ShouldEqual, LDAP.ErrInvalidCredentials) + So(err, ShouldEqual, ldap.ErrInvalidCredentials) So(sc.loginAttemptValidationWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue) @@ -118,7 +118,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) - mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc) + mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) mockLoginUsingLdap(true, nil, sc) mockSaveInvalidLoginAttempt(sc) @@ -136,7 +136,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) { customErr := errors.New("custom") mockLoginAttemptValidation(nil, sc) - mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc) + mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) mockLoginUsingLdap(true, customErr, sc) mockSaveInvalidLoginAttempt(sc) @@ -154,13 +154,13 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc) - mockLoginUsingLdap(true, LDAP.ErrInvalidCredentials, sc) + mockLoginUsingLdap(true, ldap.ErrInvalidCredentials, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) Convey("it should result in", func() { - So(err, ShouldEqual, LDAP.ErrInvalidCredentials) + So(err, ShouldEqual, ldap.ErrInvalidCredentials) So(sc.loginAttemptValidationWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue) @@ -171,7 +171,7 @@ func TestAuthenticateUser(t *testing.T) { } type authScenarioContext struct { - loginUserQuery *m.LoginUserQuery + loginUserQuery *models.LoginUserQuery grafanaLoginWasCalled bool ldapLoginWasCalled bool loginAttemptValidationWasCalled bool @@ -181,14 +181,14 @@ type authScenarioContext struct { type authScenarioFunc func(sc *authScenarioContext) func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) { - loginUsingGrafanaDB = func(query *m.LoginUserQuery) error { + loginUsingGrafanaDB = func(query *models.LoginUserQuery) error { sc.grafanaLoginWasCalled = true return err } } func mockLoginUsingLdap(enabled bool, err error, sc *authScenarioContext) { - loginUsingLdap = func(query *m.LoginUserQuery) (bool, error) { + loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) { sc.ldapLoginWasCalled = true return enabled, err } @@ -202,7 +202,7 @@ func mockLoginAttemptValidation(err error, sc *authScenarioContext) { } func mockSaveInvalidLoginAttempt(sc *authScenarioContext) { - saveInvalidLoginAttempt = func(query *m.LoginUserQuery) { + saveInvalidLoginAttempt = func(query *models.LoginUserQuery) { sc.saveInvalidLoginAttemptWasCalled = true } } @@ -215,7 +215,7 @@ func authScenario(desc string, fn authScenarioFunc) { origSaveInvalidLoginAttempt := saveInvalidLoginAttempt sc := &authScenarioContext{ - loginUserQuery: &m.LoginUserQuery{ + loginUserQuery: &models.LoginUserQuery{ Username: "user", Password: "pwd", IpAddress: "192.168.1.1:56433", diff --git a/pkg/login/ldap_login.go b/pkg/login/ldap_login.go index abd861cbe6dd..0b41e612a2ee 100644 --- a/pkg/login/ldap_login.go +++ b/pkg/login/ldap_login.go @@ -2,13 +2,20 @@ package login import ( "github.com/grafana/grafana/pkg/models" - LDAP "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/services/multildap" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" ) -var newLDAP = LDAP.New -var getLDAPConfig = LDAP.GetConfig -var isLDAPEnabled = LDAP.IsEnabled +// getLDAPConfig gets LDAP config +var getLDAPConfig = multildap.GetConfig + +// isLDAPEnabled checks if LDAP is enabled +var isLDAPEnabled = multildap.IsEnabled + +// newLDAP creates multiple LDAP instance +var newLDAP = multildap.New // loginUsingLdap logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be // populated with the logged in user if successful. @@ -23,18 +30,21 @@ var loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) { if err != nil { return true, errutil.Wrap("Failed to get LDAP config", err) } - if len(config.Servers) == 0 { - return true, ErrNoLDAPServers - } - for _, server := range config.Servers { - auth := newLDAP(server) + externalUser, err := newLDAP(config.Servers).Login(query) + if err != nil { + return true, err + } - err := auth.Login(query) - if err == nil || err != LDAP.ErrInvalidCredentials { - return true, err - } + login, err := user.Upsert(&user.UpsertArgs{ + ExternalUser: externalUser, + SignupAllowed: setting.LdapAllowSignup, + }) + if err != nil { + return true, err } - return true, LDAP.ErrInvalidCredentials + query.User = login + + return true, nil } diff --git a/pkg/login/ldap_login_test.go b/pkg/login/ldap_login_test.go index 3ea82d0a8ed6..b93a4890bc31 100644 --- a/pkg/login/ldap_login_test.go +++ b/pkg/login/ldap_login_test.go @@ -6,8 +6,9 @@ import ( . "github.com/smartystreets/goconvey/convey" - m "github.com/grafana/grafana/pkg/models" - LDAP "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/setting" ) @@ -18,11 +19,11 @@ func TestLdapLogin(t *testing.T) { Convey("Given ldap enabled and no server configured", func() { setting.LdapEnabled = true - ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) { + LDAPLoginScenario("When login", func(sc *LDAPLoginScenarioContext) { sc.withLoginResult(false) - getLDAPConfig = func() (*LDAP.Config, error) { - config := &LDAP.Config{ - Servers: []*LDAP.ServerConfig{}, + getLDAPConfig = func() (*ldap.Config, error) { + config := &ldap.Config{ + Servers: []*ldap.ServerConfig{}, } return config, nil @@ -35,11 +36,11 @@ func TestLdapLogin(t *testing.T) { }) Convey("it should return no LDAP servers error", func() { - So(err, ShouldEqual, ErrNoLDAPServers) + So(err, ShouldEqual, errTest) }) Convey("it should not call ldap login", func() { - So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse) + So(sc.LDAPAuthenticatorMock.loginCalled, ShouldBeTrue) }) }) }) @@ -47,9 +48,9 @@ func TestLdapLogin(t *testing.T) { Convey("Given ldap disabled", func() { setting.LdapEnabled = false - ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) { + LDAPLoginScenario("When login", func(sc *LDAPLoginScenarioContext) { sc.withLoginResult(false) - enabled, err := loginUsingLdap(&m.LoginUserQuery{ + enabled, err := loginUsingLdap(&models.LoginUserQuery{ Username: "user", Password: "pwd", }) @@ -63,75 +64,88 @@ func TestLdapLogin(t *testing.T) { }) Convey("it should not call ldap login", func() { - So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse) + So(sc.LDAPAuthenticatorMock.loginCalled, ShouldBeFalse) }) }) }) }) } -func mockLdapAuthenticator(valid bool) *mockAuth { - mock := &mockAuth{ - validLogin: valid, - } - - newLDAP = func(server *LDAP.ServerConfig) LDAP.IAuth { - return mock - } - - return mock -} - type mockAuth struct { validLogin bool loginCalled bool } -func (auth *mockAuth) Login(query *m.LoginUserQuery) error { +func (auth *mockAuth) Login(query *models.LoginUserQuery) ( + *models.ExternalUserInfo, + error, +) { auth.loginCalled = true if !auth.validLogin { - return errTest + return nil, errTest } - return nil + return nil, nil } -func (auth *mockAuth) Users() ([]*LDAP.UserInfo, error) { +func (auth *mockAuth) Users(logins []string) ( + []*models.ExternalUserInfo, + error, +) { return nil, nil } -func (auth *mockAuth) SyncUser(query *m.LoginUserQuery) error { +func (auth *mockAuth) User(login string) ( + *models.ExternalUserInfo, + error, +) { + return nil, nil +} + +func (auth *mockAuth) Add(dn string, values map[string][]string) error { return nil } -func (auth *mockAuth) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LDAP.UserInfo) (*m.User, error) { - return nil, nil +func (auth *mockAuth) Remove(dn string) error { + return nil +} + +func mockLDAPAuthenticator(valid bool) *mockAuth { + mock := &mockAuth{ + validLogin: valid, + } + + newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { + return mock + } + + return mock } -type ldapLoginScenarioContext struct { - loginUserQuery *m.LoginUserQuery - ldapAuthenticatorMock *mockAuth +type LDAPLoginScenarioContext struct { + loginUserQuery *models.LoginUserQuery + LDAPAuthenticatorMock *mockAuth } -type ldapLoginScenarioFunc func(c *ldapLoginScenarioContext) +type LDAPLoginScenarioFunc func(c *LDAPLoginScenarioContext) -func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) { +func LDAPLoginScenario(desc string, fn LDAPLoginScenarioFunc) { Convey(desc, func() { mock := &mockAuth{} - sc := &ldapLoginScenarioContext{ - loginUserQuery: &m.LoginUserQuery{ + sc := &LDAPLoginScenarioContext{ + loginUserQuery: &models.LoginUserQuery{ Username: "user", Password: "pwd", IpAddress: "192.168.1.1:56433", }, - ldapAuthenticatorMock: mock, + LDAPAuthenticatorMock: mock, } - getLDAPConfig = func() (*LDAP.Config, error) { - config := &LDAP.Config{ - Servers: []*LDAP.ServerConfig{ + getLDAPConfig = func() (*ldap.Config, error) { + config := &ldap.Config{ + Servers: []*ldap.ServerConfig{ { Host: "", }, @@ -141,19 +155,19 @@ func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) { return config, nil } - newLDAP = func(server *LDAP.ServerConfig) LDAP.IAuth { + newLDAP = func(server []*ldap.ServerConfig) multildap.IMultiLDAP { return mock } defer func() { - newLDAP = LDAP.New - getLDAPConfig = LDAP.GetConfig + newLDAP = multildap.New + getLDAPConfig = multildap.GetConfig }() fn(sc) }) } -func (sc *ldapLoginScenarioContext) withLoginResult(valid bool) { - sc.ldapAuthenticatorMock = mockLdapAuthenticator(valid) +func (sc *LDAPLoginScenarioContext) withLoginResult(valid bool) { + sc.LDAPAuthenticatorMock = mockLDAPAuthenticator(valid) } diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index d3d8d8e77d8a..890fd5e4f24b 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -35,8 +35,8 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, return true } - // Try to get user id from various sources - id, err := auth.GetUserID() + // Try to log in user from various providers + id, err := auth.Login() if err != nil { ctx.Handle(500, err.Error(), err.DetailsError) return true @@ -54,7 +54,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, ctx.IsSignedIn = true // Remember user data it in cache - if err := auth.Remember(); err != nil { + if err := auth.Remember(id); err != nil { ctx.Handle(500, err.Error(), err.DetailsError) return true } diff --git a/pkg/middleware/auth_proxy/auth_proxy.go b/pkg/middleware/auth_proxy/auth_proxy.go index 98bacbeccf47..4cb2de38c7f1 100644 --- a/pkg/middleware/auth_proxy/auth_proxy.go +++ b/pkg/middleware/auth_proxy/auth_proxy.go @@ -12,6 +12,8 @@ import ( "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/services/multildap" + "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -21,10 +23,14 @@ const ( CachePrefix = "auth-proxy-sync-ttl:%s" ) -var ( - getLDAPConfig = ldap.GetConfig - isLDAPEnabled = ldap.IsEnabled -) +// getLDAPConfig gets LDAP config +var getLDAPConfig = ldap.GetConfig + +// isLDAPEnabled checks if LDAP is enabled +var isLDAPEnabled = ldap.IsEnabled + +// newLDAP creates multiple LDAP instance +var newLDAP = multildap.New // AuthProxy struct type AuthProxy struct { @@ -33,13 +39,13 @@ type AuthProxy struct { orgID int64 header string - LDAP func(server *ldap.ServerConfig) ldap.IAuth - - enabled bool - whitelistIP string - headerType string - headers map[string]string - cacheTTL int + enabled bool + LdapAllowSignup bool + AuthProxyAutoSignUp bool + whitelistIP string + headerType string + headers map[string]string + cacheTTL int } // Error auth proxy specific error @@ -78,13 +84,13 @@ func New(options *Options) *AuthProxy { orgID: options.OrgID, header: header, - LDAP: ldap.New, - - enabled: setting.AuthProxyEnabled, - headerType: setting.AuthProxyHeaderProperty, - headers: setting.AuthProxyHeaders, - whitelistIP: setting.AuthProxyWhitelist, - cacheTTL: setting.AuthProxyLdapSyncTtl, + enabled: setting.AuthProxyEnabled, + headerType: setting.AuthProxyHeaderProperty, + headers: setting.AuthProxyHeaders, + whitelistIP: setting.AuthProxyWhitelist, + cacheTTL: setting.AuthProxyLdapSyncTtl, + LdapAllowSignup: setting.LdapAllowSignup, + AuthProxyAutoSignUp: setting.AuthProxyAutoSignUp, } } @@ -144,34 +150,22 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) { return false, newError("Proxy authentication required", err) } -// InCache checks if we have user in cache -func (auth *AuthProxy) InCache() bool { - userID, _ := auth.GetUserIDViaCache() - - if userID == 0 { - return false - } - - return true -} - // getKey forms a key for the cache func (auth *AuthProxy) getKey() string { return fmt.Sprintf(CachePrefix, auth.header) } -// GetUserID gets user id with whatever means possible -func (auth *AuthProxy) GetUserID() (int64, *Error) { - if auth.InCache() { +// Login logs in user id with whatever means possible +func (auth *AuthProxy) Login() (int64, *Error) { + id, _ := auth.GetUserViaCache() + if id != 0 { // Error here means absent cache - we don't need to handle that - id, _ := auth.GetUserIDViaCache() - return id, nil } if isLDAPEnabled() { - id, err := auth.GetUserIDViaLDAP() + id, err := auth.LoginViaLDAP() if err == ldap.ErrInvalidCredentials { return 0, newError( @@ -181,16 +175,16 @@ func (auth *AuthProxy) GetUserID() (int64, *Error) { } if err != nil { - return 0, newError("Failed to sync user", err) + return 0, newError("Failed to get the user", err) } return id, nil } - id, err := auth.GetUserIDViaHeader() + id, err := auth.LoginViaHeader() if err != nil { return 0, newError( - "Failed to login as user specified in auth proxy header", + "Failed to log in as user, specified in auth proxy header", err, ) } @@ -198,8 +192,8 @@ func (auth *AuthProxy) GetUserID() (int64, *Error) { return id, nil } -// GetUserIDViaCache gets the user from cache -func (auth *AuthProxy) GetUserIDViaCache() (int64, error) { +// GetUserViaCache gets user id from cache +func (auth *AuthProxy) GetUserViaCache() (int64, error) { var ( cacheKey = auth.getKey() userID, err = auth.store.Get(cacheKey) @@ -212,33 +206,34 @@ func (auth *AuthProxy) GetUserIDViaCache() (int64, error) { return userID.(int64), nil } -// GetUserIDViaLDAP gets user via LDAP request -func (auth *AuthProxy) GetUserIDViaLDAP() (int64, *Error) { - query := &models.LoginUserQuery{ - ReqContext: auth.ctx, - Username: auth.header, - } - +// LoginViaLDAP logs in user via LDAP request +func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) { config, err := getLDAPConfig() if err != nil { return 0, newError("Failed to get LDAP config", nil) } - if len(config.Servers) == 0 { - return 0, newError("No LDAP servers available", nil) + + extUser, err := newLDAP(config.Servers).User(auth.header) + if err != nil { + return 0, newError(err.Error(), nil) } - for _, server := range config.Servers { - author := auth.LDAP(server) - if err := author.SyncUser(query); err != nil { - return 0, newError(err.Error(), nil) - } + // Have to sync grafana and LDAP user during log in + user, err := user.Upsert(&user.UpsertArgs{ + ReqContext: auth.ctx, + SignupAllowed: auth.LdapAllowSignup, + ExternalUser: extUser, + }) + if err != nil { + return 0, newError(err.Error(), nil) } - return query.User.Id, nil + return user.Id, nil } -// GetUserIDViaHeader gets user from the header only -func (auth *AuthProxy) GetUserIDViaHeader() (int64, error) { +// LoginViaHeader logs in user from the header only +// TODO: refactor - cyclomatic complexity should be much lower +func (auth *AuthProxy) LoginViaHeader() (int64, error) { extUser := &models.ExternalUserInfo{ AuthModule: "authproxy", AuthId: auth.header, @@ -269,18 +264,16 @@ func (auth *AuthProxy) GetUserIDViaHeader() (int64, error) { } } - // add/update user in grafana - cmd := &models.UpsertUserCommand{ + result, err := user.Upsert(&user.UpsertArgs{ ReqContext: auth.ctx, + SignupAllowed: true, ExternalUser: extUser, - SignupAllowed: setting.AuthProxyAutoSignUp, - } - err := bus.Dispatch(cmd) + }) if err != nil { return 0, err } - return cmd.Result.Id, nil + return result.Id, nil } // GetSignedUser get full signed user info @@ -298,21 +291,18 @@ func (auth *AuthProxy) GetSignedUser(userID int64) (*models.SignedInUser, *Error } // Remember user in cache -func (auth *AuthProxy) Remember() *Error { +func (auth *AuthProxy) Remember(id int64) *Error { + key := auth.getKey() - // Make sure we do not rewrite the expiration time - if auth.InCache() { + // Check if user already in cache + userID, _ := auth.store.Get(key) + if userID != nil { return nil } - var ( - key = auth.getKey() - value, _ = auth.GetUserIDViaCache() - expiration = time.Duration(-auth.cacheTTL) * time.Minute - - err = auth.store.Set(key, value, expiration) - ) + expiration := time.Duration(-auth.cacheTTL) * time.Minute + err := auth.store.Set(key, id, expiration) if err != nil { return newError(err.Error(), nil) } diff --git a/pkg/middleware/auth_proxy/auth_proxy_test.go b/pkg/middleware/auth_proxy/auth_proxy_test.go index fbddca81d401..4a1edc66ba5a 100644 --- a/pkg/middleware/auth_proxy/auth_proxy_test.go +++ b/pkg/middleware/auth_proxy/auth_proxy_test.go @@ -1,6 +1,7 @@ package authproxy import ( + "errors" "fmt" "net/http" "testing" @@ -8,24 +9,40 @@ import ( . "github.com/smartystreets/goconvey/convey" "gopkg.in/macaron.v1" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/ldap" + "github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/setting" ) -type TestLDAP struct { - ldap.Auth - ID int64 - syncCalled bool +type TestMultiLDAP struct { + multildap.MultiLDAP + ID int64 + userCalled bool + loginCalled bool } -func (stub *TestLDAP) SyncUser(query *models.LoginUserQuery) error { - stub.syncCalled = true - query.User = &models.User{ - Id: stub.ID, +func (stub *TestMultiLDAP) Login(query *models.LoginUserQuery) ( + *models.ExternalUserInfo, error, +) { + stub.loginCalled = true + result := &models.ExternalUserInfo{ + UserId: stub.ID, } - return nil + return result, nil +} + +func (stub *TestMultiLDAP) User(login string) ( + *models.ExternalUserInfo, + error, +) { + stub.userCalled = true + result := &models.ExternalUserInfo{ + UserId: stub.ID, + } + return result, nil } func TestMiddlewareContext(t *testing.T) { @@ -44,7 +61,7 @@ func TestMiddlewareContext(t *testing.T) { }, } - Convey("gets data from the cache", func() { + Convey("logs in user from the cache", func() { store := remotecache.NewFakeStore(t) key := fmt.Sprintf(CachePrefix, name) store.Set(key, int64(33), 0) @@ -55,53 +72,64 @@ func TestMiddlewareContext(t *testing.T) { OrgID: 4, }) - id, err := auth.GetUserID() + id, err := auth.Login() So(err, ShouldBeNil) So(id, ShouldEqual, 33) }) Convey("LDAP", func() { - Convey("gets data from the LDAP", func() { + Convey("logs in via LDAP", func() { + bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error { + cmd.Result = &models.User{ + Id: 42, + } + + return nil + }) + isLDAPEnabled = func() bool { return true } + stub := &TestMultiLDAP{ + ID: 42, + } + getLDAPConfig = func() (*ldap.Config, error) { config := &ldap.Config{ Servers: []*ldap.ServerConfig{ - {}, + { + SearchBaseDNs: []string{"BaseDNHere"}, + }, }, } return config, nil } + newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { + return stub + } + defer func() { + newLDAP = multildap.New isLDAPEnabled = ldap.IsEnabled getLDAPConfig = ldap.GetConfig }() store := remotecache.NewFakeStore(t) - auth := New(&Options{ + server := New(&Options{ Store: store, Ctx: ctx, OrgID: 4, }) - stub := &TestLDAP{ - ID: 42, - } - - auth.LDAP = func(server *ldap.ServerConfig) ldap.IAuth { - return stub - } - - id, err := auth.GetUserID() + id, err := server.Login() So(err, ShouldBeNil) So(id, ShouldEqual, 42) - So(stub.syncCalled, ShouldEqual, true) + So(stub.userCalled, ShouldEqual, true) }) Convey("gets nice error if ldap is enabled but not configured", func() { @@ -110,13 +138,11 @@ func TestMiddlewareContext(t *testing.T) { } getLDAPConfig = func() (*ldap.Config, error) { - config := &ldap.Config{ - Servers: []*ldap.ServerConfig{}, - } - return config, nil + return nil, errors.New("Something went wrong") } defer func() { + newLDAP = multildap.New isLDAPEnabled = ldap.IsEnabled getLDAPConfig = ldap.GetConfig }() @@ -129,20 +155,20 @@ func TestMiddlewareContext(t *testing.T) { OrgID: 4, }) - stub := &TestLDAP{ + stub := &TestMultiLDAP{ ID: 42, } - auth.LDAP = func(server *ldap.ServerConfig) ldap.IAuth { + newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { return stub } - id, err := auth.GetUserID() + id, err := auth.Login() So(err, ShouldNotBeNil) - So(err.Error(), ShouldContainSubstring, "Failed to sync user") + So(err.Error(), ShouldContainSubstring, "Failed to get the user") So(id, ShouldNotEqual, 42) - So(stub.syncCalled, ShouldEqual, false) + So(stub.loginCalled, ShouldEqual, false) }) }) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index ec7194a7fc66..1b465e7a4e54 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -7,6 +7,8 @@ import ( "strings" "time" + macaron "gopkg.in/macaron.v1" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/infra/log" @@ -14,7 +16,6 @@ import ( m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" - macaron "gopkg.in/macaron.v1" ) var ( diff --git a/pkg/services/ldap/hooks.go b/pkg/services/ldap/hooks.go deleted file mode 100644 index ece98e5e73b0..000000000000 --- a/pkg/services/ldap/hooks.go +++ /dev/null @@ -1,5 +0,0 @@ -package ldap - -var ( - hookDial func(*Auth) error -) diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index d6b5b69d7525..20953db97c31 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -8,39 +8,38 @@ import ( "io/ioutil" "strings" - "github.com/davecgh/go-spew/spew" - LDAP "gopkg.in/ldap.v3" + "gopkg.in/ldap.v3" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" - models "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/models" ) // IConnection is interface for LDAP connection manipulation type IConnection interface { Bind(username, password string) error UnauthenticatedBind(username string) error - Search(*LDAP.SearchRequest) (*LDAP.SearchResult, error) + Add(*ldap.AddRequest) error + Del(*ldap.DelRequest) error + Search(*ldap.SearchRequest) (*ldap.SearchResult, error) StartTLS(*tls.Config) error Close() } -// IAuth is interface for LDAP authorization -type IAuth interface { - Login(query *models.LoginUserQuery) error - SyncUser(query *models.LoginUserQuery) error - GetGrafanaUserFor( - ctx *models.ReqContext, - user *UserInfo, - ) (*models.User, error) - Users() ([]*UserInfo, error) +// IServer is interface for LDAP authorization +type IServer interface { + Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error) + Add(string, map[string][]string) error + Remove(string) error + Users([]string) ([]*models.ExternalUserInfo, error) + ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error) + Dial() error + Close() } -// Auth is basic struct of LDAP authorization -type Auth struct { - server *ServerConfig - conn IConnection +// Server is basic struct of LDAP authorization +type Server struct { + config *ServerConfig + connection IConnection requireSecondBind bool log log.Logger } @@ -52,28 +51,24 @@ var ( ) var dial = func(network, addr string) (IConnection, error) { - return LDAP.Dial(network, addr) + return ldap.Dial(network, addr) } // New creates the new LDAP auth -func New(server *ServerConfig) IAuth { - return &Auth{ - server: server, +func New(config *ServerConfig) IServer { + return &Server{ + config: config, log: log.New("ldap"), } } // Dial dials in the LDAP -func (auth *Auth) Dial() error { - if hookDial != nil { - return hookDial(auth) - } - +func (server *Server) Dial() error { var err error var certPool *x509.CertPool - if auth.server.RootCACert != "" { + if server.config.RootCACert != "" { certPool = x509.NewCertPool() - for _, caCertFile := range strings.Split(auth.server.RootCACert, " ") { + for _, caCertFile := range strings.Split(server.config.RootCACert, " ") { pem, err := ioutil.ReadFile(caCertFile) if err != nil { return err @@ -84,35 +79,35 @@ func (auth *Auth) Dial() error { } } var clientCert tls.Certificate - if auth.server.ClientCert != "" && auth.server.ClientKey != "" { - clientCert, err = tls.LoadX509KeyPair(auth.server.ClientCert, auth.server.ClientKey) + if server.config.ClientCert != "" && server.config.ClientKey != "" { + clientCert, err = tls.LoadX509KeyPair(server.config.ClientCert, server.config.ClientKey) if err != nil { return err } } - for _, host := range strings.Split(auth.server.Host, " ") { - address := fmt.Sprintf("%s:%d", host, auth.server.Port) - if auth.server.UseSSL { + for _, host := range strings.Split(server.config.Host, " ") { + address := fmt.Sprintf("%s:%d", host, server.config.Port) + if server.config.UseSSL { tlsCfg := &tls.Config{ - InsecureSkipVerify: auth.server.SkipVerifySSL, + InsecureSkipVerify: server.config.SkipVerifySSL, ServerName: host, RootCAs: certPool, } if len(clientCert.Certificate) > 0 { tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert) } - if auth.server.StartTLS { - auth.conn, err = dial("tcp", address) + if server.config.StartTLS { + server.connection, err = dial("tcp", address) if err == nil { - if err = auth.conn.StartTLS(tlsCfg); err == nil { + if err = server.connection.StartTLS(tlsCfg); err == nil { return nil } } } else { - auth.conn, err = LDAP.DialTLS("tcp", address, tlsCfg) + server.connection, err = ldap.DialTLS("tcp", address, tlsCfg) } } else { - auth.conn, err = dial("tcp", address) + server.connection, err = dial("tcp", address) } if err == nil { @@ -122,91 +117,206 @@ func (auth *Auth) Dial() error { return err } -// Login logs in the user -func (auth *Auth) Login(query *models.LoginUserQuery) error { - // connect to ldap server - if err := auth.Dial(); err != nil { - return err - } - defer auth.conn.Close() +// Close closes the LDAP connection +func (server *Server) Close() { + server.connection.Close() +} - // perform initial authentication - if err := auth.initialBind(query.Username, query.Password); err != nil { - return err +// Login intialBinds the user, search it and then serialize it +func (server *Server) Login(query *models.LoginUserQuery) ( + *models.ExternalUserInfo, error, +) { + + // Perform initial authentication + err := server.intialBind(query.Username, query.Password) + if err != nil { + return nil, err } - // find user entry & attributes - user, err := auth.searchForUser(query.Username) + // Find user entry & attributes + users, err := server.Users([]string{query.Username}) if err != nil { - return err + return nil, err } - auth.log.Debug("Ldap User found", "info", spew.Sdump(user)) + // If we couldn't find the user - + // we should show incorrect credentials err + if len(users) == 0 { + return nil, ErrInvalidCredentials + } - // check if a second user bind is needed - if auth.requireSecondBind { - err = auth.secondBind(user, query.Password) + // Check if a second user bind is needed + user := users[0] + if server.requireSecondBind { + err = server.secondBind(user, query.Password) if err != nil { - return err + return nil, err } } - grafanaUser, err := auth.GetGrafanaUserFor(query.ReqContext, user) + return user, nil +} + +// Add adds stuff to LDAP +func (server *Server) Add(dn string, values map[string][]string) error { + err := server.intialBind( + server.config.BindDN, + server.config.BindPassword, + ) if err != nil { return err } - query.User = grafanaUser - return nil -} + attributes := make([]ldap.Attribute, 0) + for key, value := range values { + attributes = append(attributes, ldap.Attribute{ + Type: key, + Vals: value, + }) + } -// SyncUser syncs user with Grafana -func (auth *Auth) SyncUser(query *models.LoginUserQuery) error { - // connect to ldap server - err := auth.Dial() + request := &ldap.AddRequest{ + DN: dn, + Attributes: attributes, + } + + err = server.connection.Add(request) if err != nil { return err } - defer auth.conn.Close() - err = auth.serverBind() + return nil +} + +// Remove removes stuff from LDAP +func (server *Server) Remove(dn string) error { + err := server.intialBind( + server.config.BindDN, + server.config.BindPassword, + ) if err != nil { return err } - // find user entry & attributes - user, err := auth.searchForUser(query.Username) + request := ldap.NewDelRequest(dn, nil) + err = server.connection.Del(request) if err != nil { - auth.log.Error("Failed searching for user in ldap", "error", err) return err } - auth.log.Debug("Ldap User found", "info", spew.Sdump(user)) + return nil +} + +// Users gets LDAP users +func (server *Server) Users(logins []string) ( + []*models.ExternalUserInfo, + error, +) { + var result *ldap.SearchResult + var err error + var config = server.config - grafanaUser, err := auth.GetGrafanaUserFor(query.ReqContext, user) + for _, base := range config.SearchBaseDNs { + result, err = server.connection.Search( + server.getSearchRequest(base, logins), + ) + if err != nil { + return nil, err + } + + if len(result.Entries) > 0 { + break + } + } + + serializedUsers, err := server.serializeUsers(result) if err != nil { - return err + return nil, err + } + + return serializedUsers, nil +} + +// ExtractGrafanaUser extracts external user info from LDAP user +func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserInfo, error) { + result := server.buildGrafanaUser(user) + if err := server.validateGrafanaUser(result); err != nil { + return nil, err + } + + return result, nil +} + +// validateGrafanaUser validates user access. +// If there are no ldap group mappings access is true +// otherwise a single group must match +func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error { + if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 { + server.log.Error( + "user does not belong in any of the specified LDAP groups", + "username", user.Login, + "groups", user.Groups, + ) + return ErrInvalidCredentials } - query.User = grafanaUser return nil } -func (auth *Auth) GetGrafanaUserFor( - ctx *models.ReqContext, - user *UserInfo, -) (*models.User, error) { +// getSearchRequest returns LDAP search request for users +func (server *Server) getSearchRequest( + base string, + logins []string, +) *ldap.SearchRequest { + attributes := []string{} + + inputs := server.config.Attr + attributes = appendIfNotEmpty( + attributes, + inputs.Username, + inputs.Surname, + inputs.Email, + inputs.Name, + inputs.MemberOf, + ) + + search := "" + for _, login := range logins { + query := strings.Replace( + server.config.SearchFilter, + "%s", ldap.EscapeFilter(login), + -1, + ) + + search = search + query + } + + filter := fmt.Sprintf("(|%s)", search) + + return &ldap.SearchRequest{ + BaseDN: base, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + Attributes: attributes, + Filter: filter, + } +} + +// buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo +func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo { extUser := &models.ExternalUserInfo{ AuthModule: "ldap", AuthId: user.DN, - Name: fmt.Sprintf("%s %s", user.FirstName, user.LastName), - Login: user.Username, - Email: user.Email, - Groups: user.MemberOf, - OrgRoles: map[int64]models.RoleType{}, + Name: strings.TrimSpace( + fmt.Sprintf("%s %s", user.FirstName, user.LastName), + ), + Login: user.Username, + Email: user.Email, + Groups: user.MemberOf, + OrgRoles: map[int64]models.RoleType{}, } - for _, group := range auth.server.Groups { + for _, group := range server.config.Groups { // only use the first match for each org if extUser.OrgRoles[group.OrgId] != "" { continue @@ -220,49 +330,28 @@ func (auth *Auth) GetGrafanaUserFor( } } - // validate that the user has access - // if there are no ldap group mappings access is true - // otherwise a single group must match - if len(auth.server.Groups) > 0 && len(extUser.OrgRoles) < 1 { - auth.log.Info( - "Ldap Auth: user does not belong in any of the specified ldap groups", - "username", user.Username, - "groups", user.MemberOf, - ) - return nil, ErrInvalidCredentials - } - - // add/update user in grafana - upsertUserCmd := &models.UpsertUserCommand{ - ReqContext: ctx, - ExternalUser: extUser, - SignupAllowed: setting.LdapAllowSignup, - } - - err := bus.Dispatch(upsertUserCmd) - if err != nil { - return nil, err - } - - return upsertUserCmd.Result, nil + return extUser } -func (auth *Auth) serverBind() error { +func (server *Server) serverBind() error { bindFn := func() error { - return auth.conn.Bind(auth.server.BindDN, auth.server.BindPassword) + return server.connection.Bind( + server.config.BindDN, + server.config.BindPassword, + ) } - if auth.server.BindPassword == "" { + if server.config.BindPassword == "" { bindFn = func() error { - return auth.conn.UnauthenticatedBind(auth.server.BindDN) + return server.connection.UnauthenticatedBind(server.config.BindDN) } } // bind_dn and bind_password to bind if err := bindFn(); err != nil { - auth.log.Info("LDAP initial bind failed, %v", err) + server.log.Info("LDAP initial bind failed, %v", err) - if ldapErr, ok := err.(*LDAP.Error); ok { + if ldapErr, ok := err.(*ldap.Error); ok { if ldapErr.ResultCode == 49 { return ErrInvalidCredentials } @@ -273,11 +362,15 @@ func (auth *Auth) serverBind() error { return nil } -func (auth *Auth) secondBind(user *UserInfo, userPassword string) error { - if err := auth.conn.Bind(user.DN, userPassword); err != nil { - auth.log.Info("Second bind failed", "error", err) +func (server *Server) secondBind( + user *models.ExternalUserInfo, + userPassword string, +) error { + err := server.connection.Bind(user.AuthId, userPassword) + if err != nil { + server.log.Info("Second bind failed", "error", err) - if ldapErr, ok := err.(*LDAP.Error); ok { + if ldapErr, ok := err.(*ldap.Error); ok { if ldapErr.ResultCode == 49 { return ErrInvalidCredentials } @@ -288,31 +381,31 @@ func (auth *Auth) secondBind(user *UserInfo, userPassword string) error { return nil } -func (auth *Auth) initialBind(username, userPassword string) error { - if auth.server.BindPassword != "" || auth.server.BindDN == "" { - userPassword = auth.server.BindPassword - auth.requireSecondBind = true +func (server *Server) intialBind(username, userPassword string) error { + if server.config.BindPassword != "" || server.config.BindDN == "" { + userPassword = server.config.BindPassword + server.requireSecondBind = true } - bindPath := auth.server.BindDN + bindPath := server.config.BindDN if strings.Contains(bindPath, "%s") { - bindPath = fmt.Sprintf(auth.server.BindDN, username) + bindPath = fmt.Sprintf(server.config.BindDN, username) } bindFn := func() error { - return auth.conn.Bind(bindPath, userPassword) + return server.connection.Bind(bindPath, userPassword) } if userPassword == "" { bindFn = func() error { - return auth.conn.UnauthenticatedBind(bindPath) + return server.connection.UnauthenticatedBind(bindPath) } } if err := bindFn(); err != nil { - auth.log.Info("Initial bind failed", "error", err) + server.log.Info("Initial bind failed", "error", err) - if ldapErr, ok := err.(*LDAP.Error); ok { + if ldapErr, ok := err.(*ldap.Error); ok { if ldapErr.ResultCode == 49 { return ErrInvalidCredentials } @@ -323,199 +416,124 @@ func (auth *Auth) initialBind(username, userPassword string) error { return nil } -func (auth *Auth) searchForUser(username string) (*UserInfo, error) { - var searchResult *LDAP.SearchResult - var err error - - for _, searchBase := range auth.server.SearchBaseDNs { - attributes := make([]string, 0) - inputs := auth.server.Attr - attributes = appendIfNotEmpty(attributes, - inputs.Username, - inputs.Surname, - inputs.Email, - inputs.Name, - inputs.MemberOf) - - searchReq := LDAP.SearchRequest{ - BaseDN: searchBase, - Scope: LDAP.ScopeWholeSubtree, - DerefAliases: LDAP.NeverDerefAliases, - Attributes: attributes, - Filter: strings.Replace( - auth.server.SearchFilter, - "%s", LDAP.EscapeFilter(username), - -1, - ), - } - - auth.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq)) - - searchResult, err = auth.conn.Search(&searchReq) - if err != nil { - return nil, err - } - - if len(searchResult.Entries) > 0 { - break - } - } - - if len(searchResult.Entries) == 0 { - return nil, ErrInvalidCredentials - } - - if len(searchResult.Entries) > 1 { - return nil, errors.New("Ldap search matched more than one entry, please review your filter setting") - } - +// requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups +func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) { var memberOf []string - if auth.server.GroupSearchFilter == "" { - memberOf = getLdapAttrArray(auth.server.Attr.MemberOf, searchResult) - } else { - // If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups - var groupSearchResult *LDAP.SearchResult - for _, groupSearchBase := range auth.server.GroupSearchBaseDNs { - var filter_replace string - if auth.server.GroupSearchFilterUserAttribute == "" { - filter_replace = getLdapAttr(auth.server.Attr.Username, searchResult) - } else { - filter_replace = getLdapAttr(auth.server.GroupSearchFilterUserAttribute, searchResult) - } - - filter := strings.Replace( - auth.server.GroupSearchFilter, "%s", - LDAP.EscapeFilter(filter_replace), - -1, - ) - - auth.log.Info("Searching for user's groups", "filter", filter) - - // support old way of reading settings - groupIdAttribute := auth.server.Attr.MemberOf - // but prefer dn attribute if default settings are used - if groupIdAttribute == "" || groupIdAttribute == "memberOf" { - groupIdAttribute = "dn" - } - - groupSearchReq := LDAP.SearchRequest{ - BaseDN: groupSearchBase, - Scope: LDAP.ScopeWholeSubtree, - DerefAliases: LDAP.NeverDerefAliases, - Attributes: []string{groupIdAttribute}, - Filter: filter, - } - - groupSearchResult, err = auth.conn.Search(&groupSearchReq) - if err != nil { - return nil, err - } - if len(groupSearchResult.Entries) > 0 { - for i := range groupSearchResult.Entries { - memberOf = append(memberOf, getLdapAttrN(groupIdAttribute, groupSearchResult, i)) - } - break - } + for _, groupSearchBase := range server.config.GroupSearchBaseDNs { + var filterReplace string + if server.config.GroupSearchFilterUserAttribute == "" { + filterReplace = getLdapAttr(server.config.Attr.Username, searchResult) + } else { + filterReplace = getLdapAttr(server.config.GroupSearchFilterUserAttribute, searchResult) } - } - - return &UserInfo{ - DN: searchResult.Entries[0].DN, - LastName: getLdapAttr(auth.server.Attr.Surname, searchResult), - FirstName: getLdapAttr(auth.server.Attr.Name, searchResult), - Username: getLdapAttr(auth.server.Attr.Username, searchResult), - Email: getLdapAttr(auth.server.Attr.Email, searchResult), - MemberOf: memberOf, - }, nil -} -func (ldap *Auth) Users() ([]*UserInfo, error) { - var result *LDAP.SearchResult - var err error - server := ldap.server - - if err := ldap.Dial(); err != nil { - return nil, err - } - defer ldap.conn.Close() - - for _, base := range server.SearchBaseDNs { - attributes := make([]string, 0) - inputs := server.Attr - attributes = appendIfNotEmpty( - attributes, - inputs.Username, - inputs.Surname, - inputs.Email, - inputs.Name, - inputs.MemberOf, + filter := strings.Replace( + server.config.GroupSearchFilter, "%s", + ldap.EscapeFilter(filterReplace), + -1, ) - req := LDAP.SearchRequest{ - BaseDN: base, - Scope: LDAP.ScopeWholeSubtree, - DerefAliases: LDAP.NeverDerefAliases, - Attributes: attributes, + server.log.Info("Searching for user's groups", "filter", filter) + + // support old way of reading settings + groupIDAttribute := server.config.Attr.MemberOf + // but prefer dn attribute if default settings are used + if groupIDAttribute == "" || groupIDAttribute == "memberOf" { + groupIDAttribute = "dn" + } - // Doing a star here to get all the users in one go - Filter: strings.Replace(server.SearchFilter, "%s", "*", -1), + groupSearchReq := ldap.SearchRequest{ + BaseDN: groupSearchBase, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + Attributes: []string{groupIDAttribute}, + Filter: filter, } - result, err = ldap.conn.Search(&req) + groupSearchResult, err := server.connection.Search(&groupSearchReq) if err != nil { return nil, err } - if len(result.Entries) > 0 { + if len(groupSearchResult.Entries) > 0 { + for i := range groupSearchResult.Entries { + memberOf = append(memberOf, getLdapAttrN(groupIDAttribute, groupSearchResult, i)) + } break } } - return ldap.serializeUsers(result), nil + return memberOf, nil } -func (ldap *Auth) serializeUsers(users *LDAP.SearchResult) []*UserInfo { - var serialized []*UserInfo +// serializeUsers serializes the users +// from LDAP result to ExternalInfo struct +func (server *Server) serializeUsers( + users *ldap.SearchResult, +) ([]*models.ExternalUserInfo, error) { + var serialized []*models.ExternalUserInfo for index := range users.Entries { - serialize := &UserInfo{ + memberOf, err := server.getMemberOf(users) + if err != nil { + return nil, err + } + + userInfo := &UserInfo{ DN: getLdapAttrN( "dn", users, index, ), LastName: getLdapAttrN( - ldap.server.Attr.Surname, + server.config.Attr.Surname, users, index, ), FirstName: getLdapAttrN( - ldap.server.Attr.Name, + server.config.Attr.Name, users, index, ), Username: getLdapAttrN( - ldap.server.Attr.Username, + server.config.Attr.Username, users, index, ), Email: getLdapAttrN( - ldap.server.Attr.Email, - users, - index, - ), - MemberOf: getLdapAttrArrayN( - ldap.server.Attr.MemberOf, + server.config.Attr.Email, users, index, ), + MemberOf: memberOf, } - serialized = append(serialized, serialize) + serialized = append( + serialized, + server.buildGrafanaUser(userInfo), + ) + } + + return serialized, nil +} + +// getMemberOf finds memberOf property or request it +func (server *Server) getMemberOf(search *ldap.SearchResult) ( + []string, error, +) { + if server.config.GroupSearchFilter == "" { + memberOf := getLdapAttrArray(server.config.Attr.MemberOf, search) + + return memberOf, nil + } + + memberOf, err := server.requestMemberOf(search) + if err != nil { + return nil, err } - return serialized + return memberOf, nil } func appendIfNotEmpty(slice []string, values ...string) []string { @@ -527,11 +545,11 @@ func appendIfNotEmpty(slice []string, values ...string) []string { return slice } -func getLdapAttr(name string, result *LDAP.SearchResult) string { +func getLdapAttr(name string, result *ldap.SearchResult) string { return getLdapAttrN(name, result, 0) } -func getLdapAttrN(name string, result *LDAP.SearchResult, n int) string { +func getLdapAttrN(name string, result *ldap.SearchResult, n int) string { if strings.ToLower(name) == "dn" { return result.Entries[n].DN } @@ -545,11 +563,11 @@ func getLdapAttrN(name string, result *LDAP.SearchResult, n int) string { return "" } -func getLdapAttrArray(name string, result *LDAP.SearchResult) []string { +func getLdapAttrArray(name string, result *ldap.SearchResult) []string { return getLdapAttrArrayN(name, result, 0) } -func getLdapAttrArrayN(name string, result *LDAP.SearchResult, n int) []string { +func getLdapAttrArrayN(name string, result *ldap.SearchResult, n int) []string { for _, attr := range result.Entries[n].Attributes { if attr.Name == name { return attr.Values diff --git a/pkg/services/ldap/ldap_helpers_test.go b/pkg/services/ldap/ldap_helpers_test.go new file mode 100644 index 000000000000..d995d0fd6992 --- /dev/null +++ b/pkg/services/ldap/ldap_helpers_test.go @@ -0,0 +1,140 @@ +package ldap + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ldap.v3" + + "github.com/grafana/grafana/pkg/infra/log" +) + +func TestLDAPHelpers(t *testing.T) { + Convey("serializeUsers()", t, func() { + Convey("simple case", func() { + server := &Server{ + config: &ServerConfig{ + Attr: AttributeMap{ + Username: "username", + Name: "name", + MemberOf: "memberof", + Email: "email", + }, + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: &mockConnection{}, + log: log.New("test-logger"), + } + + entry := ldap.Entry{ + DN: "dn", Attributes: []*ldap.EntryAttribute{ + {Name: "username", Values: []string{"roelgerrits"}}, + {Name: "surname", Values: []string{"Gerrits"}}, + {Name: "email", Values: []string{"roel@test.com"}}, + {Name: "name", Values: []string{"Roel"}}, + {Name: "memberof", Values: []string{"admins"}}, + }} + users := &ldap.SearchResult{Entries: []*ldap.Entry{&entry}} + + result, err := server.serializeUsers(users) + + So(err, ShouldBeNil) + So(result[0].Login, ShouldEqual, "roelgerrits") + So(result[0].Email, ShouldEqual, "roel@test.com") + So(result[0].Groups, ShouldContain, "admins") + }) + + Convey("without lastname", func() { + server := &Server{ + config: &ServerConfig{ + Attr: AttributeMap{ + Username: "username", + Name: "name", + MemberOf: "memberof", + Email: "email", + }, + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: &mockConnection{}, + log: log.New("test-logger"), + } + + entry := ldap.Entry{ + DN: "dn", Attributes: []*ldap.EntryAttribute{ + {Name: "username", Values: []string{"roelgerrits"}}, + {Name: "email", Values: []string{"roel@test.com"}}, + {Name: "name", Values: []string{"Roel"}}, + {Name: "memberof", Values: []string{"admins"}}, + }} + users := &ldap.SearchResult{Entries: []*ldap.Entry{&entry}} + + result, err := server.serializeUsers(users) + + So(err, ShouldBeNil) + So(result[0].Name, ShouldEqual, "Roel") + }) + }) + + Convey("serverBind()", t, func() { + Convey("Given bind dn and password configured", func() { + connection := &mockConnection{} + var actualUsername, actualPassword string + connection.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + server := &Server{ + connection: connection, + config: &ServerConfig{ + BindDN: "o=users,dc=grafana,dc=org", + BindPassword: "bindpwd", + }, + } + err := server.serverBind() + So(err, ShouldBeNil) + So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "bindpwd") + }) + + Convey("Given bind dn configured", func() { + connection := &mockConnection{} + unauthenticatedBindWasCalled := false + var actualUsername string + connection.unauthenticatedBindProvider = func(username string) error { + unauthenticatedBindWasCalled = true + actualUsername = username + return nil + } + server := &Server{ + connection: connection, + config: &ServerConfig{ + BindDN: "o=users,dc=grafana,dc=org", + }, + } + err := server.serverBind() + So(err, ShouldBeNil) + So(unauthenticatedBindWasCalled, ShouldBeTrue) + So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org") + }) + + Convey("Given empty bind dn and password", func() { + connection := &mockConnection{} + unauthenticatedBindWasCalled := false + var actualUsername string + connection.unauthenticatedBindProvider = func(username string) error { + unauthenticatedBindWasCalled = true + actualUsername = username + return nil + } + server := &Server{ + connection: connection, + config: &ServerConfig{}, + } + err := server.serverBind() + So(err, ShouldBeNil) + So(unauthenticatedBindWasCalled, ShouldBeTrue) + So(actualUsername, ShouldBeEmpty) + }) + }) +} diff --git a/pkg/services/ldap/ldap_login_test.go b/pkg/services/ldap/ldap_login_test.go index b8dd502667ea..9da82cc6e981 100644 --- a/pkg/services/ldap/ldap_login_test.go +++ b/pkg/services/ldap/ldap_login_test.go @@ -7,23 +7,94 @@ import ( "gopkg.in/ldap.v3" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/user" ) -func TestLdapLogin(t *testing.T) { - Convey("Login using ldap", t, func() { - AuthScenario("When login with invalid credentials", func(scenario *scenarioContext) { - conn := &mockLdapConn{} +func TestLDAPLogin(t *testing.T) { + Convey("Login()", t, func() { + authScenario("When user is log in and updated", func(sc *scenarioContext) { + // arrange + mockConnection := &mockConnection{} + + auth := &Server{ + config: &ServerConfig{ + Host: "", + RootCACert: "", + Groups: []*GroupToOrgRole{ + {GroupDN: "*", OrgRole: "Admin"}, + }, + Attr: AttributeMap{ + Username: "username", + Surname: "surname", + Email: "email", + Name: "name", + MemberOf: "memberof", + }, + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: mockConnection, + log: log.New("test-logger"), + } + + entry := ldap.Entry{ + DN: "dn", Attributes: []*ldap.EntryAttribute{ + {Name: "username", Values: []string{"roelgerrits"}}, + {Name: "surname", Values: []string{"Gerrits"}}, + {Name: "email", Values: []string{"roel@test.com"}}, + {Name: "name", Values: []string{"Roel"}}, + {Name: "memberof", Values: []string{"admins"}}, + }} + result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} + mockConnection.setSearchResult(&result) + + query := &models.LoginUserQuery{ + Username: "roelgerrits", + } + + sc.userQueryReturns(&models.User{ + Id: 1, + Email: "roel@test.net", + Name: "Roel Gerrits", + Login: "roelgerrits", + }) + sc.userOrgsQueryReturns([]*models.UserOrgDTO{}) + + // act + extUser, _ := auth.Login(query) + userInfo, err := user.Upsert(&user.UpsertArgs{ + SignupAllowed: true, + ExternalUser: extUser, + }) + + // assert + + // Check absence of the error + So(err, ShouldBeNil) + + // User should be searched in ldap + So(mockConnection.searchCalled, ShouldBeTrue) + + // Info should be updated (email differs) + So(userInfo.Email, ShouldEqual, "roel@test.com") + + // User should have admin privileges + So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin") + }) + + authScenario("When login with invalid credentials", func(scenario *scenarioContext) { + connection := &mockConnection{} entry := ldap.Entry{} result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} - conn.setSearchResult(&result) + connection.setSearchResult(&result) - conn.bindProvider = func(username, password string) error { + connection.bindProvider = func(username, password string) error { return &ldap.Error{ ResultCode: 49, } } - auth := &Auth{ - server: &ServerConfig{ + auth := &Server{ + config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -31,19 +102,19 @@ func TestLdapLogin(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - conn: conn, - log: log.New("test-logger"), + connection: connection, + log: log.New("test-logger"), } - err := auth.Login(scenario.loginUserQuery) + _, err := auth.Login(scenario.loginUserQuery) Convey("it should return invalid credentials error", func() { So(err, ShouldEqual, ErrInvalidCredentials) }) }) - AuthScenario("When login with valid credentials", func(scenario *scenarioContext) { - conn := &mockLdapConn{} + authScenario("When login with valid credentials", func(scenario *scenarioContext) { + connection := &mockConnection{} entry := ldap.Entry{ DN: "dn", Attributes: []*ldap.EntryAttribute{ {Name: "username", Values: []string{"markelog"}}, @@ -54,13 +125,13 @@ func TestLdapLogin(t *testing.T) { }, } result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} - conn.setSearchResult(&result) + connection.setSearchResult(&result) - conn.bindProvider = func(username, password string) error { + connection.bindProvider = func(username, password string) error { return nil } - auth := &Auth{ - server: &ServerConfig{ + auth := &Server{ + config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -68,19 +139,14 @@ func TestLdapLogin(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - conn: conn, - log: log.New("test-logger"), + connection: connection, + log: log.New("test-logger"), } - err := auth.Login(scenario.loginUserQuery) + resp, err := auth.Login(scenario.loginUserQuery) - Convey("it should not return error", func() { - So(err, ShouldBeNil) - }) - - Convey("it should get user", func() { - So(scenario.loginUserQuery.User.Login, ShouldEqual, "markelog") - }) + So(err, ShouldBeNil) + So(resp.Login, ShouldEqual, "markelog") }) }) } diff --git a/pkg/services/ldap/ldap_test.go b/pkg/services/ldap/ldap_test.go index 4da041ae1642..266fe22a4fc7 100644 --- a/pkg/services/ldap/ldap_test.go +++ b/pkg/services/ldap/ldap_test.go @@ -1,496 +1,157 @@ package ldap import ( - "context" "testing" . "github.com/smartystreets/goconvey/convey" - "gopkg.in/ldap.v3" + ldap "gopkg.in/ldap.v3" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" - m "github.com/grafana/grafana/pkg/models" ) func TestAuth(t *testing.T) { - Convey("initialBind", t, func() { - Convey("Given bind dn and password configured", func() { - conn := &mockLdapConn{} - var actualUsername, actualPassword string - conn.bindProvider = func(username, password string) error { - actualUsername = username - actualPassword = password - return nil - } - Auth := &Auth{ - conn: conn, - server: &ServerConfig{ - BindDN: "cn=%s,o=users,dc=grafana,dc=org", - BindPassword: "bindpwd", - }, - } - err := Auth.initialBind("user", "pwd") - So(err, ShouldBeNil) - So(Auth.requireSecondBind, ShouldBeTrue) - So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") - So(actualPassword, ShouldEqual, "bindpwd") - }) + Convey("Add()", t, func() { + connection := &mockConnection{} - Convey("Given bind dn configured", func() { - conn := &mockLdapConn{} - var actualUsername, actualPassword string - conn.bindProvider = func(username, password string) error { - actualUsername = username - actualPassword = password - return nil - } - Auth := &Auth{ - conn: conn, - server: &ServerConfig{ - BindDN: "cn=%s,o=users,dc=grafana,dc=org", - }, - } - err := Auth.initialBind("user", "pwd") - So(err, ShouldBeNil) - So(Auth.requireSecondBind, ShouldBeFalse) - So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") - So(actualPassword, ShouldEqual, "pwd") - }) + auth := &Server{ + config: &ServerConfig{ + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: connection, + log: log.New("test-logger"), + } - Convey("Given empty bind dn and password", func() { - conn := &mockLdapConn{} - unauthenticatedBindWasCalled := false - var actualUsername string - conn.unauthenticatedBindProvider = func(username string) error { - unauthenticatedBindWasCalled = true - actualUsername = username - return nil - } - Auth := &Auth{ - conn: conn, - server: &ServerConfig{}, - } - err := Auth.initialBind("user", "pwd") - So(err, ShouldBeNil) - So(Auth.requireSecondBind, ShouldBeTrue) - So(unauthenticatedBindWasCalled, ShouldBeTrue) - So(actualUsername, ShouldBeEmpty) - }) - }) + Convey("Adds user", func() { + err := auth.Add( + "cn=ldap-tuz,ou=users,dc=grafana,dc=org", + map[string][]string{ + "mail": {"ldap-viewer@grafana.com"}, + "userPassword": {"grafana"}, + "objectClass": { + "person", + "top", + "inetOrgPerson", + "organizationalPerson", + }, + "sn": {"ldap-tuz"}, + "cn": {"ldap-tuz"}, + }, + ) + + hasMail := false + hasUserPassword := false + hasObjectClass := false + hasSN := false + hasCN := false - Convey("serverBind", t, func() { - Convey("Given bind dn and password configured", func() { - conn := &mockLdapConn{} - var actualUsername, actualPassword string - conn.bindProvider = func(username, password string) error { - actualUsername = username - actualPassword = password - return nil - } - Auth := &Auth{ - conn: conn, - server: &ServerConfig{ - BindDN: "o=users,dc=grafana,dc=org", - BindPassword: "bindpwd", - }, - } - err := Auth.serverBind() So(err, ShouldBeNil) - So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org") - So(actualPassword, ShouldEqual, "bindpwd") - }) - - Convey("Given bind dn configured", func() { - conn := &mockLdapConn{} - unauthenticatedBindWasCalled := false - var actualUsername string - conn.unauthenticatedBindProvider = func(username string) error { - unauthenticatedBindWasCalled = true - actualUsername = username - return nil - } - Auth := &Auth{ - conn: conn, - server: &ServerConfig{ - BindDN: "o=users,dc=grafana,dc=org", - }, + So(connection.addParams.Controls, ShouldBeNil) + So(connection.addCalled, ShouldBeTrue) + So( + connection.addParams.DN, + ShouldEqual, + "cn=ldap-tuz,ou=users,dc=grafana,dc=org", + ) + + attrs := connection.addParams.Attributes + for _, value := range attrs { + if value.Type == "mail" { + So(value.Vals, ShouldContain, "ldap-viewer@grafana.com") + hasMail = true + } + + if value.Type == "userPassword" { + hasUserPassword = true + So(value.Vals, ShouldContain, "grafana") + } + + if value.Type == "objectClass" { + hasObjectClass = true + So(value.Vals, ShouldContain, "person") + So(value.Vals, ShouldContain, "top") + So(value.Vals, ShouldContain, "inetOrgPerson") + So(value.Vals, ShouldContain, "organizationalPerson") + } + + if value.Type == "sn" { + hasSN = true + So(value.Vals, ShouldContain, "ldap-tuz") + } + + if value.Type == "cn" { + hasCN = true + So(value.Vals, ShouldContain, "ldap-tuz") + } } - err := Auth.serverBind() - So(err, ShouldBeNil) - So(unauthenticatedBindWasCalled, ShouldBeTrue) - So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org") - }) - Convey("Given empty bind dn and password", func() { - conn := &mockLdapConn{} - unauthenticatedBindWasCalled := false - var actualUsername string - conn.unauthenticatedBindProvider = func(username string) error { - unauthenticatedBindWasCalled = true - actualUsername = username - return nil - } - Auth := &Auth{ - conn: conn, - server: &ServerConfig{}, - } - err := Auth.serverBind() - So(err, ShouldBeNil) - So(unauthenticatedBindWasCalled, ShouldBeTrue) - So(actualUsername, ShouldBeEmpty) + So(hasMail, ShouldBeTrue) + So(hasUserPassword, ShouldBeTrue) + So(hasObjectClass, ShouldBeTrue) + So(hasSN, ShouldBeTrue) + So(hasCN, ShouldBeTrue) }) }) - Convey("When translating ldap user to grafana user", t, func() { + Convey("Remove()", t, func() { + connection := &mockConnection{} - var user1 = &m.User{} - - bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpsertUserCommand) error { - cmd.Result = user1 - cmd.Result.Login = "torkelo" - return nil - }) - - Convey("Given no ldap group map match", func() { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{{}}, - }) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{}) - - So(err, ShouldEqual, ErrInvalidCredentials) - }) - - AuthScenario("Given wildcard group match", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "*", OrgRole: "Admin"}, - }, - }) - - sc.userQueryReturns(user1) - - result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{}) - So(err, ShouldBeNil) - So(result, ShouldEqual, user1) - }) - - AuthScenario("Given exact group match", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=users", OrgRole: "Admin"}, - }, - }) - - sc.userQueryReturns(user1) - - result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{MemberOf: []string{"cn=users"}}) - So(err, ShouldBeNil) - So(result, ShouldEqual, user1) - }) - - AuthScenario("Given group match with different case", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=users", OrgRole: "Admin"}, - }, - }) - - sc.userQueryReturns(user1) - - result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{MemberOf: []string{"CN=users"}}) - So(err, ShouldBeNil) - So(result, ShouldEqual, user1) - }) - - AuthScenario("Given no existing grafana user", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=admin", OrgRole: "Admin"}, - {GroupDN: "cn=editor", OrgRole: "Editor"}, - {GroupDN: "*", OrgRole: "Viewer"}, - }, - }) - - sc.userQueryReturns(nil) - - result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - DN: "torkelo", - Username: "torkelo", - Email: "my@email.com", - MemberOf: []string{"cn=editor"}, - }) - - So(err, ShouldBeNil) - - Convey("Should return new user", func() { - So(result.Login, ShouldEqual, "torkelo") - }) - - Convey("Should set isGrafanaAdmin to false by default", func() { - So(result.IsAdmin, ShouldBeFalse) - }) - - }) - - }) - - Convey("When syncing ldap groups to grafana org roles", t, func() { - AuthScenario("given no current user orgs", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=users", OrgRole: "Admin"}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=users"}, - }) - - Convey("Should create new org user", func() { - So(err, ShouldBeNil) - So(sc.addOrgUserCmd, ShouldNotBeNil) - So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN) - }) - }) - - AuthScenario("given different current org role", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}}) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=users"}, - }) - - Convey("Should update org role", func() { - So(err, ShouldBeNil) - So(sc.updateOrgUserCmd, ShouldNotBeNil) - So(sc.updateOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN) - So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1) - }) - }) - - AuthScenario("given current org role is removed in ldap", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=users", OrgId: 2, OrgRole: "Admin"}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{ - {OrgId: 1, Role: m.ROLE_EDITOR}, - {OrgId: 2, Role: m.ROLE_EDITOR}, - }) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=users"}, - }) - - Convey("Should remove org role", func() { - So(err, ShouldBeNil) - So(sc.removeOrgUserCmd, ShouldNotBeNil) - So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 2) - }) - }) - - AuthScenario("given org role is updated in config", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=admin", OrgId: 1, OrgRole: "Admin"}, - {GroupDN: "cn=users", OrgId: 1, OrgRole: "Viewer"}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}}) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=users"}, - }) - - Convey("Should update org role", func() { - So(err, ShouldBeNil) - So(sc.removeOrgUserCmd, ShouldBeNil) - So(sc.updateOrgUserCmd, ShouldNotBeNil) - So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1) - }) - }) - - AuthScenario("given multiple matching ldap groups", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"}, - {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}}) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=admins"}, - }) - - Convey("Should take first match, and ignore subsequent matches", func() { - So(err, ShouldBeNil) - So(sc.updateOrgUserCmd, ShouldBeNil) - So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1) - }) - }) - - AuthScenario("given multiple matching ldap groups and no existing groups", func(sc *scenarioContext) { - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"}, - {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=admins"}, - }) - - Convey("Should take first match, and ignore subsequent matches", func() { - So(err, ShouldBeNil) - So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN) - So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1) - }) - - Convey("Should not update permissions unless specified", func() { - So(err, ShouldBeNil) - So(sc.updateUserPermissionsCmd, ShouldBeNil) - }) - }) - - AuthScenario("given ldap groups with grafana_admin=true", func(sc *scenarioContext) { - trueVal := true - - Auth := New(&ServerConfig{ - Groups: []*GroupToOrgRole{ - {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin", IsGrafanaAdmin: &trueVal}, - }, - }) - - sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) - _, err := Auth.GetGrafanaUserFor(nil, &UserInfo{ - MemberOf: []string{"cn=admins"}, - }) - - Convey("Should create user with admin set to true", func() { - So(err, ShouldBeNil) - So(sc.updateUserPermissionsCmd.IsGrafanaAdmin, ShouldBeTrue) - }) - }) - }) - - Convey("When calling SyncUser", t, func() { - mockLdapConnection := &mockLdapConn{} - - auth := &Auth{ - server: &ServerConfig{ - Host: "", - RootCACert: "", - Groups: []*GroupToOrgRole{ - {GroupDN: "*", OrgRole: "Admin"}, - }, - Attr: AttributeMap{ - Username: "username", - Surname: "surname", - Email: "email", - Name: "name", - MemberOf: "memberof", - }, + auth := &Server{ + config: &ServerConfig{ SearchBaseDNs: []string{"BaseDNHere"}, }, - conn: mockLdapConnection, - log: log.New("test-logger"), - } - - dialCalled := false - dial = func(network, addr string) (IConnection, error) { - dialCalled = true - return mockLdapConnection, nil + connection: connection, + log: log.New("test-logger"), } - entry := ldap.Entry{ - DN: "dn", Attributes: []*ldap.EntryAttribute{ - {Name: "username", Values: []string{"roelgerrits"}}, - {Name: "surname", Values: []string{"Gerrits"}}, - {Name: "email", Values: []string{"roel@test.com"}}, - {Name: "name", Values: []string{"Roel"}}, - {Name: "memberof", Values: []string{"admins"}}, - }} - result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} - mockLdapConnection.setSearchResult(&result) + Convey("Removes the user", func() { + dn := "cn=ldap-tuz,ou=users,dc=grafana,dc=org" + err := auth.Remove(dn) - AuthScenario("When ldapUser found call syncInfo and orgRoles", func(sc *scenarioContext) { - // arrange - query := &m.LoginUserQuery{ - Username: "roelgerrits", - } - - hookDial = nil - - sc.userQueryReturns(&m.User{ - Id: 1, - Email: "roel@test.net", - Name: "Roel Gerrits", - Login: "roelgerrits", - }) - sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) - - // act - syncErrResult := auth.SyncUser(query) - - // assert - So(dialCalled, ShouldBeTrue) - So(syncErrResult, ShouldBeNil) - // User should be searched in ldap - So(mockLdapConnection.searchCalled, ShouldBeTrue) - // Info should be updated (email differs) - So(sc.updateUserCmd.Email, ShouldEqual, "roel@test.com") - // User should have admin privileges - So(sc.addOrgUserCmd.UserId, ShouldEqual, 1) - So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin") + So(err, ShouldBeNil) + So(connection.delCalled, ShouldBeTrue) + So(connection.delParams.Controls, ShouldBeNil) + So(connection.delParams.DN, ShouldEqual, dn) }) }) - Convey("When searching for a user and not all five attributes are mapped", t, func() { - mockLdapConnection := &mockLdapConn{} - entry := ldap.Entry{ - DN: "dn", Attributes: []*ldap.EntryAttribute{ - {Name: "username", Values: []string{"roelgerrits"}}, - {Name: "surname", Values: []string{"Gerrits"}}, - {Name: "email", Values: []string{"roel@test.com"}}, - {Name: "name", Values: []string{"Roel"}}, - {Name: "memberof", Values: []string{"admins"}}, - }} - result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} - mockLdapConnection.setSearchResult(&result) - - // Set up attribute map without surname and email - Auth := &Auth{ - server: &ServerConfig{ - Attr: AttributeMap{ - Username: "username", - Name: "name", - MemberOf: "memberof", - }, - SearchBaseDNs: []string{"BaseDNHere"}, - }, - conn: mockLdapConnection, - log: log.New("test-logger"), - } + Convey("Users()", t, func() { + Convey("find one user", func() { + mockConnection := &mockConnection{} + entry := ldap.Entry{ + DN: "dn", Attributes: []*ldap.EntryAttribute{ + {Name: "username", Values: []string{"roelgerrits"}}, + {Name: "surname", Values: []string{"Gerrits"}}, + {Name: "email", Values: []string{"roel@test.com"}}, + {Name: "name", Values: []string{"Roel"}}, + {Name: "memberof", Values: []string{"admins"}}, + }} + result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} + mockConnection.setSearchResult(&result) + + // Set up attribute map without surname and email + server := &Server{ + config: &ServerConfig{ + Attr: AttributeMap{ + Username: "username", + Name: "name", + MemberOf: "memberof", + }, + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: mockConnection, + log: log.New("test-logger"), + } - searchResult, err := Auth.searchForUser("roelgerrits") + searchResult, err := server.Users([]string{"roelgerrits"}) - So(err, ShouldBeNil) - So(searchResult, ShouldNotBeNil) + So(err, ShouldBeNil) + So(searchResult, ShouldNotBeNil) - // User should be searched in ldap - So(mockLdapConnection.searchCalled, ShouldBeTrue) + // User should be searched in ldap + So(mockConnection.searchCalled, ShouldBeTrue) - // No empty attributes should be added to the search request - So(len(mockLdapConnection.searchAttributes), ShouldEqual, 3) + // No empty attributes should be added to the search request + So(len(mockConnection.searchAttributes), ShouldEqual, 3) + }) }) } diff --git a/pkg/services/ldap/settings.go b/pkg/services/ldap/settings.go index 0a0f66d9d734..15f1583fea71 100644 --- a/pkg/services/ldap/settings.go +++ b/pkg/services/ldap/settings.go @@ -13,10 +13,12 @@ import ( "github.com/grafana/grafana/pkg/util/errutil" ) +// Config holds list of connections to LDAP type Config struct { Servers []*ServerConfig `toml:"servers"` } +// ServerConfig holds connection data to LDAP type ServerConfig struct { Host string `toml:"host"` Port int `toml:"port"` @@ -108,11 +110,11 @@ func readConfig(configFile string) (*Config, error) { _, err := toml.DecodeFile(configFile, result) if err != nil { - return nil, errutil.Wrap("Failed to load ldap config file", err) + return nil, errutil.Wrap("Failed to load LDAP config file", err) } if len(result.Servers) == 0 { - return nil, xerrors.New("ldap enabled but no ldap servers defined in config file") + return nil, xerrors.New("LDAP enabled but no LDAP servers defined in config file") } // set default org id diff --git a/pkg/services/ldap/test.go b/pkg/services/ldap/test.go index 98d169b9a1ad..07fd9c6317c5 100644 --- a/pkg/services/ldap/test.go +++ b/pkg/services/ldap/test.go @@ -12,15 +12,22 @@ import ( "github.com/grafana/grafana/pkg/services/login" ) -type mockLdapConn struct { - result *ldap.SearchResult - searchCalled bool - searchAttributes []string +type mockConnection struct { + searchResult *ldap.SearchResult + searchCalled bool + searchAttributes []string + + addParams *ldap.AddRequest + addCalled bool + + delParams *ldap.DelRequest + delCalled bool + bindProvider func(username, password string) error unauthenticatedBindProvider func(username string) error } -func (c *mockLdapConn) Bind(username, password string) error { +func (c *mockConnection) Bind(username, password string) error { if c.bindProvider != nil { return c.bindProvider(username, password) } @@ -28,7 +35,7 @@ func (c *mockLdapConn) Bind(username, password string) error { return nil } -func (c *mockLdapConn) UnauthenticatedBind(username string) error { +func (c *mockConnection) UnauthenticatedBind(username string) error { if c.unauthenticatedBindProvider != nil { return c.unauthenticatedBindProvider(username) } @@ -36,23 +43,35 @@ func (c *mockLdapConn) UnauthenticatedBind(username string) error { return nil } -func (c *mockLdapConn) Close() {} +func (c *mockConnection) Close() {} -func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) { - c.result = result +func (c *mockConnection) setSearchResult(result *ldap.SearchResult) { + c.searchResult = result } -func (c *mockLdapConn) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) { +func (c *mockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) { c.searchCalled = true c.searchAttributes = sr.Attributes - return c.result, nil + return c.searchResult, nil +} + +func (c *mockConnection) Add(request *ldap.AddRequest) error { + c.addCalled = true + c.addParams = request + return nil } -func (c *mockLdapConn) StartTLS(*tls.Config) error { +func (c *mockConnection) Del(request *ldap.DelRequest) error { + c.delCalled = true + c.delParams = request return nil } -func AuthScenario(desc string, fn scenarioFunc) { +func (c *mockConnection) StartTLS(*tls.Config) error { + return nil +} + +func authScenario(desc string, fn scenarioFunc) { Convey(desc, func() { defer bus.ClearBusHandlers() @@ -64,10 +83,6 @@ func AuthScenario(desc string, fn scenarioFunc) { }, } - hookDial = func(auth *Auth) error { - return nil - } - loginService := &login.LoginService{ Bus: bus.GetBus(), } diff --git a/pkg/services/multildap/multildap.go b/pkg/services/multildap/multildap.go new file mode 100644 index 000000000000..1b309c646e17 --- /dev/null +++ b/pkg/services/multildap/multildap.go @@ -0,0 +1,204 @@ +package multildap + +import ( + "errors" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ldap" +) + +// GetConfig gets LDAP config +var GetConfig = ldap.GetConfig + +// IsEnabled checks if LDAP is enabled +var IsEnabled = ldap.IsEnabled + +// ErrInvalidCredentials is returned if username and password do not match +var ErrInvalidCredentials = ldap.ErrInvalidCredentials + +// ErrNoLDAPServers is returned when there is no LDAP servers specified +var ErrNoLDAPServers = errors.New("No LDAP servers are configured") + +// ErrDidNotFindUser if request for user is unsuccessful +var ErrDidNotFindUser = errors.New("Did not find a user") + +// IMultiLDAP is interface for MultiLDAP +type IMultiLDAP interface { + Login(query *models.LoginUserQuery) ( + *models.ExternalUserInfo, error, + ) + + Users(logins []string) ( + []*models.ExternalUserInfo, error, + ) + + User(login string) ( + *models.ExternalUserInfo, error, + ) + + Add(dn string, values map[string][]string) error + Remove(dn string) error +} + +// MultiLDAP is basic struct of LDAP authorization +type MultiLDAP struct { + configs []*ldap.ServerConfig +} + +// New creates the new LDAP auth +func New(configs []*ldap.ServerConfig) IMultiLDAP { + return &MultiLDAP{ + configs: configs, + } +} + +// Add adds user to the *first* defined LDAP +func (multiples *MultiLDAP) Add( + dn string, + values map[string][]string, +) error { + if len(multiples.configs) == 0 { + return ErrNoLDAPServers + } + + config := multiples.configs[0] + ldap := ldap.New(config) + + if err := ldap.Dial(); err != nil { + return err + } + + defer ldap.Close() + + err := ldap.Add(dn, values) + if err != nil { + return err + } + + return nil +} + +// Remove removes user from the *first* defined LDAP +func (multiples *MultiLDAP) Remove(dn string) error { + if len(multiples.configs) == 0 { + return ErrNoLDAPServers + } + + config := multiples.configs[0] + ldap := ldap.New(config) + + if err := ldap.Dial(); err != nil { + return err + } + + defer ldap.Close() + + err := ldap.Remove(dn) + if err != nil { + return err + } + + return nil +} + +// Login tries to log in the user in multiples LDAP +func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) ( + *models.ExternalUserInfo, error, +) { + if len(multiples.configs) == 0 { + return nil, ErrNoLDAPServers + } + + for _, config := range multiples.configs { + server := ldap.New(config) + + if err := server.Dial(); err != nil { + return nil, err + } + + defer server.Close() + + user, err := server.Login(query) + + if user != nil { + return user, nil + } + + // Continue if we couldn't find the user + if err == ErrInvalidCredentials { + continue + } + + if err != nil { + return nil, err + } + + return user, nil + } + + // Return invalid credentials if we couldn't find the user anywhere + return nil, ErrInvalidCredentials +} + +// User gets a user by login +func (multiples *MultiLDAP) User(login string) ( + *models.ExternalUserInfo, + error, +) { + + if len(multiples.configs) == 0 { + return nil, ErrNoLDAPServers + } + + search := []string{login} + for _, config := range multiples.configs { + server := ldap.New(config) + + if err := server.Dial(); err != nil { + return nil, err + } + + defer server.Close() + + users, err := server.Users(search) + if err != nil { + return nil, err + } + + if len(users) != 0 { + return users[0], nil + } + } + + return nil, ErrDidNotFindUser +} + +// Users gets users from multiple LDAP servers +func (multiples *MultiLDAP) Users(logins []string) ( + []*models.ExternalUserInfo, + error, +) { + var result []*models.ExternalUserInfo + + if len(multiples.configs) == 0 { + return nil, ErrNoLDAPServers + } + + for _, config := range multiples.configs { + server := ldap.New(config) + + if err := server.Dial(); err != nil { + return nil, err + } + + defer server.Close() + + users, err := server.Users(logins) + if err != nil { + return nil, err + } + result = append(result, users...) + } + + return result, nil +} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 44d0f545bfcc..58bcc5578593 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -8,7 +8,6 @@ import ( "path" "path/filepath" "strings" - "testing" "time" "github.com/go-sql-driver/mysql" @@ -280,7 +279,14 @@ func (ss *SqlStore) readConfig() { ss.dbCfg.CacheMode = sec.Key("cache_mode").MustString("private") } -func InitTestDB(t *testing.T) *SqlStore { +// Interface of arguments for testing db +type ITestDB interface { + Helper() + Fatalf(format string, args ...interface{}) +} + +// InitTestDB initiliaze test DB +func InitTestDB(t ITestDB) *SqlStore { t.Helper() sqlstore := &SqlStore{} sqlstore.skipEnsureAdmin = true diff --git a/pkg/services/user/user.go b/pkg/services/user/user.go new file mode 100644 index 000000000000..94762c811b08 --- /dev/null +++ b/pkg/services/user/user.go @@ -0,0 +1,39 @@ +package user + +import ( + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/models" +) + +// UpsertArgs are object for Upsert method +type UpsertArgs struct { + ReqContext *models.ReqContext + ExternalUser *models.ExternalUserInfo + SignupAllowed bool +} + +// Upsert add/update grafana user +func Upsert(args *UpsertArgs) (*models.User, error) { + query := &models.UpsertUserCommand{ + ReqContext: args.ReqContext, + ExternalUser: args.ExternalUser, + SignupAllowed: args.SignupAllowed, + } + err := bus.Dispatch(query) + if err != nil { + return nil, err + } + + return query.Result, nil +} + +// Get the users +func Get( + query *models.SearchUsersQuery, +) ([]*models.UserSearchHitDTO, error) { + if err := bus.Dispatch(query); err != nil { + return nil, err + } + + return query.Result.Users, nil +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 192f300021b8..194617f3bcb5 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -805,6 +805,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { // auth proxy authProxy := iniFile.Section("auth.proxy") AuthProxyEnabled = authProxy.Key("enabled").MustBool(false) + AuthProxyHeaderName, err = valueAsString(authProxy, "header_name", "") if err != nil { return err diff --git a/vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md b/vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md new file mode 100644 index 000000000000..ec6e6d7a3767 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md @@ -0,0 +1,134 @@ +go test -bench=. -benchmem +goos: darwin +goarch: amd64 +pkg: github.com/brianvoe/gofakeit +Table generated with tablesgenerator.com/markdown_tables + +| Benchmark | Ops | CPU | MEM | MEM alloc | +|---------------------------------|-----------|-------------|------------|--------------| +| BenchmarkAddress-4 | 1000000 | 1998 ns/op | 248 B/op | 7 allocs/op | +| BenchmarkStreet-4 | 1000000 | 1278 ns/op | 62 B/op | 3 allocs/op | +| BenchmarkStreetNumber-4 | 5000000 | 344 ns/op | 36 B/op | 2 allocs/op | +| BenchmarkStreetPrefix-4 | 10000000 | 121 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkStreetName-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkStreetSuffix-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkCity-4 | 5000000 | 326 ns/op | 15 B/op | 1 allocs/op | +| BenchmarkState-4 | 10000000 | 120 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkStateAbr-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkZip-4 | 5000000 | 315 ns/op | 5 B/op | 1 allocs/op | +| BenchmarkCountry-4 | 10000000 | 126 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkCountryAbr-4 | 10000000 | 123 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLatitude-4 | 100000000 | 23.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLongitude-4 | 100000000 | 23.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLatitudeInRange-4 | 50000000 | 27.7 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLongitudeInRange-4 | 50000000 | 27.8 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBeerName-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBeerStyle-4 | 10000000 | 119 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBeerHop-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBeerYeast-4 | 20000000 | 106 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBeerMalt-4 | 20000000 | 114 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBeerIbu-4 | 20000000 | 71.0 ns/op | 8 B/op | 1 allocs/op | +| BenchmarkBeerAlcohol-4 | 5000000 | 335 ns/op | 40 B/op | 3 allocs/op | +| BenchmarkBeerBlg-4 | 5000000 | 338 ns/op | 48 B/op | 3 allocs/op | +| BenchmarkBool-4 | 50000000 | 34.2 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkColor-4 | 20000000 | 112 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkSafeColor-4 | 20000000 | 102 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHexColor-4 | 3000000 | 491 ns/op | 24 B/op | 3 allocs/op | +| BenchmarkRGBColor-4 | 20000000 | 103 ns/op | 32 B/op | 1 allocs/op | +| BenchmarkCompany-4 | 5000000 | 353 ns/op | 22 B/op | 1 allocs/op | +| BenchmarkCompanySuffix-4 | 20000000 | 89.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBuzzWord-4 | 20000000 | 99.0 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBS-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkContact-4 | 1000000 | 1121 ns/op | 178 B/op | 7 allocs/op | +| BenchmarkPhone-4 | 5000000 | 346 ns/op | 16 B/op | 1 allocs/op | +| BenchmarkPhoneFormatted-4 | 3000000 | 456 ns/op | 16 B/op | 1 allocs/op | +| BenchmarkEmail-4 | 2000000 | 715 ns/op | 130 B/op | 5 allocs/op | +| BenchmarkCurrency-4 | 10000000 | 125 ns/op | 32 B/op | 1 allocs/op | +| BenchmarkCurrencyShort-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkCurrencyLong-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkPrice-4 | 50000000 | 27.2 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkDate-4 | 5000000 | 371 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkDateRange-4 | 10000000 | 238 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkMonth-4 | 30000000 | 44.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkDay-4 | 50000000 | 39.2 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkWeekDay-4 | 30000000 | 44.7 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkYear-4 | 20000000 | 115 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHour-4 | 30000000 | 39.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkMinute-4 | 50000000 | 40.4 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkSecond-4 | 30000000 | 40.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkNanoSecond-4 | 30000000 | 42.2 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkTimeZone-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkTimeZoneFull-4 | 20000000 | 118 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkTimeZoneAbv-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkTimeZoneOffset-4 | 10000000 | 147 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkMimeType-4 | 20000000 | 99.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkExtension-4 | 20000000 | 109 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkGenerate-4 | 1000000 | 1588 ns/op | 414 B/op | 11 allocs/op | +| BenchmarkHackerPhrase-4 | 300000 | 4576 ns/op | 2295 B/op | 26 allocs/op | +| BenchmarkHackerAbbreviation-4 | 20000000 | 101 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHackerAdjective-4 | 20000000 | 101 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHackerNoun-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHackerVerb-4 | 20000000 | 113 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHackerIngverb-4 | 20000000 | 98.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHipsterWord-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHipsterSentence-4 | 1000000 | 1636 ns/op | 353 B/op | 3 allocs/op | +| BenchmarkHipsterParagraph-4 | 50000 | 31677 ns/op | 12351 B/op | 64 allocs/op | +| BenchmarkImageURL-4 | 20000000 | 108 ns/op | 38 B/op | 3 allocs/op | +| BenchmarkDomainName-4 | 3000000 | 491 ns/op | 76 B/op | 3 allocs/op | +| BenchmarkDomainSuffix-4 | 20000000 | 99.4 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkURL-4 | 1000000 | 1201 ns/op | 278 B/op | 8 allocs/op | +| BenchmarkHTTPMethod-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkIPv4Address-4 | 3000000 | 407 ns/op | 48 B/op | 5 allocs/op | +| BenchmarkIPv6Address-4 | 3000000 | 552 ns/op | 96 B/op | 7 allocs/op | +| BenchmarkUsername-4 | 5000000 | 307 ns/op | 16 B/op | 2 allocs/op | +| BenchmarkJob-4 | 2000000 | 726 ns/op | 86 B/op | 2 allocs/op | +| BenchmarkJobTitle-4 | 20000000 | 98.7 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkJobDescriptor-4 | 20000000 | 98.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkJobLevel-4 | 20000000 | 110 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLogLevel-4 | 20000000 | 107 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkReplaceWithNumbers-4 | 3000000 | 570 ns/op | 32 B/op | 1 allocs/op | +| BenchmarkName-4 | 5000000 | 285 ns/op | 17 B/op | 1 allocs/op | +| BenchmarkFirstName-4 | 20000000 | 102 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLastName-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkNamePrefix-4 | 20000000 | 98.0 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkNameSuffix-4 | 20000000 | 109 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkNumber-4 | 50000000 | 34.5 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkUint8-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkUint16-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkUint32-4 | 50000000 | 27.0 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkUint64-4 | 50000000 | 34.6 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkInt8-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkInt16-4 | 50000000 | 28.4 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkInt32-4 | 50000000 | 27.0 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkInt64-4 | 50000000 | 34.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkFloat32-4 | 50000000 | 27.7 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkFloat32Range-4 | 50000000 | 27.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkFloat64-4 | 50000000 | 25.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkFloat64Range-4 | 50000000 | 26.5 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkNumerify-4 | 5000000 | 354 ns/op | 16 B/op | 1 allocs/op | +| BenchmarkShuffleInts-4 | 10000000 | 226 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkPassword-4 | 2000000 | 655 ns/op | 304 B/op | 6 allocs/op | +| BenchmarkCreditCard-4 | 2000000 | 997 ns/op | 88 B/op | 4 allocs/op | +| BenchmarkCreditCardType-4 | 20000000 | 92.7 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkCreditCardNumber-4 | 3000000 | 572 ns/op | 16 B/op | 1 allocs/op | +| BenchmarkCreditCardNumberLuhn-4 | 300000 | 5815 ns/op | 159 B/op | 9 allocs/op | +| BenchmarkCreditCardExp-4 | 10000000 | 129 ns/op | 5 B/op | 1 allocs/op | +| BenchmarkCreditCardCvv-4 | 10000000 | 128 ns/op | 3 B/op | 1 allocs/op | +| BenchmarkSSN-4 | 20000000 | 84.2 ns/op | 16 B/op | 1 allocs/op | +| BenchmarkGender-4 | 50000000 | 38.0 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkPerson-4 | 300000 | 5563 ns/op | 805 B/op | 26 allocs/op | +| BenchmarkSimpleStatusCode-4 | 20000000 | 72.9 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkStatusCode-4 | 20000000 | 75.8 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLetter-4 | 50000000 | 38.4 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkDigit-4 | 50000000 | 38.2 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkLexify-4 | 10000000 | 222 ns/op | 8 B/op | 1 allocs/op | +| BenchmarkShuffleStrings-4 | 10000000 | 197 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkUUID-4 | 20000000 | 106 ns/op | 48 B/op | 1 allocs/op | +| BenchmarkUserAgent-4 | 1000000 | 1236 ns/op | 305 B/op | 5 allocs/op | +| BenchmarkChromeUserAgent-4 | 2000000 | 881 ns/op | 188 B/op | 5 allocs/op | +| BenchmarkFirefoxUserAgent-4 | 1000000 | 1595 ns/op | 386 B/op | 7 allocs/op | +| BenchmarkSafariUserAgent-4 | 1000000 | 1396 ns/op | 551 B/op | 7 allocs/op | +| BenchmarkOperaUserAgent-4 | 2000000 | 950 ns/op | 216 B/op | 5 allocs/op | +| BenchmarkWord-4 | 20000000 | 99.1 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkSentence-4 | 1000000 | 1540 ns/op | 277 B/op | 2 allocs/op | +| BenchmarkParagraph-4 | 50000 | 30978 ns/op | 11006 B/op | 61 allocs/op | \ No newline at end of file diff --git a/vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md b/vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..99d12c90fecf --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at brian@webiswhatido.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md b/vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md new file mode 100644 index 000000000000..5a4812c28ee8 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md @@ -0,0 +1 @@ +# Make a pull request and submit it and ill take a look at it. Thanks! diff --git a/vendor/github.com/brianvoe/gofakeit/LICENSE.txt b/vendor/github.com/brianvoe/gofakeit/LICENSE.txt new file mode 100644 index 000000000000..21984c9d5eaa --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/brianvoe/gofakeit/README.md b/vendor/github.com/brianvoe/gofakeit/README.md new file mode 100644 index 000000000000..4e3723fd5117 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/README.md @@ -0,0 +1,254 @@ +![alt text](https://raw.githubusercontent.com/brianvoe/gofakeit/master/logo.png) + +# gofakeit [![Go Report Card](https://goreportcard.com/badge/github.com/brianvoe/gofakeit)](https://goreportcard.com/report/github.com/brianvoe/gofakeit) [![Build Status](https://travis-ci.org/brianvoe/gofakeit.svg?branch=master)](https://travis-ci.org/brianvoe/gofakeit) [![codecov.io](https://codecov.io/github/brianvoe/gofakeit/branch/master/graph/badge.svg)](https://codecov.io/github/brianvoe/gofakeit) [![GoDoc](https://godoc.org/github.com/brianvoe/gofakeit?status.svg)](https://godoc.org/github.com/brianvoe/gofakeit) [![license](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/brianvoe/gofakeit/master/LICENSE.txt) +Random data generator written in go + +Buy Me A Coffee + +### Features +- Every function has an example and a benchmark, +[see benchmarks](https://github.com/brianvoe/gofakeit/blob/master/BENCHMARKS.md) +- Zero dependencies +- Randomizes user defined structs +- Numerous functions for regular use + +### 120+ Functions!!! +If there is something that is generic enough missing from this package [add an issue](https://github.com/brianvoe/gofakeit/issues) and let me know what you need. +Most of the time i'll add it! + +## Person +```go +Person() *PersonInfo +Name() string +NamePrefix() string +NameSuffix() string +FirstName() string +LastName() string +Gender() string +SSN() string +Contact() *ContactInfo +Email() string +Phone() string +PhoneFormatted() string +Username() string +Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string +``` + +## Address +```go +Address() *AddressInfo +City() string +Country() string +CountryAbr() string +State() string +StateAbr() string +StatusCode() string +Street() string +StreetName() string +StreetNumber() string +StreetPrefix() string +StreetSuffix() string +Zip() string +Latitude() float64 +LatitudeInRange() (float64, error) +Longitude() float64 +LongitudeInRange() (float64, error) +``` + +## Beer +```go +BeerAlcohol() string +BeerBlg() string +BeerHop() string +BeerIbu() string +BeerMalt() string +BeerName() string +BeerStyle() string +BeerYeast() string +``` + +## Cars +```go +Vehicle() *VehicleInfo +CarMaker() string +CarModel() string +VehicleType() string +FuelType() string +TransmissionGearType() string +``` + +## Words +```go +Word() string +Sentence(wordCount int) string +Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string +Question() string +Quote() string +``` + +## Misc +```go +Struct(v interface{}) +Generate() string +Bool() bool +UUID() string +``` + +## Colors +```go +Color() string +HexColor() string +RGBColor() string +SafeColor() string +``` + +## Internet +```go +URL() string +ImageURL(width int, height int) string +DomainName() string +DomainSuffix() string +IPv4Address() string +IPv6Address() string +SimpleStatusCode() int +LogLevel(logType string) string +HTTPMethod() string +UserAgent() string +ChromeUserAgent() string +FirefoxUserAgent() string +OperaUserAgent() string +SafariUserAgent() string +``` + +## Date/Time +```go +Date() time.Time +DateRange(start, end time.Time) time.Time +NanoSecond() int +Second() int +Minute() int +Hour() int +Month() string +Day() int +WeekDay() string +Year() int +TimeZone() string +TimeZoneAbv() string +TimeZoneFull() string +TimeZoneOffset() float32 +``` + +## Payment +```go +Price(min, max float64) float64 +CreditCard() *CreditCardInfo +CreditCardCvv() string +CreditCardExp() string +CreditCardNumber() int +CreditCardNumberLuhn() int +CreditCardType() string +Currency() *CurrencyInfo +CurrencyLong() string +CurrencyShort() string +``` + +## Company +```go +BS() string +BuzzWord() string +Company() string +CompanySuffix() string +Job() *JobInfo +JobDescriptor() string +JobLevel() string +JobTitle() string +``` + +## Hacker +```go +HackerAbbreviation() string +HackerAdjective() string +HackerIngverb() string +HackerNoun() string +HackerPhrase() string +HackerVerb() string +``` + +## Hipster +```go +HipsterWord() string +HipsterSentence(wordCount int) string +HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string +``` + +## File +```go +Extension() string +MimeType() string +``` + +## Numbers +```go +Number(min int, max int) int +Numerify(str string) string +Int8() int8 +Int16() int16 +Int32() int32 +Int64() int64 +Uint8() uint8 +Uint16() uint16 +Uint32() uint32 +Uint64() uint64 +Float32() float32 +Float32Range(min, max float32) float32 +Float64() float64 +Float64Range(min, max float64) float64 +ShuffleInts(a []int) +``` + +## String +```go +Digit() string +Letter() string +Lexify(str string) string +RandString(a []string) string +ShuffleStrings(a []string) +``` + +## Documentation +[![GoDoc](https://godoc.org/github.com/brianvoe/gofakeit?status.svg)](https://godoc.org/github.com/brianvoe/gofakeit) + +## Example +```go +import "github.com/brianvoe/gofakeit" + +gofakeit.Name() // Markus Moen +gofakeit.Email() // alaynawuckert@kozey.biz +gofakeit.Phone() // (570)245-7485 +gofakeit.BS() // front-end +gofakeit.BeerName() // Duvel +gofakeit.Color() // MediumOrchid +gofakeit.Company() // Moen, Pagac and Wuckert +gofakeit.CreditCardNumber() // 4287271570245748 +gofakeit.HackerPhrase() // Connecting the array won't do anything, we need to generate the haptic COM driver! +gofakeit.JobTitle() // Director +gofakeit.Password(true, true, true, true, true, 32) // WV10MzLxq2DX79w1omH97_0ga59j8!kj +gofakeit.CurrencyShort() // USD +// 120+ more!!! + +// Create structs with random injected data +type Foo struct { + Bar string + Baz string + Int int + Pointer *int + Skip *string `fake:"skip"` // Set to "skip" to not generate data for +} +var f Foo +gofakeit.Struct(&f) +fmt.Printf("f.Bar:%s\n", f.Bar) // f.Bar:hrukpttuezptneuvunh +fmt.Printf("f.Baz:%s\n", f.Baz) // f.Baz:uksqvgzadxlgghejkmv +fmt.Printf("f.Int:%d\n", f.Int) // f.Int:-7825289004089916589 +fmt.Printf("f.Pointer:%d\n", *f.Pointer) // f.Pointer:-343806609094473732 +fmt.Printf("f.Skip:%v\n", f.Skip) // f.Skip: +``` diff --git a/vendor/github.com/brianvoe/gofakeit/TODO.txt b/vendor/github.com/brianvoe/gofakeit/TODO.txt new file mode 100644 index 000000000000..7a492842136b --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/TODO.txt @@ -0,0 +1,3 @@ +* Take a look at [chance.js](http://chancejs.com/) and see if i missed anything. +* Look into [National Baby Name List](http://www.ssa.gov/oact/babynames/limits.html) and see if that makes sense to replace over what we currently have. +* Look at [data list](https://github.com/dariusk/corpora/tree/master/data) and see if it makes sense to add that data in or if it seems unncessary. diff --git a/vendor/github.com/brianvoe/gofakeit/address.go b/vendor/github.com/brianvoe/gofakeit/address.go new file mode 100644 index 000000000000..82fc6b00e191 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/address.go @@ -0,0 +1,131 @@ +package gofakeit + +import ( + "errors" + "math/rand" + "strings" +) + +// AddressInfo is a struct full of address information +type AddressInfo struct { + Address string + Street string + City string + State string + Zip string + Country string + Latitude float64 + Longitude float64 +} + +// Address will generate a struct of address information +func Address() *AddressInfo { + street := Street() + city := City() + state := State() + zip := Zip() + + return &AddressInfo{ + Address: street + ", " + city + ", " + state + " " + zip, + Street: street, + City: city, + State: state, + Zip: zip, + Country: Country(), + Latitude: Latitude(), + Longitude: Longitude(), + } +} + +// Street will generate a random address street string +func Street() (street string) { + switch randInt := randIntRange(1, 2); randInt { + case 1: + street = StreetNumber() + " " + StreetPrefix() + " " + StreetName() + StreetSuffix() + case 2: + street = StreetNumber() + " " + StreetName() + StreetSuffix() + } + + return +} + +// StreetNumber will generate a random address street number string +func StreetNumber() string { + return strings.TrimLeft(replaceWithNumbers(getRandValue([]string{"address", "number"})), "0") +} + +// StreetPrefix will generate a random address street prefix string +func StreetPrefix() string { + return getRandValue([]string{"address", "street_prefix"}) +} + +// StreetName will generate a random address street name string +func StreetName() string { + return getRandValue([]string{"address", "street_name"}) +} + +// StreetSuffix will generate a random address street suffix string +func StreetSuffix() string { + return getRandValue([]string{"address", "street_suffix"}) +} + +// City will generate a random city string +func City() (city string) { + switch randInt := randIntRange(1, 3); randInt { + case 1: + city = FirstName() + StreetSuffix() + case 2: + city = LastName() + StreetSuffix() + case 3: + city = StreetPrefix() + " " + LastName() + } + + return +} + +// State will generate a random state string +func State() string { + return getRandValue([]string{"address", "state"}) +} + +// StateAbr will generate a random abbreviated state string +func StateAbr() string { + return getRandValue([]string{"address", "state_abr"}) +} + +// Zip will generate a random Zip code string +func Zip() string { + return replaceWithNumbers(getRandValue([]string{"address", "zip"})) +} + +// Country will generate a random country string +func Country() string { + return getRandValue([]string{"address", "country"}) +} + +// CountryAbr will generate a random abbreviated country string +func CountryAbr() string { + return getRandValue([]string{"address", "country_abr"}) +} + +// Latitude will generate a random latitude float64 +func Latitude() float64 { return (rand.Float64() * 180) - 90 } + +// LatitudeInRange will generate a random latitude within the input range +func LatitudeInRange(min, max float64) (float64, error) { + if min > max || min < -90 || min > 90 || max < -90 || max > 90 { + return 0, errors.New("input range is invalid") + } + return randFloat64Range(min, max), nil +} + +// Longitude will generate a random longitude float64 +func Longitude() float64 { return (rand.Float64() * 360) - 180 } + +// LongitudeInRange will generate a random longitude within the input range +func LongitudeInRange(min, max float64) (float64, error) { + if min > max || min < -180 || min > 180 || max < -180 || max > 180 { + return 0, errors.New("input range is invalid") + } + return randFloat64Range(min, max), nil +} diff --git a/vendor/github.com/brianvoe/gofakeit/beer.go b/vendor/github.com/brianvoe/gofakeit/beer.go new file mode 100644 index 000000000000..53297d537809 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/beer.go @@ -0,0 +1,45 @@ +package gofakeit + +import "strconv" + +// Faker::Beer.blg #=> "18.5°Blg" + +// BeerName will return a random beer name +func BeerName() string { + return getRandValue([]string{"beer", "name"}) +} + +// BeerStyle will return a random beer style +func BeerStyle() string { + return getRandValue([]string{"beer", "style"}) +} + +// BeerHop will return a random beer hop +func BeerHop() string { + return getRandValue([]string{"beer", "hop"}) +} + +// BeerYeast will return a random beer yeast +func BeerYeast() string { + return getRandValue([]string{"beer", "yeast"}) +} + +// BeerMalt will return a random beer malt +func BeerMalt() string { + return getRandValue([]string{"beer", "malt"}) +} + +// BeerIbu will return a random beer ibu value between 10 and 100 +func BeerIbu() string { + return strconv.Itoa(randIntRange(10, 100)) + " IBU" +} + +// BeerAlcohol will return a random beer alcohol level between 2.0 and 10.0 +func BeerAlcohol() string { + return strconv.FormatFloat(randFloat64Range(2.0, 10.0), 'f', 1, 64) + "%" +} + +// BeerBlg will return a random beer blg between 5.0 and 20.0 +func BeerBlg() string { + return strconv.FormatFloat(randFloat64Range(5.0, 20.0), 'f', 1, 64) + "°Blg" +} diff --git a/vendor/github.com/brianvoe/gofakeit/bool.go b/vendor/github.com/brianvoe/gofakeit/bool.go new file mode 100644 index 000000000000..f63eeedd3241 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/bool.go @@ -0,0 +1,10 @@ +package gofakeit + +// Bool will generate a random boolean value +func Bool() bool { + if randIntRange(0, 1) == 1 { + return true + } + + return false +} diff --git a/vendor/github.com/brianvoe/gofakeit/color.go b/vendor/github.com/brianvoe/gofakeit/color.go new file mode 100644 index 000000000000..63a737e99a62 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/color.go @@ -0,0 +1,44 @@ +package gofakeit + +import "math/rand" + +// Color will generate a random color string +func Color() string { + return getRandValue([]string{"color", "full"}) +} + +// SafeColor will generate a random safe color string +func SafeColor() string { + return getRandValue([]string{"color", "safe"}) +} + +// HexColor will generate a random hexadecimal color string +func HexColor() string { + color := make([]byte, 6) + hashQuestion := []byte("?#") + for i := 0; i < 6; i++ { + color[i] = hashQuestion[rand.Intn(2)] + } + + return "#" + replaceWithLetters(replaceWithNumbers(string(color))) + + // color := "" + // for i := 1; i <= 6; i++ { + // color += RandString([]string{"?", "#"}) + // } + + // // Replace # with number + // color = replaceWithNumbers(color) + + // // Replace ? with letter + // for strings.Count(color, "?") > 0 { + // color = strings.Replace(color, "?", RandString(letters), 1) + // } + + // return "#" + color +} + +// RGBColor will generate a random int slice color +func RGBColor() []int { + return []int{randIntRange(0, 255), randIntRange(0, 255), randIntRange(0, 255)} +} diff --git a/vendor/github.com/brianvoe/gofakeit/company.go b/vendor/github.com/brianvoe/gofakeit/company.go new file mode 100644 index 000000000000..abdb2aa698f1 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/company.go @@ -0,0 +1,30 @@ +package gofakeit + +// Company will generate a random company name string +func Company() (company string) { + switch randInt := randIntRange(1, 3); randInt { + case 1: + company = LastName() + ", " + LastName() + " and " + LastName() + case 2: + company = LastName() + "-" + LastName() + case 3: + company = LastName() + " " + CompanySuffix() + } + + return +} + +// CompanySuffix will generate a random company suffix string +func CompanySuffix() string { + return getRandValue([]string{"company", "suffix"}) +} + +// BuzzWord will generate a random company buzz word string +func BuzzWord() string { + return getRandValue([]string{"company", "buzzwords"}) +} + +// BS will generate a random company bs string +func BS() string { + return getRandValue([]string{"company", "bs"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/contact.go b/vendor/github.com/brianvoe/gofakeit/contact.go new file mode 100644 index 000000000000..1eb0ae05303d --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/contact.go @@ -0,0 +1,40 @@ +package gofakeit + +import ( + "strings" +) + +// ContactInfo struct full of contact info +type ContactInfo struct { + Phone string + Email string +} + +// Contact will generate a struct with information randomly populated contact information +func Contact() *ContactInfo { + return &ContactInfo{ + Phone: Phone(), + Email: Email(), + } +} + +// Phone will generate a random phone number string +func Phone() string { + return replaceWithNumbers("##########") +} + +// PhoneFormatted will generate a random phone number string +func PhoneFormatted() string { + return replaceWithNumbers(getRandValue([]string{"contact", "phone"})) +} + +// Email will generate a random email string +func Email() string { + var email string + + email = getRandValue([]string{"person", "first"}) + getRandValue([]string{"person", "last"}) + email += "@" + email += getRandValue([]string{"person", "last"}) + "." + getRandValue([]string{"internet", "domain_suffix"}) + + return strings.ToLower(email) +} diff --git a/vendor/github.com/brianvoe/gofakeit/currency.go b/vendor/github.com/brianvoe/gofakeit/currency.go new file mode 100644 index 000000000000..c25e4d62a7aa --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/currency.go @@ -0,0 +1,38 @@ +package gofakeit + +import ( + "math" + "math/rand" + + "github.com/brianvoe/gofakeit/data" +) + +// CurrencyInfo is a struct of currency information +type CurrencyInfo struct { + Short string + Long string +} + +// Currency will generate a struct with random currency information +func Currency() *CurrencyInfo { + index := rand.Intn(len(data.Data["currency"]["short"])) + return &CurrencyInfo{ + Short: data.Data["currency"]["short"][index], + Long: data.Data["currency"]["long"][index], + } +} + +// CurrencyShort will generate a random short currency value +func CurrencyShort() string { + return getRandValue([]string{"currency", "short"}) +} + +// CurrencyLong will generate a random long currency name +func CurrencyLong() string { + return getRandValue([]string{"currency", "long"}) +} + +// Price will take in a min and max value and return a formatted price +func Price(min, max float64) float64 { + return math.Floor(randFloat64Range(min, max)*100) / 100 +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/address.go b/vendor/github.com/brianvoe/gofakeit/data/address.go new file mode 100644 index 000000000000..671cdda91375 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/address.go @@ -0,0 +1,15 @@ +package data + +// Address consists of address information +var Address = map[string][]string{ + "number": {"#####", "####", "###"}, + "street_prefix": {"North", "East", "West", "South", "New", "Lake", "Port"}, + "street_name": {"Alley", "Avenue", "Branch", "Bridge", "Brook", "Brooks", "Burg", "Burgs", "Bypass", "Camp", "Canyon", "Cape", "Causeway", "Center", "Centers", "Circle", "Circles", "Cliff", "Cliffs", "Club", "Common", "Corner", "Corners", "Course", "Court", "Courts", "Cove", "Coves", "Creek", "Crescent", "Crest", "Crossing", "Crossroad", "Curve", "Dale", "Dam", "Divide", "Drive", "Drive", "Drives", "Estate", "Estates", "Expressway", "Extension", "Extensions", "Fall", "Falls", "Ferry", "Field", "Fields", "Flat", "Flats", "Ford", "Fords", "Forest", "Forge", "Forges", "Fork", "Forks", "Fort", "Freeway", "Garden", "Gardens", "Gateway", "Glen", "Glens", "Green", "Greens", "Grove", "Groves", "Harbor", "Harbors", "Haven", "Heights", "Highway", "Hill", "Hills", "Hollow", "Inlet", "Inlet", "Island", "Island", "Islands", "Islands", "Isle", "Isle", "Junction", "Junctions", "Key", "Keys", "Knoll", "Knolls", "Lake", "Lakes", "Land", "Landing", "Lane", "Light", "Lights", "Loaf", "Lock", "Locks", "Locks", "Lodge", "Lodge", "Loop", "Mall", "Manor", "Manors", "Meadow", "Meadows", "Mews", "Mill", "Mills", "Mission", "Mission", "Motorway", "Mount", "Mountain", "Mountain", "Mountains", "Mountains", "Neck", "Orchard", "Oval", "Overpass", "Park", "Parks", "Parkway", "Parkways", "Pass", "Passage", "Path", "Pike", "Pine", "Pines", "Place", "Plain", "Plains", "Plains", "Plaza", "Plaza", "Point", "Points", "Port", "Port", "Ports", "Ports", "Prairie", "Prairie", "Radial", "Ramp", "Ranch", "Rapid", "Rapids", "Rest", "Ridge", "Ridges", "River", "Road", "Road", "Roads", "Roads", "Route", "Row", "Rue", "Run", "Shoal", "Shoals", "Shore", "Shores", "Skyway", "Spring", "Springs", "Springs", "Spur", "Spurs", "Square", "Square", "Squares", "Squares", "Station", "Station", "Stravenue", "Stravenue", "Stream", "Stream", "Street", "Street", "Streets", "Summit", "Summit", "Terrace", "Throughway", "Trace", "Track", "Trafficway", "Trail", "Trail", "Tunnel", "Tunnel", "Turnpike", "Turnpike", "Underpass", "Union", "Unions", "Valley", "Valleys", "Via", "Viaduct", "View", "Views", "Village", "Village", "Villages", "Ville", "Vista", "Vista", "Walk", "Walks", "Wall", "Way", "Ways", "Well", "Wells"}, + "street_suffix": {"town", "ton", "land", "ville", "berg", "burgh", "borough", "bury", "view", "port", "mouth", "stad", "furt", "chester", "mouth", "fort", "haven", "side", "shire"}, + "city": {"{address.street_prefix} {name.first}{address.street_suffix}", "{address.street_prefix} {name.first}", "{name.first}{address.street_suffix}", "{name.last}{address.street_suffix}"}, + "state": {"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"}, + "state_abr": {"AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI", "VA", "WA", "WV", "WI", "WY", "AE", "AA", "AP"}, + "zip": {"#####"}, + "country": {"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", "Congo", "Cook Islands", "Costa Rica", "Cote Divoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Faroe Islands", "Falkland Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard Island and McDonald Islands", "Holy See (Vatican City State)", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea", "Korea", "Kuwait", "Kyrgyz Republic", "Lao Peoples Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands Antilles", "Netherlands", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Palestinian Territory", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia (Slovak Republic)", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard & Jan Mayen Islands", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States of America", "United States Minor Outlying Islands", "United States Virgin Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"}, + "country_abr": {"AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CK", "CR", "CI", "HR", "CU", "CY", "CZ", "DK", "DJ", "DM", "DO", "TL", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "FX", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GN", "GW", "GY", "HT", "HM", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IL", "IT", "JM", "JP", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "AN", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "NO", "OM", "PK", "PW", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "KN", "LC", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SK", "SI", "SB", "SO", "ZA", "ES", "LK", "SH", "PM", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VA", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "YU", "ZR", "ZM", "ZW"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/beer.go b/vendor/github.com/brianvoe/gofakeit/data/beer.go new file mode 100644 index 000000000000..1192907d5f29 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/beer.go @@ -0,0 +1,10 @@ +package data + +// Beer consists of various beer information +var Beer = map[string][]string{ + "name": {"Pliny The Elder", "Founders Kentucky Breakfast", "Trappistes Rochefort 10", "HopSlam Ale", "Stone Imperial Russian Stout", "St. Bernardus Abt 12", "Founders Breakfast Stout", "Weihenstephaner Hefeweissbier", "Péché Mortel", "Celebrator Doppelbock", "Duvel", "Dreadnaught IPA", "Nugget Nectar", "La Fin Du Monde", "Bourbon County Stout", "Old Rasputin Russian Imperial Stout", "Two Hearted Ale", "Ruination IPA", "Schneider Aventinus", "Double Bastard Ale", "90 Minute IPA", "Hop Rod Rye", "Trappistes Rochefort 8", "Chimay Grande Réserve", "Stone IPA", "Arrogant Bastard Ale", "Edmund Fitzgerald Porter", "Chocolate St", "Oak Aged Yeti Imperial Stout", "Ten FIDY", "Storm King Stout", "Shakespeare Oatmeal", "Alpha King Pale Ale", "Westmalle Trappist Tripel", "Samuel Smith’s Imperial IPA", "Yeti Imperial Stout", "Hennepin", "Samuel Smith’s Oatmeal Stout", "Brooklyn Black", "Oaked Arrogant Bastard Ale", "Sublimely Self-Righteous Ale", "Trois Pistoles", "Bell’s Expedition", "Sierra Nevada Celebration Ale", "Sierra Nevada Bigfoot Barleywine Style Ale", "Racer 5 India Pale Ale, Bear Republic Bre", "Orval Trappist Ale", "Hercules Double IPA", "Maharaj", "Maudite"}, + "hop": {"Ahtanum", "Amarillo", "Bitter Gold", "Bravo", "Brewer’s Gold", "Bullion", "Cascade", "Cashmere", "Centennial", "Chelan", "Chinook", "Citra", "Cluster", "Columbia", "Columbus", "Comet", "Crystal", "Equinox", "Eroica", "Fuggle", "Galena", "Glacier", "Golding", "Hallertau", "Horizon", "Liberty", "Magnum", "Millennium", "Mosaic", "Mt. Hood", "Mt. Rainier", "Newport", "Northern Brewer", "Nugget", "Olympic", "Palisade", "Perle", "Saaz", "Santiam", "Simcoe", "Sorachi Ace", "Sterling", "Summit", "Tahoma", "Tettnang", "TriplePearl", "Ultra", "Vanguard", "Warrior", "Willamette", "Yakima Gol"}, + "yeast": {"1007 - German Ale", "1010 - American Wheat", "1028 - London Ale", "1056 - American Ale", "1084 - Irish Ale", "1098 - British Ale", "1099 - Whitbread Ale", "1187 - Ringwood Ale", "1272 - American Ale II", "1275 - Thames Valley Ale", "1318 - London Ale III", "1332 - Northwest Ale", "1335 - British Ale II", "1450 - Dennys Favorite 50", "1469 - West Yorkshire Ale", "1728 - Scottish Ale", "1968 - London ESB Ale", "2565 - Kölsch", "1214 - Belgian Abbey", "1388 - Belgian Strong Ale", "1762 - Belgian Abbey II", "3056 - Bavarian Wheat Blend", "3068 - Weihenstephan Weizen", "3278 - Belgian Lambic Blend", "3333 - German Wheat", "3463 - Forbidden Fruit", "3522 - Belgian Ardennes", "3638 - Bavarian Wheat", "3711 - French Saison", "3724 - Belgian Saison", "3763 - Roeselare Ale Blend", "3787 - Trappist High Gravity", "3942 - Belgian Wheat", "3944 - Belgian Witbier", "2000 - Budvar Lager", "2001 - Urquell Lager", "2007 - Pilsen Lager", "2035 - American Lager", "2042 - Danish Lager", "2112 - California Lager", "2124 - Bohemian Lager", "2206 - Bavarian Lager", "2278 - Czech Pils", "2308 - Munich Lager", "2633 - Octoberfest Lager Blend", "5112 - Brettanomyces bruxellensis", "5335 - Lactobacillus", "5526 - Brettanomyces lambicus", "5733 - Pediococcus"}, + "malt": {"Black malt", "Caramel", "Carapils", "Chocolate", "Munich", "Caramel", "Carapils", "Chocolate malt", "Munich", "Pale", "Roasted barley", "Rye malt", "Special roast", "Victory", "Vienna", "Wheat mal"}, + "style": {"Light Lager", "Pilsner", "European Amber Lager", "Dark Lager", "Bock", "Light Hybrid Beer", "Amber Hybrid Beer", "English Pale Ale", "Scottish And Irish Ale", "Merican Ale", "English Brown Ale", "Porter", "Stout", "India Pale Ale", "German Wheat And Rye Beer", "Belgian And French Ale", "Sour Ale", "Belgian Strong Ale", "Strong Ale", "Fruit Beer", "Vegetable Beer", "Smoke-flavored", "Wood-aged Beer"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/colors.go b/vendor/github.com/brianvoe/gofakeit/data/colors.go new file mode 100644 index 000000000000..3aca817d69f3 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/colors.go @@ -0,0 +1,7 @@ +package data + +// Colors consists of color information +var Colors = map[string][]string{ + "safe": {"black", "maroon", "green", "navy", "olive", "purple", "teal", "lime", "blue", "silver", "gray", "yellow", "fuchsia", "aqua", "white"}, + "full": {"AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "BlanchedAlmond", "Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue", "DarkCyan", "DarkGoldenRod", "DarkGray", "DarkGreen", "DarkKhaki", "DarkMagenta", "DarkOliveGreen", "Darkorange", "DarkOrchid", "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DimGrey", "DodgerBlue", "FireBrick", "FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "GoldenRod", "Gray", "Green", "GreenYellow", "HoneyDew", "HotPink", "IndianRed ", "Indigo ", "Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", "LightCoral", "LightCyan", "LightGoldenRodYellow", "LightGray", "LightGreen", "LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon", "MediumAquaMarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", "Orchid", "PaleGoldenRod", "PaleGreen", "PaleTurquoise", "PaleVioletRed", "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown", "Salmon", "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver", "SkyBlue", "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", "Teal", "Thistle", "Tomato", "Turquoise", "Violet", "Wheat", "White", "WhiteSmoke", "Yellow", "YellowGreen"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/company.go b/vendor/github.com/brianvoe/gofakeit/data/company.go new file mode 100644 index 000000000000..b2a3790c7c68 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/company.go @@ -0,0 +1,9 @@ +package data + +// Company consists of company information +var Company = map[string][]string{ + "name": {"{person.last} {company.suffix}", "{person.last}-{person.last}", "{person.last}, {person.last} and {person.last}"}, + "suffix": {"Inc", "and Sons", "LLC", "Group"}, + "buzzwords": {"Adaptive", "Advanced", "Ameliorated", "Assimilated", "Automated", "Balanced", "Business-focused", "Centralized", "Cloned", "Compatible", "Configurable", "Cross-group", "Cross-platform", "Customer-focused", "Customizable", "De-engineered", "Decentralized", "Devolved", "Digitized", "Distributed", "Diverse", "Down-sized", "Enhanced", "Enterprise-wide", "Ergonomic", "Exclusive", "Expanded", "Extended", "Face to face", "Focused", "Front-line", "Fully-configurable", "Function-based", "Fundamental", "Future-proofed", "Grass-roots", "Horizontal", "Implemented", "Innovative", "Integrated", "Intuitive", "Inverse", "Managed", "Mandatory", "Monitored", "Multi-channelled", "Multi-lateral", "Multi-layered", "Multi-tiered", "Networked", "Object-based", "Open-architected", "Open-source", "Operative", "Optimized", "Optional", "Organic", "Organized", "Persevering", "Persistent", "Phased", "Polarised", "Pre-emptive", "Proactive", "Profit-focused", "Profound", "Programmable", "Progressive", "Public-key", "Quality-focused", "Re-contextualized", "Re-engineered", "Reactive", "Realigned", "Reduced", "Reverse-engineered", "Right-sized", "Robust", "Seamless", "Secured", "Self-enabling", "Sharable", "Stand-alone", "Streamlined", "Switchable", "Synchronised", "Synergistic", "Synergized", "Team-oriented", "Total", "Triple-buffered", "Universal", "Up-sized", "Upgradable", "User-centric", "User-friendly", "Versatile", "Virtual", "Vision-oriented", "Visionary", "24 hour", "24/7", "3rd generation", "4th generation", "5th generation", "6th generation", "actuating", "analyzing", "asymmetric", "asynchronous", "attitude-oriented", "background", "bandwidth-monitored", "bi-directional", "bifurcated", "bottom-line", "clear-thinking", "client-driven", "client-server", "coherent", "cohesive", "composite", "content-based", "context-sensitive", "contextually-based", "dedicated", "demand-driven", "didactic", "directional", "discrete", "disintermediate", "dynamic", "eco-centric", "empowering", "encompassing", "even-keeled", "executive", "explicit", "exuding", "fault-tolerant", "foreground", "fresh-thinking", "full-range", "global", "grid-enabled", "heuristic", "high-level", "holistic", "homogeneous", "human-resource", "hybrid", "impactful", "incremental", "intangible", "interactive", "intermediate", "leading edge", "local", "logistical", "maximized", "methodical", "mission-critical", "mobile", "modular", "motivating", "multi-state", "multi-tasking", "multimedia", "national", "needs-based", "neutral", "next generation", "non-volatile", "object-oriented", "optimal", "optimizing", "radical", "real-time", "reciprocal", "regional", "responsive", "scalable", "secondary", "solution-oriented", "stable", "static", "system-worthy", "systematic", "systemic", "tangible", "tertiary", "transitional", "uniform", "upward-trending", "user-facing", "value-added", "web-enabled", "well-modulated", "zero administration", "zero defect", "zero tolerance", "Graphic Interface", "Graphical User Interface", "ability", "access", "adapter", "algorithm", "alliance", "analyzer", "application", "approach", "architecture", "archive", "array", "artificial intelligence", "attitude", "benchmark", "budgetary management", "capability", "capacity", "challenge", "circuit", "collaboration", "complexity", "concept", "conglomeration", "contingency", "core", "customer loyalty", "data-warehouse", "database", "definition", "emulation", "encoding", "encryption", "extranet", "firmware", "flexibility", "focus group", "forecast", "frame", "framework", "function", "functionalities", "groupware", "hardware", "help-desk", "hierarchy", "hub", "implementation", "info-mediaries", "infrastructure", "initiative", "installation", "instruction set", "interface", "internet solution", "intranet", "knowledge base", "knowledge user", "leverage", "local area network", "matrices", "matrix", "methodology", "middleware", "migration", "model", "moderator", "monitoring", "moratorium", "neural-net", "open architecture", "open system", "orchestration", "paradigm", "parallelism", "policy", "portal", "pricing structure", "process improvement", "product", "productivity", "project", "projection", "protocol", "secured line", "service-desk", "software", "solution", "standardization", "strategy", "structure", "success", "superstructure", "support", "synergy", "system engine", "task-force", "throughput", "time-frame", "toolset", "utilisation", "website", "workforce"}, + "bs": {"aggregate", "architect", "benchmark", "brand", "cultivate", "deliver", "deploy", "disintermediate", "drive", "e-enable", "embrace", "empower", "enable", "engage", "engineer", "enhance", "envisioneer", "evolve", "expedite", "exploit", "extend", "facilitate", "generate", "grow", "harness", "implement", "incentivize", "incubate", "innovate", "integrate", "iterate", "leverage", "matrix", "maximize", "mesh", "monetize", "morph", "optimize", "orchestrate", "productize", "recontextualize", "redefine", "reintermediate", "reinvent", "repurpose", "revolutionize", "scale", "seize", "strategize", "streamline", "syndicate", "synergize", "synthesize", "target", "transform", "transition", "unleash", "utilize", "visualize", "whiteboard", "24/365", "24/7", "B2B", "B2C", "back-end", "best-of-breed", "bleeding-edge", "bricks-and-clicks", "clicks-and-mortar", "collaborative", "compelling", "cross-media", "cross-platform", "customized", "cutting-edge", "distributed", "dot-com", "dynamic", "e-business", "efficient", "end-to-end", "enterprise", "extensible", "frictionless", "front-end", "global", "granular", "holistic", "impactful", "innovative", "integrated", "interactive", "intuitive", "killer", "leading-edge", "magnetic", "mission-critical", "next-generation", "one-to-one", "open-source", "out-of-the-box", "plug-and-play", "proactive", "real-time", "revolutionary", "rich", "robust", "scalable", "seamless", "sexy", "sticky", "strategic", "synergistic", "transparent", "turn-key", "ubiquitous", "user-centric", "value-added", "vertical", "viral", "virtual", "visionary", "web-enabled", "wireless", "world-class", "ROI", "action-items", "applications", "architectures", "bandwidth", "channels", "communities", "content", "convergence", "deliverables", "e-business", "e-commerce", "e-markets", "e-services", "e-tailers", "experiences", "eyeballs", "functionalities", "infomediaries", "infrastructures", "initiatives", "interfaces", "markets", "methodologies", "metrics", "mindshare", "models", "networks", "niches", "paradigms", "partnerships", "platforms", "portals", "relationships", "schemas", "solutions", "supply-chains", "synergies", "systems", "technologies", "users", "vortals", "web services", "web-readiness"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/computer.go b/vendor/github.com/brianvoe/gofakeit/data/computer.go new file mode 100644 index 000000000000..b682c6f820cc --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/computer.go @@ -0,0 +1,8 @@ +package data + +// Computer consists of computer information +var Computer = map[string][]string{ + "linux_processor": {"i686", "x86_64"}, + "mac_processor": {"Intel", "PPC", "U; Intel", "U; PPC"}, + "windows_platform": {"Windows NT 6.2", "Windows NT 6.1", "Windows NT 6.0", "Windows NT 5.2", "Windows NT 5.1", "Windows NT 5.01", "Windows NT 5.0", "Windows NT 4.0", "Windows 98; Win 9x 4.90", "Windows 98", "Windows 95", "Windows CE"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/contact.go b/vendor/github.com/brianvoe/gofakeit/data/contact.go new file mode 100644 index 000000000000..88b957961dbb --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/contact.go @@ -0,0 +1,6 @@ +package data + +// Contact consists of contact information +var Contact = map[string][]string{ + "phone": {"###-###-####", "(###)###-####", "1-###-###-####", "###.###.####"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/currency.go b/vendor/github.com/brianvoe/gofakeit/data/currency.go new file mode 100644 index 000000000000..13b8019973ca --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/currency.go @@ -0,0 +1,7 @@ +package data + +// Currency consists of currency information +var Currency = map[string][]string{ + "short": {"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYR", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SPL", "SRD", "STD", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TVD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XCD", "XDR", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWD"}, + "long": {"United Arab Emirates Dirham", "Afghanistan Afghani", "Albania Lek", "Armenia Dram", "Netherlands Antilles Guilder", "Angola Kwanza", "Argentina Peso", "Australia Dollar", "Aruba Guilder", "Azerbaijan New Manat", "Bosnia and Herzegovina Convertible Marka", "Barbados Dollar", "Bangladesh Taka", "Bulgaria Lev", "Bahrain Dinar", "Burundi Franc", "Bermuda Dollar", "Brunei Darussalam Dollar", "Bolivia Boliviano", "Brazil Real", "Bahamas Dollar", "Bhutan Ngultrum", "Botswana Pula", "Belarus Ruble", "Belize Dollar", "Canada Dollar", "Congo/Kinshasa Franc", "Switzerland Franc", "Chile Peso", "China Yuan Renminbi", "Colombia Peso", "Costa Rica Colon", "Cuba Convertible Peso", "Cuba Peso", "Cape Verde Escudo", "Czech Republic Koruna", "Djibouti Franc", "Denmark Krone", "Dominican Republic Peso", "Algeria Dinar", "Egypt Pound", "Eritrea Nakfa", "Ethiopia Birr", "Euro Member Countries", "Fiji Dollar", "Falkland Islands (Malvinas) Pound", "United Kingdom Pound", "Georgia Lari", "Guernsey Pound", "Ghana Cedi", "Gibraltar Pound", "Gambia Dalasi", "Guinea Franc", "Guatemala Quetzal", "Guyana Dollar", "Hong Kong Dollar", "Honduras Lempira", "Croatia Kuna", "Haiti Gourde", "Hungary Forint", "Indonesia Rupiah", "Israel Shekel", "Isle of Man Pound", "India Rupee", "Iraq Dinar", "Iran Rial", "Iceland Krona", "Jersey Pound", "Jamaica Dollar", "Jordan Dinar", "Japan Yen", "Kenya Shilling", "Kyrgyzstan Som", "Cambodia Riel", "Comoros Franc", "Korea (North) Won", "Korea (South) Won", "Kuwait Dinar", "Cayman Islands Dollar", "Kazakhstan Tenge", "Laos Kip", "Lebanon Pound", "Sri Lanka Rupee", "Liberia Dollar", "Lesotho Loti", "Lithuania Litas", "Libya Dinar", "Morocco Dirham", "Moldova Leu", "Madagascar Ariary", "Macedonia Denar", "Myanmar (Burma) Kyat", "Mongolia Tughrik", "Macau Pataca", "Mauritania Ouguiya", "Mauritius Rupee", "Maldives (Maldive Islands) Rufiyaa", "Malawi Kwacha", "Mexico Peso", "Malaysia Ringgit", "Mozambique Metical", "Namibia Dollar", "Nigeria Naira", "Nicaragua Cordoba", "Norway Krone", "Nepal Rupee", "New Zealand Dollar", "Oman Rial", "Panama Balboa", "Peru Nuevo Sol", "Papua New Guinea Kina", "Philippines Peso", "Pakistan Rupee", "Poland Zloty", "Paraguay Guarani", "Qatar Riyal", "Romania New Leu", "Serbia Dinar", "Russia Ruble", "Rwanda Franc", "Saudi Arabia Riyal", "Solomon Islands Dollar", "Seychelles Rupee", "Sudan Pound", "Sweden Krona", "Singapore Dollar", "Saint Helena Pound", "Sierra Leone Leone", "Somalia Shilling", "Seborga Luigino", "Suriname Dollar", "São Tomé and Príncipe Dobra", "El Salvador Colon", "Syria Pound", "Swaziland Lilangeni", "Thailand Baht", "Tajikistan Somoni", "Turkmenistan Manat", "Tunisia Dinar", "Tonga Pa'anga", "Turkey Lira", "Trinidad and Tobago Dollar", "Tuvalu Dollar", "Taiwan New Dollar", "Tanzania Shilling", "Ukraine Hryvnia", "Uganda Shilling", "United States Dollar", "Uruguay Peso", "Uzbekistan Som", "Venezuela Bolivar", "Viet Nam Dong", "Vanuatu Vatu", "Samoa Tala", "Communauté Financière Africaine (BEAC) CFA Franc BEAC", "East Caribbean Dollar", "International Monetary Fund (IMF) Special Drawing Rights", "Communauté Financière Africaine (BCEAO) Franc", "Comptoirs Français du Pacifique (CFP) Franc", "Yemen Rial", "South Africa Rand", "Zambia Kwacha", "Zimbabwe Dollar"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/data.go b/vendor/github.com/brianvoe/gofakeit/data/data.go new file mode 100644 index 000000000000..d751c9994356 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/data.go @@ -0,0 +1,28 @@ +package data + +// Data consists of the main set of fake information +var Data = map[string]map[string][]string{ + "person": Person, + "contact": Contact, + "address": Address, + "company": Company, + "job": Job, + "lorem": Lorem, + "internet": Internet, + "file": Files, + "color": Colors, + "computer": Computer, + "payment": Payment, + "hipster": Hipster, + "beer": Beer, + "hacker": Hacker, + "currency": Currency, + "log_level": LogLevels, + "timezone": TimeZone, + "vehicle": Vehicle, +} + +// IntData consists of the main set of fake information (integer only) +var IntData = map[string]map[string][]int{ + "status_code": StatusCodes, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/datetime.go b/vendor/github.com/brianvoe/gofakeit/data/datetime.go new file mode 100644 index 000000000000..3347120a67e2 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/datetime.go @@ -0,0 +1,9 @@ +package data + +// TimeZone is an array of short and long timezones +var TimeZone = map[string][]string{ + "offset": {"-12", "-11", "-10", "-8", "-7", "-7", "-8", "-7", "-6", "-6", "-6", "-5", "-5", "-6", "-5", "-4", "-4", "-4.5", "-4", "-3", "-4", "-4", "-4", "-2.5", "-3", "-3", "-3", "-3", "-3", "-3", "-2", "-1", "0", "-1", "1", "0", "0", "1", "1", "0", "2", "2", "2", "2", "1", "1", "3", "3", "2", "3", "3", "2", "3", "3", "3", "2", "3", "3", "3", "3", "3", "3", "4", "4.5", "4", "5", "4", "4", "4", "4.5", "5", "5", "5", "5.5", "5.5", "5.75", "6", "6", "6.5", "7", "7", "8", "8", "8", "8", "8", "8", "9", "9", "9", "9.5", "9.5", "10", "10", "10", "10", "10", "11", "11", "12", "12", "12", "12", "13", "13", "13"}, + "abr": {"DST", "U", "HST", "AKDT", "PDT", "PDT", "PST", "UMST", "MDT", "MDT", "CAST", "CDT", "CDT", "CCST", "SPST", "EDT", "UEDT", "VST", "PYT", "ADT", "CBST", "SWST", "PSST", "NDT", "ESAST", "AST", "SEST", "GDT", "MST", "BST", "U", "MDT", "ADT", "CVST", "MDT", "UTC", "GMT", "BST", "GDT", "GST", "WEDT", "CEDT", "RDT", "CEDT", "WCAST", "NST", "GDT", "MEDT", "EST", "SDT", "EEDT", "SAST", "FDT", "TDT", "JDT", "LST", "JST", "AST", "KST", "AST", "EAST", "MSK", "SAMT", "IDT", "AST", "ADT", "MST", "GST", "CST", "AST", "WAST", "YEKT", "PKT", "IST", "SLST", "NST", "CAST", "BST", "MST", "SAST", "NCAST", "CST", "NAST", "MPST", "WAST", "TST", "UST", "NAEST", "JST", "KST", "CAST", "ACST", "EAST", "AEST", "WPST", "TST", "YST", "CPST", "VST", "NZST", "U", "FST", "MST", "KDT", "TST", "SST"}, + "text": {"Dateline Standard Time", "UTC-11", "Hawaiian Standard Time", "Alaskan Standard Time", "Pacific Standard Time (Mexico)", "Pacific Daylight Time", "Pacific Standard Time", "US Mountain Standard Time", "Mountain Standard Time (Mexico)", "Mountain Standard Time", "Central America Standard Time", "Central Standard Time", "Central Standard Time (Mexico)", "Canada Central Standard Time", "SA Pacific Standard Time", "Eastern Standard Time", "US Eastern Standard Time", "Venezuela Standard Time", "Paraguay Standard Time", "Atlantic Standard Time", "Central Brazilian Standard Time", "SA Western Standard Time", "Pacific SA Standard Time", "Newfoundland Standard Time", "E. South America Standard Time", "Argentina Standard Time", "SA Eastern Standard Time", "Greenland Standard Time", "Montevideo Standard Time", "Bahia Standard Time", "UTC-02", "Mid-Atlantic Standard Time", "Azores Standard Time", "Cape Verde Standard Time", "Morocco Standard Time", "UTC", "Greenwich Mean Time", "British Summer Time", "GMT Standard Time", "Greenwich Standard Time", "W. Europe Standard Time", "Central Europe Standard Time", "Romance Standard Time", "Central European Standard Time", "W. Central Africa Standard Time", "Namibia Standard Time", "GTB Standard Time", "Middle East Standard Time", "Egypt Standard Time", "Syria Standard Time", "E. Europe Standard Time", "South Africa Standard Time", "FLE Standard Time", "Turkey Standard Time", "Israel Standard Time", "Libya Standard Time", "Jordan Standard Time", "Arabic Standard Time", "Kaliningrad Standard Time", "Arab Standard Time", "E. Africa Standard Time", "Moscow Standard Time", "Samara Time", "Iran Standard Time", "Arabian Standard Time", "Azerbaijan Standard Time", "Mauritius Standard Time", "Georgian Standard Time", "Caucasus Standard Time", "Afghanistan Standard Time", "West Asia Standard Time", "Yekaterinburg Time", "Pakistan Standard Time", "India Standard Time", "Sri Lanka Standard Time", "Nepal Standard Time", "Central Asia Standard Time", "Bangladesh Standard Time", "Myanmar Standard Time", "SE Asia Standard Time", "N. Central Asia Standard Time", "China Standard Time", "North Asia Standard Time", "Singapore Standard Time", "W. Australia Standard Time", "Taipei Standard Time", "Ulaanbaatar Standard Time", "North Asia East Standard Time", "Japan Standard Time", "Korea Standard Time", "Cen. Australia Standard Time", "AUS Central Standard Time", "E. Australia Standard Time", "AUS Eastern Standard Time", "West Pacific Standard Time", "Tasmania Standard Time", "Yakutsk Standard Time", "Central Pacific Standard Time", "Vladivostok Standard Time", "New Zealand Standard Time", "UTC+12", "Fiji Standard Time", "Magadan Standard Time", "Kamchatka Standard Time", "Tonga Standard Time", "Samoa Standard Time"}, + "full": {"(UTC-12:00) International Date Line West", "(UTC-11:00) Coordinated Universal Time-11", "(UTC-10:00) Hawaii", "(UTC-09:00) Alaska", "(UTC-08:00) Baja California", "(UTC-07:00) Pacific Time (US & Canada)", "(UTC-08:00) Pacific Time (US & Canada)", "(UTC-07:00) Arizona", "(UTC-07:00) Chihuahua, La Paz, Mazatlan", "(UTC-07:00) Mountain Time (US & Canada)", "(UTC-06:00) Central America", "(UTC-06:00) Central Time (US & Canada)", "(UTC-06:00) Guadalajara, Mexico City, Monterrey", "(UTC-06:00) Saskatchewan", "(UTC-05:00) Bogota, Lima, Quito", "(UTC-05:00) Eastern Time (US & Canada)", "(UTC-05:00) Indiana (East)", "(UTC-04:30) Caracas", "(UTC-04:00) Asuncion", "(UTC-04:00) Atlantic Time (Canada)", "(UTC-04:00) Cuiaba", "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan", "(UTC-04:00) Santiago", "(UTC-03:30) Newfoundland", "(UTC-03:00) Brasilia", "(UTC-03:00) Buenos Aires", "(UTC-03:00) Cayenne, Fortaleza", "(UTC-03:00) Greenland", "(UTC-03:00) Montevideo", "(UTC-03:00) Salvador", "(UTC-02:00) Coordinated Universal Time-02", "(UTC-02:00) Mid-Atlantic - Old", "(UTC-01:00) Azores", "(UTC-01:00) Cape Verde Is.", "(UTC) Casablanca", "(UTC) Coordinated Universal Time", "(UTC) Edinburgh, London", "(UTC+01:00) Edinburgh, London", "(UTC) Dublin, Lisbon", "(UTC) Monrovia, Reykjavik", "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", "(UTC+01:00) West Central Africa", "(UTC+01:00) Windhoek", "(UTC+02:00) Athens, Bucharest", "(UTC+02:00) Beirut", "(UTC+02:00) Cairo", "(UTC+02:00) Damascus", "(UTC+02:00) E. Europe", "(UTC+02:00) Harare, Pretoria", "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", "(UTC+03:00) Istanbul", "(UTC+02:00) Jerusalem", "(UTC+02:00) Tripoli", "(UTC+03:00) Amman", "(UTC+03:00) Baghdad", "(UTC+03:00) Kaliningrad, Minsk", "(UTC+03:00) Kuwait, Riyadh", "(UTC+03:00) Nairobi", "(UTC+03:00) Moscow, St. Petersburg, Volgograd", "(UTC+04:00) Samara, Ulyanovsk, Saratov", "(UTC+03:30) Tehran", "(UTC+04:00) Abu Dhabi, Muscat", "(UTC+04:00) Baku", "(UTC+04:00) Port Louis", "(UTC+04:00) Tbilisi", "(UTC+04:00) Yerevan", "(UTC+04:30) Kabul", "(UTC+05:00) Ashgabat, Tashkent", "(UTC+05:00) Yekaterinburg", "(UTC+05:00) Islamabad, Karachi", "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", "(UTC+05:30) Sri Jayawardenepura", "(UTC+05:45) Kathmandu", "(UTC+06:00) Astana", "(UTC+06:00) Dhaka", "(UTC+06:30) Yangon (Rangoon)", "(UTC+07:00) Bangkok, Hanoi, Jakarta", "(UTC+07:00) Novosibirsk", "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", "(UTC+08:00) Krasnoyarsk", "(UTC+08:00) Kuala Lumpur, Singapore", "(UTC+08:00) Perth", "(UTC+08:00) Taipei", "(UTC+08:00) Ulaanbaatar", "(UTC+09:00) Irkutsk", "(UTC+09:00) Osaka, Sapporo, Tokyo", "(UTC+09:00) Seoul", "(UTC+09:30) Adelaide", "(UTC+09:30) Darwin", "(UTC+10:00) Brisbane", "(UTC+10:00) Canberra, Melbourne, Sydney", "(UTC+10:00) Guam, Port Moresby", "(UTC+10:00) Hobart", "(UTC+10:00) Yakutsk", "(UTC+11:00) Solomon Is., New Caledonia", "(UTC+11:00) Vladivostok", "(UTC+12:00) Auckland, Wellington", "(UTC+12:00) Coordinated Universal Time+12", "(UTC+12:00) Fiji", "(UTC+12:00) Magadan", "(UTC+12:00) Petropavlovsk-Kamchatsky - Old", "(UTC+13:00) Nuku'alofa", "(UTC+13:00) Samoa"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/files.go b/vendor/github.com/brianvoe/gofakeit/data/files.go new file mode 100644 index 000000000000..363b840017f5 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/files.go @@ -0,0 +1,7 @@ +package data + +// Files consists of file information +var Files = map[string][]string{ + "mime_type": {"x-world/x-3dmf", "application/octet-stream", "application/x-authorware-bin", "application/x-authorware-map", "application/x-authorware-seg", "text/vnd.abc", "text/html", "video/animaflex", "application/postscript", "audio/aiff", "audio/x-aiff", "audio/aiff", "audio/x-aiff", "audio/aiff", "audio/x-aiff", "application/x-aim", "text/x-audiosoft-intra", "application/x-navi-animation", "application/x-nokia-9000-communicator-add-on-software", "application/mime", "application/octet-stream", "application/arj", "application/octet-stream", "image/x-jg", "video/x-ms-asf", "text/x-asm", "text/asp", "application/x-mplayer2", "video/x-ms-asf", "video/x-ms-asf-plugin", "audio/basic", "audio/x-au", "application/x-troff-msvideo", "video/avi", "video/msvideo", "video/x-msvideo", "video/avs-video", "application/x-bcpio", "application/mac-binary", "application/macbinary", "application/octet-stream", "application/x-binary", "application/x-macbinary", "image/bmp", "image/bmp", "image/x-windows-bmp", "application/book", "application/book", "application/x-bzip2", "application/x-bsh", "application/x-bzip", "application/x-bzip2", "text/plain", "text/x-c", "text/plain", "application/vnd.ms-pki.seccat", "text/plain", "text/x-c", "application/clariscad", "application/x-cocoa", "application/cdf", "application/x-cdf", "application/x-netcdf", "application/pkix-cert", "application/x-x509-ca-cert", "application/x-chat", "application/x-chat", "application/java", "application/java-byte-code", "application/x-java-class", "application/octet-stream", "text/plain", "text/plain", "application/x-cpio", "text/x-c", "application/mac-compactpro", "application/x-compactpro", "application/x-cpt", "application/pkcs-crl", "application/pkix-crl", "application/pkix-cert", "application/x-x509-ca-cert", "application/x-x509-user-cert", "application/x-csh", "text/x-script.csh", "application/x-pointplus", "text/css", "text/plain", "application/x-director", "application/x-deepv", "text/plain", "application/x-x509-ca-cert", "video/x-dv", "application/x-director", "video/dl", "video/x-dl", "application/msword", "application/msword", "application/commonground", "application/drafting", "application/octet-stream", "video/x-dv", "application/x-dvi", "drawing/x-dwf (old)", "model/vnd.dwf", "application/acad", "image/vnd.dwg", "image/x-dwg", "application/dxf", "image/vnd.dwg", "image/x-dwg", "application/x-director", "text/x-script.elisp", "application/x-bytecode.elisp (compiled elisp)", "application/x-elc", "application/x-envoy", "application/postscript", "application/x-esrehber", "text/x-setext", "application/envoy", "application/x-envoy", "application/octet-stream", "text/plain", "text/x-fortran", "text/x-fortran", "text/plain", "text/x-fortran", "application/vnd.fdf", "application/fractals", "image/fif", "video/fli", "video/x-fli", "image/florian", "text/vnd.fmi.flexstor", "video/x-atomic3d-feature", "text/plain", "text/x-fortran", "image/vnd.fpx", "image/vnd.net-fpx", "application/freeloader", "audio/make", "text/plain", "image/g3fax", "image/gif", "video/gl", "video/x-gl", "audio/x-gsm", "audio/x-gsm", "application/x-gsp", "application/x-gss", "application/x-gtar", "application/x-compressed", "application/x-gzip", "application/x-gzip", "multipart/x-gzip", "text/plain", "text/x-h", "application/x-hdf", "application/x-helpfile", "application/vnd.hp-hpgl", "text/plain", "text/x-h", "text/x-script", "application/hlp", "application/x-helpfile", "application/x-winhelp", "application/vnd.hp-hpgl", "application/vnd.hp-hpgl", "application/binhex", "application/binhex4", "application/mac-binhex", "application/mac-binhex40", "application/x-binhex40", "application/x-mac-binhex40", "application/hta", "text/x-component", "text/html", "text/html", "text/html", "text/webviewhtml", "text/html", "x-conference/x-cooltalk", "image/x-icon", "text/plain", "image/ief", "image/ief", "application/iges", "model/iges", "application/iges", "model/iges", "application/x-ima", "application/x-httpd-imap", "application/inf", "application/x-internett-signup", "application/x-ip2", "video/x-isvideo", "audio/it", "application/x-inventor", "i-world/i-vrml", "application/x-livescreen", "audio/x-jam", "text/plain", "text/x-java-source", "text/plain", "text/x-java-source", "application/x-java-commerce", "image/jpeg", "image/pjpeg", "image/jpeg", "image/jpeg", "image/pjpeg", "image/jpeg", "image/pjpeg", "image/jpeg", "image/pjpeg", "image/x-jps", "application/x-javascript", "image/jutvision", "audio/midi", "music/x-karaoke", "application/x-ksh", "text/x-script.ksh", "audio/nspaudio", "audio/x-nspaudio", "audio/x-liveaudio", "application/x-latex", "application/lha", "application/octet-stream", "application/x-lha", "application/octet-stream", "text/plain", "audio/nspaudio", "audio/x-nspaudio", "text/plain", "application/x-lisp", "text/x-script.lisp", "text/plain", "text/x-la-asf", "application/x-latex", "application/octet-stream", "application/x-lzh", "application/lzx", "application/octet-stream", "application/x-lzx", "text/plain", "text/x-m", "video/mpeg", "audio/mpeg", "video/mpeg", "audio/x-mpequrl", "application/x-troff-man", "application/x-navimap", "text/plain", "application/mbedlet", "application/mcad", "application/x-mathcad", "image/vasa", "text/mcf", "application/netmc", "application/x-troff-me", "message/rfc822", "message/rfc822", "application/x-midi", "audio/midi", "audio/x-mid", "audio/x-midi", "music/crescendo", "x-music/x-midi", "application/x-midi", "audio/midi", "audio/x-mid", "audio/x-midi", "music/crescendo", "x-music/x-midi", "application/x-frame", "application/x-mif", "message/rfc822", "www/mime", "video/x-motion-jpeg", "application/base64", "application/x-meme", "application/base64", "audio/mod", "audio/x-mod", "video/quicktime", "video/quicktime", "video/x-sgi-movie", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "video/x-mpeq2a", "audio/mpeg3", "audio/x-mpeg-3", "video/mpeg", "video/x-mpeg", "audio/mpeg", "video/mpeg", "application/x-project", "video/mpeg", "video/mpeg", "audio/mpeg", "video/mpeg", "audio/mpeg", "application/vnd.ms-project", "application/x-project", "application/x-project", "application/x-project", "application/marc", "application/x-troff-ms", "video/x-sgi-movie", "audio/make", "application/x-vnd.audioexplosion.mzz", "image/naplps", "image/naplps", "application/x-netcdf", "application/vnd.nokia.configuration-message", "image/x-niff", "image/x-niff", "application/x-mix-transfer", "application/x-conference", "application/x-navidoc", "application/octet-stream", "application/oda", "application/x-omc", "application/x-omcdatamaker", "application/x-omcregerator", "text/x-pascal", "application/pkcs10", "application/x-pkcs10", "application/pkcs-12", "application/x-pkcs12", "application/x-pkcs7-signature", "application/pkcs7-mime", "application/x-pkcs7-mime", "application/pkcs7-mime", "application/x-pkcs7-mime", "application/x-pkcs7-certreqresp", "application/pkcs7-signature", "application/pro_eng", "text/pascal", "image/x-portable-bitmap", "application/vnd.hp-pcl", "application/x-pcl", "image/x-pict", "image/x-pcx", "chemical/x-pdb", "application/pdf", "audio/make", "audio/make.my.funk", "image/x-portable-graymap", "image/x-portable-greymap", "image/pict", "image/pict", "application/x-newton-compatible-pkg", "application/vnd.ms-pki.pko", "text/plain", "text/x-script.perl", "application/x-pixclscript", "image/x-xpixmap", "text/x-script.perl-module", "application/x-pagemaker", "application/x-pagemaker", "image/png", "application/x-portable-anymap", "image/x-portable-anymap", "application/mspowerpoint", "application/vnd.ms-powerpoint", "model/x-pov", "application/vnd.ms-powerpoint", "image/x-portable-pixmap", "application/mspowerpoint", "application/vnd.ms-powerpoint", "application/mspowerpoint", "application/powerpoint", "application/vnd.ms-powerpoint", "application/x-mspowerpoint", "application/mspowerpoint", "application/x-freelance", "application/pro_eng", "application/postscript", "application/octet-stream", "paleovu/x-pv", "application/vnd.ms-powerpoint", "text/x-script.phyton", "application/x-bytecode.python", "audio/vnd.qcelp", "x-world/x-3dmf", "x-world/x-3dmf", "image/x-quicktime", "video/quicktime", "video/x-qtc", "image/x-quicktime", "image/x-quicktime", "audio/x-pn-realaudio", "audio/x-pn-realaudio-plugin", "audio/x-realaudio", "audio/x-pn-realaudio", "application/x-cmu-raster", "image/cmu-raster", "image/x-cmu-raster", "image/cmu-raster", "text/x-script.rexx", "image/vnd.rn-realflash", "image/x-rgb", "application/vnd.rn-realmedia", "audio/x-pn-realaudio", "audio/mid", "audio/x-pn-realaudio", "audio/x-pn-realaudio", "audio/x-pn-realaudio-plugin", "application/ringing-tones", "application/vnd.nokia.ringing-tone", "application/vnd.rn-realplayer", "application/x-troff", "image/vnd.rn-realpix", "audio/x-pn-realaudio-plugin", "text/richtext", "text/vnd.rn-realtext", "application/rtf", "application/x-rtf", "text/richtext", "application/rtf", "text/richtext", "video/vnd.rn-realvideo", "text/x-asm", "audio/s3m", "application/octet-stream", "application/x-tbook", "application/x-lotusscreencam", "text/x-script.guile", "text/x-script.scheme", "video/x-scm", "text/plain", "application/sdp", "application/x-sdp", "application/sounder", "application/sea", "application/x-sea", "application/set", "text/sgml", "text/x-sgml", "text/sgml", "text/x-sgml", "application/x-bsh", "application/x-sh", "application/x-shar", "text/x-script.sh", "application/x-bsh", "application/x-shar", "text/html", "text/x-server-parsed-html", "audio/x-psid", "application/x-sit", "application/x-stuffit", "application/x-koan", "application/x-koan", "application/x-koan", "application/x-koan", "application/x-seelogo", "application/smil", "application/smil", "audio/basic", "audio/x-adpcm", "application/solids", "application/x-pkcs7-certificates", "text/x-speech", "application/futuresplash", "application/x-sprite", "application/x-sprite", "application/x-wais-source", "text/x-server-parsed-html", "application/streamingmedia", "application/vnd.ms-pki.certstore", "application/step", "application/sla", "application/vnd.ms-pki.stl", "application/x-navistyle", "application/step", "application/x-sv4cpio", "application/x-sv4crc", "image/vnd.dwg", "image/x-dwg", "application/x-world", "x-world/x-svr", "application/x-shockwave-flash", "application/x-troff", "text/x-speech", "application/x-tar", "application/toolbook", "application/x-tbook", "application/x-tcl", "text/x-script.tcl", "text/x-script.tcsh", "application/x-tex", "application/x-texinfo", "application/x-texinfo", "application/plain", "text/plain", "application/gnutar", "application/x-compressed", "image/tiff", "image/x-tiff", "image/tiff", "image/x-tiff", "application/x-troff", "audio/tsp-audio", "application/dsptype", "audio/tsplayer", "text/tab-separated-values", "image/florian", "text/plain", "text/x-uil", "text/uri-list", "text/uri-list", "application/i-deas", "text/uri-list", "text/uri-list", "application/x-ustar", "multipart/x-ustar", "application/octet-stream", "text/x-uuencode", "text/x-uuencode", "application/x-cdlink", "text/x-vcalendar", "application/vda", "video/vdo", "application/groupwise", "video/vivo", "video/vnd.vivo", "video/vivo", "video/vnd.vivo", "application/vocaltec-media-desc", "application/vocaltec-media-file", "audio/voc", "audio/x-voc", "video/vosaic", "audio/voxware", "audio/x-twinvq-plugin", "audio/x-twinvq", "audio/x-twinvq-plugin", "application/x-vrml", "model/vrml", "x-world/x-vrml", "x-world/x-vrt", "application/x-visio", "application/x-visio", "application/x-visio", "application/wordperfect6.0", "application/wordperfect6.1", "application/msword", "audio/wav", "audio/x-wav", "application/x-qpro", "image/vnd.wap.wbmp", "application/vnd.xara", "application/msword", "application/x-123", "windows/metafile", "text/vnd.wap.wml", "application/vnd.wap.wmlc", "text/vnd.wap.wmlscript", "application/vnd.wap.wmlscriptc", "application/msword", "application/wordperfect", "application/wordperfect", "application/wordperfect6.0", "application/wordperfect", "application/wordperfect", "application/x-wpwin", "application/x-lotus", "application/mswrite", "application/x-wri", "application/x-world", "model/vrml", "x-world/x-vrml", "model/vrml", "x-world/x-vrml", "text/scriplet", "application/x-wais-source", "application/x-wintalk", "image/x-xbitmap", "image/x-xbm", "image/xbm", "video/x-amt-demorun", "xgl/drawing", "image/vnd.xiff", "application/excel", "application/excel", "application/x-excel", "application/x-msexcel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/x-excel", "application/excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/x-msexcel", "application/excel", "application/x-excel", "application/excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/x-msexcel", "audio/xm", "application/xml", "text/xml", "xgl/movie", "application/x-vnd.ls-xpix", "image/x-xpixmap", "image/xpm", "image/png", "video/x-amt-showrun", "image/x-xwd", "image/x-xwindowdump", "chemical/x-pdb", "application/x-compress", "application/x-compressed", "application/x-compressed", "application/x-zip-compressed", "application/zip", "multipart/x-zip", "application/octet-stream", "text/x-script.zsh"}, + "extension": {"doc", "docx", "log", "msg", "odt", "pages", "rtf", "tex", "txt", "wpd", "wps", "csv", "dat", "gbr", "ged", "key", "keychain", "pps", "ppt", "pptx", "sdf", "tar", "vcf", "xml", "aif", "iff", "mid", "mpa", "ra", "wav", "wma", "asf", "asx", "avi", "flv", "mov", "mpg", "rm", "srt", "swf", "vob", "wmv", "max", "obj", "bmp", "dds", "gif", "jpg", "png", "psd", "pspimage", "tga", "thm", "tif", "tiff", "yuv", "ai", "eps", "ps", "svg", "indd", "pct", "pdf", "xlr", "xls", "xlsx", "accdb", "db", "dbf", "mdb", "pdb", "sql", "apk", "app", "bat", "cgi", "com", "exe", "gadget", "jar", "pif", "vb", "wsf", "dem", "gam", "nes", "rom", "sav", "dwg", "dxf", "gpx", "kml", "kmz", "asp", "aspx", "cer", "cfm", "csr", "css", "htm", "html", "js", "jsp", "php", "rss", "xhtml", "crx", "plugin", "fnt", "fon", "otf", "ttf", "cab", "cpl", "cur", "deskthemepack", "dll", "dmp", "drv", "icns", "ico", "lnk", "sys", "cfg", "ini", "prf", "hqx", "mim", "uue", "cbr", "deb", "gz", "pkg", "rar", "rpm", "sitx", "gz", "zip", "zipx", "bin", "cue", "dmg", "iso", "mdf", "toast", "vcd", "class", "cpp", "cs", "dtd", "fla", "java", "lua", "pl", "py", "sh", "sln", "swift", "vcxproj", "xcodeproj", "bak", "tmp", "crdownload", "ics", "msi", "part", "torrent"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/hacker.go b/vendor/github.com/brianvoe/gofakeit/data/hacker.go new file mode 100644 index 000000000000..4735f7d560af --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/hacker.go @@ -0,0 +1,20 @@ +package data + +// Hacker consists of random hacker phrases +var Hacker = map[string][]string{ + "abbreviation": {"TCP", "HTTP", "SDD", "RAM", "GB", "CSS", "SSL", "AGP", "SQL", "FTP", "PCI", "AI", "ADP", "RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "SAS", "IB", "SCSI", "JSON", "XSS", "JBOD"}, + "adjective": {"auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform", "redundant", "online", "haptic", "multi-byte", "bluetooth", "wireless", "1080p", "neural", "optical", "solid state", "mobile"}, + "noun": {"driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card", "array", "interface", "system", "sensor", "firewall", "hard drive", "pixel", "alarm", "feed", "monitor", "application", "transmitter", "bus", "circuit", "capacitor", "matrix"}, + "verb": {"back up", "bypass", "hack", "override", "compress", "copy", "navigate", "index", "connect", "generate", "quantify", "calculate", "synthesize", "input", "transmit", "program", "reboot", "parse"}, + "ingverb": {"backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating", "indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing", "transmitting", "programming", "parsing"}, + "phrase": { + "If we {hacker.verb} the {hacker.noun}, we can get to the {hacker.abbreviation} {hacker.noun} through the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", + "We need to {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", + "Try to {hacker.verb} the {hacker.abbreviation} {hacker.noun}, maybe it will {hacker.verb} the {hacker.adjective} {hacker.noun}!", + "You can't {hacker.verb} the {hacker.noun} without {hacker.ingverb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", + "Use the {hacker.adjective} {hacker.abbreviation} {hacker.noun}, then you can {hacker.verb} the {hacker.adjective} {hacker.noun}!", + "The {hacker.abbreviation} {hacker.noun} is down, {hacker.verb} the {hacker.adjective} {hacker.noun} so we can {hacker.verb} the {hacker.abbreviation} {hacker.noun}!", + "{hacker.ingverb} the {hacker.noun} won't do anything, we need to {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", + "I'll {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}, that should {hacker.verb} the {hacker.abbreviation} {hacker.noun}!", + }, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/hipster.go b/vendor/github.com/brianvoe/gofakeit/data/hipster.go new file mode 100644 index 000000000000..f036f4639bc8 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/hipster.go @@ -0,0 +1,6 @@ +package data + +// Hipster consists of random hipster words +var Hipster = map[string][]string{ + "word": {"Wes Anderson", "chicharrones", "narwhal", "food truck", "marfa", "aesthetic", "keytar", "art party", "sustainable", "forage", "mlkshk", "gentrify", "locavore", "swag", "hoodie", "microdosing", "VHS", "before they sold out", "pabst", "plaid", "Thundercats", "freegan", "scenester", "hella", "occupy", "truffaut", "raw denim", "beard", "post-ironic", "photo booth", "twee", "90's", "pitchfork", "cray", "cornhole", "kale chips", "pour-over", "yr", "five dollar toast", "kombucha", "you probably haven't heard of them", "mustache", "fixie", "try-hard", "franzen", "kitsch", "austin", "stumptown", "keffiyeh", "whatever", "tumblr", "DIY", "shoreditch", "biodiesel", "vegan", "pop-up", "banjo", "kogi", "cold-pressed", "letterpress", "chambray", "butcher", "synth", "trust fund", "hammock", "farm-to-table", "intelligentsia", "loko", "ugh", "offal", "poutine", "gastropub", "Godard", "jean shorts", "sriracha", "dreamcatcher", "leggings", "fashion axe", "church-key", "meggings", "tote bag", "disrupt", "readymade", "helvetica", "flannel", "meh", "roof", "hashtag", "knausgaard", "cronut", "schlitz", "green juice", "waistcoat", "normcore", "viral", "ethical", "actually", "fingerstache", "humblebrag", "deep v", "wayfarers", "tacos", "taxidermy", "selvage", "put a bird on it", "ramps", "portland", "retro", "kickstarter", "bushwick", "brunch", "distillery", "migas", "flexitarian", "XOXO", "small batch", "messenger bag", "heirloom", "tofu", "bicycle rights", "bespoke", "salvia", "wolf", "selfies", "echo", "park", "listicle", "craft beer", "chartreuse", "sartorial", "pinterest", "mumblecore", "kinfolk", "vinyl", "etsy", "umami", "8-bit", "polaroid", "banh mi", "crucifix", "bitters", "brooklyn", "PBR&B", "drinking", "vinegar", "squid", "tattooed", "skateboard", "vice", "authentic", "literally", "lomo", "celiac", "health", "goth", "artisan", "chillwave", "blue bottle", "pickled", "next level", "neutra", "organic", "Yuccie", "paleo", "blog", "single-origin coffee", "seitan", "street", "gluten-free", "mixtape", "venmo", "irony", "everyday", "carry", "slow-carb", "3 wolf moon", "direct trade", "lo-fi", "tousled", "tilde", "semiotics", "cred", "chia", "master", "cleanse", "ennui", "quinoa", "pug", "iPhone", "fanny pack", "cliche", "cardigan", "asymmetrical", "meditation", "YOLO", "typewriter", "pork belly", "shabby chic", "+1", "lumbersexual", "williamsburg"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/internet.go b/vendor/github.com/brianvoe/gofakeit/data/internet.go new file mode 100644 index 000000000000..1f16db95c765 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/internet.go @@ -0,0 +1,8 @@ +package data + +// Internet consists of various internet information +var Internet = map[string][]string{ + "browser": {"firefox", "chrome", "internetExplorer", "opera", "safari"}, + "domain_suffix": {"com", "biz", "info", "name", "net", "org", "io"}, + "http_method": {"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/job.go b/vendor/github.com/brianvoe/gofakeit/data/job.go new file mode 100644 index 000000000000..905dd74ee023 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/job.go @@ -0,0 +1,8 @@ +package data + +// Job consists of job data +var Job = map[string][]string{ + "title": {"Administrator", "Agent", "Analyst", "Architect", "Assistant", "Associate", "Consultant", "Coordinator", "Designer", "Developer", "Director", "Engineer", "Executive", "Facilitator", "Liaison", "Manager", "Officer", "Orchestrator", "Planner", "Producer", "Representative", "Specialist", "Strategist", "Supervisor", "Technician"}, + "descriptor": {"Central", "Chief", "Corporate", "Customer", "Direct", "District", "Dynamic", "Dynamic", "Forward", "Future", "Global", "Human", "Internal", "International", "Investor", "Lead", "Legacy", "National", "Principal", "Product", "Regional", "Senior"}, + "level": {"Accountability", "Accounts", "Applications", "Assurance", "Brand", "Branding", "Communications", "Configuration", "Creative", "Data", "Directives", "Division", "Factors", "Functionality", "Group", "Identity", "Implementation", "Infrastructure", "Integration", "Interactions", "Intranet", "Marketing", "Markets", "Metrics", "Mobility", "Operations", "Optimization", "Paradigm", "Program", "Quality", "Research", "Response", "Security", "Solutions", "Tactics", "Usability", "Web"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/log_level.go b/vendor/github.com/brianvoe/gofakeit/data/log_level.go new file mode 100644 index 000000000000..01d98b63c6b6 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/log_level.go @@ -0,0 +1,8 @@ +package data + +// LogLevels consists of log levels for several types +var LogLevels = map[string][]string{ + "general": {"error", "warning", "info", "fatal", "trace", "debug"}, + "syslog": {"emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"}, + "apache": {"emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace1-8"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/lorem.go b/vendor/github.com/brianvoe/gofakeit/data/lorem.go new file mode 100644 index 000000000000..b0a8f8a1378f --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/lorem.go @@ -0,0 +1,6 @@ +package data + +// Lorem consists of lorem ipsum information +var Lorem = map[string][]string{ + "word": {"alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/payment.go b/vendor/github.com/brianvoe/gofakeit/data/payment.go new file mode 100644 index 000000000000..e50903a72af6 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/payment.go @@ -0,0 +1,20 @@ +package data + +// Payment contains payment information +var Payment = map[string][]string{ + "card_type": {"Visa", "MasterCard", "American Express", "Discover"}, + "number": { + // Visa + "4###############", + "4###############", + // Mastercard + "222100##########", + "272099##########", + // American Express + "34#############", + "37#############", + // Discover + "65##############", + "65##############", + }, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/person.go b/vendor/github.com/brianvoe/gofakeit/data/person.go new file mode 100644 index 000000000000..129b59ba6e3c --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/person.go @@ -0,0 +1,9 @@ +package data + +// Person consists of a slice of people information +var Person = map[string][]string{ + "prefix": {"Mr.", "Mrs.", "Ms.", "Miss", "Dr."}, + "suffix": {"Jr.", "Sr.", "I", "II", "III", "IV", "V", "MD", "DDS", "PhD", "DVM"}, + "first": {"Aaliyah", "Aaron", "Abagail", "Abbey", "Abbie", "Abbigail", "Abby", "Abdiel", "Abdul", "Abdullah", "Abe", "Abel", "Abelardo", "Abigail", "Abigale", "Abigayle", "Abner", "Abraham", "Ada", "Adah", "Adalberto", "Adaline", "Adam", "Adan", "Addie", "Addison", "Adela", "Adelbert", "Adele", "Adelia", "Adeline", "Adell", "Adella", "Adelle", "Aditya", "Adolf", "Adolfo", "Adolph", "Adolphus", "Adonis", "Adrain", "Adrian", "Adriana", "Adrianna", "Adriel", "Adrien", "Adrienne", "Afton", "Aglae", "Agnes", "Agustin", "Agustina", "Ahmad", "Ahmed", "Aida", "Aidan", "Aiden", "Aileen", "Aimee", "Aisha", "Aiyana", "Akeem", "Al", "Alaina", "Alan", "Alana", "Alanis", "Alanna", "Alayna", "Alba", "Albert", "Alberta", "Albertha", "Alberto", "Albin", "Albina", "Alda", "Alden", "Alec", "Aleen", "Alejandra", "Alejandrin", "Alek", "Alena", "Alene", "Alessandra", "Alessandro", "Alessia", "Aletha", "Alex", "Alexa", "Alexander", "Alexandra", "Alexandre", "Alexandrea", "Alexandria", "Alexandrine", "Alexandro", "Alexane", "Alexanne", "Alexie", "Alexis", "Alexys", "Alexzander", "Alf", "Alfonso", "Alfonzo", "Alford", "Alfred", "Alfreda", "Alfredo", "Ali", "Alia", "Alice", "Alicia", "Alisa", "Alisha", "Alison", "Alivia", "Aliya", "Aliyah", "Aliza", "Alize", "Allan", "Allen", "Allene", "Allie", "Allison", "Ally", "Alphonso", "Alta", "Althea", "Alva", "Alvah", "Alvena", "Alvera", "Alverta", "Alvina", "Alvis", "Alyce", "Alycia", "Alysa", "Alysha", "Alyson", "Alysson", "Amalia", "Amanda", "Amani", "Amara", "Amari", "Amaya", "Amber", "Ambrose", "Amelia", "Amelie", "Amely", "America", "Americo", "Amie", "Amina", "Amir", "Amira", "Amiya", "Amos", "Amparo", "Amy", "Amya", "Ana", "Anabel", "Anabelle", "Anahi", "Anais", "Anastacio", "Anastasia", "Anderson", "Andre", "Andreane", "Andreanne", "Andres", "Andrew", "Andy", "Angel", "Angela", "Angelica", "Angelina", "Angeline", "Angelita", "Angelo", "Angie", "Angus", "Anibal", "Anika", "Anissa", "Anita", "Aniya", "Aniyah", "Anjali", "Anna", "Annabel", "Annabell", "Annabelle", "Annalise", "Annamae", "Annamarie", "Anne", "Annetta", "Annette", "Annie", "Ansel", "Ansley", "Anthony", "Antoinette", "Antone", "Antonetta", "Antonette", "Antonia", "Antonietta", "Antonina", "Antonio", "Antwan", "Antwon", "Anya", "April", "Ara", "Araceli", "Aracely", "Arch", "Archibald", "Ardella", "Arden", "Ardith", "Arely", "Ari", "Ariane", "Arianna", "Aric", "Ariel", "Arielle", "Arjun", "Arlene", "Arlie", "Arlo", "Armand", "Armando", "Armani", "Arnaldo", "Arne", "Arno", "Arnold", "Arnoldo", "Arnulfo", "Aron", "Art", "Arthur", "Arturo", "Arvel", "Arvid", "Arvilla", "Aryanna", "Asa", "Asha", "Ashlee", "Ashleigh", "Ashley", "Ashly", "Ashlynn", "Ashton", "Ashtyn", "Asia", "Assunta", "Astrid", "Athena", "Aubree", "Aubrey", "Audie", "Audra", "Audreanne", "Audrey", "August", "Augusta", "Augustine", "Augustus", "Aurelia", "Aurelie", "Aurelio", "Aurore", "Austen", "Austin", "Austyn", "Autumn", "Ava", "Avery", "Avis", "Axel", "Ayana", "Ayden", "Ayla", "Aylin", "Baby", "Bailee", "Bailey", "Barbara", "Barney", "Baron", "Barrett", "Barry", "Bart", "Bartholome", "Barton", "Baylee", "Beatrice", "Beau", "Beaulah", "Bell", "Bella", "Belle", "Ben", "Benedict", "Benjamin", "Bennett", "Bennie", "Benny", "Benton", "Berenice", "Bernadette", "Bernadine", "Bernard", "Bernardo", "Berneice", "Bernhard", "Bernice", "Bernie", "Berniece", "Bernita", "Berry", "Bert", "Berta", "Bertha", "Bertram", "Bertrand", "Beryl", "Bessie", "Beth", "Bethany", "Bethel", "Betsy", "Bette", "Bettie", "Betty", "Bettye", "Beulah", "Beverly", "Bianka", "Bill", "Billie", "Billy", "Birdie", "Blair", "Blaise", "Blake", "Blanca", "Blanche", "Blaze", "Bo", "Bobbie", "Bobby", "Bonita", "Bonnie", "Boris", "Boyd", "Brad", "Braden", "Bradford", "Bradley", "Bradly", "Brady", "Braeden", "Brain", "Brandi", "Brando", "Brandon", "Brandt", "Brandy", "Brandyn", "Brannon", "Branson", "Brant", "Braulio", "Braxton", "Brayan", "Breana", "Breanna", "Breanne", "Brenda", "Brendan", "Brenden", "Brendon", "Brenna", "Brennan", "Brennon", "Brent", "Bret", "Brett", "Bria", "Brian", "Briana", "Brianne", "Brice", "Bridget", "Bridgette", "Bridie", "Brielle", "Brigitte", "Brionna", "Brisa", "Britney", "Brittany", "Brock", "Broderick", "Brody", "Brook", "Brooke", "Brooklyn", "Brooks", "Brown", "Bruce", "Bryana", "Bryce", "Brycen", "Bryon", "Buck", "Bud", "Buddy", "Buford", "Bulah", "Burdette", "Burley", "Burnice", "Buster", "Cade", "Caden", "Caesar", "Caitlyn", "Cale", "Caleb", "Caleigh", "Cali", "Calista", "Callie", "Camden", "Cameron", "Camila", "Camilla", "Camille", "Camren", "Camron", "Camryn", "Camylle", "Candace", "Candelario", "Candice", "Candida", "Candido", "Cara", "Carey", "Carissa", "Carlee", "Carleton", "Carley", "Carli", "Carlie", "Carlo", "Carlos", "Carlotta", "Carmel", "Carmela", "Carmella", "Carmelo", "Carmen", "Carmine", "Carol", "Carolanne", "Carole", "Carolina", "Caroline", "Carolyn", "Carolyne", "Carrie", "Carroll", "Carson", "Carter", "Cary", "Casandra", "Casey", "Casimer", "Casimir", "Casper", "Cassandra", "Cassandre", "Cassidy", "Cassie", "Catalina", "Caterina", "Catharine", "Catherine", "Cathrine", "Cathryn", "Cathy", "Cayla", "Ceasar", "Cecelia", "Cecil", "Cecile", "Cecilia", "Cedrick", "Celestine", "Celestino", "Celia", "Celine", "Cesar", "Chad", "Chadd", "Chadrick", "Chaim", "Chance", "Chandler", "Chanel", "Chanelle", "Charity", "Charlene", "Charles", "Charley", "Charlie", "Charlotte", "Chase", "Chasity", "Chauncey", "Chaya", "Chaz", "Chelsea", "Chelsey", "Chelsie", "Chesley", "Chester", "Chet", "Cheyanne", "Cheyenne", "Chloe", "Chris", "Christ", "Christa", "Christelle", "Christian", "Christiana", "Christina", "Christine", "Christop", "Christophe", "Christopher", "Christy", "Chyna", "Ciara", "Cicero", "Cielo", "Cierra", "Cindy", "Citlalli", "Clair", "Claire", "Clara", "Clarabelle", "Clare", "Clarissa", "Clark", "Claud", "Claude", "Claudia", "Claudie", "Claudine", "Clay", "Clemens", "Clement", "Clementina", "Clementine", "Clemmie", "Cleo", "Cleora", "Cleta", "Cletus", "Cleve", "Cleveland", "Clifford", "Clifton", "Clint", "Clinton", "Clotilde", "Clovis", "Cloyd", "Clyde", "Coby", "Cody", "Colby", "Cole", "Coleman", "Colin", "Colleen", "Collin", "Colt", "Colten", "Colton", "Columbus", "Concepcion", "Conner", "Connie", "Connor", "Conor", "Conrad", "Constance", "Constantin", "Consuelo", "Cooper", "Cora", "Coralie", "Corbin", "Cordelia", "Cordell", "Cordia", "Cordie", "Corene", "Corine", "Cornelius", "Cornell", "Corrine", "Cortez", "Cortney", "Cory", "Coty", "Courtney", "Coy", "Craig", "Crawford", "Creola", "Cristal", "Cristian", "Cristina", "Cristobal", "Cristopher", "Cruz", "Crystal", "Crystel", "Cullen", "Curt", "Curtis", "Cydney", "Cynthia", "Cyril", "Cyrus", "Dagmar", "Dahlia", "Daija", "Daisha", "Daisy", "Dakota", "Dale", "Dallas", "Dallin", "Dalton", "Damaris", "Dameon", "Damian", "Damien", "Damion", "Damon", "Dan", "Dana", "Dandre", "Dane", "Dangelo", "Dangelo", "Danial", "Daniela", "Daniella", "Danielle", "Danika", "Dannie", "Danny", "Dante", "Danyka", "Daphne", "Daphnee", "Daphney", "Darby", "Daren", "Darian", "Dariana", "Darien", "Dario", "Darion", "Darius", "Darlene", "Daron", "Darrel", "Darrell", "Darren", "Darrick", "Darrin", "Darrion", "Darron", "Darryl", "Darwin", "Daryl", "Dashawn", "Dasia", "Dave", "David", "Davin", "Davion", "Davon", "Davonte", "Dawn", "Dawson", "Dax", "Dayana", "Dayna", "Dayne", "Dayton", "Dean", "Deangelo", "Deanna", "Deborah", "Declan", "Dedric", "Dedrick", "Dee", "Deion", "Deja", "Dejah", "Dejon", "Dejuan", "Delaney", "Delbert", "Delfina", "Delia", "Delilah", "Dell", "Della", "Delmer", "Delores", "Delpha", "Delphia", "Delphine", "Delta", "Demarco", "Demarcus", "Demario", "Demetris", "Demetrius", "Demond", "Dena", "Denis", "Dennis", "Deon", "Deondre", "Deontae", "Deonte", "Dereck", "Derek", "Derick", "Deron", "Derrick", "Deshaun", "Deshawn", "Desiree", "Desmond", "Dessie", "Destany", "Destin", "Destinee", "Destiney", "Destini", "Destiny", "Devan", "Devante", "Deven", "Devin", "Devon", "Devonte", "Devyn", "Dewayne", "Dewitt", "Dexter", "Diamond", "Diana", "Dianna", "Diego", "Dillan", "Dillon", "Dimitri", "Dina", "Dino", "Dion", "Dixie", "Dock", "Dolly", "Dolores", "Domenic", "Domenica", "Domenick", "Domenico", "Domingo", "Dominic", "Dominique", "Don", "Donald", "Donato", "Donavon", "Donna", "Donnell", "Donnie", "Donny", "Dora", "Dorcas", "Dorian", "Doris", "Dorothea", "Dorothy", "Dorris", "Dortha", "Dorthy", "Doug", "Douglas", "Dovie", "Doyle", "Drake", "Drew", "Duane", "Dudley", "Dulce", "Duncan", "Durward", "Dustin", "Dusty", "Dwight", "Dylan", "Earl", "Earlene", "Earline", "Earnest", "Earnestine", "Easter", "Easton", "Ebba", "Ebony", "Ed", "Eda", "Edd", "Eddie", "Eden", "Edgar", "Edgardo", "Edison", "Edmond", "Edmund", "Edna", "Eduardo", "Edward", "Edwardo", "Edwin", "Edwina", "Edyth", "Edythe", "Effie", "Efrain", "Efren", "Eileen", "Einar", "Eino", "Eladio", "Elaina", "Elbert", "Elda", "Eldon", "Eldora", "Eldred", "Eldridge", "Eleanora", "Eleanore", "Eleazar", "Electa", "Elena", "Elenor", "Elenora", "Eleonore", "Elfrieda", "Eli", "Elian", "Eliane", "Elias", "Eliezer", "Elijah", "Elinor", "Elinore", "Elisa", "Elisabeth", "Elise", "Eliseo", "Elisha", "Elissa", "Eliza", "Elizabeth", "Ella", "Ellen", "Ellie", "Elliot", "Elliott", "Ellis", "Ellsworth", "Elmer", "Elmira", "Elmo", "Elmore", "Elna", "Elnora", "Elody", "Eloisa", "Eloise", "Elouise", "Eloy", "Elroy", "Elsa", "Else", "Elsie", "Elta", "Elton", "Elva", "Elvera", "Elvie", "Elvis", "Elwin", "Elwyn", "Elyse", "Elyssa", "Elza", "Emanuel", "Emelia", "Emelie", "Emely", "Emerald", "Emerson", "Emery", "Emie", "Emil", "Emile", "Emilia", "Emiliano", "Emilie", "Emilio", "Emily", "Emma", "Emmalee", "Emmanuel", "Emmanuelle", "Emmet", "Emmett", "Emmie", "Emmitt", "Emmy", "Emory", "Ena", "Enid", "Enoch", "Enola", "Enos", "Enrico", "Enrique", "Ephraim", "Era", "Eriberto", "Eric", "Erica", "Erich", "Erick", "Ericka", "Erik", "Erika", "Erin", "Erling", "Erna", "Ernest", "Ernestina", "Ernestine", "Ernesto", "Ernie", "Ervin", "Erwin", "Eryn", "Esmeralda", "Esperanza", "Esta", "Esteban", "Estefania", "Estel", "Estell", "Estella", "Estelle", "Estevan", "Esther", "Estrella", "Etha", "Ethan", "Ethel", "Ethelyn", "Ethyl", "Ettie", "Eudora", "Eugene", "Eugenia", "Eula", "Eulah", "Eulalia", "Euna", "Eunice", "Eusebio", "Eva", "Evalyn", "Evan", "Evangeline", "Evans", "Eve", "Eveline", "Evelyn", "Everardo", "Everett", "Everette", "Evert", "Evie", "Ewald", "Ewell", "Ezekiel", "Ezequiel", "Ezra", "Fabian", "Fabiola", "Fae", "Fannie", "Fanny", "Fatima", "Faustino", "Fausto", "Favian", "Fay", "Faye", "Federico", "Felicia", "Felicita", "Felicity", "Felipa", "Felipe", "Felix", "Felton", "Fermin", "Fern", "Fernando", "Ferne", "Fidel", "Filiberto", "Filomena", "Finn", "Fiona", "Flavie", "Flavio", "Fleta", "Fletcher", "Flo", "Florence", "Florencio", "Florian", "Florida", "Florine", "Flossie", "Floy", "Floyd", "Ford", "Forest", "Forrest", "Foster", "Frances", "Francesca", "Francesco", "Francis", "Francisca", "Francisco", "Franco", "Frank", "Frankie", "Franz", "Fred", "Freda", "Freddie", "Freddy", "Frederic", "Frederick", "Frederik", "Frederique", "Fredrick", "Fredy", "Freeda", "Freeman", "Freida", "Frida", "Frieda", "Friedrich", "Fritz", "Furman", "Gabe", "Gabriel", "Gabriella", "Gabrielle", "Gaetano", "Gage", "Gail", "Gardner", "Garett", "Garfield", "Garland", "Garnet", "Garnett", "Garret", "Garrett", "Garrick", "Garrison", "Garry", "Garth", "Gaston", "Gavin", "Gay", "Gayle", "Gaylord", "Gene", "General", "Genesis", "Genevieve", "Gennaro", "Genoveva", "Geo", "Geoffrey", "George", "Georgette", "Georgiana", "Georgianna", "Geovanni", "Geovanny", "Geovany", "Gerald", "Geraldine", "Gerard", "Gerardo", "Gerda", "Gerhard", "Germaine", "German", "Gerry", "Gerson", "Gertrude", "Gia", "Gianni", "Gideon", "Gilbert", "Gilberto", "Gilda", "Giles", "Gillian", "Gina", "Gino", "Giovani", "Giovanna", "Giovanni", "Giovanny", "Gisselle", "Giuseppe", "Gladyce", "Gladys", "Glen", "Glenda", "Glenna", "Glennie", "Gloria", "Godfrey", "Golda", "Golden", "Gonzalo", "Gordon", "Grace", "Gracie", "Graciela", "Grady", "Graham", "Grant", "Granville", "Grayce", "Grayson", "Green", "Greg", "Gregg", "Gregoria", "Gregorio", "Gregory", "Greta", "Gretchen", "Greyson", "Griffin", "Grover", "Guadalupe", "Gudrun", "Guido", "Guillermo", "Guiseppe", "Gunnar", "Gunner", "Gus", "Gussie", "Gust", "Gustave", "Guy", "Gwen", "Gwendolyn", "Hadley", "Hailee", "Hailey", "Hailie", "Hal", "Haleigh", "Haley", "Halie", "Halle", "Hallie", "Hank", "Hanna", "Hannah", "Hans", "Hardy", "Harley", "Harmon", "Harmony", "Harold", "Harrison", "Harry", "Harvey", "Haskell", "Hassan", "Hassie", "Hattie", "Haven", "Hayden", "Haylee", "Hayley", "Haylie", "Hazel", "Hazle", "Heath", "Heather", "Heaven", "Heber", "Hector", "Heidi", "Helen", "Helena", "Helene", "Helga", "Hellen", "Helmer", "Heloise", "Henderson", "Henri", "Henriette", "Henry", "Herbert", "Herman", "Hermann", "Hermina", "Herminia", "Herminio", "Hershel", "Herta", "Hertha", "Hester", "Hettie", "Hilario", "Hilbert", "Hilda", "Hildegard", "Hillard", "Hillary", "Hilma", "Hilton", "Hipolito", "Hiram", "Hobart", "Holden", "Hollie", "Hollis", "Holly", "Hope", "Horace", "Horacio", "Hortense", "Hosea", "Houston", "Howard", "Howell", "Hoyt", "Hubert", "Hudson", "Hugh", "Hulda", "Humberto", "Hunter", "Hyman", "Ian", "Ibrahim", "Icie", "Ida", "Idell", "Idella", "Ignacio", "Ignatius", "Ike", "Ila", "Ilene", "Iliana", "Ima", "Imani", "Imelda", "Immanuel", "Imogene", "Ines", "Irma", "Irving", "Irwin", "Isaac", "Isabel", "Isabell", "Isabella", "Isabelle", "Isac", "Isadore", "Isai", "Isaiah", "Isaias", "Isidro", "Ismael", "Isobel", "Isom", "Israel", "Issac", "Itzel", "Iva", "Ivah", "Ivory", "Ivy", "Izabella", "Izaiah", "Jabari", "Jace", "Jacey", "Jacinthe", "Jacinto", "Jack", "Jackeline", "Jackie", "Jacklyn", "Jackson", "Jacky", "Jaclyn", "Jacquelyn", "Jacques", "Jacynthe", "Jada", "Jade", "Jaden", "Jadon", "Jadyn", "Jaeden", "Jaida", "Jaiden", "Jailyn", "Jaime", "Jairo", "Jakayla", "Jake", "Jakob", "Jaleel", "Jalen", "Jalon", "Jalyn", "Jamaal", "Jamal", "Jamar", "Jamarcus", "Jamel", "Jameson", "Jamey", "Jamie", "Jamil", "Jamir", "Jamison", "Jammie", "Jan", "Jana", "Janae", "Jane", "Janelle", "Janessa", "Janet", "Janice", "Janick", "Janie", "Janis", "Janiya", "Jannie", "Jany", "Jaquan", "Jaquelin", "Jaqueline", "Jared", "Jaren", "Jarod", "Jaron", "Jarred", "Jarrell", "Jarret", "Jarrett", "Jarrod", "Jarvis", "Jasen", "Jasmin", "Jason", "Jasper", "Jaunita", "Javier", "Javon", "Javonte", "Jay", "Jayce", "Jaycee", "Jayda", "Jayde", "Jayden", "Jaydon", "Jaylan", "Jaylen", "Jaylin", "Jaylon", "Jayme", "Jayne", "Jayson", "Jazlyn", "Jazmin", "Jazmyn", "Jazmyne", "Jean", "Jeanette", "Jeanie", "Jeanne", "Jed", "Jedediah", "Jedidiah", "Jeff", "Jefferey", "Jeffery", "Jeffrey", "Jeffry", "Jena", "Jenifer", "Jennie", "Jennifer", "Jennings", "Jennyfer", "Jensen", "Jerad", "Jerald", "Jeramie", "Jeramy", "Jerel", "Jeremie", "Jeremy", "Jermain", "Jermaine", "Jermey", "Jerod", "Jerome", "Jeromy", "Jerrell", "Jerrod", "Jerrold", "Jerry", "Jess", "Jesse", "Jessica", "Jessie", "Jessika", "Jessy", "Jessyca", "Jesus", "Jett", "Jettie", "Jevon", "Jewel", "Jewell", "Jillian", "Jimmie", "Jimmy", "Jo", "Joan", "Joana", "Joanie", "Joanne", "Joannie", "Joanny", "Joany", "Joaquin", "Jocelyn", "Jodie", "Jody", "Joe", "Joel", "Joelle", "Joesph", "Joey", "Johan", "Johann", "Johanna", "Johathan", "John", "Johnathan", "Johnathon", "Johnnie", "Johnny", "Johnpaul", "Johnson", "Jolie", "Jon", "Jonas", "Jonatan", "Jonathan", "Jonathon", "Jordan", "Jordane", "Jordi", "Jordon", "Jordy", "Jordyn", "Jorge", "Jose", "Josefa", "Josefina", "Joseph", "Josephine", "Josh", "Joshua", "Joshuah", "Josiah", "Josiane", "Josianne", "Josie", "Josue", "Jovan", "Jovani", "Jovanny", "Jovany", "Joy", "Joyce", "Juana", "Juanita", "Judah", "Judd", "Jude", "Judge", "Judson", "Judy", "Jules", "Julia", "Julian", "Juliana", "Julianne", "Julie", "Julien", "Juliet", "Julio", "Julius", "June", "Junior", "Junius", "Justen", "Justice", "Justina", "Justine", "Juston", "Justus", "Justyn", "Juvenal", "Juwan", "Kacey", "Kaci", "Kacie", "Kade", "Kaden", "Kadin", "Kaela", "Kaelyn", "Kaia", "Kailee", "Kailey", "Kailyn", "Kaitlin", "Kaitlyn", "Kale", "Kaleb", "Kaleigh", "Kaley", "Kali", "Kallie", "Kameron", "Kamille", "Kamren", "Kamron", "Kamryn", "Kane", "Kara", "Kareem", "Karelle", "Karen", "Kari", "Kariane", "Karianne", "Karina", "Karine", "Karl", "Karlee", "Karley", "Karli", "Karlie", "Karolann", "Karson", "Kasandra", "Kasey", "Kassandra", "Katarina", "Katelin", "Katelyn", "Katelynn", "Katharina", "Katherine", "Katheryn", "Kathleen", "Kathlyn", "Kathryn", "Kathryne", "Katlyn", "Katlynn", "Katrina", "Katrine", "Kattie", "Kavon", "Kay", "Kaya", "Kaycee", "Kayden", "Kayla", "Kaylah", "Kaylee", "Kayleigh", "Kayley", "Kayli", "Kaylie", "Kaylin", "Keagan", "Keanu", "Keara", "Keaton", "Keegan", "Keeley", "Keely", "Keenan", "Keira", "Keith", "Kellen", "Kelley", "Kelli", "Kellie", "Kelly", "Kelsi", "Kelsie", "Kelton", "Kelvin", "Ken", "Kendall", "Kendra", "Kendrick", "Kenna", "Kennedi", "Kennedy", "Kenneth", "Kennith", "Kenny", "Kenton", "Kenya", "Kenyatta", "Kenyon", "Keon", "Keshaun", "Keshawn", "Keven", "Kevin", "Kevon", "Keyon", "Keyshawn", "Khalid", "Khalil", "Kian", "Kiana", "Kianna", "Kiara", "Kiarra", "Kiel", "Kiera", "Kieran", "Kiley", "Kim", "Kimberly", "King", "Kip", "Kira", "Kirk", "Kirsten", "Kirstin", "Kitty", "Kobe", "Koby", "Kody", "Kolby", "Kole", "Korbin", "Korey", "Kory", "Kraig", "Kris", "Krista", "Kristian", "Kristin", "Kristina", "Kristofer", "Kristoffer", "Kristopher", "Kristy", "Krystal", "Krystel", "Krystina", "Kurt", "Kurtis", "Kyla", "Kyle", "Kylee", "Kyleigh", "Kyler", "Kylie", "Kyra", "Lacey", "Lacy", "Ladarius", "Lafayette", "Laila", "Laisha", "Lamar", "Lambert", "Lamont", "Lance", "Landen", "Lane", "Laney", "Larissa", "Laron", "Larry", "Larue", "Laura", "Laurel", "Lauren", "Laurence", "Lauretta", "Lauriane", "Laurianne", "Laurie", "Laurine", "Laury", "Lauryn", "Lavada", "Lavern", "Laverna", "Laverne", "Lavina", "Lavinia", "Lavon", "Lavonne", "Lawrence", "Lawson", "Layla", "Layne", "Lazaro", "Lea", "Leann", "Leanna", "Leanne", "Leatha", "Leda", "Lee", "Leif", "Leila", "Leilani", "Lela", "Lelah", "Leland", "Lelia", "Lempi", "Lemuel", "Lenna", "Lennie", "Lenny", "Lenora", "Lenore", "Leo", "Leola", "Leon", "Leonard", "Leonardo", "Leone", "Leonel", "Leonie", "Leonor", "Leonora", "Leopold", "Leopoldo", "Leora", "Lera", "Lesley", "Leslie", "Lesly", "Lessie", "Lester", "Leta", "Letha", "Letitia", "Levi", "Lew", "Lewis", "Lexi", "Lexie", "Lexus", "Lia", "Liam", "Liana", "Libbie", "Libby", "Lila", "Lilian", "Liliana", "Liliane", "Lilla", "Lillian", "Lilliana", "Lillie", "Lilly", "Lily", "Lilyan", "Lina", "Lincoln", "Linda", "Lindsay", "Lindsey", "Linnea", "Linnie", "Linwood", "Lionel", "Lisa", "Lisandro", "Lisette", "Litzy", "Liza", "Lizeth", "Lizzie", "Llewellyn", "Lloyd", "Logan", "Lois", "Lola", "Lolita", "Loma", "Lon", "London", "Lonie", "Lonnie", "Lonny", "Lonzo", "Lora", "Loraine", "Loren", "Lorena", "Lorenz", "Lorenza", "Lorenzo", "Lori", "Lorine", "Lorna", "Lottie", "Lou", "Louie", "Louisa", "Lourdes", "Louvenia", "Lowell", "Loy", "Loyal", "Loyce", "Lucas", "Luciano", "Lucie", "Lucienne", "Lucile", "Lucinda", "Lucio", "Lucious", "Lucius", "Lucy", "Ludie", "Ludwig", "Lue", "Luella", "Luigi", "Luis", "Luisa", "Lukas", "Lula", "Lulu", "Luna", "Lupe", "Lura", "Lurline", "Luther", "Luz", "Lyda", "Lydia", "Lyla", "Lynn", "Lyric", "Lysanne", "Mabel", "Mabelle", "Mable", "Mac", "Macey", "Maci", "Macie", "Mack", "Mackenzie", "Macy", "Madaline", "Madalyn", "Maddison", "Madeline", "Madelyn", "Madelynn", "Madge", "Madie", "Madilyn", "Madisen", "Madison", "Madisyn", "Madonna", "Madyson", "Mae", "Maegan", "Maeve", "Mafalda", "Magali", "Magdalen", "Magdalena", "Maggie", "Magnolia", "Magnus", "Maia", "Maida", "Maiya", "Major", "Makayla", "Makenna", "Makenzie", "Malachi", "Malcolm", "Malika", "Malinda", "Mallie", "Mallory", "Malvina", "Mandy", "Manley", "Manuel", "Manuela", "Mara", "Marc", "Marcel", "Marcelina", "Marcelino", "Marcella", "Marcelle", "Marcellus", "Marcelo", "Marcia", "Marco", "Marcos", "Marcus", "Margaret", "Margarete", "Margarett", "Margaretta", "Margarette", "Margarita", "Marge", "Margie", "Margot", "Margret", "Marguerite", "Maria", "Mariah", "Mariam", "Marian", "Mariana", "Mariane", "Marianna", "Marianne", "Mariano", "Maribel", "Marie", "Mariela", "Marielle", "Marietta", "Marilie", "Marilou", "Marilyne", "Marina", "Mario", "Marion", "Marisa", "Marisol", "Maritza", "Marjolaine", "Marjorie", "Marjory", "Mark", "Markus", "Marlee", "Marlen", "Marlene", "Marley", "Marlin", "Marlon", "Marques", "Marquis", "Marquise", "Marshall", "Marta", "Martin", "Martina", "Martine", "Marty", "Marvin", "Mary", "Maryam", "Maryjane", "Maryse", "Mason", "Mateo", "Mathew", "Mathias", "Mathilde", "Matilda", "Matilde", "Matt", "Matteo", "Mattie", "Maud", "Maude", "Maudie", "Maureen", "Maurice", "Mauricio", "Maurine", "Maverick", "Mavis", "Max", "Maxie", "Maxime", "Maximilian", "Maximillia", "Maximillian", "Maximo", "Maximus", "Maxine", "Maxwell", "May", "Maya", "Maybell", "Maybelle", "Maye", "Maymie", "Maynard", "Mayra", "Mazie", "Mckayla", "Mckenna", "Mckenzie", "Meagan", "Meaghan", "Meda", "Megane", "Meggie", "Meghan", "Mekhi", "Melany", "Melba", "Melisa", "Melissa", "Mellie", "Melody", "Melvin", "Melvina", "Melyna", "Melyssa", "Mercedes", "Meredith", "Merl", "Merle", "Merlin", "Merritt", "Mertie", "Mervin", "Meta", "Mia", "Micaela", "Micah", "Michael", "Michaela", "Michale", "Micheal", "Michel", "Michele", "Michelle", "Miguel", "Mikayla", "Mike", "Mikel", "Milan", "Miles", "Milford", "Miller", "Millie", "Milo", "Milton", "Mina", "Minerva", "Minnie", "Miracle", "Mireille", "Mireya", "Misael", "Missouri", "Misty", "Mitchel", "Mitchell", "Mittie", "Modesta", "Modesto", "Mohamed", "Mohammad", "Mohammed", "Moises", "Mollie", "Molly", "Mona", "Monica", "Monique", "Monroe", "Monserrat", "Monserrate", "Montana", "Monte", "Monty", "Morgan", "Moriah", "Morris", "Mortimer", "Morton", "Mose", "Moses", "Moshe", "Mossie", "Mozell", "Mozelle", "Muhammad", "Muriel", "Murl", "Murphy", "Murray", "Mustafa", "Mya", "Myah", "Mylene", "Myles", "Myra", "Myriam", "Myrl", "Myrna", "Myron", "Myrtice", "Myrtie", "Myrtis", "Myrtle", "Nadia", "Nakia", "Name", "Nannie", "Naomi", "Naomie", "Napoleon", "Narciso", "Nash", "Nasir", "Nat", "Natalia", "Natalie", "Natasha", "Nathan", "Nathanael", "Nathanial", "Nathaniel", "Nathen", "Nayeli", "Neal", "Ned", "Nedra", "Neha", "Neil", "Nelda", "Nella", "Nelle", "Nellie", "Nels", "Nelson", "Neoma", "Nestor", "Nettie", "Neva", "Newell", "Newton", "Nia", "Nicholas", "Nicholaus", "Nichole", "Nick", "Nicklaus", "Nickolas", "Nico", "Nicola", "Nicolas", "Nicole", "Nicolette", "Nigel", "Nikita", "Nikki", "Nikko", "Niko", "Nikolas", "Nils", "Nina", "Noah", "Noble", "Noe", "Noel", "Noelia", "Noemi", "Noemie", "Noemy", "Nola", "Nolan", "Nona", "Nora", "Norbert", "Norberto", "Norene", "Norma", "Norris", "Norval", "Norwood", "Nova", "Novella", "Nya", "Nyah", "Nyasia", "Obie", "Oceane", "Ocie", "Octavia", "Oda", "Odell", "Odessa", "Odie", "Ofelia", "Okey", "Ola", "Olaf", "Ole", "Olen", "Oleta", "Olga", "Olin", "Oliver", "Ollie", "Oma", "Omari", "Omer", "Ona", "Onie", "Opal", "Ophelia", "Ora", "Oral", "Oran", "Oren", "Orie", "Orin", "Orion", "Orland", "Orlando", "Orlo", "Orpha", "Orrin", "Orval", "Orville", "Osbaldo", "Osborne", "Oscar", "Osvaldo", "Oswald", "Oswaldo", "Otha", "Otho", "Otilia", "Otis", "Ottilie", "Ottis", "Otto", "Ova", "Owen", "Ozella", "Pablo", "Paige", "Palma", "Pamela", "Pansy", "Paolo", "Paris", "Parker", "Pascale", "Pasquale", "Pat", "Patience", "Patricia", "Patrick", "Patsy", "Pattie", "Paul", "Paula", "Pauline", "Paxton", "Payton", "Pearl", "Pearlie", "Pearline", "Pedro", "Peggie", "Penelope", "Percival", "Percy", "Perry", "Pete", "Peter", "Petra", "Peyton", "Philip", "Phoebe", "Phyllis", "Pierce", "Pierre", "Pietro", "Pink", "Pinkie", "Piper", "Polly", "Porter", "Precious", "Presley", "Preston", "Price", "Prince", "Princess", "Priscilla", "Providenci", "Prudence", "Queen", "Queenie", "Quentin", "Quincy", "Quinn", "Quinten", "Quinton", "Rachael", "Rachel", "Rachelle", "Rae", "Raegan", "Rafael", "Rafaela", "Raheem", "Rahsaan", "Rahul", "Raina", "Raleigh", "Ralph", "Ramiro", "Ramon", "Ramona", "Randal", "Randall", "Randi", "Randy", "Ransom", "Raoul", "Raphael", "Raphaelle", "Raquel", "Rashad", "Rashawn", "Rasheed", "Raul", "Raven", "Ray", "Raymond", "Raymundo", "Reagan", "Reanna", "Reba", "Rebeca", "Rebecca", "Rebeka", "Rebekah", "Reece", "Reed", "Reese", "Regan", "Reggie", "Reginald", "Reid", "Reilly", "Reina", "Reinhold", "Remington", "Rene", "Renee", "Ressie", "Reta", "Retha", "Retta", "Reuben", "Reva", "Rex", "Rey", "Reyes", "Reymundo", "Reyna", "Reynold", "Rhea", "Rhett", "Rhianna", "Rhiannon", "Rhoda", "Ricardo", "Richard", "Richie", "Richmond", "Rick", "Rickey", "Rickie", "Ricky", "Rico", "Rigoberto", "Riley", "Rita", "River", "Robb", "Robbie", "Robert", "Roberta", "Roberto", "Robin", "Robyn", "Rocio", "Rocky", "Rod", "Roderick", "Rodger", "Rodolfo", "Rodrick", "Rodrigo", "Roel", "Rogelio", "Roger", "Rogers", "Rolando", "Rollin", "Roma", "Romaine", "Roman", "Ron", "Ronaldo", "Ronny", "Roosevelt", "Rory", "Rosa", "Rosalee", "Rosalia", "Rosalind", "Rosalinda", "Rosalyn", "Rosamond", "Rosanna", "Rosario", "Roscoe", "Rose", "Rosella", "Roselyn", "Rosemarie", "Rosemary", "Rosendo", "Rosetta", "Rosie", "Rosina", "Roslyn", "Ross", "Rossie", "Rowan", "Rowena", "Rowland", "Roxane", "Roxanne", "Roy", "Royal", "Royce", "Rozella", "Ruben", "Rubie", "Ruby", "Rubye", "Rudolph", "Rudy", "Rupert", "Russ", "Russel", "Russell", "Rusty", "Ruth", "Ruthe", "Ruthie", "Ryan", "Ryann", "Ryder", "Rylan", "Rylee", "Ryleigh", "Ryley", "Sabina", "Sabrina", "Sabryna", "Sadie", "Sadye", "Sage", "Saige", "Sallie", "Sally", "Salma", "Salvador", "Salvatore", "Sam", "Samanta", "Samantha", "Samara", "Samir", "Sammie", "Sammy", "Samson", "Sandra", "Sandrine", "Sandy", "Sanford", "Santa", "Santiago", "Santina", "Santino", "Santos", "Sarah", "Sarai", "Sarina", "Sasha", "Saul", "Savanah", "Savanna", "Savannah", "Savion", "Scarlett", "Schuyler", "Scot", "Scottie", "Scotty", "Seamus", "Sean", "Sebastian", "Sedrick", "Selena", "Selina", "Selmer", "Serena", "Serenity", "Seth", "Shad", "Shaina", "Shakira", "Shana", "Shane", "Shanel", "Shanelle", "Shania", "Shanie", "Shaniya", "Shanna", "Shannon", "Shanny", "Shanon", "Shany", "Sharon", "Shaun", "Shawn", "Shawna", "Shaylee", "Shayna", "Shayne", "Shea", "Sheila", "Sheldon", "Shemar", "Sheridan", "Sherman", "Sherwood", "Shirley", "Shyann", "Shyanne", "Sibyl", "Sid", "Sidney", "Sienna", "Sierra", "Sigmund", "Sigrid", "Sigurd", "Silas", "Sim", "Simeon", "Simone", "Sincere", "Sister", "Skye", "Skyla", "Skylar", "Sofia", "Soledad", "Solon", "Sonia", "Sonny", "Sonya", "Sophia", "Sophie", "Spencer", "Stacey", "Stacy", "Stan", "Stanford", "Stanley", "Stanton", "Stefan", "Stefanie", "Stella", "Stephan", "Stephania", "Stephanie", "Stephany", "Stephen", "Stephon", "Sterling", "Steve", "Stevie", "Stewart", "Stone", "Stuart", "Summer", "Sunny", "Susan", "Susana", "Susanna", "Susie", "Suzanne", "Sven", "Syble", "Sydnee", "Sydney", "Sydni", "Sydnie", "Sylvan", "Sylvester", "Sylvia", "Tabitha", "Tad", "Talia", "Talon", "Tamara", "Tamia", "Tania", "Tanner", "Tanya", "Tara", "Taryn", "Tate", "Tatum", "Tatyana", "Taurean", "Tavares", "Taya", "Taylor", "Teagan", "Ted", "Telly", "Terence", "Teresa", "Terrance", "Terrell", "Terrence", "Terrill", "Terry", "Tess", "Tessie", "Tevin", "Thad", "Thaddeus", "Thalia", "Thea", "Thelma", "Theo", "Theodora", "Theodore", "Theresa", "Therese", "Theresia", "Theron", "Thomas", "Thora", "Thurman", "Tia", "Tiana", "Tianna", "Tiara", "Tierra", "Tiffany", "Tillman", "Timmothy", "Timmy", "Timothy", "Tina", "Tito", "Titus", "Tobin", "Toby", "Tod", "Tom", "Tomas", "Tomasa", "Tommie", "Toney", "Toni", "Tony", "Torey", "Torrance", "Torrey", "Toy", "Trace", "Tracey", "Tracy", "Travis", "Travon", "Tre", "Tremaine", "Tremayne", "Trent", "Trenton", "Tressa", "Tressie", "Treva", "Trever", "Trevion", "Trevor", "Trey", "Trinity", "Trisha", "Tristian", "Tristin", "Triston", "Troy", "Trudie", "Trycia", "Trystan", "Turner", "Twila", "Tyler", "Tyra", "Tyree", "Tyreek", "Tyrel", "Tyrell", "Tyrese", "Tyrique", "Tyshawn", "Tyson", "Ubaldo", "Ulices", "Ulises", "Una", "Unique", "Urban", "Uriah", "Uriel", "Ursula", "Vada", "Valentin", "Valentina", "Valentine", "Valerie", "Vallie", "Van", "Vance", "Vanessa", "Vaughn", "Veda", "Velda", "Vella", "Velma", "Velva", "Vena", "Verda", "Verdie", "Vergie", "Verla", "Verlie", "Vern", "Verna", "Verner", "Vernice", "Vernie", "Vernon", "Verona", "Veronica", "Vesta", "Vicenta", "Vicente", "Vickie", "Vicky", "Victor", "Victoria", "Vida", "Vidal", "Vilma", "Vince", "Vincent", "Vincenza", "Vincenzo", "Vinnie", "Viola", "Violet", "Violette", "Virgie", "Virgil", "Virginia", "Virginie", "Vita", "Vito", "Viva", "Vivian", "Viviane", "Vivianne", "Vivien", "Vivienne", "Vladimir", "Wade", "Waino", "Waldo", "Walker", "Wallace", "Walter", "Walton", "Wanda", "Ward", "Warren", "Watson", "Wava", "Waylon", "Wayne", "Webster", "Weldon", "Wellington", "Wendell", "Wendy", "Werner", "Westley", "Weston", "Whitney", "Wilber", "Wilbert", "Wilburn", "Wiley", "Wilford", "Wilfred", "Wilfredo", "Wilfrid", "Wilhelm", "Wilhelmine", "Will", "Willa", "Willard", "William", "Willie", "Willis", "Willow", "Willy", "Wilma", "Wilmer", "Wilson", "Wilton", "Winfield", "Winifred", "Winnifred", "Winona", "Winston", "Woodrow", "Wyatt", "Wyman", "Xander", "Xavier", "Xzavier", "Yadira", "Yasmeen", "Yasmin", "Yasmine", "Yazmin", "Yesenia", "Yessenia", "Yolanda", "Yoshiko", "Yvette", "Yvonne", "Zachariah", "Zachary", "Zachery", "Zack", "Zackary", "Zackery", "Zakary", "Zander", "Zane", "Zaria", "Zechariah", "Zelda", "Zella", "Zelma", "Zena", "Zetta", "Zion", "Zita", "Zoe", "Zoey", "Zoie", "Zoila", "Zola", "Zora", "Zula"}, + "last": {"Abbott", "Abernathy", "Abshire", "Adams", "Altenwerth", "Anderson", "Ankunding", "Armstrong", "Auer", "Aufderhar", "Bahringer", "Bailey", "Balistreri", "Barrows", "Bartell", "Bartoletti", "Barton", "Bashirian", "Batz", "Bauch", "Baumbach", "Bayer", "Beahan", "Beatty", "Bechtelar", "Becker", "Bednar", "Beer", "Beier", "Berge", "Bergnaum", "Bergstrom", "Bernhard", "Bernier", "Bins", "Blanda", "Blick", "Block", "Bode", "Boehm", "Bogan", "Bogisich", "Borer", "Bosco", "Botsford", "Boyer", "Boyle", "Bradtke", "Brakus", "Braun", "Breitenberg", "Brekke", "Brown", "Bruen", "Buckridge", "Carroll", "Carter", "Cartwright", "Casper", "Cassin", "Champlin", "Christiansen", "Cole", "Collier", "Collins", "Conn", "Connelly", "Conroy", "Considine", "Corkery", "Cormier", "Corwin", "Cremin", "Crist", "Crona", "Cronin", "Crooks", "Cruickshank", "Cummerata", "Cummings", "Dach", "Damore", "Daniel", "Dare", "Daugherty", "Davis", "Deckow", "Denesik", "Dibbert", "Dickens", "Dicki", "Dickinson", "Dietrich", "Donnelly", "Dooley", "Douglas", "Doyle", "DuBuque", "Durgan", "Ebert", "Effertz", "Eichmann", "Emard", "Emmerich", "Erdman", "Ernser", "Fadel", "Fahey", "Farrell", "Fay", "Feeney", "Feest", "Feil", "Ferry", "Fisher", "Flatley", "Frami", "Franecki", "Friesen", "Fritsch", "Funk", "Gaylord", "Gerhold", "Gerlach", "Gibson", "Gislason", "Gleason", "Gleichner", "Glover", "Goldner", "Goodwin", "Gorczany", "Gottlieb", "Goyette", "Grady", "Graham", "Grant", "Green", "Greenfelder", "Greenholt", "Grimes", "Gulgowski", "Gusikowski", "Gutkowski", "Gutmann", "Haag", "Hackett", "Hagenes", "Hahn", "Haley", "Halvorson", "Hamill", "Hammes", "Hand", "Hane", "Hansen", "Harber", "Harris", "Hartmann", "Harvey", "Hauck", "Hayes", "Heaney", "Heathcote", "Hegmann", "Heidenreich", "Heller", "Herman", "Hermann", "Hermiston", "Herzog", "Hessel", "Hettinger", "Hickle", "Hilll", "Hills", "Hilpert", "Hintz", "Hirthe", "Hodkiewicz", "Hoeger", "Homenick", "Hoppe", "Howe", "Howell", "Hudson", "Huel", "Huels", "Hyatt", "Jacobi", "Jacobs", "Jacobson", "Jakubowski", "Jaskolski", "Jast", "Jenkins", "Jerde", "Jewess", "Johns", "Johnson", "Johnston", "Jones", "Kassulke", "Kautzer", "Keebler", "Keeling", "Kemmer", "Kerluke", "Kertzmann", "Kessler", "Kiehn", "Kihn", "Kilback", "King", "Kirlin", "Klein", "Kling", "Klocko", "Koch", "Koelpin", "Koepp", "Kohler", "Konopelski", "Koss", "Kovacek", "Kozey", "Krajcik", "Kreiger", "Kris", "Kshlerin", "Kub", "Kuhic", "Kuhlman", "Kuhn", "Kulas", "Kunde", "Kunze", "Kuphal", "Kutch", "Kuvalis", "Labadie", "Lakin", "Lang", "Langosh", "Langworth", "Larkin", "Larson", "Leannon", "Lebsack", "Ledner", "Leffler", "Legros", "Lehner", "Lemke", "Lesch", "Leuschke", "Lind", "Lindgren", "Littel", "Little", "Lockman", "Lowe", "Lubowitz", "Lueilwitz", "Luettgen", "Lynch", "Macejkovic", "Maggio", "Mann", "Mante", "Marks", "Marquardt", "Marvin", "Mayer", "Mayert", "McClure", "McCullough", "McDermott", "McGlynn", "McKenzie", "McLaughlin", "Medhurst", "Mertz", "Metz", "Miller", "Mills", "Mitchell", "Moen", "Mohr", "Monahan", "Moore", "Morar", "Morissette", "Mosciski", "Mraz", "Mueller", "Muller", "Murazik", "Murphy", "Murray", "Nader", "Nicolas", "Nienow", "Nikolaus", "Nitzsche", "Nolan", "Oberbrunner", "Okuneva", "Olson", "Ondricka", "OReilly", "Orn", "Ortiz", "Osinski", "Pacocha", "Padberg", "Pagac", "Parisian", "Parker", "Paucek", "Pfannerstill", "Pfeffer", "Pollich", "Pouros", "Powlowski", "Predovic", "Price", "Prohaska", "Prosacco", "Purdy", "Quigley", "Quitzon", "Rath", "Ratke", "Rau", "Raynor", "Reichel", "Reichert", "Reilly", "Reinger", "Rempel", "Renner", "Reynolds", "Rice", "Rippin", "Ritchie", "Robel", "Roberts", "Rodriguez", "Rogahn", "Rohan", "Rolfson", "Romaguera", "Roob", "Rosenbaum", "Rowe", "Ruecker", "Runolfsdottir", "Runolfsson", "Runte", "Russel", "Rutherford", "Ryan", "Sanford", "Satterfield", "Sauer", "Sawayn", "Schaden", "Schaefer", "Schamberger", "Schiller", "Schimmel", "Schinner", "Schmeler", "Schmidt", "Schmitt", "Schneider", "Schoen", "Schowalter", "Schroeder", "Schulist", "Schultz", "Schumm", "Schuppe", "Schuster", "Senger", "Shanahan", "Shields", "Simonis", "Sipes", "Skiles", "Smith", "Smitham", "Spencer", "Spinka", "Sporer", "Stamm", "Stanton", "Stark", "Stehr", "Steuber", "Stiedemann", "Stokes", "Stoltenberg", "Stracke", "Streich", "Stroman", "Strosin", "Swaniawski", "Swift", "Terry", "Thiel", "Thompson", "Tillman", "Torp", "Torphy", "Towne", "Toy", "Trantow", "Tremblay", "Treutel", "Tromp", "Turcotte", "Turner", "Ullrich", "Upton", "Vandervort", "Veum", "Volkman", "Von", "VonRueden", "Waelchi", "Walker", "Walsh", "Walter", "Ward", "Waters", "Watsica", "Weber", "Wehner", "Weimann", "Weissnat", "Welch", "West", "White", "Wiegand", "Wilderman", "Wilkinson", "Will", "Williamson", "Willms", "Windler", "Wintheiser", "Wisoky", "Wisozk", "Witting", "Wiza", "Wolf", "Wolff", "Wuckert", "Wunsch", "Wyman", "Yost", "Yundt", "Zboncak", "Zemlak", "Ziemann", "Zieme", "Zulauf"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/status_code.go b/vendor/github.com/brianvoe/gofakeit/data/status_code.go new file mode 100644 index 000000000000..7d78fd995026 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/status_code.go @@ -0,0 +1,7 @@ +package data + +// StatusCodes consists of commonly used HTTP status codes +var StatusCodes = map[string][]int{ + "simple": {200, 301, 302, 400, 404, 500}, + "general": {100, 200, 201, 203, 204, 205, 301, 302, 304, 400, 401, 403, 404, 405, 406, 416, 500, 501, 502, 503, 504}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/data/vehicle.go b/vendor/github.com/brianvoe/gofakeit/data/vehicle.go new file mode 100644 index 000000000000..3b96728bccad --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/data/vehicle.go @@ -0,0 +1,10 @@ +package data + +// Vehicle Beer consists of various beer information +var Vehicle = map[string][]string{ + "vehicle_type": {"Passenger car mini", "Passenger car light", "Passenger car compact", "Passenger car medium", "Passenger car heavy", "Sport utility vehicle", "Pickup truck", "Van"}, + "fuel_type": {"Gasoline", "Methanol", "Ethanol", "Diesel", "LPG", "CNG", "Electric"}, + "transmission_type": {"Manual", "Automatic"}, + "maker": {"Alfa Romeo", "Aston Martin", "Audi", "Bentley", "Benz", "BMW", "Bugatti", "Cadillac", "Chevrolet", "Chrysler", "Citroen", "Corvette", "DAF", "Dacia", "Daewoo", "Daihatsu", "Datsun", "De Lorean", "Dino", "Dodge", "Farboud", "Ferrari", "Fiat", "Ford", "Honda", "Hummer", "Hyundai", "Jaguar", "Jeep", "KIA", "Koenigsegg", "Lada", "Lamborghini", "Lancia", "Land Rover", "Lexus", "Ligier", "Lincoln", "Lotus", "Martini", "Maserati", "Maybach", "Mazda", "McLaren", "Mercedes", "Mercedes-Benz", "Mini", "Mitsubishi", "Nissan", "Noble", "Opel", "Peugeot", "Pontiac", "Porsche", "Renault", "Rolls-Royce", "Rover", "Saab", "Seat", "Skoda", "Smart", "Spyker", "Subaru", "Suzuki", "Toyota", "Tesla", "Vauxhall", "Volkswagen", "Volvo"}, + "model": {"Db9 Coupe", "Db9 Coupe Manual", "Db9 Volante", "V12 Vanquish S", "V8 Vantage", "A3", "A4", "A4 Avant Quattro", "A4 Cabriolet", "A4 Cabriolet Quattro", "A4 Quattro", "A6", "A6 Avant Quattro", "A6 Quattro", "A8 L", "Gti", "Passat", "S4", "S4 Avant", "S4 Cabriolet", "Tt Coupe", "Tt Roadster", "Bentley Arnage", "Continental Flying Spur", "Continental Gt", " 325ci Convertible", " 325i", " 325xi", " 325xi Sport Wagon", " 330ci Convertible", " 330i", " 330xi", " 525i", " 525xi", " 530i", " 530xi", " 530xi Sport Wagon", " 550i", " 650ci", " 650ci Convertible", " 750li", " 760li", " M3", " M3 Convertible", " M5", " M6", " Mini Cooper", " Mini Cooper Convertible", " Mini Cooper S", " Mini Cooper S Convertible", " X3", " X5", " X5 4.8is", " Z4 3.0 Si Coupe", " Z4 3.0i", " Z4 3.0si", " Z4 M Roadster", "Veyron", "300c/srt-8", "Caravan 2wd", "Charger", "Commander 4wd", "Crossfire Roadster", "Dakota Pickup 2wd", "Dakota Pickup 4wd", "Durango 2wd", "Durango 4wd", "Grand Cherokee 2wd", "Grand Cherokee 4wd", "Liberty/cherokee 2wd", "Liberty/cherokee 4wd", "Pacifica 2wd", "Pacifica Awd", "Pt Cruiser", "Ram 1500 Pickup 2wd", "Ram 1500 Pickup 4wd", "Sebring 4-dr", "Stratus 4-dr", "Town & Country 2wd", "Viper Convertible", "Wrangler/tj 4wd", "F430", "Ferrari 612 Scaglietti", "Ferrari F141", "B4000 4wd", "Crown Victoria Police", "E150 Club Wagon", "E150 Econoline 2wd", "Escape 4wd", "Escape Fwd", "Escape Hybrid 4wd", "Escape Hybrid Fwd", "Expedition 2wd", "Explorer 2wd", "Explorer 4wd", "F150 Ffv 2wd", "F150 Ffv 4wd", "F150 Pickup 2wd", "F150 Pickup 4wd", "Five Hundred Awd", "Focus Fwd", "Focus Station Wag", "Freestar Wagon Fwd", "Freestyle Awd", "Freestyle Fwd", "Grand Marquis", "Gt 2wd", "Ls", "Mark Lt", "Milan", "Monterey Wagon Fwd", "Mountaineer 4wd", "Mustang", "Navigator 2wd", "Ranger Pickup 2wd", "Ranger Pickup 4wd", "Taurus", "Taurus Ethanol Ffv", "Thunderbird", "Town Car", "Zephyr", "B9 Tribeca Awd", "Baja Awd", "Forester Awd", "Impreza Awd", "Impreza Wgn/outback Spt Awd", "Legacy Awd", "Legacy Wagon Awd", "Outback Awd", "Outback Wagon Awd", "9-3 Convertible", "9-3 Sport Sedan", "9-5 Sedan", "C15 Silverado Hybrid 2wd", "C1500 Silverado 2wd", "C1500 Suburban 2wd", "C1500 Tahoe 2wd", "C1500 Yukon 2wd", "Cobalt", "Colorado 2wd", "Colorado 4wd", "Colorado Cab Chassis Inc 2wd", "Colorado Crew Cab 2wd", "Colorado Crew Cab 4wd", "Corvette", "Cts", "Dts", "Envoy 2wd", "Envoy Xl 4wd", "Equinox Awd", "Equinox Fwd", "Escalade 2wd", "Escalade Esv Awd", "G15/25chev Van 2wd Conv", "G1500/2500 Chevy Express 2wd", "G1500/2500 Chevy Van 2wd", "G6", "G6 Gt/gtp Convertible", "Grand Prix", "Gto", "H3 4wd", "Hhr Fwd", "I-280 2wd Ext Cab", "Impala", "K15 Silverado Hybrid 4wd", "K1500 Avalanche 4wd", "K1500 Silverado 4wd", "K1500 Tahoe 4wd", "Lacrosse/allure", "Limousine", "Malibu", "Montana Sv6 Awd", "Monte Carlo", "Rendezvous Awd", "Rendezvous Fwd", "Solstice", "Srx 2wd", "Srx Awd", "Ssr Pickup 2wd", "Sts", "Sts Awd", "Terraza Fwd", "Trailblazer 2wd", "Trailblazer 4wd", "Trailblazer Awd", "Trailblazer Ext 4wd", "Uplander Fwd", "Vue Awd", "Vue Fwd", "Xlr", "Aveo", "Forenza", "Forenza Wagon", "Verona", "Accord", "Accord Hybrid", "Civic", "Civic Hybrid", "Cr-v 4wd", "Element 2wd", "Element 4wd", "Insight", "Mdx 4wd", "Odyssey 2wd", "Pilot 2wd", "Pilot 4wd", "Ridgeline 4wd", "Rl", "Rsx", "S2000", "Tl", "Tsx", "Accent", "Azera", "Elantra", "Santafe 2wd", "Santafe 4wd", "Sonata", "Tiburon", "Tucson 2wd", "Tucson 4wd", "S-type 3.0 Litre", "S-type 4.2 Litre", "S-type R", "Vdp Lwb", "Xj8", "Xk8 Convertible", "Xkr Convertible", "X-type", "X-type Sport Brake", "Amanti", "Optima", "Optima(ms)", "Rio", "Sedona", "Sorento 2wd", "Sorento 4wd", "Spectra(ld)", "Sportage 2wd", "Sportage 4wd", "L-140/715 Gallardo", "L-147/148 Murcielago", "Lr3", "Range Rover", "Range Rover Sport", "Elise/exige", "Coupe Cambiocorsa/gt/g-sport", "Quattroporte", "Mazda 3", "Mazda 5", "Mazda 6", "Mazda 6 Sport Wagon", "Mazda Rx-8", "Mpv", "Mx-5", "C230", "C280", "C280 4matic", "C350", "C350 4matic", "C55 Amg", "Cl65 Amg", "Clk350", "Clk350 (cabriolet)", "Clk55 Amg (cabriolet)", "Cls500", "Cls55 Amg", "E320 Cdi", "E350", "E350 (wagon)", "E350 4matic", "E350 4matic (wagon)", "E500", "E55 Amg", "E55 Amg (wagon)", "Maybach 57s", "Maybach 62", "Ml350", "Ml500", "R350", "R500", "S350", "S430", "Sl500", "Sl600", "Sl65 Amg", "Slk280", "Slk350", "Slr", "Eclipse", "Endeavor 2wd", "Endeavor 4wd", "Galant", "Lancer", "Lancer Evolution", "Lancer Sportback", "Montero", "Outlander 2wd", "Outlander 4wd", "Vibe", "350z", "350z Roadster", "Altima", "Armada 2wd", "Armada 4wd", "Frontier 2wd", "Frontier V6-2wd", "Frontier V6-4wd", "Fx35 Awd", "Fx35 Rwd", "Fx45 Awd", "G35", "M35", "M35x", "M45", "Maxima", "Murano Awd", "Murano Fwd", "Pathfinder 2wd", "Pathfinder 4wd", "Q45", "Q45 Sport", "Quest", "Qx56 4wd", "Sentra", "Titan 2wd", "Titan 4wd", "Xterra 2wd", "Xterra 4wd", "Boxster", "Boxster S", "Carrera 2 Coupe", "Cayenne", "Cayenne S", "Cayenne Turbo", "Cayman S", "Phantom", "F150 Supercrew 4wd", "C8 Spyder", "Aerio", "Aerio Sx", "Aerio Sx Awd", "Grand Vitara Xl-7", "Grand Vitara Xl-7 4wd", "Grand Vitara Xv6", "Grand Vitara Xv6 Awd", "4runner 2wd", "4runner 4wd", "Avalon", "Camry", "Camry Solara", "Camry Solara Convertible", "Corolla", "Corolla Matrix", "Es 330", "Gs 300 4wd", "Gs 300/gs 430", "Gx 470", "Highlander 2wd", "Highlander 4wd", "Highlander Hybrid 2wd", "Highlander Hybrid 4wd", "Is 250", "Is 250 Awd", "Is 350", "Ls 430", "Lx 470", "Prius", "Rav4 2wd", "Rav4 4wd", "Rx 330 2wd", "Rx 330 4wd", "Rx 400h 4wd", "Sc 430", "Scion Tc", "Scion Xa", "Scion Xb", "Sequoia 2wd", "Sequoia 4wd", "Sienna 2wd", "Sienna 4wd", "Toyota Tacoma 2wd", "Toyota Tacoma 4wd", "Toyota Tundra 2wd", "Toyota Tundra 4wd", "Yaris", "A3 Quattro", "Golf", "Jetta", "New Beetle", "New Beetle Convertible", "Passat Wagon 4motion", "Phaeton", "Rabbit", "Touareg", "Tt Coupe Quattro", "Tt Roadster Quattro", "C70 Convertible", "S40 Awd", "S40 Fwd", "S60 Awd", "S60 Fwd", "S60 R Awd", "S80 Fwd", "V50 Awd", "V70 Fwd", "V70 R Awd", "Xc 70 Awd", "Xc 90 Awd", "Xc 90 Fwd"}, +} diff --git a/vendor/github.com/brianvoe/gofakeit/datetime.go b/vendor/github.com/brianvoe/gofakeit/datetime.go new file mode 100644 index 000000000000..8c064473d3d8 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/datetime.go @@ -0,0 +1,77 @@ +package gofakeit + +import ( + "strconv" + "time" +) + +// Date will generate a random time.Time struct +func Date() time.Time { + return time.Date(Year(), time.Month(Number(0, 12)), Day(), Hour(), Minute(), Second(), NanoSecond(), time.UTC) +} + +// DateRange will generate a random time.Time struct between a start and end date +func DateRange(start, end time.Time) time.Time { + return time.Unix(0, int64(Number(int(start.UnixNano()), int(end.UnixNano())))).UTC() +} + +// Month will generate a random month string +func Month() string { + return time.Month(Number(1, 12)).String() +} + +// Day will generate a random day between 1 - 31 +func Day() int { + return Number(1, 31) +} + +// WeekDay will generate a random weekday string (Monday-Sunday) +func WeekDay() string { + return time.Weekday(Number(0, 6)).String() +} + +// Year will generate a random year between 1900 - current year +func Year() int { + return Number(1900, time.Now().Year()) +} + +// Hour will generate a random hour - in military time +func Hour() int { + return Number(0, 23) +} + +// Minute will generate a random minute +func Minute() int { + return Number(0, 59) +} + +// Second will generate a random second +func Second() int { + return Number(0, 59) +} + +// NanoSecond will generate a random nano second +func NanoSecond() int { + return Number(0, 999999999) +} + +// TimeZone will select a random timezone string +func TimeZone() string { + return getRandValue([]string{"timezone", "text"}) +} + +// TimeZoneFull will select a random full timezone string +func TimeZoneFull() string { + return getRandValue([]string{"timezone", "full"}) +} + +// TimeZoneAbv will select a random timezone abbreviation string +func TimeZoneAbv() string { + return getRandValue([]string{"timezone", "abr"}) +} + +// TimeZoneOffset will select a random timezone offset +func TimeZoneOffset() float32 { + value, _ := strconv.ParseFloat(getRandValue([]string{"timezone", "offset"}), 32) + return float32(value) +} diff --git a/vendor/github.com/brianvoe/gofakeit/doc.go b/vendor/github.com/brianvoe/gofakeit/doc.go new file mode 100644 index 000000000000..c53335e634f1 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/doc.go @@ -0,0 +1,10 @@ +/* +Package gofakeit is a random data generator written in go + +Every function has an example and a benchmark + +See the full list here https://godoc.org/github.com/brianvoe/gofakeit + +80+ Functions!!! +*/ +package gofakeit diff --git a/vendor/github.com/brianvoe/gofakeit/faker.go b/vendor/github.com/brianvoe/gofakeit/faker.go new file mode 100644 index 000000000000..38062d5cdf91 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/faker.go @@ -0,0 +1,15 @@ +package gofakeit + +import ( + "math/rand" + "time" +) + +// Seed random. Setting seed to 0 will use time.Now().UnixNano() +func Seed(seed int64) { + if seed == 0 { + rand.Seed(time.Now().UTC().UnixNano()) + } else { + rand.Seed(seed) + } +} diff --git a/vendor/github.com/brianvoe/gofakeit/file.go b/vendor/github.com/brianvoe/gofakeit/file.go new file mode 100644 index 000000000000..6c1e8d56cba1 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/file.go @@ -0,0 +1,11 @@ +package gofakeit + +// MimeType will generate a random mime file type +func MimeType() string { + return getRandValue([]string{"file", "mime_type"}) +} + +// Extension will generate a random file extension +func Extension() string { + return getRandValue([]string{"file", "extension"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/generate.go b/vendor/github.com/brianvoe/gofakeit/generate.go new file mode 100644 index 000000000000..284eef8bb108 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/generate.go @@ -0,0 +1,41 @@ +package gofakeit + +import ( + "strings" +) + +// Generate fake information from given string. String should contain {category.subcategory} +// +// Ex: {person.first} - random firstname +// +// Ex: {person.first}###{person.last}@{person.last}.{internet.domain_suffix} - billy834smith@smith.com +// +// Ex: ### - 481 - random numbers +// +// Ex: ??? - fda - random letters +// +// For a complete list possible categories use the Categories() function. +func Generate(dataVal string) string { + // Identify items between brackets: {person.first} + for strings.Count(dataVal, "{") > 0 && strings.Count(dataVal, "}") > 0 { + catValue := "" + startIndex := strings.Index(dataVal, "{") + endIndex := strings.Index(dataVal, "}") + replace := dataVal[(startIndex + 1):endIndex] + categories := strings.Split(replace, ".") + + if len(categories) >= 2 && dataCheck([]string{categories[0], categories[1]}) { + catValue = getRandValue([]string{categories[0], categories[1]}) + } + + dataVal = strings.Replace(dataVal, "{"+replace+"}", catValue, 1) + } + + // Replace # with numbers + dataVal = replaceWithNumbers(dataVal) + + // Replace ? with letters + dataVal = replaceWithLetters(dataVal) + + return dataVal +} diff --git a/vendor/github.com/brianvoe/gofakeit/hacker.go b/vendor/github.com/brianvoe/gofakeit/hacker.go new file mode 100644 index 000000000000..0ac73b7109f3 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/hacker.go @@ -0,0 +1,35 @@ +package gofakeit + +import "strings" + +// HackerPhrase will return a random hacker sentence +func HackerPhrase() string { + words := strings.Split(Generate(getRandValue([]string{"hacker", "phrase"})), " ") + words[0] = strings.Title(words[0]) + return strings.Join(words, " ") +} + +// HackerAbbreviation will return a random hacker abbreviation +func HackerAbbreviation() string { + return getRandValue([]string{"hacker", "abbreviation"}) +} + +// HackerAdjective will return a random hacker adjective +func HackerAdjective() string { + return getRandValue([]string{"hacker", "adjective"}) +} + +// HackerNoun will return a random hacker noun +func HackerNoun() string { + return getRandValue([]string{"hacker", "noun"}) +} + +// HackerVerb will return a random hacker verb +func HackerVerb() string { + return getRandValue([]string{"hacker", "verb"}) +} + +// HackerIngverb will return a random hacker ingverb +func HackerIngverb() string { + return getRandValue([]string{"hacker", "ingverb"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/hipster.go b/vendor/github.com/brianvoe/gofakeit/hipster.go new file mode 100644 index 000000000000..3166a9966a13 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/hipster.go @@ -0,0 +1,20 @@ +package gofakeit + +// HipsterWord will return a single hipster word +func HipsterWord() string { + return getRandValue([]string{"hipster", "word"}) +} + +// HipsterSentence will generate a random sentence +func HipsterSentence(wordCount int) string { + return sentence(wordCount, HipsterWord) +} + +// HipsterParagraph will generate a random paragraphGenerator +// Set Paragraph Count +// Set Sentence Count +// Set Word Count +// Set Paragraph Separator +func HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string { + return paragraphGenerator(paragrapOptions{paragraphCount, sentenceCount, wordCount, separator}, HipsterSentence) +} diff --git a/vendor/github.com/brianvoe/gofakeit/image.go b/vendor/github.com/brianvoe/gofakeit/image.go new file mode 100644 index 000000000000..de5a2e6d916c --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/image.go @@ -0,0 +1,8 @@ +package gofakeit + +import "strconv" + +// ImageURL will generate a random Image Based Upon Height And Width. https://picsum.photos/ +func ImageURL(width int, height int) string { + return "https://picsum.photos/" + strconv.Itoa(width) + "/" + strconv.Itoa(height) +} diff --git a/vendor/github.com/brianvoe/gofakeit/internet.go b/vendor/github.com/brianvoe/gofakeit/internet.go new file mode 100644 index 000000000000..69dd700e5231 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/internet.go @@ -0,0 +1,55 @@ +package gofakeit + +import ( + "fmt" + "math/rand" + "strings" +) + +// DomainName will generate a random url domain name +func DomainName() string { + return strings.ToLower(JobDescriptor()+BS()) + "." + DomainSuffix() +} + +// DomainSuffix will generate a random domain suffix +func DomainSuffix() string { + return getRandValue([]string{"internet", "domain_suffix"}) +} + +// URL will generate a random url string +func URL() string { + url := "http" + RandString([]string{"s", ""}) + "://www." + url += DomainName() + + // Slugs + num := Number(1, 4) + slug := make([]string, num) + for i := 0; i < num; i++ { + slug[i] = BS() + } + url += "/" + strings.ToLower(strings.Join(slug, "/")) + + return url +} + +// HTTPMethod will generate a random http method +func HTTPMethod() string { + return getRandValue([]string{"internet", "http_method"}) +} + +// IPv4Address will generate a random version 4 ip address +func IPv4Address() string { + num := func() int { return 2 + rand.Intn(254) } + return fmt.Sprintf("%d.%d.%d.%d", num(), num(), num(), num()) +} + +// IPv6Address will generate a random version 6 ip address +func IPv6Address() string { + num := 65536 + return fmt.Sprintf("2001:cafe:%x:%x:%x:%x:%x:%x", rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num)) +} + +// Username will genrate a random username based upon picking a random lastname and random numbers at the end +func Username() string { + return getRandValue([]string{"person", "last"}) + replaceWithNumbers("####") +} diff --git a/vendor/github.com/brianvoe/gofakeit/job.go b/vendor/github.com/brianvoe/gofakeit/job.go new file mode 100644 index 000000000000..c156bde77243 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/job.go @@ -0,0 +1,34 @@ +package gofakeit + +// JobInfo is a struct of job information +type JobInfo struct { + Company string + Title string + Descriptor string + Level string +} + +// Job will generate a struct with random job information +func Job() *JobInfo { + return &JobInfo{ + Company: Company(), + Title: JobTitle(), + Descriptor: JobDescriptor(), + Level: JobLevel(), + } +} + +// JobTitle will generate a random job title string +func JobTitle() string { + return getRandValue([]string{"job", "title"}) +} + +// JobDescriptor will generate a random job descriptor string +func JobDescriptor() string { + return getRandValue([]string{"job", "descriptor"}) +} + +// JobLevel will generate a random job level string +func JobLevel() string { + return getRandValue([]string{"job", "level"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/log_level.go b/vendor/github.com/brianvoe/gofakeit/log_level.go new file mode 100644 index 000000000000..bde9bf310588 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/log_level.go @@ -0,0 +1,15 @@ +package gofakeit + +import ( + "github.com/brianvoe/gofakeit/data" +) + +// LogLevel will generate a random log level +// See data/LogLevels for list of available levels +func LogLevel(logType string) string { + if _, ok := data.LogLevels[logType]; ok { + return getRandValue([]string{"log_level", logType}) + } + + return getRandValue([]string{"log_level", "general"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/logo.png b/vendor/github.com/brianvoe/gofakeit/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a97962030afda2145a035db6a01b2f3c256384fa GIT binary patch literal 36022 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 '9' { + return false + } + if i&1 == odd { + sum += t[c-'0'] + } else { + sum += int(c - '0') + } + } + return sum%10 == 0 +} diff --git a/vendor/github.com/brianvoe/gofakeit/person.go b/vendor/github.com/brianvoe/gofakeit/person.go new file mode 100644 index 000000000000..5fd6cbe22a11 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/person.go @@ -0,0 +1,45 @@ +package gofakeit + +import "strconv" + +// SSN will generate a random Social Security Number +func SSN() string { + return strconv.Itoa(randIntRange(100000000, 999999999)) +} + +// Gender will generate a random gender string +func Gender() string { + if Bool() == true { + return "male" + } + + return "female" +} + +// PersonInfo is a struct of person information +type PersonInfo struct { + FirstName string + LastName string + Gender string + SSN string + Image string + Job *JobInfo + Address *AddressInfo + Contact *ContactInfo + CreditCard *CreditCardInfo +} + +// Person will generate a struct with person information +func Person() *PersonInfo { + return &PersonInfo{ + FirstName: FirstName(), + LastName: LastName(), + Gender: Gender(), + SSN: SSN(), + Image: ImageURL(300, 300) + "/people", + Job: Job(), + Address: Address(), + Contact: Contact(), + CreditCard: CreditCard(), + } +} diff --git a/vendor/github.com/brianvoe/gofakeit/status_code.go b/vendor/github.com/brianvoe/gofakeit/status_code.go new file mode 100644 index 000000000000..1751c0fbe401 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/status_code.go @@ -0,0 +1,11 @@ +package gofakeit + +// SimpleStatusCode will generate a random simple status code +func SimpleStatusCode() int { + return getRandIntValue([]string{"status_code", "simple"}) +} + +// StatusCode will generate a random status code +func StatusCode() int { + return getRandIntValue([]string{"status_code", "general"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/string.go b/vendor/github.com/brianvoe/gofakeit/string.go new file mode 100644 index 000000000000..fc646cf38ac1 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/string.go @@ -0,0 +1,48 @@ +package gofakeit + +import ( + "math/rand" +) + +// Letter will generate a single random lower case ASCII letter +func Letter() string { + return string(randLetter()) +} + +// Digit will generate a single ASCII digit +func Digit() string { + return string(randDigit()) +} + +// Lexify will replace ? will random generated letters +func Lexify(str string) string { + return replaceWithLetters(str) +} + +// ShuffleStrings will randomize a slice of strings +func ShuffleStrings(a []string) { + swap := func(i, j int) { + a[i], a[j] = a[j], a[i] + } + //to avoid upgrading to 1.10 I copied the algorithm + n := len(a) + if n <= 1 { + return + } + + //if size is > int32 probably it will never finish, or ran out of entropy + i := n - 1 + for ; i > 0; i-- { + j := int(rand.Int31n(int32(i + 1))) + swap(i, j) + } +} + +// RandString will take in a slice of string and return a randomly selected value +func RandString(a []string) string { + size := len(a) + if size == 0 { + return "" + } + return a[rand.Intn(size)] +} diff --git a/vendor/github.com/brianvoe/gofakeit/struct.go b/vendor/github.com/brianvoe/gofakeit/struct.go new file mode 100644 index 000000000000..2c68a9a3cb21 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/struct.go @@ -0,0 +1,87 @@ +package gofakeit + +import ( + "reflect" +) + +// Struct fills in exported elements of a struct with random data +// based on the value of `fake` tag of exported elements. +// Use `fake:"skip"` to explicitly skip an element. +// All built-in types are supported, with templating support +// for string types. +func Struct(v interface{}) { + r(reflect.TypeOf(v), reflect.ValueOf(v), "") +} + +func r(t reflect.Type, v reflect.Value, template string) { + switch t.Kind() { + case reflect.Ptr: + rPointer(t, v, template) + case reflect.Struct: + rStruct(t, v) + case reflect.String: + rString(template, v) + case reflect.Uint8: + v.SetUint(uint64(Uint8())) + case reflect.Uint16: + v.SetUint(uint64(Uint16())) + case reflect.Uint32: + v.SetUint(uint64(Uint32())) + case reflect.Uint64: + //capped at [0, math.MaxInt64) + v.SetUint(uint64(Uint64())) + case reflect.Int: + v.SetInt(int64(Int64())) + case reflect.Int8: + v.SetInt(int64(Int8())) + case reflect.Int16: + v.SetInt(int64(Int16())) + case reflect.Int32: + v.SetInt(int64(Int32())) + case reflect.Int64: + v.SetInt(int64(Int64())) + case reflect.Float64: + v.SetFloat(Float64()) + case reflect.Float32: + v.SetFloat(float64(Float32())) + case reflect.Bool: + v.SetBool(Bool()) + } +} + +func rString(template string, v reflect.Value) { + if template != "" { + r := Generate(template) + v.SetString(r) + } else { + v.SetString(Generate("???????????????????")) + // we don't have a String(len int) string function!! + } +} + +func rStruct(t reflect.Type, v reflect.Value) { + n := t.NumField() + for i := 0; i < n; i++ { + elementT := t.Field(i) + elementV := v.Field(i) + fake := true + t, ok := elementT.Tag.Lookup("fake") + if ok && t == "skip" { + fake = false + } + if fake && elementV.CanSet() { + r(elementT.Type, elementV, t) + } + } +} + +func rPointer(t reflect.Type, v reflect.Value, template string) { + elemT := t.Elem() + if v.IsNil() { + nv := reflect.New(elemT) + r(elemT, nv.Elem(), template) + v.Set(nv) + } else { + r(elemT, v.Elem(), template) + } +} diff --git a/vendor/github.com/brianvoe/gofakeit/unique.go b/vendor/github.com/brianvoe/gofakeit/unique.go new file mode 100644 index 000000000000..4b969a7e9b8f --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/unique.go @@ -0,0 +1,34 @@ +package gofakeit + +import ( + "encoding/hex" + "math/rand" +) + +// UUID (version 4) will generate a random unique identifier based upon random nunbers +// Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +func UUID() string { + version := byte(4) + uuid := make([]byte, 16) + rand.Read(uuid) + + // Set version + uuid[6] = (uuid[6] & 0x0f) | (version << 4) + + // Set variant + uuid[8] = (uuid[8] & 0xbf) | 0x80 + + buf := make([]byte, 36) + var dash byte = '-' + hex.Encode(buf[0:8], uuid[0:4]) + buf[8] = dash + hex.Encode(buf[9:13], uuid[4:6]) + buf[13] = dash + hex.Encode(buf[14:18], uuid[6:8]) + buf[18] = dash + hex.Encode(buf[19:23], uuid[8:10]) + buf[23] = dash + hex.Encode(buf[24:], uuid[10:]) + + return string(buf) +} diff --git a/vendor/github.com/brianvoe/gofakeit/user_agent.go b/vendor/github.com/brianvoe/gofakeit/user_agent.go new file mode 100644 index 000000000000..2ba334121452 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/user_agent.go @@ -0,0 +1,92 @@ +package gofakeit + +import "strconv" + +// UserAgent will generate a random broswer user agent +func UserAgent() string { + randNum := randIntRange(0, 4) + switch randNum { + case 0: + return ChromeUserAgent() + case 1: + return FirefoxUserAgent() + case 2: + return SafariUserAgent() + case 3: + return OperaUserAgent() + default: + return ChromeUserAgent() + } +} + +// ChromeUserAgent will generate a random chrome browser user agent string +func ChromeUserAgent() string { + randNum1 := strconv.Itoa(randIntRange(531, 536)) + strconv.Itoa(randIntRange(0, 2)) + randNum2 := strconv.Itoa(randIntRange(36, 40)) + randNum3 := strconv.Itoa(randIntRange(800, 899)) + return "Mozilla/5.0 " + "(" + randomPlatform() + ") AppleWebKit/" + randNum1 + " (KHTML, like Gecko) Chrome/" + randNum2 + ".0." + randNum3 + ".0 Mobile Safari/" + randNum1 +} + +// FirefoxUserAgent will generate a random firefox broswer user agent string +func FirefoxUserAgent() string { + ver := "Gecko/" + Date().Format("2006-02-01") + " Firefox/" + strconv.Itoa(randIntRange(35, 37)) + ".0" + platforms := []string{ + "(" + windowsPlatformToken() + "; " + "en-US" + "; rv:1.9." + strconv.Itoa(randIntRange(0, 3)) + ".20) " + ver, + "(" + linuxPlatformToken() + "; rv:" + strconv.Itoa(randIntRange(5, 8)) + ".0) " + ver, + "(" + macPlatformToken() + " rv:" + strconv.Itoa(randIntRange(2, 7)) + ".0) " + ver, + } + + return "Mozilla/5.0 " + RandString(platforms) +} + +// SafariUserAgent will generate a random safari browser user agent string +func SafariUserAgent() string { + randNum := strconv.Itoa(randIntRange(531, 536)) + "." + strconv.Itoa(randIntRange(1, 51)) + "." + strconv.Itoa(randIntRange(1, 8)) + ver := strconv.Itoa(randIntRange(4, 6)) + "." + strconv.Itoa(randIntRange(0, 2)) + + mobileDevices := []string{ + "iPhone; CPU iPhone OS", + "iPad; CPU OS", + } + + platforms := []string{ + "(Windows; U; " + windowsPlatformToken() + ") AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + ver + " Safari/" + randNum, + "(" + macPlatformToken() + " rv:" + strconv.Itoa(randIntRange(4, 7)) + ".0; en-US) AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + ver + " Safari/" + randNum, + "(" + RandString(mobileDevices) + " " + strconv.Itoa(randIntRange(7, 9)) + "_" + strconv.Itoa(randIntRange(0, 3)) + "_" + strconv.Itoa(randIntRange(1, 3)) + " like Mac OS X; " + "en-US" + ") AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + strconv.Itoa(randIntRange(3, 5)) + ".0.5 Mobile/8B" + strconv.Itoa(randIntRange(111, 120)) + " Safari/6" + randNum, + } + + return "Mozilla/5.0 " + RandString(platforms) +} + +// OperaUserAgent will generate a random opera browser user agent string +func OperaUserAgent() string { + platform := "(" + randomPlatform() + "; en-US) Presto/2." + strconv.Itoa(randIntRange(8, 13)) + "." + strconv.Itoa(randIntRange(160, 355)) + " Version/" + strconv.Itoa(randIntRange(10, 13)) + ".00" + + return "Opera/" + strconv.Itoa(randIntRange(8, 10)) + "." + strconv.Itoa(randIntRange(10, 99)) + " " + platform +} + +// linuxPlatformToken will generate a random linux platform +func linuxPlatformToken() string { + return "X11; Linux " + getRandValue([]string{"computer", "linux_processor"}) +} + +// macPlatformToken will generate a random mac platform +func macPlatformToken() string { + return "Macintosh; " + getRandValue([]string{"computer", "mac_processor"}) + " Mac OS X 10_" + strconv.Itoa(randIntRange(5, 9)) + "_" + strconv.Itoa(randIntRange(0, 10)) +} + +// windowsPlatformToken will generate a random windows platform +func windowsPlatformToken() string { + return getRandValue([]string{"computer", "windows_platform"}) +} + +// randomPlatform will generate a random platform +func randomPlatform() string { + platforms := []string{ + linuxPlatformToken(), + macPlatformToken(), + windowsPlatformToken(), + } + + return RandString(platforms) +} diff --git a/vendor/github.com/brianvoe/gofakeit/vehicle.go b/vendor/github.com/brianvoe/gofakeit/vehicle.go new file mode 100644 index 000000000000..093fe3a1d84c --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/vehicle.go @@ -0,0 +1,55 @@ +package gofakeit + +// VehicleInfo is a struct dataset of all vehicle information +type VehicleInfo struct { + // Vehicle type + VehicleType string + // Fuel type + Fuel string + // Transmission type + TransmissionGear string + // Brand name + Brand string + // Vehicle model + Model string + // Vehicle model year + Year int +} + +// Vehicle will generate a struct with vehicle information +func Vehicle() *VehicleInfo { + return &VehicleInfo{ + VehicleType: VehicleType(), + Fuel: FuelType(), + TransmissionGear: TransmissionGearType(), + Brand: CarMaker(), + Model: CarModel(), + Year: Year(), + } + +} + +// VehicleType will generate a random vehicle type string +func VehicleType() string { + return getRandValue([]string{"vehicle", "vehicle_type"}) +} + +// FuelType will return a random fuel type +func FuelType() string { + return getRandValue([]string{"vehicle", "fuel_type"}) +} + +// TransmissionGearType will return a random transmission gear type +func TransmissionGearType() string { + return getRandValue([]string{"vehicle", "transmission_type"}) +} + +// CarMaker will return a random car maker +func CarMaker() string { + return getRandValue([]string{"vehicle", "maker"}) +} + +// CarModel will return a random car model +func CarModel() string { + return getRandValue([]string{"vehicle", "model"}) +} diff --git a/vendor/github.com/brianvoe/gofakeit/words.go b/vendor/github.com/brianvoe/gofakeit/words.go new file mode 100644 index 000000000000..631e45c7ddd7 --- /dev/null +++ b/vendor/github.com/brianvoe/gofakeit/words.go @@ -0,0 +1,100 @@ +package gofakeit + +import ( + "bytes" + "strings" + "unicode" +) + +type paragrapOptions struct { + paragraphCount int + sentenceCount int + wordCount int + separator string +} + +const bytesPerWordEstimation = 6 + +type sentenceGenerator func(wordCount int) string +type wordGenerator func() string + +// Word will generate a random word +func Word() string { + return getRandValue([]string{"lorem", "word"}) +} + +// Sentence will generate a random sentence +func Sentence(wordCount int) string { + return sentence(wordCount, Word) +} + +// Paragraph will generate a random paragraphGenerator +// Set Paragraph Count +// Set Sentence Count +// Set Word Count +// Set Paragraph Separator +func Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string { + return paragraphGenerator(paragrapOptions{paragraphCount, sentenceCount, wordCount, separator}, Sentence) +} + +func sentence(wordCount int, word wordGenerator) string { + if wordCount <= 0 { + return "" + } + + wordSeparator := ' ' + sentence := bytes.Buffer{} + sentence.Grow(wordCount * bytesPerWordEstimation) + + for i := 0; i < wordCount; i++ { + word := word() + if i == 0 { + runes := []rune(word) + runes[0] = unicode.ToTitle(runes[0]) + word = string(runes) + } + sentence.WriteString(word) + if i < wordCount-1 { + sentence.WriteRune(wordSeparator) + } + } + sentence.WriteRune('.') + return sentence.String() +} + +func paragraphGenerator(opts paragrapOptions, sentecer sentenceGenerator) string { + if opts.paragraphCount <= 0 || opts.sentenceCount <= 0 || opts.wordCount <= 0 { + return "" + } + + //to avoid making Go 1.10 dependency, we cannot use strings.Builder + paragraphs := bytes.Buffer{} + //we presume the length + paragraphs.Grow(opts.paragraphCount * opts.sentenceCount * opts.wordCount * bytesPerWordEstimation) + wordSeparator := ' ' + + for i := 0; i < opts.paragraphCount; i++ { + for e := 0; e < opts.sentenceCount; e++ { + paragraphs.WriteString(sentecer(opts.wordCount)) + if e < opts.sentenceCount-1 { + paragraphs.WriteRune(wordSeparator) + } + } + + if i < opts.paragraphCount-1 { + paragraphs.WriteString(opts.separator) + } + } + + return paragraphs.String() +} + +// Question will return a random question +func Question() string { + return strings.Replace(HipsterSentence(Number(3, 10)), ".", "?", 1) +} + +// Quote will return a random quote from a random person +func Quote() string { + return `"` + HipsterSentence(Number(3, 10)) + `" - ` + FirstName() + " " + LastName() +} diff --git a/vendor/github.com/robfig/cron/README.md b/vendor/github.com/robfig/cron/README.md index ec40c95fcb9d..4e0ae1c25f39 100644 --- a/vendor/github.com/robfig/cron/README.md +++ b/vendor/github.com/robfig/cron/README.md @@ -1,4 +1,4 @@ -[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) +[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) [![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron) # cron diff --git a/vendor/github.com/robfig/cron/doc.go b/vendor/github.com/robfig/cron/doc.go index d02ec2f3b563..1ce84f7bf462 100644 --- a/vendor/github.com/robfig/cron/doc.go +++ b/vendor/github.com/robfig/cron/doc.go @@ -84,7 +84,7 @@ You may use one of several pre-defined schedules in place of a cron expression. Intervals -You may also schedule a job to execute at fixed intervals, starting at the time it's added +You may also schedule a job to execute at fixed intervals, starting at the time it's added or cron is run. This is supported by formatting the cron spec like this: @every diff --git a/vendor/modules.txt b/vendor/modules.txt index 19e66874848b..1cf623aa57d4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -56,6 +56,9 @@ github.com/benbjohnson/clock github.com/beorn7/perks/quantile # github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 github.com/bradfitz/gomemcache/memcache +# github.com/brianvoe/gofakeit v3.17.0+incompatible +github.com/brianvoe/gofakeit +github.com/brianvoe/gofakeit/data # github.com/codegangsta/cli v1.20.0 github.com/codegangsta/cli # github.com/davecgh/go-spew v1.1.1 From 0d34dfc775ce04f12d16598e4c1a263d46f2669c Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 17 May 2019 13:14:01 -0700 Subject: [PATCH 05/92] Chore: fix codespell issue with build (#17144) --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0cd47bfa6d1d..1b713003a389 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -69,7 +69,7 @@ jobs: - run: name: cache server tests command: './scripts/circle-test-cache-servers.sh' - + end-to-end-test: docker: - image: circleci/node:8-browsers @@ -103,7 +103,7 @@ jobs: - run: # Important: all words have to be in lowercase, and separated by "\n". name: exclude known exceptions - command: 'echo -e "unknwon" > words_to_ignore.txt' + command: 'echo -e "unknwon\nreferer\nerrorstring" > words_to_ignore.txt' - run: name: check documentation spelling errors command: 'codespell -I ./words_to_ignore.txt docs/' From ae1df1cf899c37c36bc6279d0a1934ad5868fb4d Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Sat, 18 May 2019 08:31:25 -0700 Subject: [PATCH 06/92] Plugins: expose rxjs matching 6.4.0 (#17148) * expose rxjs * remove super old observable export --- public/app/features/plugins/plugin_loader.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 2ee31a7b6aa2..55531559d1d0 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -75,6 +75,10 @@ exposeToPlugin('angular', angular); exposeToPlugin('d3', d3); exposeToPlugin('rxjs/Subject', Subject); exposeToPlugin('rxjs/Observable', Observable); +exposeToPlugin('rxjs', { + Subject: Subject, + Observable: Observable, +}); // Experimental modules exposeToPlugin('prismjs', prismjs); @@ -84,12 +88,6 @@ exposeToPlugin('slate-plain-serializer', slatePlain); exposeToPlugin('react', react); exposeToPlugin('react-dom', reactDom); -// backward compatible path -exposeToPlugin('vendor/npm/rxjs/Rx', { - Subject: Subject, - Observable: Observable, -}); - exposeToPlugin('app/features/dashboard/impression_store', { impressions: impressionSrv, __esModule: true, From 12e0616413cad2a9de09d583c199c5b877bf6649 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 20 May 2019 08:44:37 +0200 Subject: [PATCH 07/92] Explore: display log line context (#17097) * Extend DataSourceAPI to enable log row context retrieval * Add react-use package * Display log row context in UI * Make Loki datasource return "after" log context in correct order * Don't show Load more context links when there are no more new results * Update getLogRowContext to return DataQueryResponse * Use DataQueryResponse in log row context provider, filter out original row being duplicated in context --- package.json | 1 + packages/grafana-ui/src/types/datasource.ts | 9 + public/app/features/explore/LogRow.tsx | 249 ++++++++++++++---- public/app/features/explore/LogRowContext.tsx | 239 +++++++++++++++++ .../explore/LogRowContextProvider.tsx | 104 ++++++++ public/app/features/explore/Logs.tsx | 5 +- public/app/features/explore/LogsContainer.tsx | 29 +- public/app/features/explore/state/reducers.ts | 1 - .../app/plugins/datasource/loki/datasource.ts | 78 ++++++ public/app/types/explore.ts | 1 + public/sass/components/_panel_logs.scss | 1 + yarn.lock | 131 ++++++++- 12 files changed, 785 insertions(+), 63 deletions(-) create mode 100644 public/app/features/explore/LogRowContext.tsx create mode 100644 public/app/features/explore/LogRowContextProvider.tsx diff --git a/package.json b/package.json index c7cc8a83644e..35eb408ef02f 100644 --- a/package.json +++ b/package.json @@ -224,6 +224,7 @@ "react-sizeme": "2.5.2", "react-table": "6.9.2", "react-transition-group": "2.6.1", + "react-use": "9.0.0", "react-virtualized": "9.21.0", "react-window": "1.7.1", "redux": "4.0.1", diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 2b40064d7d9d..759ee05d681f 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -172,6 +172,11 @@ export abstract class DataSourceApi< */ getQueryDisplayText?(query: TQuery): string; + /** + * Retrieve context for a given log row + */ + getLogRowContext?(row: any, limit?: number): Promise; + /** * Set after constructor call, as the data source instance is the most common thing to pass around * we attach the components to this instance for easy access @@ -282,6 +287,10 @@ export interface DataQueryResponse { data: DataQueryResponseData[]; } +export interface LogRowContextQueryResponse { + data: Array>; +} + export interface DataQuery { /** * A - Z diff --git a/public/app/features/explore/LogRow.tsx b/public/app/features/explore/LogRow.tsx index 8e3de04749b8..37229ee5b75d 100644 --- a/public/app/features/explore/LogRow.tsx +++ b/public/app/features/explore/LogRow.tsx @@ -8,6 +8,16 @@ import { LogLabels } from './LogLabels'; import { findHighlightChunksInText } from 'app/core/utils/text'; import { LogLabelStats } from './LogLabelStats'; import { LogMessageAnsi } from './LogMessageAnsi'; +import { css, cx } from 'emotion'; +import { + LogRowContextProvider, + LogRowContextRows, + HasMoreContextRows, + LogRowContextQueryErrors, +} from './LogRowContextProvider'; +import { ThemeContext, selectThemeVariant, GrafanaTheme, DataQueryResponse } from '@grafana/ui'; +import { LogRowContext } from './LogRowContext'; +import tinycolor from 'tinycolor2'; interface Props { highlighterExpressions?: string[]; @@ -18,6 +28,9 @@ interface Props { showUtc: boolean; getRows: () => LogRowModel[]; onClickLabel?: (label: string, value: string) => void; + onContextClick?: () => void; + getRowContext?: (row: LogRowModel, limit: number) => Promise; + className?: string; } interface State { @@ -29,6 +42,7 @@ interface State { parser?: LogsParser; parsedFieldHighlights: string[]; showFieldStats: boolean; + showContext: boolean; } /** @@ -44,6 +58,32 @@ const FieldHighlight = onClick => props => { ); }; +const logRowStyles = css` + position: relative; + /* z-index: 0; */ + /* outline: none; */ +`; + +const getLogRowWithContextStyles = (theme: GrafanaTheme, state: State) => { + const outlineColor = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.black, + }, + theme.type + ); + + return { + row: css` + z-index: 1; + outline: 9999px solid + ${tinycolor(outlineColor) + .setAlpha(0.7) + .toRgbString()}; + `, + }; +}; + /** * Renders a log line. * @@ -63,6 +103,7 @@ export class LogRow extends PureComponent { parser: undefined, parsedFieldHighlights: [], showFieldStats: false, + showContext: false, }; componentWillUnmount() { @@ -89,11 +130,21 @@ export class LogRow extends PureComponent { }; onMouseOverMessage = () => { + if (this.state.showContext) { + // When showing context we don't want to the LogRow rerender as it will mess up state of context block + // making the "after" context to be scrolled to the top, what is desired only on open + // The log row message needs to be refactored to separate component that encapsulates parsing and parsed message state + return; + } // Don't parse right away, user might move along this.mouseMessageTimer = setTimeout(this.parseMessage, 500); }; onMouseOutMessage = () => { + if (this.state.showContext) { + // See comment in onMouseOverMessage method + return; + } clearTimeout(this.mouseMessageTimer); this.setState({ parsed: false }); }; @@ -110,7 +161,25 @@ export class LogRow extends PureComponent { } }; - render() { + toggleContext = () => { + this.setState(state => { + return { + showContext: !state.showContext, + }; + }); + }; + + onContextToggle = (e: React.SyntheticEvent) => { + e.stopPropagation(); + this.toggleContext(); + }; + + renderLogRow( + context?: LogRowContextRows, + errors?: LogRowContextQueryErrors, + hasMoreContextRows?: HasMoreContextRows, + updateLimit?: () => void + ) { const { getRows, highlighterExpressions, @@ -129,6 +198,7 @@ export class LogRow extends PureComponent { parsed, parsedFieldHighlights, showFieldStats, + showContext, } = this.state; const { entry, hasAnsi, raw } = row; const previewHighlights = highlighterExpressions && !_.isEqual(highlighterExpressions, row.searchWords); @@ -139,59 +209,132 @@ export class LogRow extends PureComponent { }); return ( -
- {showDuplicates && ( -
{row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
- )} -
- {showUtc && ( -
- {row.timestamp} -
- )} - {showLocalTime && ( -
- {row.timeLocal} -
- )} - {showLabels && ( -
- -
- )} -
- {parsed && ( - - )} - {!parsed && needsHighlighter && ( - - )} - {hasAnsi && !parsed && !needsHighlighter && } - {!hasAnsi && !parsed && !needsHighlighter && entry} - {showFieldStats && ( -
- + + {theme => { + const styles = this.state.showContext + ? cx(logRowStyles, getLogRowWithContextStyles(theme, this.state).row) + : logRowStyles; + console.log(styles); + return ( +
+ {showDuplicates && ( +
{row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
+ )} +
+ {showUtc && ( +
+ {row.timestamp} +
+ )} + {showLocalTime && ( +
+ {row.timeLocal} +
+ )} + {showLabels && ( +
+ +
+ )} +
+
+ {showContext && context && ( + { + if (updateLimit) { + updateLimit(); + } + }} + /> + )} + + {parsed && ( + + )} + {!parsed && needsHighlighter && ( + + )} + {hasAnsi && !parsed && !needsHighlighter && } + {!hasAnsi && !parsed && !needsHighlighter && entry} + {showFieldStats && ( +
+ +
+ )} +
+ {row.searchWords && row.searchWords.length > 0 && ( + + {showContext ? 'Hide' : 'Show'} context + + )} +
+
- )} -
-
+ ); + }} + ); } + + render() { + const { showContext } = this.state; + + if (showContext) { + return ( + <> + + {({ result, errors, hasMoreContextRows, updateLimit }) => { + return <>{this.renderLogRow(result, errors, hasMoreContextRows, updateLimit)}; + }} + + + ); + } + + return this.renderLogRow(); + } } diff --git a/public/app/features/explore/LogRowContext.tsx b/public/app/features/explore/LogRowContext.tsx new file mode 100644 index 000000000000..f873b50958b5 --- /dev/null +++ b/public/app/features/explore/LogRowContext.tsx @@ -0,0 +1,239 @@ +import React, { useContext, useRef, useState, useLayoutEffect } from 'react'; +import { + ThemeContext, + List, + GrafanaTheme, + selectThemeVariant, + ClickOutsideWrapper, + CustomScrollbar, + DataQueryError, +} from '@grafana/ui'; +import { css, cx } from 'emotion'; +import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider'; +import { LogRowModel } from 'app/core/logs_model'; +import { Alert } from './Error'; + +interface LogRowContextProps { + row: LogRowModel; + context: LogRowContextRows; + errors?: LogRowContextQueryErrors; + hasMoreContextRows: HasMoreContextRows; + onOutsideClick: () => void; + onLoadMoreContext: () => void; +} + +const getLogRowContextStyles = (theme: GrafanaTheme) => { + const gradientTop = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.dark1, + }, + theme.type + ); + const gradientBottom = selectThemeVariant( + { + light: theme.colors.gray7, + dark: theme.colors.dark2, + }, + theme.type + ); + + const boxShadowColor = selectThemeVariant( + { + light: theme.colors.gray5, + dark: theme.colors.black, + }, + theme.type + ); + const borderColor = selectThemeVariant( + { + light: theme.colors.gray5, + dark: theme.colors.dark9, + }, + theme.type + ); + + return { + commonStyles: css` + position: absolute; + width: calc(100% + 20px); + left: -10px; + height: 250px; + z-index: 2; + overflow: hidden; + background: ${theme.colors.pageBg}; + background: linear-gradient(180deg, ${gradientTop} 0%, ${gradientBottom} 104.25%); + box-shadow: 0px 2px 4px ${boxShadowColor}, 0px 0px 2px ${boxShadowColor}; + border: 1px solid ${borderColor}; + border-radius: ${theme.border.radius.md}; + `, + header: css` + height: 30px; + padding: 0 10px; + display: flex; + align-items: center; + background: ${borderColor}; + `, + logs: css` + height: 220px; + padding: 10px; + `, + }; +}; + +interface LogRowContextGroupHeaderProps { + row: LogRowModel; + rows: Array; + onLoadMoreContext: () => void; + shouldScrollToBottom?: boolean; + canLoadMoreRows?: boolean; +} +interface LogRowContextGroupProps extends LogRowContextGroupHeaderProps { + rows: Array; + className: string; + error?: string; +} + +const LogRowContextGroupHeader: React.FunctionComponent = ({ + row, + rows, + onLoadMoreContext, + canLoadMoreRows, +}) => { + const theme = useContext(ThemeContext); + const { header } = getLogRowContextStyles(theme); + + // Filtering out the original row from the context. + // Loki requires a rowTimestamp+1ns for the following logs to be queried. + // We don't to ns-precision calculations in Loki log row context retrieval, hence the filtering here + // Also see: https://github.com/grafana/loki/issues/597 + const logRowsToRender = rows.filter(contextRow => contextRow !== row.raw); + + return ( +
+ + Found {logRowsToRender.length} rows. + + {(rows.length >= 10 || (rows.length > 10 && rows.length % 10 !== 0)) && canLoadMoreRows && ( + onLoadMoreContext()} + > + Load 10 more + + )} +
+ ); +}; + +const LogRowContextGroup: React.FunctionComponent = ({ + row, + rows, + error, + className, + shouldScrollToBottom, + canLoadMoreRows, + onLoadMoreContext, +}) => { + const theme = useContext(ThemeContext); + const { commonStyles, logs } = getLogRowContextStyles(theme); + const [scrollTop, setScrollTop] = useState(0); + const listContainerRef = useRef(); + + useLayoutEffect(() => { + if (shouldScrollToBottom && listContainerRef.current) { + setScrollTop(listContainerRef.current.offsetHeight); + } + }); + + const headerProps = { + row, + rows, + onLoadMoreContext, + canLoadMoreRows, + }; + + return ( +
+ {/* When displaying "after" context */} + {shouldScrollToBottom && !error && } +
+ +
+ {!error && ( + { + return ( +
+ {item} +
+ ); + }} + /> + )} + {error && } +
+
+
+ {/* When displaying "before" context */} + {!shouldScrollToBottom && !error && } +
+ ); +}; + +export const LogRowContext: React.FunctionComponent = ({ + row, + context, + errors, + onOutsideClick, + onLoadMoreContext, + hasMoreContextRows, +}) => { + return ( + +
+ {context.after && ( + + )} + + {context.before && ( + + )} +
+
+ ); +}; diff --git a/public/app/features/explore/LogRowContextProvider.tsx b/public/app/features/explore/LogRowContextProvider.tsx new file mode 100644 index 000000000000..d3fa0bfdcc83 --- /dev/null +++ b/public/app/features/explore/LogRowContextProvider.tsx @@ -0,0 +1,104 @@ +import { LogRowModel } from 'app/core/logs_model'; +import { LogRowContextQueryResponse, SeriesData, DataQueryResponse, DataQueryError } from '@grafana/ui'; +import { useState, useEffect } from 'react'; +import useAsync from 'react-use/lib/useAsync'; + +export interface LogRowContextRows { + before?: Array; + after?: Array; +} +export interface LogRowContextQueryErrors { + before?: string; + after?: string; +} + +export interface HasMoreContextRows { + before: boolean; + after: boolean; +} + +interface LogRowContextProviderProps { + row: LogRowModel; + getRowContext: (row: LogRowModel, limit: number) => Promise; + children: (props: { + result: LogRowContextRows; + errors: LogRowContextQueryErrors; + hasMoreContextRows: HasMoreContextRows; + updateLimit: () => void; + }) => JSX.Element; +} + +export const LogRowContextProvider: React.FunctionComponent = ({ + getRowContext, + row, + children, +}) => { + const [limit, setLimit] = useState(10); + const [result, setResult] = useState(null); + const [errors, setErrors] = useState(null); + const [hasMoreContextRows, setHasMoreContextRows] = useState({ + before: true, + after: true, + }); + + const { value } = useAsync(async () => { + const context = await getRowContext(row, limit); + return { + data: context.data.map(series => { + if ((series as SeriesData).rows) { + return (series as SeriesData).rows.map(row => row[1]); + } else { + return [series]; + } + return []; + }), + }; + }, [limit]); + + useEffect(() => { + if (value) { + setResult(currentResult => { + let hasMoreLogsBefore = true, + hasMoreLogsAfter = true; + let beforeContextError, afterContextError; + + if (currentResult && currentResult.data[0].length === value.data[0].length) { + hasMoreLogsBefore = false; + } + + if (currentResult && currentResult.data[1].length === value.data[1].length) { + hasMoreLogsAfter = false; + } + + if (value.data[0] && value.data[0].length > 0 && value.data[0][0].message) { + beforeContextError = value.data[0][0].message; + } + if (value.data[1] && value.data[1].length > 0 && value.data[1][0].message) { + afterContextError = value.data[1][0].message; + } + + setHasMoreContextRows({ + before: hasMoreLogsBefore, + after: hasMoreLogsAfter, + }); + + setErrors({ + before: beforeContextError, + after: afterContextError, + }); + + return value; + }); + } + }, [value]); + + return children({ + result: { + before: result ? result.data[0] : [], + after: result ? result.data[1] : [], + }, + errors, + hasMoreContextRows, + updateLimit: () => setLimit(limit + 10), + }); +}; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 6603ac0330dc..3719ed0e88f5 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -5,7 +5,7 @@ import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; import { RawTimeRange, Switch, LogLevel, TimeZone, TimeRange, AbsoluteTimeRange } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; -import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind } from 'app/core/logs_model'; +import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind, LogRowModel } from 'app/core/logs_model'; import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; @@ -60,6 +60,7 @@ interface Props { onStopScanning?: () => void; onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; onToggleLogLevel: (hiddenLogLevels: Set) => void; + getRowContext?: (row: LogRowModel, limit: number) => Promise; } interface State { @@ -252,6 +253,7 @@ export default class Logs extends PureComponent { { { }); }; + getLogRowContext = async (row: LogRowModel, limit: number) => { + const { datasourceInstance } = this.props; + + if (datasourceInstance) { + return datasourceInstance.getLogRowContext(row, limit); + } + + return []; + }; + render() { const { exploreId, + loading, logsHighlighterExpressions, logsResult, @@ -97,6 +118,7 @@ export class LogsContainer extends PureComponent { scanRange={scanRange} width={width} hiddenLogLevels={hiddenLogLevels} + getRowContext={this.getLogRowContext} /> ); @@ -106,7 +128,7 @@ export class LogsContainer extends PureComponent { function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range } = item; + const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range, datasourceInstance } = item; const loading = logIsLoading; const { dedupStrategy } = exploreItemUIStateSelector(item); const hiddenLogLevels = new Set(item.hiddenLogLevels); @@ -124,6 +146,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { dedupStrategy, hiddenLogLevels, dedupedResult, + datasourceInstance, }; } diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 743294f0e74c..58fcc301c874 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -379,7 +379,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const { queryIntervals } = state; const { result, resultType, latency } = action.payload; const results = calculateResultsFromQueryTransactions(result, resultType, queryIntervals.intervalMs); - return { ...state, graphResult: resultType === 'Graph' ? results.graphResult : state.graphResult, diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 04f846584911..906f5cb2fc8b 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -21,6 +21,7 @@ import { LokiQuery, LokiOptions } from './types'; import { BackendSrv } from 'app/core/services/backend_srv'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { safeStringifyValue } from 'app/core/utils/explore'; +import { LogRowModel } from 'app/core/logs_model'; export const DEFAULT_MAX_LINES = 1000; @@ -187,6 +188,83 @@ export class LokiDatasource extends DataSourceApi { return Math.ceil(date.valueOf() * 1e6); } + prepareLogRowContextQueryTargets = (row: LogRowModel, limit: number) => { + const query = Object.keys(row.labels) + .map(label => { + return `${label}="${row.labels[label]}"`; + }) + .join(','); + const contextTimeBuffer = 2 * 60 * 60 * 1000 * 1e6; // 2h buffer + const timeEpochNs = row.timeEpochMs * 1e6; + + const commontTargetOptons = { + limit, + query: `{${query}}`, + }; + return [ + // Target for "before" context + { + ...commontTargetOptons, + start: timeEpochNs - contextTimeBuffer, + end: timeEpochNs, + direction: 'BACKWARD', + }, + // Target for "after" context + { + ...commontTargetOptons, + start: timeEpochNs, // TODO: We should add 1ns here for the original row not no be included in the result + end: timeEpochNs + contextTimeBuffer, + direction: 'FORWARD', + }, + ]; + }; + + getLogRowContext = (row: LogRowModel, limit?: number) => { + // Preparing two targets, for preceeding and following log queries + const targets = this.prepareLogRowContextQueryTargets(row, limit || 10); + + return Promise.all( + targets.map(target => { + return this._request('/api/prom/query', target).catch(e => { + const error: DataQueryError = { + message: 'Error during context query. Please check JS console logs.', + status: e.status, + statusText: e.statusText, + }; + return error; + }); + }) + ).then((results: any[]) => { + const series: Array> = []; + const emptySeries = { + fields: [], + rows: [], + } as SeriesData; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + series[i] = []; + if (result.data) { + for (const stream of result.data.streams || []) { + const seriesData = logStreamToSeriesData(stream); + series[i].push(seriesData); + } + } else { + series[i].push(result); + } + } + + // Following context logs are requested in "forward" direction. + // This means, that we need to reverse those to make them sorted + // in descending order (by timestamp) + if (series[1][0] && (series[1][0] as SeriesData).rows) { + (series[1][0] as SeriesData).rows.reverse(); + } + + return { data: [series[0][0] || emptySeries, series[1][0] || emptySeries] }; + }); + }; + testDatasource() { return this._request('/api/prom/label') .then(res => { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 6f70ecaa25bb..b7ed76dee824 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -179,6 +179,7 @@ export interface ExploreItemState { * Log query result to be displayed in the logs result viewer. */ logsResult?: LogsModel; + /** * Query intervals for graph queries to determine how many datapoints to return. * Needs to be updated when `datasourceInstance` or `containerWidth` is changed. diff --git a/public/sass/components/_panel_logs.scss b/public/sass/components/_panel_logs.scss index 8007e77a81d8..3c6ffd83a6fe 100644 --- a/public/sass/components/_panel_logs.scss +++ b/public/sass/components/_panel_logs.scss @@ -73,6 +73,7 @@ $column-horizontal-spacing: 10px; padding-right: $column-horizontal-spacing; border-top: 1px solid transparent; border-bottom: 1px solid transparent; + height: 100%; } &:hover { diff --git a/yarn.lock b/yarn.lock index 1710b7e87102..3163fadbb361 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4054,6 +4054,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bowser@^1.7.3: + version "1.9.4" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" + integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== + boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" @@ -5143,7 +5148,7 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copy-to-clipboard@^3.0.8: +copy-to-clipboard@^3.0.8, copy-to-clipboard@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467" integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w== @@ -5366,6 +5371,14 @@ css-declaration-sorter@^4.0.1: postcss "^7.0.1" timsort "^0.3.0" +css-in-js-utils@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99" + integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA== + dependencies: + hyphenate-style-name "^1.0.2" + isobject "^3.0.1" + css-loader@2.1.1, css-loader@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea" @@ -5416,7 +5429,7 @@ css-tree@1.0.0-alpha.28: mdn-data "~1.1.0" source-map "^0.5.3" -css-tree@1.0.0-alpha.29: +css-tree@1.0.0-alpha.29, css-tree@^1.0.0-alpha.28: version "1.0.0-alpha.29" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg== @@ -5541,7 +5554,7 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7: +csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7: version "2.6.4" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.4.tgz#d585a6062096e324e7187f80e04f92bd0f00e37f" integrity sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg== @@ -6801,6 +6814,13 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d" + integrity sha512-E1fPutRDdIj/hohG0UpT5mayXNCxXP9d+snxFsPU9X0XgccOumKraa3juDMwTUyi7+Bu5+mCGagjg4IYeNbOdw== + dependencies: + stackframe "^1.0.4" + es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0, es-abstract@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" @@ -7355,6 +7375,11 @@ fast-text-encoding@^1.0.0: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== +fastest-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028" + integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg= + fastparse@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" @@ -8837,6 +8862,11 @@ husky@1.3.1: run-node "^1.0.0" slash "^2.0.0" +hyphenate-style-name@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" + integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== + iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -9024,6 +9054,14 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" +inline-style-prefixer@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911" + integrity sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg== + dependencies: + bowser "^1.7.3" + css-in-js-utils "^2.0.0" + inquirer@6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" @@ -11565,6 +11603,20 @@ nan@^2.10.0, nan@^2.12.1, nan@^2.6.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== +nano-css@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.1.0.tgz#03c2b4ea2edefd445ac0c0e0f2565ea62e2aa81a" + integrity sha512-08F1rBmp0JuAteOR/uk/c40q/+UxWr224m/ZCHjjgy8dhkFQptvNwj/408KYQc13PIV9aGvqmtUD49PqBB5Ppg== + dependencies: + css-tree "^1.0.0-alpha.28" + csstype "^2.5.5" + fastest-stable-stringify "^1.0.1" + inline-style-prefixer "^4.0.0" + rtl-css-js "^1.9.0" + sourcemap-codec "^1.4.1" + stacktrace-js "^2.0.0" + stylis "3.5.0" + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -14100,7 +14152,7 @@ react-error-overlay@^5.1.4: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.5.tgz#884530fd055476c764eaa8ab13b8ecf1f57bbf2c" integrity sha512-O9JRum1Zq/qCPFH5qVEvDDrVun8Jv9vbHtZXCR1EuRj9sKg1xJTlHxBzU6AkCzpvxRLuiY4OKImy3cDLQ+UTdg== -react-fast-compare@^2.0.2: +react-fast-compare@^2.0.2, react-fast-compare@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== @@ -14379,6 +14431,19 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-use@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-9.0.0.tgz#142bec53fa465db2a6e43c68a8c9ef2acc000592" + integrity sha512-jlXJneB96yl4VvAXDKyE6cmdIeWk0cO7Gomh870Qu0vXZ9YM2JjjR09E9vIPPPI2M27RWo2dZKXspv44Wxtoog== + dependencies: + copy-to-clipboard "^3.1.0" + nano-css "^5.1.0" + react-fast-compare "^2.0.4" + react-wait "^0.3.0" + screenfull "^4.1.0" + throttle-debounce "^2.0.1" + ts-easing "^0.2.0" + react-virtualized@9.21.0: version "9.21.0" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.0.tgz#8267c40ffb48db35b242a36dea85edcf280a6506" @@ -14391,6 +14456,11 @@ react-virtualized@9.21.0: prop-types "^15.6.0" react-lifecycles-compat "^3.0.4" +react-wait@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/react-wait/-/react-wait-0.3.0.tgz#0cdd4d919012451a5bc3ab0a16d00c6fd9a8c10b" + integrity sha512-kB5x/kMKWcn0uVr9gBdNz21/oGbQwEQnF3P9p6E9yLfJ9DRcKS0fagbgYMFI0YFOoyKDj+2q6Rwax0kTYJF37g== + react-window@1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.7.1.tgz#c1db640415b97b85bc0a1c66eb82dadabca39b86" @@ -15206,6 +15276,13 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.4.tgz#b50e6b34583f3dd89329a2f23a8a2be072845911" integrity sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA== +rtl-css-js@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.11.0.tgz#a7151930ef9d54656607d754ebb172ddfc9ef836" + integrity sha512-YnZ6jWxZxlWlcQAGF9vOmiF9bEmoQmSHE+wsrsiILkdK9HqiRPAIll4SY/QDzbvEu2lB2h62+hfg3TYzjnldbA== + dependencies: + "@babel/runtime" "^7.1.2" + run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -15400,6 +15477,11 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" +screenfull@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-4.2.0.tgz#d5252a5a0f56504719abbed9ebbcd9208115da03" + integrity sha512-qpyI9XbwuMJElWRP5vTgxkFAl4k7HpyhIqBFOZEwX9QBXn0MAuRSpn7LOc6/4CeSwoz61oBu1VPV+2fbIWC+5Q== + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -15978,7 +16060,7 @@ source-map@^0.7.2, source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -sourcemap-codec@^1.4.4: +sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== @@ -16093,6 +16175,13 @@ stable@^0.1.8, stable@~0.1.3, stable@~0.1.5: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-generator@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.3.tgz#bb74385c67ffc4ccf3c4dee5831832d4e509c8a0" + integrity sha512-kdzGoqrnqsMxOEuXsXyQTmvWXZmG0f3Ql2GDx5NtmZs59sT2Bt9Vdyq0XdtxUi58q/+nxtbF9KOQ9HkV1QznGg== + dependencies: + stackframe "^1.0.4" + stack-parser@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/stack-parser/-/stack-parser-0.0.1.tgz#7d3b63a17887e9e2c2bf55dbd3318fe34a39d1e7" @@ -16103,6 +16192,28 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stackframe@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b" + integrity sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw== + +stacktrace-gps@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.2.tgz#33f8baa4467323ab2bd1816efa279942ba431ccc" + integrity sha512-9o+nWhiz5wFnrB3hBHs2PTyYrS60M1vvpSzHxwxnIbtY2q9Nt51hZvhrG1+2AxD374ecwyS+IUwfkHRE/2zuGg== + dependencies: + source-map "0.5.6" + stackframe "^1.0.4" + +stacktrace-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.0.tgz#776ca646a95bc6c6b2b90776536a7fc72c6ddb58" + integrity sha1-d2ymRqlbxsayuQd2U2p/xyxt21g= + dependencies: + error-stack-parser "^2.0.1" + stack-generator "^2.0.1" + stacktrace-gps "^3.0.1" + staged-git-files@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.2.tgz#4326d33886dc9ecfa29a6193bf511ba90a46454b" @@ -16410,6 +16521,11 @@ stylis-rule-sheet@^0.0.10: resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== +stylis@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" + integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== + stylis@^3.5.0: version "3.5.4" resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" @@ -16665,6 +16781,11 @@ throat@^4.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= +throttle-debounce@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" + integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" From 2b32e768bdb55d0ab2267c0b55622e20be899993 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 20 May 2019 09:38:41 +0200 Subject: [PATCH 08/92] Explore: Fix empty space in toolbar on smaller devices (#17110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the empty space in Explore toolbar when on mobile/tablet. Co-Authored-By: Hugo Häggmark --- public/sass/pages/_explore.scss | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 26401522118b..495c9abd7835 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -31,20 +31,24 @@ flex-flow: row wrap; justify-content: flex-start; height: auto; - padding: 0 $dashboard-padding 0 50px; + padding: 0 $dashboard-padding; border-bottom: 1px solid #0000; transition-duration: 0.35s; transition-timing-function: ease-in-out; transition-property: box-shadow, border-bottom; - - @include media-breakpoint-up(md) { - padding-left: $dashboard-padding; - } } .explore-toolbar-item { position: relative; align-self: center; + + &:first-child { + padding-left: 34px; + + @include media-breakpoint-up(md) { + padding-left: 0; + } + } } .explore-toolbar.splitted { From 0e210dc271d579ef38c5bee3011c5627eb41cf9d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 20 May 2019 09:51:57 +0200 Subject: [PATCH 09/92] Explore: Fix selection/copy of log lines (#17121) This adds some logic to identify if the user is selecting text and if so disables parsing of log messages on hover. This should resolve the issue of selecting log lines to be copied and the selection is truncated. Fixes #17072 --- public/app/features/explore/LogRow.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/LogRow.tsx b/public/app/features/explore/LogRow.tsx index 37229ee5b75d..b36163303687 100644 --- a/public/app/features/explore/LogRow.tsx +++ b/public/app/features/explore/LogRow.tsx @@ -130,7 +130,7 @@ export class LogRow extends PureComponent { }; onMouseOverMessage = () => { - if (this.state.showContext) { + if (this.state.showContext || this.isTextSelected()) { // When showing context we don't want to the LogRow rerender as it will mess up state of context block // making the "after" context to be scrolled to the top, what is desired only on open // The log row message needs to be refactored to separate component that encapsulates parsing and parsed message state @@ -161,6 +161,20 @@ export class LogRow extends PureComponent { } }; + isTextSelected() { + if (!window.getSelection) { + return false; + } + + const selection = window.getSelection(); + + if (!selection) { + return false; + } + + return selection.anchorNode !== null && selection.isCollapsed === false; + } + toggleContext = () => { this.setState(state => { return { From 058f5a16821a9b6e5c9b1479237a7c2923666b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 20 May 2019 11:18:56 +0200 Subject: [PATCH 10/92] Release: Improved cherry pick task (#17087) * Release: Improved cherry pick task * Minor tweak to formatting --- scripts/cli/tasks/cherrypick.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/cli/tasks/cherrypick.ts b/scripts/cli/tasks/cherrypick.ts index 543b46a85869..966781951828 100644 --- a/scripts/cli/tasks/cherrypick.ts +++ b/scripts/cli/tasks/cherrypick.ts @@ -16,22 +16,32 @@ const cherryPickRunner: TaskRunner = async () => { }, }); - // sort by closed date + // sort by closed date ASC res.data.sort(function(a, b) { - return new Date(b.closed_at).getTime() - new Date(a.closed_at).getTime(); + return new Date(a.closed_at).getTime() - new Date(b.closed_at).getTime(); }); + let commands = ''; + + console.log('--------------------------------------------------------------------'); + console.log('Printing PRs with cherry-pick-needed, in ASC merge date order'); + console.log('--------------------------------------------------------------------'); + for (const item of res.data) { if (!item.milestone) { console.log(item.number + ' missing milestone!'); continue; } - console.log(`${item.title} (${item.number}) closed_at ${item.closed_at}`); - console.log(`\tURL: ${item.closed_at} ${item.html_url}`); const issueDetails = await client.get(item.pull_request.url); - console.log(`\tMerge sha: ${issueDetails.data.merge_commit_sha}`); + console.log(`* ${item.title}, (#${item.number}), merge-sha: ${issueDetails.data.merge_commit_sha}`); + commands += `git cherry-pick -x ${issueDetails.data.merge_commit_sha}\n`; } + + console.log('--------------------------------------------------------------------'); + console.log('Commands (in order of how they should be executed)'); + console.log('--------------------------------------------------------------------'); + console.log(commands); }; export const cherryPickTask = new Task(); From a564a54b13ee00b04f8a7e02873230640721b486 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 20 May 2019 11:41:22 +0200 Subject: [PATCH 11/92] chore: mocks plugin loader for DataSourceSettingsPage tests (#17157) Properly mocks the plugin loader in DataSourceSettingsPage tests and by that removes console logs. --- .../datasources/settings/DataSourceSettingsPage.test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx index 6f9939e882b9..af3b4d9e786f 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx @@ -8,6 +8,12 @@ import { setDataSourceName, setIsDefault } from '../state/actions'; const pluginMock = new DataSourcePlugin({} as DataSourceConstructor); +jest.mock('app/features/plugins/plugin_loader', () => { + return { + importDataSourcePlugin: () => Promise.resolve(pluginMock), + }; +}); + const setup = (propOverrides?: object) => { const props: Props = { navModel: {} as NavModel, From bd5bcea5d04074e2c93ba400ab9ec787c48345f7 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Mon, 20 May 2019 12:13:32 +0200 Subject: [PATCH 12/92] alerting: fix a bunch of lint issues. (#17128) --- pkg/services/alerting/engine.go | 33 +++++++++-------- .../alerting/engine_integration_test.go | 5 +-- pkg/services/alerting/engine_test.go | 8 +++-- pkg/services/alerting/eval_context.go | 16 ++++++--- pkg/services/alerting/eval_context_test.go | 4 +-- pkg/services/alerting/eval_handler.go | 3 ++ pkg/services/alerting/extractor.go | 8 ++--- pkg/services/alerting/extractor_test.go | 36 +++++++++---------- pkg/services/alerting/interfaces.go | 7 ++-- pkg/services/alerting/models.go | 9 +++-- pkg/services/alerting/notifier.go | 15 ++++---- pkg/services/alerting/reader.go | 31 ++++------------ pkg/services/alerting/result_handler.go | 18 +++++----- pkg/services/alerting/rule.go | 20 ++++++++--- pkg/services/alerting/scheduler.go | 12 +++---- pkg/services/alerting/test_notification.go | 4 ++- pkg/services/alerting/test_rule.go | 2 ++ pkg/services/alerting/ticker.go | 2 +- 18 files changed, 122 insertions(+), 111 deletions(-) diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index c67b59839d79..f2fd002704ac 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -17,44 +17,43 @@ import ( "golang.org/x/sync/errgroup" ) +// AlertingService is the background process that +// schedules alert evaluations and makes sure notifications +// are sent. type AlertingService struct { RenderService rendering.Service `inject:""` - execQueue chan *Job - //clock clock.Clock + execQueue chan *Job ticker *Ticker - scheduler Scheduler - evalHandler EvalHandler - ruleReader RuleReader + scheduler scheduler + evalHandler evalHandler + ruleReader ruleReader log log.Logger - resultHandler ResultHandler + resultHandler resultHandler } func init() { registry.RegisterService(&AlertingService{}) } -func NewEngine() *AlertingService { - e := &AlertingService{} - e.Init() - return e -} - +// IsDisabled returns true if the alerting service is disable for this instance. func (e *AlertingService) IsDisabled() bool { return !setting.AlertingEnabled || !setting.ExecuteAlerts } +// Init initalizes the AlertingService. func (e *AlertingService) Init() error { e.ticker = NewTicker(time.Now(), time.Second*0, clock.New()) e.execQueue = make(chan *Job, 1000) - e.scheduler = NewScheduler() + e.scheduler = newScheduler() e.evalHandler = NewEvalHandler() - e.ruleReader = NewRuleReader() + e.ruleReader = newRuleReader() e.log = log.New("alerting.engine") - e.resultHandler = NewResultHandler(e.RenderService) + e.resultHandler = newResultHandler(e.RenderService) return nil } +// Run starts the alerting service background process. func (e *AlertingService) Run(ctx context.Context) error { alertGroup, ctx := errgroup.WithContext(ctx) alertGroup.Go(func() error { return e.alertingTicker(ctx) }) @@ -80,7 +79,7 @@ func (e *AlertingService) alertingTicker(grafanaCtx context.Context) error { case tick := <-e.ticker.C: // TEMP SOLUTION update rules ever tenth tick if tickIndex%10 == 0 { - e.scheduler.Update(e.ruleReader.Fetch()) + e.scheduler.Update(e.ruleReader.fetch()) } e.scheduler.Tick(tick, e.execQueue) @@ -211,7 +210,7 @@ func (e *AlertingService) processJob(attemptID int, attemptChan chan int, cancel // dont reuse the evalContext and get its own context. evalContext.Ctx = resultHandleCtx evalContext.Rule.State = evalContext.GetNewState() - e.resultHandler.Handle(evalContext) + e.resultHandler.handle(evalContext) span.Finish() e.log.Debug("Job Execution completed", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing, "attemptID", attemptID) close(attemptChan) diff --git a/pkg/services/alerting/engine_integration_test.go b/pkg/services/alerting/engine_integration_test.go index 3d54bdc3b4af..7d0d3360ad5a 100644 --- a/pkg/services/alerting/engine_integration_test.go +++ b/pkg/services/alerting/engine_integration_test.go @@ -17,7 +17,8 @@ import ( func TestEngineTimeouts(t *testing.T) { Convey("Alerting engine timeout tests", t, func() { - engine := NewEngine() + engine := &AlertingService{} + engine.Init() setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 engine.resultHandler = &FakeResultHandler{} @@ -89,7 +90,7 @@ func (handler *FakeCommonTimeoutHandler) Eval(evalContext *EvalContext) { evalContext.Error = errors.New("Fake evaluation timeout test failure; wrong response") } -func (handler *FakeCommonTimeoutHandler) Handle(evalContext *EvalContext) error { +func (handler *FakeCommonTimeoutHandler) handle(evalContext *EvalContext) error { // 1. prepare mock server path := "/resulthandle" srv := runBusyServer(path, handler.ServerBusySleepDuration) diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go index 2e2ed0c9b163..4ed317d982f4 100644 --- a/pkg/services/alerting/engine_test.go +++ b/pkg/services/alerting/engine_test.go @@ -6,9 +6,10 @@ import ( "math" "testing" + "time" + "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" - "time" ) type FakeEvalHandler struct { @@ -32,13 +33,14 @@ func (handler *FakeEvalHandler) Eval(evalContext *EvalContext) { type FakeResultHandler struct{} -func (handler *FakeResultHandler) Handle(evalContext *EvalContext) error { +func (handler *FakeResultHandler) handle(evalContext *EvalContext) error { return nil } func TestEngineProcessJob(t *testing.T) { Convey("Alerting engine job processing", t, func() { - engine := NewEngine() + engine := &AlertingService{} + engine.Init() setting.AlertingEvaluationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 7d9a9014086b..480303fa6d71 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +// EvalContext is the context object for an alert evaluation. type EvalContext struct { Firing bool IsTestRun bool @@ -33,6 +34,7 @@ type EvalContext struct { Ctx context.Context } +// NewEvalContext is the EvalContext constructor. func NewEvalContext(alertCtx context.Context, rule *Rule) *EvalContext { return &EvalContext{ Ctx: alertCtx, @@ -45,12 +47,14 @@ func NewEvalContext(alertCtx context.Context, rule *Rule) *EvalContext { } } +// StateDescription contains visual information about the alert state. type StateDescription struct { Color string Text string Data string } +// GetStateModel returns the `StateDescription` based on current state. func (c *EvalContext) GetStateModel() *StateDescription { switch c.Rule.State { case models.AlertStateOK: @@ -78,18 +82,21 @@ func (c *EvalContext) GetStateModel() *StateDescription { } } -func (c *EvalContext) ShouldUpdateAlertState() bool { +func (c *EvalContext) shouldUpdateAlertState() bool { return c.Rule.State != c.PrevAlertState } -func (a *EvalContext) GetDurationMs() float64 { - return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000) +// GetDurationMs returns the duration of the alert evaluation. +func (c *EvalContext) GetDurationMs() float64 { + return float64(c.EndTime.Nanosecond()-c.StartTime.Nanosecond()) / float64(1000000) } +// GetNotificationTitle returns the title of the alert rule including alert state. func (c *EvalContext) GetNotificationTitle() string { return "[" + c.GetStateModel().Text + "] " + c.Rule.Name } +// GetDashboardUID returns the dashboard uid for the alert rule. func (c *EvalContext) GetDashboardUID() (*models.DashboardRef, error) { if c.dashboardRef != nil { return c.dashboardRef, nil @@ -106,6 +113,7 @@ func (c *EvalContext) GetDashboardUID() (*models.DashboardRef, error) { const urlFormat = "%s?fullscreen&edit&tab=alert&panelId=%d&orgId=%d" +// GetRuleUrl returns the url to the dashboard containing the alert. func (c *EvalContext) GetRuleUrl() (string, error) { if c.IsTestRun { return setting.AppUrl, nil @@ -118,7 +126,7 @@ func (c *EvalContext) GetRuleUrl() (string, error) { return fmt.Sprintf(urlFormat, models.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil } -// GetNewState returns the new state from the alert rule evaluation +// GetNewState returns the new state from the alert rule evaluation. func (c *EvalContext) GetNewState() models.AlertStateType { ns := getNewStateInternal(c) if ns != models.AlertStateAlerting || c.Rule.For == 0 { diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index 06d3ae16ed80..76cbc90c640c 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -18,7 +18,7 @@ func TestStateIsUpdatedWhenNeeded(t *testing.T) { ctx.PrevAlertState = models.AlertStateOK ctx.Rule.State = models.AlertStateAlerting - if !ctx.ShouldUpdateAlertState() { + if !ctx.shouldUpdateAlertState() { t.Fatalf("expected should updated to be true") } }) @@ -27,7 +27,7 @@ func TestStateIsUpdatedWhenNeeded(t *testing.T) { ctx.PrevAlertState = models.AlertStateOK ctx.Rule.State = models.AlertStateOK - if ctx.ShouldUpdateAlertState() { + if ctx.shouldUpdateAlertState() { t.Fatalf("expected should updated to be false") } }) diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 22d172568f55..572345362a86 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -9,11 +9,13 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" ) +// DefaultEvalHandler is responsible for evaluating the alert rule. type DefaultEvalHandler struct { log log.Logger alertJobTimeout time.Duration } +// NewEvalHandler is the `DefaultEvalHandler` constructor. func NewEvalHandler() *DefaultEvalHandler { return &DefaultEvalHandler{ log: log.New("alerting.evalHandler"), @@ -21,6 +23,7 @@ func NewEvalHandler() *DefaultEvalHandler { } } +// Eval evaluated the alert rule. func (e *DefaultEvalHandler) Eval(context *EvalContext) { firing := true noDataFound := true diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 6bf5e786c198..6fb109d71a23 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/models" ) -// DashAlertExtractor extracts alerts from the dashboard json +// DashAlertExtractor extracts alerts from the dashboard json. type DashAlertExtractor struct { User *models.SignedInUser Dash *models.Dashboard @@ -19,7 +19,7 @@ type DashAlertExtractor struct { log log.Logger } -// NewDashAlertExtractor returns a new DashAlertExtractor +// NewDashAlertExtractor returns a new DashAlertExtractor. func NewDashAlertExtractor(dash *models.Dashboard, orgID int64, user *models.SignedInUser) *DashAlertExtractor { return &DashAlertExtractor{ User: user, @@ -207,7 +207,7 @@ func validateAlertRule(alert *models.Alert) bool { return alert.ValidToSave() } -// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data +// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data. func (e *DashAlertExtractor) GetAlerts() ([]*models.Alert, error) { return e.extractAlerts(validateAlertRule) } @@ -247,7 +247,7 @@ func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert } // ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id -// in the first validation pass +// in the first validation pass. func (e *DashAlertExtractor) ValidateAlerts() error { _, err := e.extractAlerts(func(alert *models.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 }) return err diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index 716ff746cd84..86f0725e4cec 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -52,10 +52,10 @@ func TestAlertRuleExtraction(t *testing.T) { So(err, ShouldBeNil) Convey("Extractor should not modify the original json", func() { - dashJson, err := simplejson.NewJson(json) + dashJSON, err := simplejson.NewJson(json) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) getTarget := func(j *simplejson.Json) string { rowObj := j.Get("rows").MustArray()[0] @@ -68,23 +68,23 @@ func TestAlertRuleExtraction(t *testing.T) { } Convey("Dashboard json rows.panels.alert.query.model.target should be empty", func() { - So(getTarget(dashJson), ShouldEqual, "") + So(getTarget(dashJSON), ShouldEqual, "") }) extractor := NewDashAlertExtractor(dash, 1, nil) _, _ = extractor.GetAlerts() Convey("Dashboard json should not be updated after extracting rules", func() { - So(getTarget(dashJson), ShouldEqual, "") + So(getTarget(dashJSON), ShouldEqual, "") }) }) Convey("Parsing and validating dashboard containing graphite alerts", func() { - dashJson, err := simplejson.NewJson(json) + dashJSON, err := simplejson.NewJson(json) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -147,12 +147,12 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Panels missing id should return error", func() { - panelWithoutId, err := ioutil.ReadFile("./testdata/panels-missing-id.json") + panelWithoutID, err := ioutil.ReadFile("./testdata/panels-missing-id.json") So(err, ShouldBeNil) - dashJson, err := simplejson.NewJson(panelWithoutId) + dashJSON, err := simplejson.NewJson(panelWithoutID) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) extractor := NewDashAlertExtractor(dash, 1, nil) _, err = extractor.GetAlerts() @@ -163,12 +163,12 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Panel with id set to zero should return error", func() { - panelWithIdZero, err := ioutil.ReadFile("./testdata/panel-with-id-0.json") + panelWithIDZero, err := ioutil.ReadFile("./testdata/panel-with-id-0.json") So(err, ShouldBeNil) - dashJson, err := simplejson.NewJson(panelWithIdZero) + dashJSON, err := simplejson.NewJson(panelWithIDZero) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) extractor := NewDashAlertExtractor(dash, 1, nil) _, err = extractor.GetAlerts() @@ -182,9 +182,9 @@ func TestAlertRuleExtraction(t *testing.T) { json, err := ioutil.ReadFile("./testdata/v5-dashboard.json") So(err, ShouldBeNil) - dashJson, err := simplejson.NewJson(json) + dashJSON, err := simplejson.NewJson(json) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -211,9 +211,9 @@ func TestAlertRuleExtraction(t *testing.T) { json, err := ioutil.ReadFile("./testdata/influxdb-alert.json") So(err, ShouldBeNil) - dashJson, err := simplejson.NewJson(json) + dashJSON, err := simplejson.NewJson(json) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -240,10 +240,10 @@ func TestAlertRuleExtraction(t *testing.T) { json, err := ioutil.ReadFile("./testdata/collapsed-panels.json") So(err, ShouldBeNil) - dashJson, err := simplejson.NewJson(json) + dashJSON, err := simplejson.NewJson(json) So(err, ShouldBeNil) - dash := models.NewDashboardFromJson(dashJson) + dash := models.NewDashboardFromJson(dashJSON) extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() diff --git a/pkg/services/alerting/interfaces.go b/pkg/services/alerting/interfaces.go index bd7ca0877699..be364d6f4cc2 100644 --- a/pkg/services/alerting/interfaces.go +++ b/pkg/services/alerting/interfaces.go @@ -7,15 +7,16 @@ import ( "github.com/grafana/grafana/pkg/models" ) -type EvalHandler interface { +type evalHandler interface { Eval(evalContext *EvalContext) } -type Scheduler interface { +type scheduler interface { Tick(time time.Time, execQueue chan *Job) Update(rules []*Rule) } +// Notifier is responsible for sending alert notifications. type Notifier interface { Notify(evalContext *EvalContext) error GetType() string @@ -48,6 +49,7 @@ func (notifiers notifierStateSlice) ShouldUploadImage() bool { return false } +// ConditionResult is the result of a condition evaluation. type ConditionResult struct { Firing bool NoDataFound bool @@ -55,6 +57,7 @@ type ConditionResult struct { EvalMatches []*EvalMatch } +// Condition is responsible for evaluating an alert condition. type Condition interface { Eval(result *EvalContext) (*ConditionResult, error) } diff --git a/pkg/services/alerting/models.go b/pkg/services/alerting/models.go index bbd8b98eea27..e802ab5a7a17 100644 --- a/pkg/services/alerting/models.go +++ b/pkg/services/alerting/models.go @@ -2,6 +2,8 @@ package alerting import "github.com/grafana/grafana/pkg/components/null" +// Job holds state about when the alert rule should +// be evaluated. type Job struct { Offset int64 OffsetWait bool @@ -10,18 +12,15 @@ type Job struct { Rule *Rule } +// ResultLogEntry represents log data for the alert evaluation. type ResultLogEntry struct { Message string Data interface{} } +// EvalMatch represents the serie violating the threshold. type EvalMatch struct { Value null.Float `json:"value"` Metric string `json:"metric"` Tags map[string]string `json:"tags"` } - -type Level struct { - Operator string - Value float64 -} diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index a2824da4a67c..de6d74239ae7 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +// NotifierPlugin holds meta information about a notifier. type NotifierPlugin struct { Type string `json:"type"` Name string `json:"name"` @@ -21,11 +22,7 @@ type NotifierPlugin struct { Factory NotifierFactory `json:"-"` } -type NotificationService interface { - SendIfNeeded(context *EvalContext) error -} - -func NewNotificationService(renderService rendering.Service) NotificationService { +func newNotificationService(renderService rendering.Service) *notificationService { return ¬ificationService{ log: log.New("alerting.notifier"), renderService: renderService, @@ -156,8 +153,8 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) { return nil } -func (n *notificationService) getNeededNotifiers(orgId int64, notificationUids []string, evalContext *EvalContext) (notifierStateSlice, error) { - query := &models.GetAlertNotificationsWithUidToSendQuery{OrgId: orgId, Uids: notificationUids} +func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids []string, evalContext *EvalContext) (notifierStateSlice, error) { + query := &models.GetAlertNotificationsWithUidToSendQuery{OrgId: orgID, Uids: notificationUids} if err := bus.Dispatch(query); err != nil { return nil, err @@ -194,7 +191,7 @@ func (n *notificationService) getNeededNotifiers(orgId int64, notificationUids [ return result, nil } -// InitNotifier instantiate a new notifier based on the model +// InitNotifier instantiate a new notifier based on the model. func InitNotifier(model *models.AlertNotification) (Notifier, error) { notifierPlugin, found := notifierFactories[model.Type] if !found { @@ -204,6 +201,7 @@ func InitNotifier(model *models.AlertNotification) (Notifier, error) { return notifierPlugin.Factory(model) } +// NotifierFactory is a signature for creating notifiers. type NotifierFactory func(notification *models.AlertNotification) (Notifier, error) var notifierFactories = make(map[string]*NotifierPlugin) @@ -213,6 +211,7 @@ func RegisterNotifier(plugin *NotifierPlugin) { notifierFactories[plugin.Type] = plugin } +// GetNotifiers returns a list of metadata about available notifiers. func GetNotifiers() []*NotifierPlugin { list := make([]*NotifierPlugin, 0) diff --git a/pkg/services/alerting/reader.go b/pkg/services/alerting/reader.go index 0df826e9ea5d..c8020510ef63 100644 --- a/pkg/services/alerting/reader.go +++ b/pkg/services/alerting/reader.go @@ -2,7 +2,6 @@ package alerting import ( "sync" - "time" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" @@ -10,35 +9,24 @@ import ( "github.com/grafana/grafana/pkg/models" ) -type RuleReader interface { - Fetch() []*Rule +type ruleReader interface { + fetch() []*Rule } -type DefaultRuleReader struct { +type defaultRuleReader struct { sync.RWMutex - serverPosition int - clusterSize int - log log.Logger + log log.Logger } -func NewRuleReader() *DefaultRuleReader { - ruleReader := &DefaultRuleReader{ +func newRuleReader() *defaultRuleReader { + ruleReader := &defaultRuleReader{ log: log.New("alerting.ruleReader"), } - go ruleReader.initReader() return ruleReader } -func (arr *DefaultRuleReader) initReader() { - heartbeat := time.NewTicker(time.Second * 10) - - for range heartbeat.C { - arr.heartbeat() - } -} - -func (arr *DefaultRuleReader) Fetch() []*Rule { +func (arr *defaultRuleReader) fetch() []*Rule { cmd := &models.GetAllAlertsQuery{} if err := bus.Dispatch(cmd); err != nil { @@ -58,8 +46,3 @@ func (arr *DefaultRuleReader) Fetch() []*Rule { metrics.M_Alerting_Active_Alerts.Set(float64(len(res))) return res } - -func (arr *DefaultRuleReader) heartbeat() { - arr.clusterSize = 1 - arr.serverPosition = 1 -} diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index d82421d7506d..7141c6cec46a 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -13,23 +13,23 @@ import ( "github.com/grafana/grafana/pkg/services/rendering" ) -type ResultHandler interface { - Handle(evalContext *EvalContext) error +type resultHandler interface { + handle(evalContext *EvalContext) error } -type DefaultResultHandler struct { - notifier NotificationService +type defaultResultHandler struct { + notifier *notificationService log log.Logger } -func NewResultHandler(renderService rendering.Service) *DefaultResultHandler { - return &DefaultResultHandler{ +func newResultHandler(renderService rendering.Service) *defaultResultHandler { + return &defaultResultHandler{ log: log.New("alerting.resultHandler"), - notifier: NewNotificationService(renderService), + notifier: newNotificationService(renderService), } } -func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { +func (handler *defaultResultHandler) handle(evalContext *EvalContext) error { executionError := "" annotationData := simplejson.New() @@ -45,7 +45,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { } metrics.M_Alerting_Result_State.WithLabelValues(string(evalContext.Rule.State)).Inc() - if evalContext.ShouldUpdateAlertState() { + if evalContext.shouldUpdateAlertState() { handler.log.Info("New state change", "alertId", evalContext.Rule.Id, "newState", evalContext.Rule.State, "prev state", evalContext.PrevAlertState) cmd := &models.SetAlertStateCommand{ diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index b5b6f4660e64..422148bc42f7 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -12,10 +12,14 @@ import ( ) var ( + // ErrFrequencyCannotBeZeroOrLess frequency cannot be below zero ErrFrequencyCannotBeZeroOrLess = errors.New(`"evaluate every" cannot be zero or below`) - ErrFrequencyCouldNotBeParsed = errors.New(`"evaluate every" field could not be parsed`) + + // ErrFrequencyCouldNotBeParsed frequency cannot be parsed + ErrFrequencyCouldNotBeParsed = errors.New(`"evaluate every" field could not be parsed`) ) +// Rule is the in-memory version of an alert rule. type Rule struct { Id int64 OrgId int64 @@ -35,6 +39,8 @@ type Rule struct { StateChanges int64 } +// ValidationError is a typed error with meta data +// about the validation error. type ValidationError struct { Reason string Err error @@ -65,8 +71,8 @@ func (e ValidationError) Error() string { } var ( - ValueFormatRegex = regexp.MustCompile(`^\d+`) - UnitFormatRegex = regexp.MustCompile(`\w{1}$`) + valueFormatRegex = regexp.MustCompile(`^\d+`) + unitFormatRegex = regexp.MustCompile(`\w{1}$`) ) var unitMultiplier = map[string]int{ @@ -79,7 +85,7 @@ var unitMultiplier = map[string]int{ func getTimeDurationStringToSeconds(str string) (int64, error) { multiplier := 1 - matches := ValueFormatRegex.FindAllString(str, 1) + matches := valueFormatRegex.FindAllString(str, 1) if len(matches) <= 0 { return 0, ErrFrequencyCouldNotBeParsed @@ -94,7 +100,7 @@ func getTimeDurationStringToSeconds(str string) (int64, error) { return 0, ErrFrequencyCannotBeZeroOrLess } - unit := UnitFormatRegex.FindAllString(str, 1)[0] + unit := unitFormatRegex.FindAllString(str, 1)[0] if val, ok := unitMultiplier[unit]; ok { multiplier = val @@ -103,6 +109,8 @@ func getTimeDurationStringToSeconds(str string) (int64, error) { return int64(value * multiplier), nil } +// NewRuleFromDBAlert mappes an db version of +// alert to an in-memory version. func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) { model := &Rule{} model.Id = ruleDef.Id @@ -159,10 +167,12 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) { return model, nil } +// ConditionFactory is the function signature for creating `Conditions`. type ConditionFactory func(model *simplejson.Json, index int) (Condition, error) var conditionFactories = make(map[string]ConditionFactory) +// RegisterCondition adds support for alerting conditions. func RegisterCondition(typeName string, factory ConditionFactory) { conditionFactories[typeName] = factory } diff --git a/pkg/services/alerting/scheduler.go b/pkg/services/alerting/scheduler.go index 9a0769d25f24..62ef35298fcb 100644 --- a/pkg/services/alerting/scheduler.go +++ b/pkg/services/alerting/scheduler.go @@ -8,19 +8,19 @@ import ( "github.com/grafana/grafana/pkg/models" ) -type SchedulerImpl struct { +type schedulerImpl struct { jobs map[int64]*Job log log.Logger } -func NewScheduler() Scheduler { - return &SchedulerImpl{ +func newScheduler() scheduler { + return &schedulerImpl{ jobs: make(map[int64]*Job), log: log.New("alerting.scheduler"), } } -func (s *SchedulerImpl) Update(rules []*Rule) { +func (s *schedulerImpl) Update(rules []*Rule) { s.log.Debug("Scheduling update", "ruleCount", len(rules)) jobs := make(map[int64]*Job) @@ -48,7 +48,7 @@ func (s *SchedulerImpl) Update(rules []*Rule) { s.jobs = jobs } -func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *Job) { +func (s *schedulerImpl) Tick(tickTime time.Time, execQueue chan *Job) { now := tickTime.Unix() for _, job := range s.jobs { @@ -72,7 +72,7 @@ func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *Job) { } } -func (s *SchedulerImpl) enqueue(job *Job, execQueue chan *Job) { +func (s *schedulerImpl) enqueue(job *Job, execQueue chan *Job) { s.log.Debug("Scheduler: Putting job on to exec queue", "name", job.Rule.Name, "id", job.Rule.Id) execQueue <- job } diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index 5cb18d2b42ee..3651fffa68bf 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -11,6 +11,8 @@ import ( "github.com/grafana/grafana/pkg/models" ) +// NotificationTestCommand initiates an test +// execution of an alert notification. type NotificationTestCommand struct { State models.AlertStateType Name string @@ -27,7 +29,7 @@ func init() { } func handleNotificationTestCommand(cmd *NotificationTestCommand) error { - notifier := NewNotificationService(nil).(*notificationService) + notifier := newNotificationService(nil) model := &models.AlertNotification{ Name: cmd.Name, diff --git a/pkg/services/alerting/test_rule.go b/pkg/services/alerting/test_rule.go index 736dd287dbec..7a44845614b1 100644 --- a/pkg/services/alerting/test_rule.go +++ b/pkg/services/alerting/test_rule.go @@ -9,6 +9,8 @@ import ( "github.com/grafana/grafana/pkg/models" ) +// AlertTestCommand initiates an test evaluation +// of an alert rule. type AlertTestCommand struct { Dashboard *simplejson.Json PanelId int64 diff --git a/pkg/services/alerting/ticker.go b/pkg/services/alerting/ticker.go index 8cee2653ee9d..9702a2cda63a 100644 --- a/pkg/services/alerting/ticker.go +++ b/pkg/services/alerting/ticker.go @@ -6,7 +6,7 @@ import ( "github.com/benbjohnson/clock" ) -// ticker is a ticker to power the alerting scheduler. it's like a time.Ticker, except: +// Ticker is a ticker to power the alerting scheduler. it's like a time.Ticker, except: // * it doesn't drop ticks for slow receivers, rather, it queues up. so that callers are in control to instrument what's going on. // * it automatically ticks every second, which is the right thing in our current design // * it ticks on second marks or very shortly after. this provides a predictable load pattern From db48ec1f08bfdfc53d087d42e4bdb8e397e10dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 20 May 2019 13:28:23 +0200 Subject: [PATCH 13/92] Explore: Adds Live option for supported datasources (#17062) * Wip: Initial commit * Refactor: Adds support in Loki datasource for streaming * Refactor: Adds Live option to RefreshInterval * Refactor: Adds styles to logrows * Style: Reverses the order of Explore layout on Live * Refactor: Adds LiveLogs component * Tests: Adds tests for epics * Style: Adds animation to Live in RefreshPicker * Refactor: Adds ElapsedTime and progress line to LiveLogs * Style: Adds specific colors to each theme * Refactor: Adds support for Lokis new API * Fix: Adds null to resulting empty array * Refactor: Limits the rate of incoming messages from websockets * Refactor: Throttles messages instead for simplicity * Refactor: Optimizes row processing performance * Refactor: Adds stop live button * Fix: Fixes so that RefreshPicker shows the correct value when called programmatically * Refactor: Merges with master and removes a console.log * Refactor: Sorts rows in correct order and fixes minor UI issues * Refactor: Adds minor improvments to sorting and container size --- package.json | 1 + .../src/components/Button/AbstractButton.tsx | 6 + .../RefreshPicker/RefreshPicker.tsx | 9 +- .../RefreshPicker/_RefreshPicker.scss | 18 + .../src/components/Select/ButtonSelect.tsx | 2 +- .../components/SetInterval/SetInterval.tsx | 15 +- packages/grafana-ui/src/types/datasource.ts | 5 + .../grafana-ui/src/utils/moment_wrapper.ts | 3 + public/app/core/utils/explore.ts | 36 +- public/app/features/explore/ElapsedTime.tsx | 33 +- public/app/features/explore/Explore.tsx | 3 + .../app/features/explore/ExploreToolbar.tsx | 30 +- public/app/features/explore/LiveLogs.tsx | 118 ++++ public/app/features/explore/LogRow.tsx | 1 - public/app/features/explore/LogsContainer.tsx | 34 +- public/app/features/explore/state/actions.ts | 11 + .../app/features/explore/state/epics.test.ts | 550 ++++++++++++++++++ public/app/features/explore/state/epics.ts | 159 +++++ public/app/features/explore/state/reducers.ts | 81 ++- .../app/plugins/datasource/loki/datasource.ts | 36 ++ public/app/store/configureStore.ts | 21 +- public/app/types/explore.ts | 3 + public/sass/pages/_explore.scss | 13 + public/test/core/redux/epicTester.ts | 60 ++ yarn.lock | 5 + 25 files changed, 1226 insertions(+), 27 deletions(-) create mode 100644 public/app/features/explore/LiveLogs.tsx create mode 100644 public/app/features/explore/state/epics.test.ts create mode 100644 public/app/features/explore/state/epics.ts create mode 100644 public/test/core/redux/epicTester.ts diff --git a/package.json b/package.json index 35eb408ef02f..30721ddaf6af 100644 --- a/package.json +++ b/package.json @@ -229,6 +229,7 @@ "react-window": "1.7.1", "redux": "4.0.1", "redux-logger": "3.0.6", + "redux-observable": "1.1.0", "redux-thunk": "2.3.0", "remarkable": "1.7.1", "reselect": "4.0.0", diff --git a/packages/grafana-ui/src/components/Button/AbstractButton.tsx b/packages/grafana-ui/src/components/Button/AbstractButton.tsx index 38f225273adb..ee59272794e3 100644 --- a/packages/grafana-ui/src/components/Button/AbstractButton.tsx +++ b/packages/grafana-ui/src/components/Button/AbstractButton.tsx @@ -75,6 +75,12 @@ const getButtonStyles = (theme: GrafanaTheme, size: ButtonSize, variant: ButtonV iconDistance = theme.spacing.xs; height = theme.height.sm; break; + case ButtonSize.Medium: + padding = `${theme.spacing.sm} ${theme.spacing.md}`; + fontSize = theme.typography.size.md; + iconDistance = theme.spacing.sm; + height = theme.height.md; + break; case ButtonSize.Large: padding = `${theme.spacing.md} ${theme.spacing.lg}`; fontSize = theme.typography.size.lg; diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index 2046a5a50cee..60a8973ca095 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -5,7 +5,9 @@ import { Tooltip } from '../Tooltip/Tooltip'; import { ButtonSelect } from '../Select/ButtonSelect'; export const offOption = { label: 'Off', value: '' }; +export const liveOption = { label: 'Live', value: 'LIVE' }; export const defaultIntervals = ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d']; +export const isLive = (refreshInterval: string): boolean => refreshInterval === liveOption.value; export interface Props { intervals?: string[]; @@ -13,6 +15,7 @@ export interface Props { onIntervalChanged: (interval: string) => void; value?: string; tooltip: string; + hasLiveOption?: boolean; } export class RefreshPicker extends PureComponent { @@ -36,6 +39,9 @@ export class RefreshPicker extends PureComponent { intervalsToOptions = (intervals: string[] = defaultIntervals): Array> => { const options = intervals.map(interval => ({ label: interval, value: interval })); + if (this.props.hasLiveOption) { + options.unshift(liveOption); + } options.unshift(offOption); return options; }; @@ -57,6 +63,7 @@ export class RefreshPicker extends PureComponent { const cssClasses = classNames({ 'refresh-picker': true, 'refresh-picker--off': selectedValue.label === offOption.label, + 'refresh-picker--live': selectedValue === liveOption, }); return ( @@ -68,7 +75,7 @@ export class RefreshPicker extends PureComponent { extends PureComponent> { isSearchable={false} options={options} onChange={this.onChange} - defaultValue={value} + value={value} maxMenuHeight={maxMenuHeight} components={combinedComponents} className="gf-form-select-box-button-select" diff --git a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx index b44a49f9603e..cdcc1f406bbb 100644 --- a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx +++ b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx @@ -1,8 +1,10 @@ import { PureComponent } from 'react'; -import { interval, Subscription, empty, Subject } from 'rxjs'; +import { interval, Subscription, Subject, of, NEVER } from 'rxjs'; import { tap, switchMap } from 'rxjs/operators'; +import _ from 'lodash'; import { stringToMs } from '../../utils/string'; +import { isLive } from '../RefreshPicker/RefreshPicker'; interface Props { func: () => any; // TODO @@ -24,7 +26,10 @@ export class SetInterval extends PureComponent { this.subscription = this.propsSubject .pipe( switchMap(props => { - return props.loading ? empty() : interval(stringToMs(props.interval)); + if (isLive(props.interval)) { + return of({}); + } + return props.loading ? NEVER : interval(stringToMs(props.interval)); }), tap(() => this.props.func()) ) @@ -32,7 +37,11 @@ export class SetInterval extends PureComponent { this.propsSubject.next(this.props); } - componentDidUpdate() { + componentDidUpdate(prevProps: Props) { + if (_.isEqual(prevProps, this.props)) { + return; + } + this.propsSubject.next(this.props); } diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 759ee05d681f..36506969117f 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -84,6 +84,7 @@ export interface DataSourcePluginMeta extends PluginMeta { category?: string; queryOptions?: PluginMetaQueryOptions; sort?: number; + supportsStreaming?: boolean; } interface PluginMetaQueryOptions { @@ -157,6 +158,10 @@ export abstract class DataSourceApi< */ abstract query(options: DataQueryRequest, observer?: DataStreamObserver): Promise; + convertToStreamTargets?(options: DataQueryRequest): Array<{ url: string; refId: string }>; + + resultToSeriesData?(data: any, refId: string): SeriesData[]; + /** * Test & verify datasource settings & connection details */ diff --git a/packages/grafana-ui/src/utils/moment_wrapper.ts b/packages/grafana-ui/src/utils/moment_wrapper.ts index 063c427372b6..755f92a899af 100644 --- a/packages/grafana-ui/src/utils/moment_wrapper.ts +++ b/packages/grafana-ui/src/utils/moment_wrapper.ts @@ -43,6 +43,9 @@ export interface DateTimeLocale { export interface DateTimeDuration { asHours: () => number; + hours: () => number; + minutes: () => number; + seconds: () => number; } export interface DateTime extends Object { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 7a19fd5a822d..e82ba9c34094 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -33,8 +33,9 @@ import { QueryOptions, ResultGetter, } from 'app/types/explore'; -import { LogsDedupStrategy, seriesDataToLogsModel } from 'app/core/logs_model'; +import { LogsDedupStrategy, seriesDataToLogsModel, LogsModel, LogRowModel } from 'app/core/logs_model'; import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; +import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -529,3 +530,36 @@ export const getRefIds = (value: any): string[] => { return _.uniq(_.flatten(refIds)); }; + +const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => { + if (a.timeEpochMs < b.timeEpochMs) { + return -1; + } + + if (a.timeEpochMs > b.timeEpochMs) { + return 1; + } + + return 0; +}; + +const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => { + if (a.timeEpochMs > b.timeEpochMs) { + return -1; + } + + if (a.timeEpochMs < b.timeEpochMs) { + return 1; + } + + return 0; +}; + +export const sortLogsResult = (logsResult: LogsModel, refreshInterval: string) => { + const rows = logsResult ? logsResult.rows : []; + const live = isLive(refreshInterval); + live ? rows.sort(sortInAscendingOrder) : rows.sort(sortInDescendingOrder); + const result: LogsModel = logsResult ? { ...logsResult, rows } : { hasUniqueLabels: false, rows }; + + return result; +}; diff --git a/public/app/features/explore/ElapsedTime.tsx b/public/app/features/explore/ElapsedTime.tsx index a2d941515cd6..7f649c497035 100644 --- a/public/app/features/explore/ElapsedTime.tsx +++ b/public/app/features/explore/ElapsedTime.tsx @@ -1,8 +1,20 @@ import React, { PureComponent } from 'react'; +import { toDuration } from '@grafana/ui/src/utils/moment_wrapper'; const INTERVAL = 150; -export default class ElapsedTime extends PureComponent { +export interface Props { + time?: number; + renderCount?: number; + className?: string; + humanize?: boolean; +} + +export interface State { + elapsed: number; +} + +export default class ElapsedTime extends PureComponent { offset: number; timer: number; @@ -21,12 +33,17 @@ export default class ElapsedTime extends PureComponent { this.setState({ elapsed }); }; - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: Props) { if (nextProps.time) { clearInterval(this.timer); } else if (this.props.time) { this.start(); } + + if (nextProps.renderCount) { + clearInterval(this.timer); + this.start(); + } } componentDidMount() { @@ -39,8 +56,16 @@ export default class ElapsedTime extends PureComponent { render() { const { elapsed } = this.state; - const { className, time } = this.props; + const { className, time, humanize } = this.props; const value = (time || elapsed) / 1000; - return {value.toFixed(1)}s; + let displayValue = `${value.toFixed(1)}s`; + if (humanize) { + const duration = toDuration(elapsed); + const hours = duration.hours(); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + displayValue = hours ? `${hours}h ${minutes}m ${seconds}s` : minutes ? ` ${minutes}m ${seconds}s` : `${seconds}s`; + } + return {displayValue}; } } diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 21e047399cd6..eef4b8b21dc9 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -87,6 +87,7 @@ interface ExploreProps { initialUI: ExploreUIState; queryErrors: DataQueryError[]; mode: ExploreMode; + isLive: boolean; } /** @@ -315,6 +316,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { update, queryErrors, mode, + isLive, } = item; const { datasource, queries, range: urlRange, ui } = (urlState || {}) as ExploreUrlState; @@ -340,6 +342,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { initialUI, queryErrors, mode, + isLive, }; } diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index f37a2e391ce4..9d6c4a1d3d96 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -39,15 +39,20 @@ const createResponsiveButton = (options: { buttonClassName?: string; iconClassName?: string; iconSide?: IconSide; + disabled?: boolean; }) => { const defaultOptions = { iconSide: IconSide.left, }; const props = { ...options, defaultOptions }; - const { title, onClick, buttonClassName, iconClassName, splitted, iconSide } = props; + const { title, onClick, buttonClassName, iconClassName, splitted, iconSide, disabled } = props; return ( -
) : null}
- - - + {!isLive && ( + + + + )} {refreshInterval && }
@@ -227,7 +240,8 @@ export class UnConnectedExploreToolbar extends PureComponent { title: 'Run Query', onClick: this.onRunQuery, buttonClassName: 'navbar-button--secondary', - iconClassName: loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon', + iconClassName: + loading && !isLive ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon', iconSide: IconSide.right, })}
@@ -252,11 +266,13 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps tableIsLoading, supportedModes, mode, + isLive, } = exploreItem; const selectedDatasource = datasourceInstance ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name) : undefined; const loading = graphIsLoading || logIsLoading || tableIsLoading; + const hasLiveOption = datasourceInstance && datasourceInstance.convertToStreamTargets ? true : false; const supportedModeOptions: Array> = []; let selectedModeOption = null; @@ -296,6 +312,8 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps refreshInterval, supportedModeOptions, selectedModeOption, + hasLiveOption, + isLive, }; }; diff --git a/public/app/features/explore/LiveLogs.tsx b/public/app/features/explore/LiveLogs.tsx new file mode 100644 index 000000000000..120b136b127c --- /dev/null +++ b/public/app/features/explore/LiveLogs.tsx @@ -0,0 +1,118 @@ +import React, { PureComponent } from 'react'; +import { css, cx } from 'emotion'; +import { Themeable, withTheme, GrafanaTheme, selectThemeVariant, LinkButton } from '@grafana/ui'; + +import { LogsModel, LogRowModel } from 'app/core/logs_model'; +import ElapsedTime from './ElapsedTime'; +import { ButtonSize, ButtonVariant } from '@grafana/ui/src/components/Button/AbstractButton'; + +const getStyles = (theme: GrafanaTheme) => ({ + logsRowsLive: css` + label: logs-rows-live; + display: flex; + flex-flow: column nowrap; + height: 65vh; + overflow-y: auto; + :first-child { + margin-top: auto !important; + } + `, + logsRowFresh: css` + label: logs-row-fresh; + color: ${theme.colors.text}; + background-color: ${selectThemeVariant({ light: theme.colors.gray6, dark: theme.colors.gray1 }, theme.type)}; + `, + logsRowOld: css` + label: logs-row-old; + opacity: 0.8; + `, + logsRowsIndicator: css` + font-size: ${theme.typography.size.md}; + padding: ${theme.spacing.sm} 0; + display: flex; + align-items: center; + `, +}); + +export interface Props extends Themeable { + logsResult?: LogsModel; + stopLive: () => void; +} + +export interface State { + renderCount: number; +} + +class LiveLogs extends PureComponent { + private liveEndDiv: HTMLDivElement = null; + + constructor(props: Props) { + super(props); + this.state = { renderCount: 0 }; + } + + componentDidUpdate(prevProps: Props) { + const prevRows: LogRowModel[] = prevProps.logsResult ? prevProps.logsResult.rows : []; + const rows: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : []; + + if (prevRows !== rows) { + this.setState({ + renderCount: this.state.renderCount + 1, + }); + } + + if (this.liveEndDiv) { + this.liveEndDiv.scrollIntoView(false); + } + } + + render() { + const { theme } = this.props; + const { renderCount } = this.state; + const styles = getStyles(theme); + const rowsToRender: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : []; + + return ( + <> +
+ {rowsToRender.map((row: any, index) => { + return ( +
+
+ {row.timeLocal} +
+
{row.entry}
+
+ ); + })} +
{ + this.liveEndDiv = element; + if (this.liveEndDiv) { + this.liveEndDiv.scrollIntoView(false); + } + }} + /> +
+
+ + Last line received: ago + + + Stop Live + +
+ + ); + } +} + +export const LiveLogsWithTheme = withTheme(LiveLogs); diff --git a/public/app/features/explore/LogRow.tsx b/public/app/features/explore/LogRow.tsx index b36163303687..719c4eba816b 100644 --- a/public/app/features/explore/LogRow.tsx +++ b/public/app/features/explore/LogRow.tsx @@ -228,7 +228,6 @@ export class LogRow extends PureComponent { const styles = this.state.showContext ? cx(logRowStyles, getLogRowWithContextStyles(theme, this.state).row) : logRowStyles; - console.log(styles); return (
{showDuplicates && ( diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 777f97e8600d..484dd993cecd 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -19,9 +19,11 @@ import { StoreState } from 'app/types'; import { changeDedupStrategy, changeTime } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; -import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes'; +import { toggleLogLevelAction, changeRefreshIntervalAction } from 'app/features/explore/state/actionTypes'; import { deduplicatedLogsSelector, exploreItemUIStateSelector } from 'app/features/explore/state/selectors'; import { getTimeZone } from '../profile/state/selectors'; +import { LiveLogsWithTheme } from './LiveLogs'; +import { offOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; interface LogsContainerProps { datasourceInstance: DataSourceApi | null; @@ -43,6 +45,8 @@ interface LogsContainerProps { hiddenLogLevels: Set; width: number; changeTime: typeof changeTime; + isLive: boolean; + stopLive: typeof changeRefreshIntervalAction; } export class LogsContainer extends PureComponent { @@ -56,6 +60,11 @@ export class LogsContainer extends PureComponent { changeTime(exploreId, range); }; + onStopLive = () => { + const { exploreId } = this.props; + this.props.stopLive({ exploreId, refreshInterval: offOption.value }); + }; + handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => { this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); }; @@ -81,7 +90,6 @@ export class LogsContainer extends PureComponent { render() { const { exploreId, - loading, logsHighlighterExpressions, logsResult, @@ -95,8 +103,17 @@ export class LogsContainer extends PureComponent { scanRange, width, hiddenLogLevels, + isLive, } = this.props; + if (isLive) { + return ( + + + + ); + } + return ( { function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range, datasourceInstance } = item; + const { + logsHighlighterExpressions, + logsResult, + logIsLoading, + scanning, + scanRange, + range, + datasourceInstance, + isLive, + } = item; const loading = logIsLoading; const { dedupStrategy } = exploreItemUIStateSelector(item); const hiddenLogLevels = new Set(item.hiddenLogLevels); @@ -147,6 +173,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { hiddenLogLevels, dedupedResult, datasourceInstance, + isLive, }; } @@ -154,6 +181,7 @@ const mapDispatchToProps = { changeDedupStrategy, toggleLogLevelAction, changeTime, + stopLive: changeRefreshIntervalAction, }; export default hot(module)( diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index c1157e01c6ae..e06218988265 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -91,6 +91,7 @@ import { LogsDedupStrategy } from 'app/core/logs_model'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; import { toDataQueryError } from 'app/features/dashboard/state/PanelQueryState'; +import { startSubscriptionsAction, subscriptionDataReceivedAction } from 'app/features/explore/state/epics'; /** * Updates UI state and save it to the URL @@ -583,6 +584,16 @@ function runQueriesForType( const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning, history } = getState().explore[ exploreId ]; + + if (resultType === 'Logs' && datasourceInstance.convertToStreamTargets) { + dispatch( + startSubscriptionsAction({ + exploreId, + dataReceivedActionCreator: subscriptionDataReceivedAction, + }) + ); + } + const datasourceId = datasourceInstance.meta.id; const transaction = buildQueryTransaction(queries, resultType, queryOptions, range, queryIntervals, scanning); dispatch(queryStartAction({ exploreId, resultType, rowIndex: 0, transaction })); diff --git a/public/app/features/explore/state/epics.test.ts b/public/app/features/explore/state/epics.test.ts new file mode 100644 index 000000000000..fbfb934a43ae --- /dev/null +++ b/public/app/features/explore/state/epics.test.ts @@ -0,0 +1,550 @@ +import { liveOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; +import { DataSourceApi, DataQuery } from '@grafana/ui/src/types/datasource'; + +import { ExploreId, ExploreState } from 'app/types'; +import { actionCreatorFactory } from 'app/core/redux/actionCreatorFactory'; +import { + startSubscriptionsEpic, + startSubscriptionsAction, + SubscriptionDataReceivedPayload, + startSubscriptionAction, + startSubscriptionEpic, + limitMessageRatePayloadAction, +} from './epics'; +import { makeExploreItemState } from './reducers'; +import { epicTester } from 'test/core/redux/epicTester'; +import { + resetExploreAction, + updateDatasourceInstanceAction, + changeRefreshIntervalAction, + clearQueriesAction, +} from './actionTypes'; + +const setup = (options: any = {}) => { + const url = '/api/datasources/proxy/20/api/prom/tail?query=%7Bfilename%3D%22%2Fvar%2Flog%2Fdocker.log%22%7D'; + const webSocketUrl = 'ws://localhost' + url; + const refId = options.refId || 'A'; + const exploreId = ExploreId.left; + const datasourceInstance: DataSourceApi = options.datasourceInstance || { + id: 1337, + query: jest.fn(), + name: 'test', + testDatasource: jest.fn(), + convertToStreamTargets: () => [ + { + url, + refId, + }, + ], + resultToSeriesData: data => [data], + }; + const itemState = makeExploreItemState(); + const explore: Partial = { + [exploreId]: { + ...itemState, + datasourceInstance, + refreshInterval: options.refreshInterval || liveOption.value, + queries: [{} as DataQuery], + }, + }; + const state: any = { + explore, + }; + + return { url, state, refId, webSocketUrl, exploreId }; +}; + +const dataReceivedActionCreator = actionCreatorFactory('test').create(); + +describe('startSubscriptionsEpic', () => { + describe('when startSubscriptionsAction is dispatched', () => { + describe('and datasource supports convertToStreamTargets', () => { + describe('and explore is Live', () => { + it('then correct actions should be dispatched', () => { + const { state, refId, webSocketUrl, exploreId } = setup(); + + epicTester(startSubscriptionsEpic, state) + .whenActionIsDispatched(startSubscriptionsAction({ exploreId, dataReceivedActionCreator })) + .thenResultingActionsEqual( + startSubscriptionAction({ + exploreId, + refId, + url: webSocketUrl, + dataReceivedActionCreator, + }) + ); + }); + }); + + describe('and explore is not Live', () => { + it('then no actions should be dispatched', () => { + const { state, exploreId } = setup({ refreshInterval: '10s' }); + + epicTester(startSubscriptionsEpic, state) + .whenActionIsDispatched(startSubscriptionsAction({ exploreId, dataReceivedActionCreator })) + .thenNoActionsWhereDispatched(); + }); + }); + }); + + describe('and datasource does not support streaming', () => { + it('then no actions should be dispatched', () => { + const { state, exploreId } = setup({ datasourceInstance: {} }); + + epicTester(startSubscriptionsEpic, state) + .whenActionIsDispatched(startSubscriptionsAction({ exploreId, dataReceivedActionCreator })) + .thenNoActionsWhereDispatched(); + }); + }); + }); +}); + +describe('startSubscriptionEpic', () => { + describe('when startSubscriptionAction is dispatched', () => { + describe('and datasource supports resultToSeriesData', () => { + it('then correct actions should be dispatched', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ url: webSocketUrl, refId, exploreId, dataReceivedActionCreator }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + + describe('and datasource does not support resultToSeriesData', () => { + it('then no actions should be dispatched', () => { + const { state, webSocketUrl, refId, exploreId } = setup({ datasourceInstance: {} }); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ url: webSocketUrl, refId, exploreId, dataReceivedActionCreator }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenNoActionsWhereDispatched(); + }); + }); + }); + + describe('when an subscription is active', () => { + describe('and resetExploreAction is dispatched', () => { + it('then subscription should be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ url: webSocketUrl, refId, exploreId, dataReceivedActionCreator }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(resetExploreAction()) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + + describe('and updateDatasourceInstanceAction is dispatched', () => { + describe('and exploreId matches the websockets', () => { + it('then subscription should be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(updateDatasourceInstanceAction({ exploreId, datasourceInstance: null })) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + + describe('and exploreId does not match the websockets', () => { + it('then subscription should not be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched( + updateDatasourceInstanceAction({ exploreId: ExploreId.right, datasourceInstance: null }) + ) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + }); + + describe('and changeRefreshIntervalAction is dispatched', () => { + describe('and exploreId matches the websockets', () => { + describe('and refreshinterval is not "Live"', () => { + it('then subscription should be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId, refreshInterval: '10s' })) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + + describe('and refreshinterval is "Live"', () => { + it('then subscription should not be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId, refreshInterval: liveOption.value })) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + }); + + describe('and exploreId does not match the websockets', () => { + it('then subscription should not be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId: ExploreId.right, refreshInterval: '10s' })) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + }); + + describe('and clearQueriesAction is dispatched', () => { + describe('and exploreId matches the websockets', () => { + it('then subscription should be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(clearQueriesAction({ exploreId })) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + + describe('and exploreId does not match the websockets', () => { + it('then subscription should not be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched(clearQueriesAction({ exploreId: ExploreId.right })) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + }); + + describe('and startSubscriptionAction is dispatched', () => { + describe('and exploreId and refId matches the websockets', () => { + it('then subscription should be unsubscribed', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + // This looks like we haven't stopped the subscription but we actually started the same again + ); + }); + + describe('and exploreId or refId does not match the websockets', () => { + it('then subscription should not be unsubscribed and another websocket is started', () => { + const { state, webSocketUrl, refId, exploreId } = setup(); + + epicTester(startSubscriptionEpic, state) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId, + exploreId, + dataReceivedActionCreator, + }) + ) + .thenNoActionsWhereDispatched() + .whenWebSocketReceivesData({ data: [1, 2, 3] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }) + ) + .whenActionIsDispatched( + startSubscriptionAction({ + url: webSocketUrl, + refId: 'B', + exploreId, + dataReceivedActionCreator, + }) + ) + .whenWebSocketReceivesData({ data: [4, 5, 6] }) + .thenResultingActionsEqual( + limitMessageRatePayloadAction({ + exploreId, + data: { data: [1, 2, 3] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }), + limitMessageRatePayloadAction({ + exploreId, + data: { data: [4, 5, 6] } as any, + dataReceivedActionCreator, + }) + ); + }); + }); + }); + }); + }); +}); diff --git a/public/app/features/explore/state/epics.ts b/public/app/features/explore/state/epics.ts new file mode 100644 index 000000000000..95ce32f11cf5 --- /dev/null +++ b/public/app/features/explore/state/epics.ts @@ -0,0 +1,159 @@ +import { Epic } from 'redux-observable'; +import { NEVER } from 'rxjs'; +import { takeUntil, mergeMap, tap, filter, map, throttleTime } from 'rxjs/operators'; + +import { StoreState, ExploreId } from 'app/types'; +import { ActionOf, ActionCreator, actionCreatorFactory } from '../../../core/redux/actionCreatorFactory'; +import { config } from '../../../core/config'; +import { + updateDatasourceInstanceAction, + resetExploreAction, + changeRefreshIntervalAction, + clearQueriesAction, +} from './actionTypes'; +import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; +import { SeriesData } from '@grafana/ui/src/types/data'; +import { EpicDependencies } from 'app/store/configureStore'; + +const convertToWebSocketUrl = (url: string) => { + const protocol = window.location.protocol === 'https' ? 'wss://' : 'ws://'; + let backend = `${protocol}${window.location.host}${config.appSubUrl}`; + if (backend.endsWith('/')) { + backend = backend.slice(0, backend.length - 1); + } + return `${backend}${url}`; +}; + +export interface StartSubscriptionsPayload { + exploreId: ExploreId; + dataReceivedActionCreator: ActionCreator; +} + +export const startSubscriptionsAction = actionCreatorFactory( + 'explore/START_SUBSCRIPTIONS' +).create(); + +export interface StartSubscriptionPayload { + url: string; + refId: string; + exploreId: ExploreId; + dataReceivedActionCreator: ActionCreator; +} + +export const startSubscriptionAction = actionCreatorFactory( + 'explore/START_SUBSCRIPTION' +).create(); + +export interface SubscriptionDataReceivedPayload { + data: SeriesData; + exploreId: ExploreId; +} + +export const subscriptionDataReceivedAction = actionCreatorFactory( + 'explore/SUBSCRIPTION_DATA_RECEIVED' +).create(); + +export interface LimitMessageRatePayload { + data: SeriesData; + exploreId: ExploreId; + dataReceivedActionCreator: ActionCreator; +} + +export const limitMessageRatePayloadAction = actionCreatorFactory( + 'explore/LIMIT_MESSAGE_RATE_PAYLOAD' +).create(); + +export const startSubscriptionsEpic: Epic, ActionOf, StoreState> = (action$, state$) => { + return action$.ofType(startSubscriptionsAction.type).pipe( + mergeMap((action: ActionOf) => { + const { exploreId, dataReceivedActionCreator } = action.payload; + const { datasourceInstance, queries, refreshInterval } = state$.value.explore[exploreId]; + + if (!datasourceInstance || !datasourceInstance.convertToStreamTargets) { + return NEVER; //do nothing if datasource does not support streaming + } + + if (!refreshInterval || !isLive(refreshInterval)) { + return NEVER; //do nothing if refresh interval is not 'LIVE' + } + + const request: any = { targets: queries }; + return datasourceInstance.convertToStreamTargets(request).map(target => + startSubscriptionAction({ + url: convertToWebSocketUrl(target.url), + refId: target.refId, + exploreId, + dataReceivedActionCreator, + }) + ); + }) + ); +}; + +export const startSubscriptionEpic: Epic, ActionOf, StoreState, EpicDependencies> = ( + action$, + state$, + { getWebSocket } +) => { + return action$.ofType(startSubscriptionAction.type).pipe( + mergeMap((action: ActionOf) => { + const { url, exploreId, refId, dataReceivedActionCreator } = action.payload; + return getWebSocket(url).pipe( + takeUntil( + action$ + .ofType( + startSubscriptionAction.type, + resetExploreAction.type, + updateDatasourceInstanceAction.type, + changeRefreshIntervalAction.type, + clearQueriesAction.type + ) + .pipe( + filter(action => { + if (action.type === resetExploreAction.type) { + return true; // stops all subscriptions if user navigates away + } + + if (action.type === updateDatasourceInstanceAction.type && action.payload.exploreId === exploreId) { + return true; // stops subscriptions if user changes data source + } + + if (action.type === changeRefreshIntervalAction.type && action.payload.exploreId === exploreId) { + return !isLive(action.payload.refreshInterval); // stops subscriptions if user changes refresh interval away from 'Live' + } + + if (action.type === clearQueriesAction.type && action.payload.exploreId === exploreId) { + return true; // stops subscriptions if user clears all queries + } + + return action.payload.exploreId === exploreId && action.payload.refId === refId; + }), + tap(value => console.log('Stopping subscription', value)) + ) + ), + mergeMap((result: any) => { + const { datasourceInstance } = state$.value.explore[exploreId]; + + if (!datasourceInstance || !datasourceInstance.resultToSeriesData) { + return [null]; //do nothing if datasource does not support streaming + } + + return datasourceInstance + .resultToSeriesData(result, refId) + .map(data => limitMessageRatePayloadAction({ exploreId, data, dataReceivedActionCreator })); + }), + filter(action => action !== null) + ); + }) + ); +}; + +export const limitMessageRateEpic: Epic, ActionOf, StoreState, EpicDependencies> = action$ => { + return action$.ofType(limitMessageRatePayloadAction.type).pipe( + throttleTime(1), + map((action: ActionOf) => { + const { exploreId, data, dataReceivedActionCreator } = action.payload; + return dataReceivedActionCreator({ exploreId, data }); + }) + ); +}; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 58fcc301c874..0d7bc298e156 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -7,6 +7,7 @@ import { parseUrlState, DEFAULT_UI_STATE, generateNewKeyAndAddRefIdIfMissing, + sortLogsResult, } from 'app/core/utils/explore'; import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore'; import { DataQuery } from '@grafana/ui/src/types'; @@ -55,6 +56,9 @@ import { import { updateLocation } from 'app/core/actions/location'; import { LocationUpdate } from 'app/types'; import TableModel from 'app/core/table_model'; +import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; +import { subscriptionDataReceivedAction, startSubscriptionAction } from './epics'; +import { LogsModel, seriesDataToLogsModel } from 'app/core/logs_model'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -109,6 +113,7 @@ export const makeExploreItemState = (): ExploreItemState => ({ latency: 0, supportedModes: [], mode: null, + isLive: false, }); /** @@ -184,9 +189,17 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: changeRefreshIntervalAction, mapper: (state, action): ExploreItemState => { const { refreshInterval } = action.payload; + const live = isLive(refreshInterval); + const logsResult = sortLogsResult(state.logsResult, refreshInterval); + return { ...state, refreshInterval: refreshInterval, + graphIsLoading: live ? true : false, + tableIsLoading: live ? true : false, + logIsLoading: live ? true : false, + isLive: live, + logsResult, }; }, }) @@ -376,22 +389,80 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: querySuccessAction, mapper: (state, action): ExploreItemState => { - const { queryIntervals } = state; + const { queryIntervals, refreshInterval } = state; const { result, resultType, latency } = action.payload; const results = calculateResultsFromQueryTransactions(result, resultType, queryIntervals.intervalMs); + const live = isLive(refreshInterval); + + if (live) { + return state; + } + return { ...state, graphResult: resultType === 'Graph' ? results.graphResult : state.graphResult, tableResult: resultType === 'Table' ? results.tableResult : state.tableResult, - logsResult: resultType === 'Logs' ? results.logsResult : state.logsResult, + logsResult: + resultType === 'Logs' + ? sortLogsResult(results.logsResult, refreshInterval) + : sortLogsResult(state.logsResult, refreshInterval), latency, - graphIsLoading: false, - logIsLoading: false, - tableIsLoading: false, + graphIsLoading: live ? true : false, + logIsLoading: live ? true : false, + tableIsLoading: live ? true : false, + showingStartPage: false, + update: makeInitialUpdateState(), + }; + }, + }) + .addMapper({ + filter: startSubscriptionAction, + mapper: (state): ExploreItemState => { + const logsResult = sortLogsResult(state.logsResult, state.refreshInterval); + + return { + ...state, + logsResult, + graphIsLoading: true, + logIsLoading: true, + tableIsLoading: true, + showingStartPage: false, update: makeInitialUpdateState(), }; }, }) + .addMapper({ + filter: subscriptionDataReceivedAction, + mapper: (state, action): ExploreItemState => { + const { queryIntervals, refreshInterval } = state; + const { data } = action.payload; + const live = isLive(refreshInterval); + + if (live) { + return state; + } + + const newResults = seriesDataToLogsModel([data], queryIntervals.intervalMs); + const rowsInState = sortLogsResult(state.logsResult, state.refreshInterval).rows; + + const processedRows = []; + for (const row of rowsInState) { + processedRows.push({ ...row, fresh: false }); + } + for (const row of newResults.rows) { + processedRows.push({ ...row, fresh: true }); + } + + const rows = processedRows.slice(processedRows.length - 1000, 1000); + + const logsResult: LogsModel = state.logsResult ? { ...state.logsResult, rows } : { hasUniqueLabels: false, rows }; + + return { + ...state, + logsResult, + }; + }, + }) .addMapper({ filter: removeQueryRowAction, mapper: (state, action): ExploreItemState => { diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 906f5cb2fc8b..932c9c0a6e2b 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -68,6 +68,42 @@ export class LokiDatasource extends DataSourceApi { return this.backendSrv.datasourceRequest(req); } + convertToStreamTargets = (options: DataQueryRequest): Array<{ url: string; refId: string }> => { + return options.targets + .filter(target => target.expr && !target.hide) + .map(target => { + const interpolated = this.templateSrv.replace(target.expr); + const { query, regexp } = parseQuery(interpolated); + const refId = target.refId; + const baseUrl = this.instanceSettings.url; + const params = serializeParams({ query, regexp }); + const url = `${baseUrl}/api/prom/tail?${params}`; + + return { + url, + refId, + }; + }); + }; + + resultToSeriesData = (data: any, refId: string): SeriesData[] => { + const toSeriesData = (stream: any, refId: string) => ({ + ...logStreamToSeriesData(stream), + refId, + }); + + if (data.streams) { + // new Loki API purposed in https://github.com/grafana/loki/pull/590 + const series: SeriesData[] = []; + for (const stream of data.streams || []) { + series.push(toSeriesData(stream, refId)); + } + return series; + } + + return [toSeriesData(data, refId)]; + }; + prepareQueryTarget(target: LokiQuery, options: DataQueryRequest) { const interpolated = this.templateSrv.replace(target.expr); const { query, regexp } = parseQuery(interpolated); diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 2638587e96d1..e561a7f5e592 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,5 +1,6 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; +import { combineEpics, createEpicMiddleware } from 'redux-observable'; // import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; @@ -14,6 +15,8 @@ import usersReducers from 'app/features/users/state/reducers'; import userReducers from 'app/features/profile/state/reducers'; import organizationReducers from 'app/features/org/state/reducers'; import { setStore } from './store'; +import { startSubscriptionsEpic, startSubscriptionEpic, limitMessageRateEpic } from 'app/features/explore/state/epics'; +import { WebSocketSubject, webSocket } from 'rxjs/webSocket'; const rootReducers = { ...sharedReducers, @@ -34,6 +37,18 @@ export function addRootReducer(reducers) { Object.assign(rootReducers, ...reducers); } +export const rootEpic: any = combineEpics(startSubscriptionsEpic, startSubscriptionEpic, limitMessageRateEpic); + +export interface EpicDependencies { + getWebSocket: (urlConfigOrSource: string) => WebSocketSubject; +} + +const dependencies: EpicDependencies = { + getWebSocket: webSocket, +}; + +const epicMiddleware = createEpicMiddleware({ dependencies }); + export function configureStore() { const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; @@ -41,8 +56,10 @@ export function configureStore() { if (process.env.NODE_ENV !== 'production') { // DEV builds we had the logger middleware - setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); + setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, epicMiddleware)))); } else { - setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); + setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, epicMiddleware)))); } + + epicMiddleware.run(rootEpic); } diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index b7ed76dee824..7bb8695e1da3 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -259,9 +259,12 @@ export interface ExploreItemState { update: ExploreUpdateState; queryErrors: DataQueryError[]; + latency: number; supportedModes: ExploreMode[]; mode: ExploreMode; + + isLive: boolean; } export interface ExploreUpdateState { diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 495c9abd7835..fba4a7333462 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -138,7 +138,13 @@ } .explore { + display: flex; flex: 1 1 auto; + flex-direction: column; +} + +.explore.explore-live { + flex-direction: column-reverse; } .explore + .explore { @@ -146,9 +152,16 @@ } .explore-container { + display: flex; + flex: 1 1 auto; + flex-direction: column; padding: $dashboard-padding; } +.explore-container.explore-live { + flex-direction: column-reverse; +} + .explore-wrapper { display: flex; diff --git a/public/test/core/redux/epicTester.ts b/public/test/core/redux/epicTester.ts new file mode 100644 index 000000000000..5c2a42469435 --- /dev/null +++ b/public/test/core/redux/epicTester.ts @@ -0,0 +1,60 @@ +import { Epic, ActionsObservable, StateObservable } from 'redux-observable'; +import { Subject } from 'rxjs'; +import { WebSocketSubject } from 'rxjs/webSocket'; + +import { ActionOf } from 'app/core/redux/actionCreatorFactory'; +import { StoreState } from 'app/types/store'; +import { EpicDependencies } from 'app/store/configureStore'; + +export const epicTester = ( + epic: Epic, ActionOf, StoreState, EpicDependencies>, + state?: StoreState +) => { + const resultingActions: Array> = []; + const action$ = new Subject>(); + const state$ = new Subject(); + const actionObservable$ = new ActionsObservable(action$); + const stateObservable$ = new StateObservable(state$, state || ({} as StoreState)); + const websockets$: Array> = []; + const dependencies: EpicDependencies = { + getWebSocket: () => { + const webSocket$ = new Subject(); + websockets$.push(webSocket$); + return webSocket$ as WebSocketSubject; + }, + }; + epic(actionObservable$, stateObservable$, dependencies).subscribe({ next: action => resultingActions.push(action) }); + + const whenActionIsDispatched = (action: ActionOf) => { + action$.next(action); + + return instance; + }; + + const whenWebSocketReceivesData = (data: any) => { + websockets$.forEach(websocket$ => websocket$.next(data)); + + return instance; + }; + + const thenResultingActionsEqual = (...actions: Array>) => { + expect(resultingActions).toEqual(actions); + + return instance; + }; + + const thenNoActionsWhereDispatched = () => { + expect(resultingActions).toEqual([]); + + return instance; + }; + + const instance = { + whenActionIsDispatched, + whenWebSocketReceivesData, + thenResultingActionsEqual, + thenNoActionsWhereDispatched, + }; + + return instance; +}; diff --git a/yarn.lock b/yarn.lock index 3163fadbb361..e092d5045691 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14735,6 +14735,11 @@ redux-mock-store@1.5.3: dependencies: lodash.isplainobject "^4.0.6" +redux-observable@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/redux-observable/-/redux-observable-1.1.0.tgz#323a8fe53e89fdb519be2807b55f08e21c13e6f1" + integrity sha512-G0nxgmTZwTK3Z3KoQIL8VQu9n0YCUwEP3wc3zxKQ8zAZm+iYkoZvBqAnBJfLi4EsD1E64KR4s4jFH/dFXpV9Og== + redux-thunk@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" From a0f5923b958883c0912415ab47d2e0d51631ace5 Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Mon, 20 May 2019 15:57:52 +0300 Subject: [PATCH 14/92] LDAP: add tests for initialBind (#17132) * LDAP: add tests for initialBind * LDAP: clarify comment for Login() --- pkg/services/ldap/ldap.go | 10 ++-- pkg/services/ldap/ldap_helpers_test.go | 65 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index 20953db97c31..bd44b60c2bed 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -122,13 +122,13 @@ func (server *Server) Close() { server.connection.Close() } -// Login intialBinds the user, search it and then serialize it +// Log in user by searching and serializing it func (server *Server) Login(query *models.LoginUserQuery) ( *models.ExternalUserInfo, error, ) { // Perform initial authentication - err := server.intialBind(query.Username, query.Password) + err := server.initialBind(query.Username, query.Password) if err != nil { return nil, err } @@ -159,7 +159,7 @@ func (server *Server) Login(query *models.LoginUserQuery) ( // Add adds stuff to LDAP func (server *Server) Add(dn string, values map[string][]string) error { - err := server.intialBind( + err := server.initialBind( server.config.BindDN, server.config.BindPassword, ) @@ -190,7 +190,7 @@ func (server *Server) Add(dn string, values map[string][]string) error { // Remove removes stuff from LDAP func (server *Server) Remove(dn string) error { - err := server.intialBind( + err := server.initialBind( server.config.BindDN, server.config.BindPassword, ) @@ -381,7 +381,7 @@ func (server *Server) secondBind( return nil } -func (server *Server) intialBind(username, userPassword string) error { +func (server *Server) initialBind(username, userPassword string) error { if server.config.BindPassword != "" || server.config.BindDN == "" { userPassword = server.config.BindPassword server.requireSecondBind = true diff --git a/pkg/services/ldap/ldap_helpers_test.go b/pkg/services/ldap/ldap_helpers_test.go index d995d0fd6992..3f25633460c6 100644 --- a/pkg/services/ldap/ldap_helpers_test.go +++ b/pkg/services/ldap/ldap_helpers_test.go @@ -75,6 +75,71 @@ func TestLDAPHelpers(t *testing.T) { }) }) + Convey("initialBind", t, func() { + Convey("Given bind dn and password configured", func() { + connection := &mockConnection{} + var actualUsername, actualPassword string + connection.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + server := &Server{ + connection: connection, + config: &ServerConfig{ + BindDN: "cn=%s,o=users,dc=grafana,dc=org", + BindPassword: "bindpwd", + }, + } + err := server.initialBind("user", "pwd") + So(err, ShouldBeNil) + So(server.requireSecondBind, ShouldBeTrue) + So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "bindpwd") + }) + + Convey("Given bind dn configured", func() { + connection := &mockConnection{} + var actualUsername, actualPassword string + connection.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + server := &Server{ + connection: connection, + config: &ServerConfig{ + BindDN: "cn=%s,o=users,dc=grafana,dc=org", + }, + } + err := server.initialBind("user", "pwd") + So(err, ShouldBeNil) + So(server.requireSecondBind, ShouldBeFalse) + So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "pwd") + }) + + Convey("Given empty bind dn and password", func() { + connection := &mockConnection{} + unauthenticatedBindWasCalled := false + var actualUsername string + connection.unauthenticatedBindProvider = func(username string) error { + unauthenticatedBindWasCalled = true + actualUsername = username + return nil + } + server := &Server{ + connection: connection, + config: &ServerConfig{}, + } + err := server.initialBind("user", "pwd") + So(err, ShouldBeNil) + So(server.requireSecondBind, ShouldBeTrue) + So(unauthenticatedBindWasCalled, ShouldBeTrue) + So(actualUsername, ShouldBeEmpty) + }) + }) + Convey("serverBind()", t, func() { Convey("Given bind dn and password configured", func() { connection := &mockConnection{} From bfa7c3d9635d197d1b90d43cbb540caeb1835296 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Mon, 20 May 2019 15:23:06 +0200 Subject: [PATCH 15/92] alerting: golint fixes for alert notifiers. (#17167) --- pkg/services/alerting/notifiers/base.go | 18 +++++- pkg/services/alerting/notifiers/base_test.go | 8 +-- pkg/services/alerting/notifiers/dingding.go | 56 ++++++++-------- .../alerting/notifiers/dingding_test.go | 7 +- pkg/services/alerting/notifiers/email.go | 21 +++--- pkg/services/alerting/notifiers/googlechat.go | 33 +++++----- .../alerting/notifiers/googlechat_test.go | 7 +- pkg/services/alerting/notifiers/hipchat.go | 37 ++++++----- .../alerting/notifiers/hipchat_test.go | 13 ++-- pkg/services/alerting/notifiers/kafka.go | 21 +++--- pkg/services/alerting/notifiers/kafka_test.go | 1 - pkg/services/alerting/notifiers/line.go | 28 ++++---- pkg/services/alerting/notifiers/line_test.go | 1 - pkg/services/alerting/notifiers/opsgenie.go | 55 ++++++++-------- .../alerting/notifiers/opsgenie_test.go | 2 +- pkg/services/alerting/notifiers/pagerduty.go | 28 ++++---- pkg/services/alerting/notifiers/pushover.go | 54 ++++++++-------- .../alerting/notifiers/pushover_test.go | 2 +- pkg/services/alerting/notifiers/sensu.go | 32 ++++++---- pkg/services/alerting/notifiers/sensu_test.go | 2 +- pkg/services/alerting/notifiers/slack.go | 64 ++++++++++--------- pkg/services/alerting/notifiers/slack_test.go | 8 +-- pkg/services/alerting/notifiers/teams.go | 22 ++++--- pkg/services/alerting/notifiers/teams_test.go | 5 +- pkg/services/alerting/notifiers/telegram.go | 62 +++++++++--------- pkg/services/alerting/notifiers/threema.go | 4 ++ .../alerting/notifiers/threema_test.go | 1 - pkg/services/alerting/notifiers/victorops.go | 22 +++---- pkg/services/alerting/notifiers/webhook.go | 32 ++++++---- .../alerting/notifiers/webhook_test.go | 2 +- 30 files changed, 356 insertions(+), 292 deletions(-) diff --git a/pkg/services/alerting/notifiers/base.go b/pkg/services/alerting/notifiers/base.go index 6bd375b53bcb..3ebe23c2d1b3 100644 --- a/pkg/services/alerting/notifiers/base.go +++ b/pkg/services/alerting/notifiers/base.go @@ -13,10 +13,11 @@ const ( triggMetrString = "Triggered metrics:\n\n" ) +// NotifierBase is the base implementation of a notifier. type NotifierBase struct { Name string Type string - Uid string + UID string IsDeault bool UploadImage bool SendReminder bool @@ -26,6 +27,7 @@ type NotifierBase struct { log log.Logger } +// NewNotifierBase returns a new `NotifierBase`. func NewNotifierBase(model *models.AlertNotification) NotifierBase { uploadImage := true value, exist := model.Settings.CheckGet("uploadImage") @@ -34,7 +36,7 @@ func NewNotifierBase(model *models.AlertNotification) NotifierBase { } return NotifierBase{ - Uid: model.Uid, + UID: model.Uid, Name: model.Name, IsDeault: model.IsDefault, Type: model.Type, @@ -108,30 +110,40 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC return true } +// GetType returns the notifier type. func (n *NotifierBase) GetType() string { return n.Type } +// NeedsImage returns true if an image is expected in the notification. func (n *NotifierBase) NeedsImage() bool { return n.UploadImage } +// GetNotifierUid returns the notifier `uid`. func (n *NotifierBase) GetNotifierUid() string { - return n.Uid + return n.UID } +// GetIsDefault returns true if the notifiers should +// be used for all alerts. func (n *NotifierBase) GetIsDefault() bool { return n.IsDeault } +// GetSendReminder returns true if reminders should be sent. func (n *NotifierBase) GetSendReminder() bool { return n.SendReminder } +// GetDisableResolveMessage returns true if ok alert notifications +// should be skipped. func (n *NotifierBase) GetDisableResolveMessage() bool { return n.DisableResolveMessage } +// GetFrequency returns the freqency for how often +// alerts should be evaluated. func (n *NotifierBase) GetFrequency() time.Duration { return n.Frequency } diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 84294bfb29d5..799a843de2d4 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -178,24 +178,24 @@ func TestShouldSendAlertNotification(t *testing.T) { func TestBaseNotifier(t *testing.T) { Convey("default constructor for notifiers", t, func() { - bJson := simplejson.New() + bJSON := simplejson.New() model := &models.AlertNotification{ Uid: "1", Name: "name", Type: "email", - Settings: bJson, + Settings: bJSON, } Convey("can parse false value", func() { - bJson.Set("uploadImage", false) + bJSON.Set("uploadImage", false) base := NewNotifierBase(model) So(base.UploadImage, ShouldBeFalse) }) Convey("can parse true value", func() { - bJson.Set("uploadImage", true) + bJSON.Set("uploadImage", true) base := NewNotifierBase(model) So(base.UploadImage, ShouldBeTrue) diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go index 45ce24c9aaa1..a418adc7e651 100644 --- a/pkg/services/alerting/notifiers/dingding.go +++ b/pkg/services/alerting/notifiers/dingding.go @@ -12,8 +12,8 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" ) -const DefaultDingdingMsgType = "link" -const DingdingOptionsTemplate = ` +const defaultDingdingMsgType = "link" +const dingdingOptionsTemplate = `

DingDing settings

Url @@ -21,7 +21,7 @@ const DingdingOptionsTemplate = `
MessageType - +
` @@ -30,57 +30,59 @@ func init() { Type: "dingding", Name: "DingDing", Description: "Sends HTTP POST request to DingDing", - Factory: NewDingDingNotifier, - OptionsTemplate: DingdingOptionsTemplate, + Factory: newDingDingNotifier, + OptionsTemplate: dingdingOptionsTemplate, }) } -func NewDingDingNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func newDingDingNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} } - msgType := model.Settings.Get("msgType").MustString(DefaultDingdingMsgType) + msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType) return &DingDingNotifier{ NotifierBase: NewNotifierBase(model), MsgType: msgType, - Url: url, + URL: url, log: log.New("alerting.notifier.dingding"), }, nil } +// DingDingNotifier is responsible for sending alert notifications to ding ding. type DingDingNotifier struct { NotifierBase MsgType string - Url string + URL string log log.Logger } -func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Sending dingding") +// Notify sends the alert notification to dingding. +func (dd *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { + dd.log.Info("Sending dingding") - messageUrl, err := evalContext.GetRuleUrl() + messageURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed to get messageUrl", "error", err, "dingding", this.Name) - messageUrl = "" + dd.log.Error("Failed to get messageUrl", "error", err, "dingding", dd.Name) + messageURL = "" } q := url.Values{ "pc_slide": {"false"}, - "url": {messageUrl}, + "url": {messageURL}, } // Use special link to auto open the message url outside of Dingding // Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9 - messageUrl = "dingtalk://dingtalkclient/page/link?" + q.Encode() + messageURL = "dingtalk://dingtalkclient/page/link?" + q.Encode() - this.log.Info("messageUrl:" + messageUrl) + dd.log.Info("messageUrl:" + messageURL) message := evalContext.Rule.Message - picUrl := evalContext.ImagePublicUrl + picURL := evalContext.ImagePublicUrl title := evalContext.GetNotificationTitle() if message == "" { message = title @@ -91,10 +93,10 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { } var bodyStr string - if this.MsgType == "actionCard" { + if dd.MsgType == "actionCard" { // Embed the pic into the markdown directly because actionCard doesn't have a picUrl field - if picUrl != "" { - message = "![](" + picUrl + ")\\n\\n" + message + if picURL != "" { + message = "![](" + picURL + ")\\n\\n" + message } bodyStr = `{ @@ -103,7 +105,7 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { "text": "` + strings.Replace(message, `"`, "'", -1) + `", "title": "` + strings.Replace(title, `"`, "'", -1) + `", "singleTitle": "More", - "singleURL": "` + messageUrl + `" + "singleURL": "` + messageURL + `" } }` } else { @@ -112,8 +114,8 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { "link": { "text": "` + message + `", "title": "` + title + `", - "picUrl": "` + picUrl + `", - "messageUrl": "` + messageUrl + `" + "picUrl": "` + picURL + `", + "messageUrl": "` + messageURL + `" } }` } @@ -121,7 +123,7 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON, err := simplejson.NewJson([]byte(bodyStr)) if err != nil { - this.log.Error("Failed to create Json data", "error", err, "dingding", this.Name) + dd.log.Error("Failed to create Json data", "error", err, "dingding", dd.Name) } body, err := bodyJSON.MarshalJSON() @@ -130,12 +132,12 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { } cmd := &models.SendWebhookSync{ - Url: this.Url, + Url: dd.URL, Body: string(body), } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send DingDing", "error", err, "dingding", this.Name) + dd.log.Error("Failed to send DingDing", "error", err, "dingding", dd.Name) return err } diff --git a/pkg/services/alerting/notifiers/dingding_test.go b/pkg/services/alerting/notifiers/dingding_test.go index 7d645d7efb62..09101af2f8f6 100644 --- a/pkg/services/alerting/notifiers/dingding_test.go +++ b/pkg/services/alerting/notifiers/dingding_test.go @@ -20,7 +20,7 @@ func TestDingDingNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewDingDingNotifier(model) + _, err := newDingDingNotifier(model) So(err, ShouldNotBeNil) }) @@ -34,14 +34,13 @@ func TestDingDingNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewDingDingNotifier(model) + not, err := newDingDingNotifier(model) notifier := not.(*DingDingNotifier) So(err, ShouldBeNil) So(notifier.Name, ShouldEqual, "dingding_testing") So(notifier.Type, ShouldEqual, "dingding") - So(notifier.Url, ShouldEqual, "https://www.google.com") + So(notifier.URL, ShouldEqual, "https://www.google.com") }) - }) } diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index 61b7b11d893c..44a6b97653ea 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -30,12 +30,16 @@ func init() { }) } +// EmailNotifier is responsible for sending +// alert notifications over email. type EmailNotifier struct { NotifierBase Addresses []string log log.Logger } +// NewEmailNotifier is the constructor function +// for the EmailNotifier. func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) { addressesString := model.Settings.Get("addresses").MustString() @@ -59,12 +63,13 @@ func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error }, nil } -func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Sending alert notification to", "addresses", this.Addresses) +// Notify sends the alert notification. +func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { + en.log.Info("Sending alert notification to", "addresses", en.Addresses) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + en.log.Error("Failed get rule link", "error", err) return err } @@ -83,13 +88,13 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { "StateModel": evalContext.GetStateModel(), "Message": evalContext.Rule.Message, "Error": error, - "RuleUrl": ruleUrl, + "RuleUrl": ruleURL, "ImageLink": "", "EmbeddedImage": "", "AlertPageUrl": setting.AppUrl + "alerting", "EvalMatches": evalContext.EvalMatches, }, - To: this.Addresses, + To: en.Addresses, Template: "alert_notification.html", EmbededFiles: []string{}, }, @@ -108,9 +113,9 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { err = bus.DispatchCtx(evalContext.Ctx, cmd) if err != nil { - this.log.Error("Failed to send alert notification email", "error", err) + en.log.Error("Failed to send alert notification email", "error", err) return err } - return nil + return nil } diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go index c00089e0dc57..a7e452991b04 100644 --- a/pkg/services/alerting/notifiers/googlechat.go +++ b/pkg/services/alerting/notifiers/googlechat.go @@ -18,7 +18,7 @@ func init() { Name: "Google Hangouts Chat", Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message " + "format (https://developers.google.com/hangouts/chat/reference/message-formats/).", - Factory: NewGoogleChatNotifier, + Factory: newGoogleChatNotifier, OptionsTemplate: `

Google Hangouts Chat settings

@@ -29,7 +29,7 @@ func init() { }) } -func NewGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func newGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} @@ -37,14 +37,16 @@ func NewGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, return &GoogleChatNotifier{ NotifierBase: NewNotifierBase(model), - Url: url, + URL: url, log: log.New("alerting.notifier.googlechat"), }, nil } +// GoogleChatNotifier is responsible for sending +// alert notifications to Google chat. type GoogleChatNotifier struct { NotifierBase - Url string + URL string log log.Logger } @@ -90,7 +92,7 @@ type imageWidget struct { } type image struct { - ImageUrl string `json:"imageUrl"` + ImageURL string `json:"imageUrl"` } type button struct { @@ -107,19 +109,20 @@ type onClick struct { } type openLink struct { - Url string `json:"url"` + URL string `json:"url"` } -func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Executing Google Chat notification") +// Notify send an alert notification to Google Chat. +func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error { + gcn.log.Info("Executing Google Chat notification") headers := map[string]string{ "Content-Type": "application/json; charset=UTF-8", } - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("evalContext returned an invalid rule URL") + gcn.log.Error("evalContext returned an invalid rule URL") } // add a text paragraph widget for the message @@ -152,11 +155,11 @@ func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error if evalContext.ImagePublicUrl != "" { widgets = append(widgets, imageWidget{ Image: image{ - ImageUrl: evalContext.ImagePublicUrl, + ImageURL: evalContext.ImagePublicUrl, }, }) } else { - this.log.Info("Could not retrieve a public image URL.") + gcn.log.Info("Could not retrieve a public image URL.") } // add a button widget (link to Grafana) @@ -167,7 +170,7 @@ func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error Text: "OPEN IN GRAFANA", OnClick: onClick{ OpenLink: openLink{ - Url: ruleUrl, + URL: ruleURL, }, }, }, @@ -200,14 +203,14 @@ func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error body, _ := json.Marshal(res1D) cmd := &models.SendWebhookSync{ - Url: this.Url, + Url: gcn.URL, HttpMethod: "POST", HttpHeader: headers, Body: string(body), } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", this.Name) + gcn.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", gcn.Name) return err } diff --git a/pkg/services/alerting/notifiers/googlechat_test.go b/pkg/services/alerting/notifiers/googlechat_test.go index 5368eb63c96b..25dd5053397d 100644 --- a/pkg/services/alerting/notifiers/googlechat_test.go +++ b/pkg/services/alerting/notifiers/googlechat_test.go @@ -22,7 +22,7 @@ func TestGoogleChatNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewGoogleChatNotifier(model) + _, err := newGoogleChatNotifier(model) So(err, ShouldNotBeNil) }) @@ -39,15 +39,14 @@ func TestGoogleChatNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewGoogleChatNotifier(model) + not, err := newGoogleChatNotifier(model) webhookNotifier := not.(*GoogleChatNotifier) So(err, ShouldBeNil) So(webhookNotifier.Name, ShouldEqual, "ops") So(webhookNotifier.Type, ShouldEqual, "googlechat") - So(webhookNotifier.Url, ShouldEqual, "http://google.com") + So(webhookNotifier.URL, ShouldEqual, "http://google.com") }) - }) }) } diff --git a/pkg/services/alerting/notifiers/hipchat.go b/pkg/services/alerting/notifiers/hipchat.go index 6b94c41065fb..e817fe9a076c 100644 --- a/pkg/services/alerting/notifiers/hipchat.go +++ b/pkg/services/alerting/notifiers/hipchat.go @@ -46,6 +46,8 @@ const ( maxFieldCount int = 4 ) +// NewHipChatNotifier is the constructor functions +// for the HipChatNotifier func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if strings.HasSuffix(url, "/") { @@ -56,31 +58,34 @@ func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, err } apikey := model.Settings.Get("apikey").MustString() - roomId := model.Settings.Get("roomid").MustString() + roomID := model.Settings.Get("roomid").MustString() return &HipChatNotifier{ NotifierBase: NewNotifierBase(model), - Url: url, - ApiKey: apikey, - RoomId: roomId, + URL: url, + APIKey: apikey, + RoomID: roomID, log: log.New("alerting.notifier.hipchat"), }, nil } +// HipChatNotifier is responsible for sending +// alert notifications to Hipchat. type HipChatNotifier struct { NotifierBase - Url string - ApiKey string - RoomId string + URL string + APIKey string + RoomID string log log.Logger } -func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) +// Notify sends an alert notification to HipChat +func (hc *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error { + hc.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.Id, "notification", hc.Name) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + hc.log.Error("Failed get rule link", "error", err) return err } @@ -133,7 +138,7 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error { // Add a card with link to the dashboard card := map[string]interface{}{ "style": "application", - "url": ruleUrl, + "url": ruleURL, "id": "1", "title": evalContext.GetNotificationTitle(), "description": message, @@ -160,13 +165,13 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error { "card": card, } - hipUrl := fmt.Sprintf("%s/v2/room/%s/notification?auth_token=%s", this.Url, this.RoomId, this.ApiKey) + hipURL := fmt.Sprintf("%s/v2/room/%s/notification?auth_token=%s", hc.URL, hc.RoomID, hc.APIKey) data, _ := json.Marshal(&body) - this.log.Info("Request payload", "json", string(data)) - cmd := &models.SendWebhookSync{Url: hipUrl, Body: string(data)} + hc.log.Info("Request payload", "json", string(data)) + cmd := &models.SendWebhookSync{Url: hipURL, Body: string(data)} if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send hipchat notification", "error", err, "webhook", this.Name) + hc.log.Error("Failed to send hipchat notification", "error", err, "webhook", hc.Name) return err } diff --git a/pkg/services/alerting/notifiers/hipchat_test.go b/pkg/services/alerting/notifiers/hipchat_test.go index 57ad03ed7c21..6d9b38f83933 100644 --- a/pkg/services/alerting/notifiers/hipchat_test.go +++ b/pkg/services/alerting/notifiers/hipchat_test.go @@ -45,9 +45,9 @@ func TestHipChatNotifier(t *testing.T) { So(err, ShouldBeNil) So(hipchatNotifier.Name, ShouldEqual, "ops") So(hipchatNotifier.Type, ShouldEqual, "hipchat") - So(hipchatNotifier.Url, ShouldEqual, "http://google.com") - So(hipchatNotifier.ApiKey, ShouldEqual, "") - So(hipchatNotifier.RoomId, ShouldEqual, "") + So(hipchatNotifier.URL, ShouldEqual, "http://google.com") + So(hipchatNotifier.APIKey, ShouldEqual, "") + So(hipchatNotifier.RoomID, ShouldEqual, "") }) Convey("from settings with Recipient and Mention", func() { @@ -71,11 +71,10 @@ func TestHipChatNotifier(t *testing.T) { So(err, ShouldBeNil) So(hipchatNotifier.Name, ShouldEqual, "ops") So(hipchatNotifier.Type, ShouldEqual, "hipchat") - So(hipchatNotifier.Url, ShouldEqual, "http://www.hipchat.com") - So(hipchatNotifier.ApiKey, ShouldEqual, "1234") - So(hipchatNotifier.RoomId, ShouldEqual, "1234") + So(hipchatNotifier.URL, ShouldEqual, "http://www.hipchat.com") + So(hipchatNotifier.APIKey, ShouldEqual, "1234") + So(hipchatNotifier.RoomID, ShouldEqual, "1234") }) - }) }) } diff --git a/pkg/services/alerting/notifiers/kafka.go b/pkg/services/alerting/notifiers/kafka.go index 168b1646b16b..9761adf2f6ae 100644 --- a/pkg/services/alerting/notifiers/kafka.go +++ b/pkg/services/alerting/notifiers/kafka.go @@ -32,6 +32,7 @@ func init() { }) } +// NewKafkaNotifier is the constructor function for the Kafka notifier. func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error) { endpoint := model.Settings.Get("kafkaRestProxy").MustString() if endpoint == "" { @@ -50,6 +51,8 @@ func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error }, nil } +// KafkaNotifier is responsible for sending +// alert notifications to Kafka. type KafkaNotifier struct { NotifierBase Endpoint string @@ -57,8 +60,8 @@ type KafkaNotifier struct { log log.Logger } -func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { - +// Notify sends the alert notification. +func (kn *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { state := evalContext.Rule.State customData := triggMetrString @@ -66,7 +69,7 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value) } - this.log.Info("Notifying Kafka", "alert_state", state) + kn.log.Info("Notifying Kafka", "alert_state", state) recordJSON := simplejson.New() records := make([]interface{}, 1) @@ -77,12 +80,12 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("details", customData) bodyJSON.Set("incident_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + kn.log.Error("Failed get rule link", "error", err) return err } - bodyJSON.Set("client_url", ruleUrl) + bodyJSON.Set("client_url", ruleURL) if evalContext.ImagePublicUrl != "" { contexts := make([]interface{}, 1) @@ -99,10 +102,10 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { recordJSON.Set("records", records) body, _ := recordJSON.MarshalJSON() - topicUrl := this.Endpoint + "/topics/" + this.Topic + topicURL := kn.Endpoint + "/topics/" + kn.Topic cmd := &models.SendWebhookSync{ - Url: topicUrl, + Url: topicURL, Body: string(body), HttpMethod: "POST", HttpHeader: map[string]string{ @@ -112,7 +115,7 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send notification to Kafka", "error", err, "body", string(body)) + kn.log.Error("Failed to send notification to Kafka", "error", err, "body", string(body)) return err } diff --git a/pkg/services/alerting/notifiers/kafka_test.go b/pkg/services/alerting/notifiers/kafka_test.go index 03a343835e15..ba3b60ec23e9 100644 --- a/pkg/services/alerting/notifiers/kafka_test.go +++ b/pkg/services/alerting/notifiers/kafka_test.go @@ -49,7 +49,6 @@ func TestKafkaNotifier(t *testing.T) { So(kafkaNotifier.Endpoint, ShouldEqual, "http://localhost:8082") So(kafkaNotifier.Topic, ShouldEqual, "topic1") }) - }) }) } diff --git a/pkg/services/alerting/notifiers/line.go b/pkg/services/alerting/notifiers/line.go index d8bf70f8b9c9..6b84ba8f091e 100644 --- a/pkg/services/alerting/notifiers/line.go +++ b/pkg/services/alerting/notifiers/line.go @@ -29,9 +29,10 @@ func init() { } const ( - lineNotifyUrl string = "https://notify-api.line.me/api/notify" + lineNotifyURL string = "https://notify-api.line.me/api/notify" ) +// NewLINENotifier is the constructor for the LINE notifier func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) { token := model.Settings.Get("token").MustString() if token == "" { @@ -45,33 +46,36 @@ func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) }, nil } +// LineNotifier is responsible for sending +// alert notifications to LINE. type LineNotifier struct { NotifierBase Token string log log.Logger } -func (this *LineNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Executing line notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) +// Notify send an alert notification to LINE +func (ln *LineNotifier) Notify(evalContext *alerting.EvalContext) error { + ln.log.Info("Executing line notification", "ruleId", evalContext.Rule.Id, "notification", ln.Name) var err error switch evalContext.Rule.State { case models.AlertStateAlerting: - err = this.createAlert(evalContext) + err = ln.createAlert(evalContext) } return err } -func (this *LineNotifier) createAlert(evalContext *alerting.EvalContext) error { - this.log.Info("Creating Line notify", "ruleId", evalContext.Rule.Id, "notification", this.Name) - ruleUrl, err := evalContext.GetRuleUrl() +func (ln *LineNotifier) createAlert(evalContext *alerting.EvalContext) error { + ln.log.Info("Creating Line notify", "ruleId", evalContext.Rule.Id, "notification", ln.Name) + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + ln.log.Error("Failed get rule link", "error", err) return err } form := url.Values{} - body := fmt.Sprintf("%s - %s\n%s", evalContext.Rule.Name, ruleUrl, evalContext.Rule.Message) + body := fmt.Sprintf("%s - %s\n%s", evalContext.Rule.Name, ruleURL, evalContext.Rule.Message) form.Add("message", body) if evalContext.ImagePublicUrl != "" { @@ -80,17 +84,17 @@ func (this *LineNotifier) createAlert(evalContext *alerting.EvalContext) error { } cmd := &models.SendWebhookSync{ - Url: lineNotifyUrl, + Url: lineNotifyURL, HttpMethod: "POST", HttpHeader: map[string]string{ - "Authorization": fmt.Sprintf("Bearer %s", this.Token), + "Authorization": fmt.Sprintf("Bearer %s", ln.Token), "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", }, Body: form.Encode(), } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send notification to LINE", "error", err, "body", body) + ln.log.Error("Failed to send notification to LINE", "error", err, "body", body) return err } diff --git a/pkg/services/alerting/notifiers/line_test.go b/pkg/services/alerting/notifiers/line_test.go index 69082d0e066e..f8f50cc9b958 100644 --- a/pkg/services/alerting/notifiers/line_test.go +++ b/pkg/services/alerting/notifiers/line_test.go @@ -44,6 +44,5 @@ func TestLineNotifier(t *testing.T) { So(lineNotifier.Type, ShouldEqual, "line") So(lineNotifier.Token, ShouldEqual, "abcdefgh0123456789") }) - }) } diff --git a/pkg/services/alerting/notifiers/opsgenie.go b/pkg/services/alerting/notifiers/opsgenie.go index 23dd453fe92e..3adcdbc74ade 100644 --- a/pkg/services/alerting/notifiers/opsgenie.go +++ b/pkg/services/alerting/notifiers/opsgenie.go @@ -44,54 +44,57 @@ var ( opsgenieAlertURL = "https://api.opsgenie.com/v2/alerts" ) +// NewOpsGenieNotifier is the constructor for OpsGenie. func NewOpsGenieNotifier(model *models.AlertNotification) (alerting.Notifier, error) { autoClose := model.Settings.Get("autoClose").MustBool(true) apiKey := model.Settings.Get("apiKey").MustString() - apiUrl := model.Settings.Get("apiUrl").MustString() + apiURL := model.Settings.Get("apiUrl").MustString() if apiKey == "" { return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"} } - if apiUrl == "" { - apiUrl = opsgenieAlertURL + if apiURL == "" { + apiURL = opsgenieAlertURL } return &OpsGenieNotifier{ NotifierBase: NewNotifierBase(model), - ApiKey: apiKey, - ApiUrl: apiUrl, + APIKey: apiKey, + APIUrl: apiURL, AutoClose: autoClose, log: log.New("alerting.notifier.opsgenie"), }, nil } +// OpsGenieNotifier is responsible for sending +// alert notifications to OpsGenie type OpsGenieNotifier struct { NotifierBase - ApiKey string - ApiUrl string + APIKey string + APIUrl string AutoClose bool log log.Logger } -func (this *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error { - +// Notify sends an alert notification to OpsGenie. +func (on *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error { var err error switch evalContext.Rule.State { case models.AlertStateOK: - if this.AutoClose { - err = this.closeAlert(evalContext) + if on.AutoClose { + err = on.closeAlert(evalContext) } case models.AlertStateAlerting: - err = this.createAlert(evalContext) + err = on.createAlert(evalContext) } return err } -func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error { - this.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name) +func (on *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error { + on.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", on.Name) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + on.log.Error("Failed get rule link", "error", err) return err } @@ -104,10 +107,10 @@ func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) err bodyJSON.Set("message", evalContext.Rule.Name) bodyJSON.Set("source", "Grafana") bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) - bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s\n%s", evalContext.Rule.Name, ruleUrl, evalContext.Rule.Message, customData)) + bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s\n%s", evalContext.Rule.Name, ruleURL, evalContext.Rule.Message, customData)) details := simplejson.New() - details.Set("url", ruleUrl) + details.Set("url", ruleURL) if evalContext.ImagePublicUrl != "" { details.Set("image", evalContext.ImagePublicUrl) } @@ -116,41 +119,41 @@ func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) err body, _ := bodyJSON.MarshalJSON() cmd := &models.SendWebhookSync{ - Url: this.ApiUrl, + Url: on.APIUrl, Body: string(body), HttpMethod: "POST", HttpHeader: map[string]string{ "Content-Type": "application/json", - "Authorization": fmt.Sprintf("GenieKey %s", this.ApiKey), + "Authorization": fmt.Sprintf("GenieKey %s", on.APIKey), }, } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) + on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) } return nil } -func (this *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error { - this.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name) +func (on *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error { + on.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", on.Name) bodyJSON := simplejson.New() bodyJSON.Set("source", "Grafana") body, _ := bodyJSON.MarshalJSON() cmd := &models.SendWebhookSync{ - Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", this.ApiUrl, evalContext.Rule.Id), + Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", on.APIUrl, evalContext.Rule.Id), Body: string(body), HttpMethod: "POST", HttpHeader: map[string]string{ "Content-Type": "application/json", - "Authorization": fmt.Sprintf("GenieKey %s", this.ApiKey), + "Authorization": fmt.Sprintf("GenieKey %s", on.APIKey), }, } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) + on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) return err } diff --git a/pkg/services/alerting/notifiers/opsgenie_test.go b/pkg/services/alerting/notifiers/opsgenie_test.go index e4954ed904af..1df57676b38b 100644 --- a/pkg/services/alerting/notifiers/opsgenie_test.go +++ b/pkg/services/alerting/notifiers/opsgenie_test.go @@ -45,7 +45,7 @@ func TestOpsGenieNotifier(t *testing.T) { So(err, ShouldBeNil) So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing") So(opsgenieNotifier.Type, ShouldEqual, "opsgenie") - So(opsgenieNotifier.ApiKey, ShouldEqual, "abcdefgh0123456789") + So(opsgenieNotifier.APIKey, ShouldEqual, "abcdefgh0123456789") }) }) }) diff --git a/pkg/services/alerting/notifiers/pagerduty.go b/pkg/services/alerting/notifiers/pagerduty.go index 2b60058ecd93..99302c1af778 100644 --- a/pkg/services/alerting/notifiers/pagerduty.go +++ b/pkg/services/alerting/notifiers/pagerduty.go @@ -40,9 +40,10 @@ func init() { } var ( - pagerdutyEventApiUrl = "https://events.pagerduty.com/v2/enqueue" + pagerdutyEventAPIURL = "https://events.pagerduty.com/v2/enqueue" ) +// NewPagerdutyNotifier is the constructor for the PagerDuty notifier func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, error) { autoResolve := model.Settings.Get("autoResolve").MustBool(false) key := model.Settings.Get("integrationKey").MustString() @@ -58,6 +59,8 @@ func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, e }, nil } +// PagerdutyNotifier is responsible for sending +// alert notifications to pagerduty type PagerdutyNotifier struct { NotifierBase Key string @@ -65,10 +68,11 @@ type PagerdutyNotifier struct { log log.Logger } -func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { +// Notify sends an alert notification to PagerDuty +func (pn *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { - if evalContext.Rule.State == models.AlertStateOK && !this.AutoResolve { - this.log.Info("Not sending a trigger to Pagerduty", "state", evalContext.Rule.State, "auto resolve", this.AutoResolve) + if evalContext.Rule.State == models.AlertStateOK && !pn.AutoResolve { + pn.log.Info("Not sending a trigger to Pagerduty", "state", evalContext.Rule.State, "auto resolve", pn.AutoResolve) return nil } @@ -81,7 +85,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value) } - this.log.Info("Notifying Pagerduty", "event_type", eventType) + pn.log.Info("Notifying Pagerduty", "event_type", eventType) payloadJSON := simplejson.New() payloadJSON.Set("summary", evalContext.Rule.Name+" - "+evalContext.Rule.Message) @@ -94,20 +98,20 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { payloadJSON.Set("custom_details", customData) bodyJSON := simplejson.New() - bodyJSON.Set("routing_key", this.Key) + bodyJSON.Set("routing_key", pn.Key) bodyJSON.Set("event_action", eventType) bodyJSON.Set("dedup_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) bodyJSON.Set("payload", payloadJSON) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + pn.log.Error("Failed get rule link", "error", err) return err } links := make([]interface{}, 1) linkJSON := simplejson.New() - linkJSON.Set("href", ruleUrl) - bodyJSON.Set("client_url", ruleUrl) + linkJSON.Set("href", ruleURL) + bodyJSON.Set("client_url", ruleURL) bodyJSON.Set("client", "Grafana") links[0] = linkJSON bodyJSON.Set("links", links) @@ -123,7 +127,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { body, _ := bodyJSON.MarshalJSON() cmd := &models.SendWebhookSync{ - Url: pagerdutyEventApiUrl, + Url: pagerdutyEventAPIURL, Body: string(body), HttpMethod: "POST", HttpHeader: map[string]string{ @@ -132,7 +136,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send notification to Pagerduty", "error", err, "body", string(body)) + pn.log.Error("Failed to send notification to Pagerduty", "error", err, "body", string(body)) return err } diff --git a/pkg/services/alerting/notifiers/pushover.go b/pkg/services/alerting/notifiers/pushover.go index a54fb1ee084d..19de6ce08a23 100644 --- a/pkg/services/alerting/notifiers/pushover.go +++ b/pkg/services/alerting/notifiers/pushover.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" ) -const PUSHOVER_ENDPOINT = "https://api.pushover.net/1/messages.json" +const pushoverEndpoint = "https://api.pushover.net/1/messages.json" func init() { sounds := ` @@ -95,9 +95,10 @@ func init() { }) } +// NewPushoverNotifier is the constructor for the Pushover Notifier func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, error) { userKey := model.Settings.Get("userKey").MustString() - apiToken := model.Settings.Get("apiToken").MustString() + APIToken := model.Settings.Get("apiToken").MustString() device := model.Settings.Get("device").MustString() priority, _ := strconv.Atoi(model.Settings.Get("priority").MustString()) retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString()) @@ -109,13 +110,13 @@ func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, er if userKey == "" { return nil, alerting.ValidationError{Reason: "User key not given"} } - if apiToken == "" { + if APIToken == "" { return nil, alerting.ValidationError{Reason: "API token not given"} } return &PushoverNotifier{ NotifierBase: NewNotifierBase(model), UserKey: userKey, - ApiToken: apiToken, + APIToken: APIToken, Priority: priority, Retry: retry, Expire: expire, @@ -127,10 +128,12 @@ func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, er }, nil } +// PushoverNotifier is responsible for sending +// alert notifications to Pushover type PushoverNotifier struct { NotifierBase UserKey string - ApiToken string + APIToken string Priority int Retry int Expire int @@ -141,10 +144,11 @@ type PushoverNotifier struct { log log.Logger } -func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { - ruleUrl, err := evalContext.GetRuleUrl() +// Notify sends a alert notification to Pushover +func (pn *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + pn.log.Error("Failed get rule link", "error", err) return err } @@ -163,34 +167,34 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { message = "Notification message missing (Set a notification message to replace this text.)" } - headers, uploadBody, err := this.genPushoverBody(evalContext, message, ruleUrl) + headers, uploadBody, err := pn.genPushoverBody(evalContext, message, ruleURL) if err != nil { - this.log.Error("Failed to generate body for pushover", "error", err) + pn.log.Error("Failed to generate body for pushover", "error", err) return err } cmd := &models.SendWebhookSync{ - Url: PUSHOVER_ENDPOINT, + Url: pushoverEndpoint, HttpMethod: "POST", HttpHeader: headers, Body: uploadBody.String(), } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send pushover notification", "error", err, "webhook", this.Name) + pn.log.Error("Failed to send pushover notification", "error", err, "webhook", pn.Name) return err } return nil } -func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, message string, ruleUrl string) (map[string]string, bytes.Buffer, error) { +func (pn *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, message string, ruleURL string) (map[string]string, bytes.Buffer, error) { var b bytes.Buffer var err error w := multipart.NewWriter(&b) // Add image only if requested and available - if this.Upload && evalContext.ImageOnDiskPath != "" { + if pn.Upload && evalContext.ImageOnDiskPath != "" { f, err := os.Open(evalContext.ImageOnDiskPath) if err != nil { return nil, b, err @@ -209,47 +213,47 @@ func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, } // Add the user token - err = w.WriteField("user", this.UserKey) + err = w.WriteField("user", pn.UserKey) if err != nil { return nil, b, err } // Add the api token - err = w.WriteField("token", this.ApiToken) + err = w.WriteField("token", pn.APIToken) if err != nil { return nil, b, err } // Add priority - err = w.WriteField("priority", strconv.Itoa(this.Priority)) + err = w.WriteField("priority", strconv.Itoa(pn.Priority)) if err != nil { return nil, b, err } - if this.Priority == 2 { - err = w.WriteField("retry", strconv.Itoa(this.Retry)) + if pn.Priority == 2 { + err = w.WriteField("retry", strconv.Itoa(pn.Retry)) if err != nil { return nil, b, err } - err = w.WriteField("expire", strconv.Itoa(this.Expire)) + err = w.WriteField("expire", strconv.Itoa(pn.Expire)) if err != nil { return nil, b, err } } // Add device - if this.Device != "" { - err = w.WriteField("device", this.Device) + if pn.Device != "" { + err = w.WriteField("device", pn.Device) if err != nil { return nil, b, err } } // Add sound - sound := this.AlertingSound + sound := pn.AlertingSound if evalContext.Rule.State == models.AlertStateOK { - sound = this.OkSound + sound = pn.OkSound } if sound != "default" { err = w.WriteField("sound", sound) @@ -265,7 +269,7 @@ func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, } // Add URL - err = w.WriteField("url", ruleUrl) + err = w.WriteField("url", ruleURL) if err != nil { return nil, b, err } diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go index f862a500618a..7b2d51a176f3 100644 --- a/pkg/services/alerting/notifiers/pushover_test.go +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -53,7 +53,7 @@ func TestPushoverNotifier(t *testing.T) { So(err, ShouldBeNil) So(pushoverNotifier.Name, ShouldEqual, "Pushover") So(pushoverNotifier.Type, ShouldEqual, "pushover") - So(pushoverNotifier.ApiToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve") + So(pushoverNotifier.APIToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve") So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o") So(pushoverNotifier.Priority, ShouldEqual, 1) So(pushoverNotifier.AlertingSound, ShouldEqual, "pushover") diff --git a/pkg/services/alerting/notifiers/sensu.go b/pkg/services/alerting/notifiers/sensu.go index cad9fc2286a3..7650cb222d92 100644 --- a/pkg/services/alerting/notifiers/sensu.go +++ b/pkg/services/alerting/notifiers/sensu.go @@ -44,6 +44,7 @@ func init() { } +// NewSensuNotifier is the constructor for the Sensu Notifier. func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { @@ -52,7 +53,7 @@ func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error return &SensuNotifier{ NotifierBase: NewNotifierBase(model), - Url: url, + URL: url, User: model.Settings.Get("username").MustString(), Source: model.Settings.Get("source").MustString(), Password: model.Settings.Get("password").MustString(), @@ -61,9 +62,11 @@ func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error }, nil } +// SensuNotifier is responsible for sending +// alert notifications to Sensu. type SensuNotifier struct { NotifierBase - Url string + URL string Source string User string Password string @@ -71,8 +74,9 @@ type SensuNotifier struct { log log.Logger } -func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Sending sensu result") +// Notify send alert notification to Sensu +func (sn *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { + sn.log.Info("Sending sensu result") bodyJSON := simplejson.New() bodyJSON.Set("ruleId", evalContext.Rule.Id) @@ -80,8 +84,8 @@ func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("name", strings.Replace(evalContext.Rule.Name, " ", "_", -1)) // Sensu alerts require a source. We set it to the user-specified value (optional), // else we fallback and use the grafana ruleID. - if this.Source != "" { - bodyJSON.Set("source", this.Source) + if sn.Source != "" { + bodyJSON.Set("source", sn.Source) } else { bodyJSON.Set("source", "grafana_rule_"+strconv.FormatInt(evalContext.Rule.Id, 10)) } @@ -98,13 +102,13 @@ func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("status", 0) } - if this.Handler != "" { - bodyJSON.Set("handler", this.Handler) + if sn.Handler != "" { + bodyJSON.Set("handler", sn.Handler) } - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err == nil { - bodyJSON.Set("ruleUrl", ruleUrl) + bodyJSON.Set("ruleUrl", ruleURL) } if evalContext.ImagePublicUrl != "" { @@ -118,15 +122,15 @@ func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { body, _ := bodyJSON.MarshalJSON() cmd := &models.SendWebhookSync{ - Url: this.Url, - User: this.User, - Password: this.Password, + Url: sn.URL, + User: sn.User, + Password: sn.Password, Body: string(body), HttpMethod: "POST", } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send sensu event", "error", err, "sensu", this.Name) + sn.log.Error("Failed to send sensu event", "error", err, "sensu", sn.Name) return err } diff --git a/pkg/services/alerting/notifiers/sensu_test.go b/pkg/services/alerting/notifiers/sensu_test.go index 40d39a5d1c3f..0c73a493ee8b 100644 --- a/pkg/services/alerting/notifiers/sensu_test.go +++ b/pkg/services/alerting/notifiers/sensu_test.go @@ -47,7 +47,7 @@ func TestSensuNotifier(t *testing.T) { So(err, ShouldBeNil) So(sensuNotifier.Name, ShouldEqual, "sensu") So(sensuNotifier.Type, ShouldEqual, "sensu") - So(sensuNotifier.Url, ShouldEqual, "http://sensu-api.example.com:4567/results") + So(sensuNotifier.URL, ShouldEqual, "http://sensu-api.example.com:4567/results") So(sensuNotifier.Source, ShouldEqual, "grafana_instance_01") So(sensuNotifier.Handler, ShouldEqual, "myhandler") }) diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index 117674448877..f8cad904270f 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -99,6 +99,7 @@ func init() { } +// NewSlackNotifier is the constructor for the Slack notifier func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { @@ -108,18 +109,18 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error recipient := model.Settings.Get("recipient").MustString() username := model.Settings.Get("username").MustString() iconEmoji := model.Settings.Get("icon_emoji").MustString() - iconUrl := model.Settings.Get("icon_url").MustString() + iconURL := model.Settings.Get("icon_url").MustString() mention := model.Settings.Get("mention").MustString() token := model.Settings.Get("token").MustString() uploadImage := model.Settings.Get("uploadImage").MustBool(true) return &SlackNotifier{ NotifierBase: NewNotifierBase(model), - Url: url, + URL: url, Recipient: recipient, Username: username, IconEmoji: iconEmoji, - IconUrl: iconUrl, + IconURL: iconURL, Mention: mention, Token: token, Upload: uploadImage, @@ -127,25 +128,28 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error }, nil } +// SlackNotifier is responsible for sending +// alert notification to Slack. type SlackNotifier struct { NotifierBase - Url string + URL string Recipient string Username string IconEmoji string - IconUrl string + IconURL string Mention string Token string Upload bool log log.Logger } -func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Executing slack notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) +// Notify send alert notification to Slack. +func (sn *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { + sn.log.Info("Executing slack notification", "ruleId", evalContext.Rule.Id, "notification", sn.Name) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + sn.log.Error("Failed get rule link", "error", err) return err } @@ -170,14 +174,14 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { }) } - message := this.Mention + message := sn.Mention if evalContext.Rule.State != models.AlertStateOK { //don't add message when going back to alert state ok. message += " " + evalContext.Rule.Message } - image_url := "" + imageURL := "" // default to file.upload API method if a token is provided - if this.Token == "" { - image_url = evalContext.ImagePublicUrl + if sn.Token == "" { + imageURL = evalContext.ImagePublicUrl } body := map[string]interface{}{ @@ -186,10 +190,10 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { "fallback": evalContext.GetNotificationTitle(), "color": evalContext.GetStateModel().Color, "title": evalContext.GetNotificationTitle(), - "title_link": ruleUrl, + "title_link": ruleURL, "text": message, "fields": fields, - "image_url": image_url, + "image_url": imageURL, "footer": "Grafana v" + setting.BuildVersion, "footer_icon": "https://grafana.com/assets/img/fav32.png", "ts": time.Now().Unix(), @@ -199,26 +203,26 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { } //recipient override - if this.Recipient != "" { - body["channel"] = this.Recipient + if sn.Recipient != "" { + body["channel"] = sn.Recipient } - if this.Username != "" { - body["username"] = this.Username + if sn.Username != "" { + body["username"] = sn.Username } - if this.IconEmoji != "" { - body["icon_emoji"] = this.IconEmoji + if sn.IconEmoji != "" { + body["icon_emoji"] = sn.IconEmoji } - if this.IconUrl != "" { - body["icon_url"] = this.IconUrl + if sn.IconURL != "" { + body["icon_url"] = sn.IconURL } data, _ := json.Marshal(&body) - cmd := &models.SendWebhookSync{Url: this.Url, Body: string(data)} + cmd := &models.SendWebhookSync{Url: sn.URL, Body: string(data)} if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send slack notification", "error", err, "webhook", this.Name) + sn.log.Error("Failed to send slack notification", "error", err, "webhook", sn.Name) return err } - if this.Token != "" && this.UploadImage { - err = SlackFileUpload(evalContext, this.log, "https://slack.com/api/files.upload", this.Recipient, this.Token) + if sn.Token != "" && sn.UploadImage { + err = slackFileUpload(evalContext, sn.log, "https://slack.com/api/files.upload", sn.Recipient, sn.Token) if err != nil { return err } @@ -226,12 +230,12 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { return nil } -func SlackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error { +func slackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error { if evalContext.ImageOnDiskPath == "" { evalContext.ImageOnDiskPath = filepath.Join(setting.HomePath, "public/img/mixed_styles.png") } log.Info("Uploading to slack via file.upload API") - headers, uploadBody, err := GenerateSlackBody(evalContext.ImageOnDiskPath, token, recipient) + headers, uploadBody, err := generateSlackBody(evalContext.ImageOnDiskPath, token, recipient) if err != nil { return err } @@ -246,7 +250,7 @@ func SlackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url stri return nil } -func GenerateSlackBody(file string, token string, recipient string) (map[string]string, bytes.Buffer, error) { +func generateSlackBody(file string, token string, recipient string) (map[string]string, bytes.Buffer, error) { // Slack requires all POSTs to files.upload to present // an "application/x-www-form-urlencoded" encoded querystring // See https://api.slack.com/methods/files.upload diff --git a/pkg/services/alerting/notifiers/slack_test.go b/pkg/services/alerting/notifiers/slack_test.go index 7dceb12676c9..c8c5b1220cf6 100644 --- a/pkg/services/alerting/notifiers/slack_test.go +++ b/pkg/services/alerting/notifiers/slack_test.go @@ -45,11 +45,11 @@ func TestSlackNotifier(t *testing.T) { So(err, ShouldBeNil) So(slackNotifier.Name, ShouldEqual, "ops") So(slackNotifier.Type, ShouldEqual, "slack") - So(slackNotifier.Url, ShouldEqual, "http://google.com") + So(slackNotifier.URL, ShouldEqual, "http://google.com") So(slackNotifier.Recipient, ShouldEqual, "") So(slackNotifier.Username, ShouldEqual, "") So(slackNotifier.IconEmoji, ShouldEqual, "") - So(slackNotifier.IconUrl, ShouldEqual, "") + So(slackNotifier.IconURL, ShouldEqual, "") So(slackNotifier.Mention, ShouldEqual, "") So(slackNotifier.Token, ShouldEqual, "") }) @@ -79,11 +79,11 @@ func TestSlackNotifier(t *testing.T) { So(err, ShouldBeNil) So(slackNotifier.Name, ShouldEqual, "ops") So(slackNotifier.Type, ShouldEqual, "slack") - So(slackNotifier.Url, ShouldEqual, "http://google.com") + So(slackNotifier.URL, ShouldEqual, "http://google.com") So(slackNotifier.Recipient, ShouldEqual, "#ds-opentsdb") So(slackNotifier.Username, ShouldEqual, "Grafana Alerts") So(slackNotifier.IconEmoji, ShouldEqual, ":smile:") - So(slackNotifier.IconUrl, ShouldEqual, "https://grafana.com/img/fav32.png") + So(slackNotifier.IconURL, ShouldEqual, "https://grafana.com/img/fav32.png") So(slackNotifier.Mention, ShouldEqual, "@carl") So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX") }) diff --git a/pkg/services/alerting/notifiers/teams.go b/pkg/services/alerting/notifiers/teams.go index 57f5d6e91c04..4a2cfa1ca911 100644 --- a/pkg/services/alerting/notifiers/teams.go +++ b/pkg/services/alerting/notifiers/teams.go @@ -26,6 +26,7 @@ func init() { } +// NewTeamsNotifier is the constructor for Teams notifier. func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { @@ -34,23 +35,26 @@ func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error return &TeamsNotifier{ NotifierBase: NewNotifierBase(model), - Url: url, + URL: url, log: log.New("alerting.notifier.teams"), }, nil } +// TeamsNotifier is responsible for sending +// alert notifications to Microsoft teams. type TeamsNotifier struct { NotifierBase - Url string + URL string log log.Logger } -func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) +// Notify send an alert notification to Microsoft teams. +func (tn *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error { + tn.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", tn.Name) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + tn.log.Error("Failed get rule link", "error", err) return err } @@ -108,7 +112,7 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error { "name": "View Rule", "targets": []map[string]interface{}{ { - "os": "default", "uri": ruleUrl, + "os": "default", "uri": ruleURL, }, }, }, @@ -126,10 +130,10 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error { } data, _ := json.Marshal(&body) - cmd := &models.SendWebhookSync{Url: this.Url, Body: string(data)} + cmd := &models.SendWebhookSync{Url: tn.URL, Body: string(data)} if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send teams notification", "error", err, "webhook", this.Name) + tn.log.Error("Failed to send teams notification", "error", err, "webhook", tn.Name) return err } diff --git a/pkg/services/alerting/notifiers/teams_test.go b/pkg/services/alerting/notifiers/teams_test.go index 1dd35c899605..10ec7edca285 100644 --- a/pkg/services/alerting/notifiers/teams_test.go +++ b/pkg/services/alerting/notifiers/teams_test.go @@ -45,7 +45,7 @@ func TestTeamsNotifier(t *testing.T) { So(err, ShouldBeNil) So(teamsNotifier.Name, ShouldEqual, "ops") So(teamsNotifier.Type, ShouldEqual, "teams") - So(teamsNotifier.Url, ShouldEqual, "http://google.com") + So(teamsNotifier.URL, ShouldEqual, "http://google.com") }) Convey("from settings with Recipient and Mention", func() { @@ -67,9 +67,8 @@ func TestTeamsNotifier(t *testing.T) { So(err, ShouldBeNil) So(teamsNotifier.Name, ShouldEqual, "ops") So(teamsNotifier.Type, ShouldEqual, "teams") - So(teamsNotifier.Url, ShouldEqual, "http://google.com") + So(teamsNotifier.URL, ShouldEqual, "http://google.com") }) - }) }) } diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 741c0fe732c5..0c2e85579823 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -18,7 +18,7 @@ const ( ) var ( - telegramApiUrl = "https://api.telegram.org/bot%s/%s" + telegramAPIURL = "https://api.telegram.org/bot%s/%s" ) func init() { @@ -52,6 +52,8 @@ func init() { } +// TelegramNotifier is responsible for sending +// alert notifications to Telegram. type TelegramNotifier struct { NotifierBase BotToken string @@ -60,50 +62,51 @@ type TelegramNotifier struct { log log.Logger } +// NewTelegramNotifier is the constructor for the Telegram notifier func NewTelegramNotifier(model *models.AlertNotification) (alerting.Notifier, error) { if model.Settings == nil { return nil, alerting.ValidationError{Reason: "No Settings Supplied"} } botToken := model.Settings.Get("bottoken").MustString() - chatId := model.Settings.Get("chatid").MustString() + chatID := model.Settings.Get("chatid").MustString() uploadImage := model.Settings.Get("uploadImage").MustBool() if botToken == "" { return nil, alerting.ValidationError{Reason: "Could not find Bot Token in settings"} } - if chatId == "" { + if chatID == "" { return nil, alerting.ValidationError{Reason: "Could not find Chat Id in settings"} } return &TelegramNotifier{ NotifierBase: NewNotifierBase(model), BotToken: botToken, - ChatID: chatId, + ChatID: chatID, UploadImage: uploadImage, log: log.New("alerting.notifier.telegram"), }, nil } -func (this *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, sendImageInline bool) *models.SendWebhookSync { +func (tn *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, sendImageInline bool) *models.SendWebhookSync { if sendImageInline { - cmd, err := this.buildMessageInlineImage(evalContext) + cmd, err := tn.buildMessageInlineImage(evalContext) if err == nil { return cmd } - this.log.Error("Could not generate Telegram message with inline image.", "err", err) + tn.log.Error("Could not generate Telegram message with inline image.", "err", err) } - return this.buildMessageLinkedImage(evalContext) + return tn.buildMessageLinkedImage(evalContext) } -func (this *TelegramNotifier) buildMessageLinkedImage(evalContext *alerting.EvalContext) *models.SendWebhookSync { +func (tn *TelegramNotifier) buildMessageLinkedImage(evalContext *alerting.EvalContext) *models.SendWebhookSync { message := fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err == nil { - message = message + fmt.Sprintf("URL: %s\n", ruleUrl) + message = message + fmt.Sprintf("URL: %s\n", ruleURL) } if evalContext.ImagePublicUrl != "" { @@ -115,14 +118,14 @@ func (this *TelegramNotifier) buildMessageLinkedImage(evalContext *alerting.Eval message = message + fmt.Sprintf("\nMetrics:%s", metrics) } - cmd := this.generateTelegramCmd(message, "text", "sendMessage", func(w *multipart.Writer) { + cmd := tn.generateTelegramCmd(message, "text", "sendMessage", func(w *multipart.Writer) { fw, _ := w.CreateFormField("parse_mode") fw.Write([]byte("html")) }) return cmd } -func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.EvalContext) (*models.SendWebhookSync, error) { +func (tn *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.EvalContext) (*models.SendWebhookSync, error) { var imageFile *os.File var err error @@ -130,7 +133,7 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval defer func() { err := imageFile.Close() if err != nil { - this.log.Error("Could not close Telegram inline image.", "err", err) + tn.log.Error("Could not close Telegram inline image.", "err", err) } }() @@ -138,27 +141,27 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval return nil, err } - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { return nil, err } metrics := generateMetricsMessage(evalContext) - message := generateImageCaption(evalContext, ruleUrl, metrics) + message := generateImageCaption(evalContext, ruleURL, metrics) - cmd := this.generateTelegramCmd(message, "caption", "sendPhoto", func(w *multipart.Writer) { + cmd := tn.generateTelegramCmd(message, "caption", "sendPhoto", func(w *multipart.Writer) { fw, _ := w.CreateFormFile("photo", evalContext.ImageOnDiskPath) io.Copy(fw, imageFile) }) return cmd, nil } -func (this *TelegramNotifier) generateTelegramCmd(message string, messageField string, apiAction string, extraConf func(writer *multipart.Writer)) *models.SendWebhookSync { +func (tn *TelegramNotifier) generateTelegramCmd(message string, messageField string, apiAction string, extraConf func(writer *multipart.Writer)) *models.SendWebhookSync { var body bytes.Buffer w := multipart.NewWriter(&body) fw, _ := w.CreateFormField("chat_id") - fw.Write([]byte(this.ChatID)) + fw.Write([]byte(tn.ChatID)) fw, _ = w.CreateFormField(messageField) fw.Write([]byte(message)) @@ -167,8 +170,8 @@ func (this *TelegramNotifier) generateTelegramCmd(message string, messageField s w.Close() - this.log.Info("Sending telegram notification", "chat_id", this.ChatID, "bot_token", this.BotToken, "apiAction", apiAction) - url := fmt.Sprintf(telegramApiUrl, this.BotToken, apiAction) + tn.log.Info("Sending telegram notification", "chat_id", tn.ChatID, "bot_token", tn.BotToken, "apiAction", apiAction) + url := fmt.Sprintf(telegramAPIURL, tn.BotToken, apiAction) cmd := &models.SendWebhookSync{ Url: url, @@ -193,7 +196,7 @@ func generateMetricsMessage(evalContext *alerting.EvalContext) string { return metrics } -func generateImageCaption(evalContext *alerting.EvalContext, ruleUrl string, metrics string) string { +func generateImageCaption(evalContext *alerting.EvalContext, ruleURL string, metrics string) string { message := evalContext.GetNotificationTitle() if len(evalContext.Rule.Message) > 0 { @@ -205,8 +208,8 @@ func generateImageCaption(evalContext *alerting.EvalContext, ruleUrl string, met } - if len(ruleUrl) > 0 { - urlLine := fmt.Sprintf("\nURL: %s", ruleUrl) + if len(ruleURL) > 0 { + urlLine := fmt.Sprintf("\nURL: %s", ruleURL) message = appendIfPossible(message, urlLine, captionLengthLimit) } @@ -226,16 +229,17 @@ func appendIfPossible(message string, extra string, sizeLimit int) string { return message } -func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { +// Notify send an alert notification to Telegram. +func (tn *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { var cmd *models.SendWebhookSync - if evalContext.ImagePublicUrl == "" && this.UploadImage { - cmd = this.buildMessage(evalContext, true) + if evalContext.ImagePublicUrl == "" && tn.UploadImage { + cmd = tn.buildMessage(evalContext, true) } else { - cmd = this.buildMessage(evalContext, false) + cmd = tn.buildMessage(evalContext, false) } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name) + tn.log.Error("Failed to send webhook", "error", err, "webhook", tn.Name) return err } diff --git a/pkg/services/alerting/notifiers/threema.go b/pkg/services/alerting/notifiers/threema.go index 6e4aa7bc946e..621a04a85d5f 100644 --- a/pkg/services/alerting/notifiers/threema.go +++ b/pkg/services/alerting/notifiers/threema.go @@ -68,6 +68,8 @@ func init() { } +// ThreemaNotifier is responsible for sending +// alert notifications to Threema. type ThreemaNotifier struct { NotifierBase GatewayID string @@ -76,6 +78,7 @@ type ThreemaNotifier struct { log log.Logger } +// NewThreemaNotifier is the constructor for the Threema notifer func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, error) { if model.Settings == nil { return nil, alerting.ValidationError{Reason: "No Settings Supplied"} @@ -114,6 +117,7 @@ func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, err }, nil } +// Notify send an alert notification to Threema func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error { notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID) notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID) diff --git a/pkg/services/alerting/notifiers/threema_test.go b/pkg/services/alerting/notifiers/threema_test.go index 2c50b7d2058c..8ec77db42129 100644 --- a/pkg/services/alerting/notifiers/threema_test.go +++ b/pkg/services/alerting/notifiers/threema_test.go @@ -113,7 +113,6 @@ func TestThreemaNotifier(t *testing.T) { So(not, ShouldBeNil) So(err.(alerting.ValidationError).Reason, ShouldEqual, "Invalid Threema Recipient ID: Must be 8 characters long") }) - }) }) } diff --git a/pkg/services/alerting/notifiers/victorops.go b/pkg/services/alerting/notifiers/victorops.go index 74988c21ed8f..b1fd78d8df38 100644 --- a/pkg/services/alerting/notifiers/victorops.go +++ b/pkg/services/alerting/notifiers/victorops.go @@ -14,7 +14,7 @@ import ( // AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state const AlertStateCritical = "CRITICAL" -const AlertStateRecovery = "RECOVERY" +const alertStateRecovery = "RECOVERY" func init() { alerting.RegisterNotifier(&alerting.NotifierPlugin{ @@ -69,17 +69,17 @@ type VictoropsNotifier struct { } // Notify sends notification to Victorops via POST to URL endpoint -func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) +func (vn *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { + vn.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", vn.Name) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err != nil { - this.log.Error("Failed get rule link", "error", err) + vn.log.Error("Failed get rule link", "error", err) return err } - if evalContext.Rule.State == models.AlertStateOK && !this.AutoResolve { - this.log.Info("Not alerting VictorOps", "state", evalContext.Rule.State, "auto resolve", this.AutoResolve) + if evalContext.Rule.State == models.AlertStateOK && !vn.AutoResolve { + vn.log.Info("Not alerting VictorOps", "state", evalContext.Rule.State, "auto resolve", vn.AutoResolve) return nil } @@ -89,7 +89,7 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { } if evalContext.Rule.State == models.AlertStateOK { - messageType = AlertStateRecovery + messageType = alertStateRecovery } fields := make(map[string]interface{}, 0) @@ -109,7 +109,7 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("state_start_time", evalContext.StartTime.Unix()) bodyJSON.Set("state_message", evalContext.Rule.Message) bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion) - bodyJSON.Set("alert_url", ruleUrl) + bodyJSON.Set("alert_url", ruleURL) bodyJSON.Set("metrics", fields) if evalContext.Error != nil { @@ -121,10 +121,10 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { } data, _ := bodyJSON.MarshalJSON() - cmd := &models.SendWebhookSync{Url: this.URL, Body: string(data)} + cmd := &models.SendWebhookSync{Url: vn.URL, Body: string(data)} if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send Victorops notification", "error", err, "webhook", this.Name) + vn.log.Error("Failed to send Victorops notification", "error", err, "webhook", vn.Name) return err } diff --git a/pkg/services/alerting/notifiers/webhook.go b/pkg/services/alerting/notifiers/webhook.go index 31b5f7a82080..3c10c50c5d03 100644 --- a/pkg/services/alerting/notifiers/webhook.go +++ b/pkg/services/alerting/notifiers/webhook.go @@ -40,6 +40,8 @@ func init() { } +// NewWebHookNotifier is the constructor for +// the WebHook notifier. func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { @@ -48,25 +50,29 @@ func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, err return &WebhookNotifier{ NotifierBase: NewNotifierBase(model), - Url: url, + URL: url, User: model.Settings.Get("username").MustString(), Password: model.Settings.Get("password").MustString(), - HttpMethod: model.Settings.Get("httpMethod").MustString("POST"), + HTTPMethod: model.Settings.Get("httpMethod").MustString("POST"), log: log.New("alerting.notifier.webhook"), }, nil } +// WebhookNotifier is responsible for sending +// alert notifications as webhooks. type WebhookNotifier struct { NotifierBase - Url string + URL string User string Password string - HttpMethod string + HTTPMethod string log log.Logger } -func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Sending webhook") +// Notify send alert notifications as +// webhook as http requests. +func (wn *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { + wn.log.Info("Sending webhook") bodyJSON := simplejson.New() bodyJSON.Set("title", evalContext.GetNotificationTitle()) @@ -75,9 +81,9 @@ func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("state", evalContext.Rule.State) bodyJSON.Set("evalMatches", evalContext.EvalMatches) - ruleUrl, err := evalContext.GetRuleUrl() + ruleURL, err := evalContext.GetRuleUrl() if err == nil { - bodyJSON.Set("ruleUrl", ruleUrl) + bodyJSON.Set("ruleUrl", ruleURL) } if evalContext.ImagePublicUrl != "" { @@ -91,15 +97,15 @@ func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { body, _ := bodyJSON.MarshalJSON() cmd := &models.SendWebhookSync{ - Url: this.Url, - User: this.User, - Password: this.Password, + Url: wn.URL, + User: wn.User, + Password: wn.Password, Body: string(body), - HttpMethod: this.HttpMethod, + HttpMethod: wn.HTTPMethod, } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { - this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name) + wn.log.Error("Failed to send webhook", "error", err, "webhook", wn.Name) return err } diff --git a/pkg/services/alerting/notifiers/webhook_test.go b/pkg/services/alerting/notifiers/webhook_test.go index af48f1f1aa6d..b8c001b6e60b 100644 --- a/pkg/services/alerting/notifiers/webhook_test.go +++ b/pkg/services/alerting/notifiers/webhook_test.go @@ -45,7 +45,7 @@ func TestWebhookNotifier(t *testing.T) { So(err, ShouldBeNil) So(webhookNotifier.Name, ShouldEqual, "ops") So(webhookNotifier.Type, ShouldEqual, "webhook") - So(webhookNotifier.Url, ShouldEqual, "http://google.com") + So(webhookNotifier.URL, ShouldEqual, "http://google.com") }) }) }) From 458bb3b0e3589025c6ef6567c4b3f86c6cdfbb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 20 May 2019 15:24:49 +0200 Subject: [PATCH 16/92] Fix: Changes WebSocket protocol to wss:// for https (#17173) --- public/app/features/explore/state/epics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/explore/state/epics.ts b/public/app/features/explore/state/epics.ts index 95ce32f11cf5..a31474f81ccf 100644 --- a/public/app/features/explore/state/epics.ts +++ b/public/app/features/explore/state/epics.ts @@ -16,7 +16,7 @@ import { SeriesData } from '@grafana/ui/src/types/data'; import { EpicDependencies } from 'app/store/configureStore'; const convertToWebSocketUrl = (url: string) => { - const protocol = window.location.protocol === 'https' ? 'wss://' : 'ws://'; + const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; let backend = `${protocol}${window.location.host}${config.appSubUrl}`; if (backend.endsWith('/')) { backend = backend.slice(0, backend.length - 1); From 7e3ac4ebde02ea2107481f62b070bb436ad6b3b3 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Mon, 20 May 2019 13:05:23 -0700 Subject: [PATCH 17/92] AppPlugin: add an init function (#17150) --- packages/grafana-ui/src/types/app.ts | 7 ++ .../features/plugins/plugin_loader.test.ts | 68 +++++++++++++++++++ public/app/features/plugins/plugin_loader.ts | 3 +- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 public/app/features/plugins/plugin_loader.test.ts diff --git a/packages/grafana-ui/src/types/app.ts b/packages/grafana-ui/src/types/app.ts index 01dd6e1a40a9..16302feefd97 100644 --- a/packages/grafana-ui/src/types/app.ts +++ b/packages/grafana-ui/src/types/app.ts @@ -26,6 +26,13 @@ export class AppPlugin extends GrafanaPlugin { // Old style pages angularPages?: { [component: string]: any }; + /** + * Called after the module has loaded, and before the app is used. + * This function may be called multiple times on the same instance. + * The first time, `this.meta` will be undefined + */ + init(meta: AppPluginMeta) {} + /** * Set the component displayed under: * /a/${plugin-id}/* diff --git a/public/app/features/plugins/plugin_loader.test.ts b/public/app/features/plugins/plugin_loader.test.ts new file mode 100644 index 000000000000..845a5dde62aa --- /dev/null +++ b/public/app/features/plugins/plugin_loader.test.ts @@ -0,0 +1,68 @@ +// Use the real plugin_loader (stubbed by default) +jest.unmock('app/features/plugins/plugin_loader'); + +(global as any).ace = { + define: jest.fn(), +}; + +jest.mock('app/core/core', () => { + return { + coreModule: { + directive: jest.fn(), + }, + }; +}); + +/* tslint:disable:import-blacklist */ +import System from 'systemjs/dist/system.js'; + +import { AppPluginMeta, PluginMetaInfo, PluginType, AppPlugin } from '@grafana/ui'; +import { importAppPlugin } from './plugin_loader'; + +class MyCustomApp extends AppPlugin { + initWasCalled = false; + calledTwice = false; + + init(meta: AppPluginMeta) { + this.initWasCalled = true; + this.calledTwice = this.meta === meta; + } +} + +describe('Load App', () => { + const app = new MyCustomApp(); + const modulePath = 'my/custom/plugin/module'; + + beforeAll(() => { + System.set(modulePath, System.newModule({ plugin: app })); + }); + + afterAll(() => { + System.delete(modulePath); + }); + + it('should call init and set meta', async () => { + const meta: AppPluginMeta = { + id: 'test-app', + module: modulePath, + baseUrl: 'xxx', + info: {} as PluginMetaInfo, + type: PluginType.app, + name: 'test', + }; + + // Check that we mocked the import OK + const m = await System.import(modulePath); + expect(m.plugin).toBe(app); + + const loaded = await importAppPlugin(meta); + expect(loaded).toBe(app); + expect(app.meta).toBe(meta); + expect(app.initWasCalled).toBeTruthy(); + expect(app.calledTwice).toBeFalsy(); + + const again = await importAppPlugin(meta); + expect(again).toBe(app); + expect(app.calledTwice).toBeTruthy(); + }); +}); diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 55531559d1d0..9c3bc4ca553b 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -181,8 +181,9 @@ export function importDataSourcePlugin(meta: DataSourcePluginMeta): Promise { return importPluginModule(meta.module).then(pluginExports => { const plugin = pluginExports.plugin ? (pluginExports.plugin as AppPlugin) : new AppPlugin(); - plugin.meta = meta; plugin.setComponentsFromLegacyExports(pluginExports); + plugin.init(meta); + plugin.meta = meta; return plugin; }); } From 66ba2aa524d1ecb8143354468a14f55d44bcac1d Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Tue, 21 May 2019 07:50:44 +0200 Subject: [PATCH 18/92] Fix gosimple issues (#17179) Signed-off-by: Mario Trangoni --- pkg/middleware/auth_proxy/auth_proxy.go | 14 +++----------- pkg/services/alerting/notifiers/discord.go | 2 +- pkg/services/alerting/notifiers/victorops.go | 2 +- pkg/services/sqlstore/login_attempt.go | 2 +- scripts/backend-lint.sh | 1 + 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/middleware/auth_proxy/auth_proxy.go b/pkg/middleware/auth_proxy/auth_proxy.go index 4cb2de38c7f1..2e1e72b82f6c 100644 --- a/pkg/middleware/auth_proxy/auth_proxy.go +++ b/pkg/middleware/auth_proxy/auth_proxy.go @@ -64,7 +64,7 @@ func newError(message string, err error) *Error { // Error returns a Error error string func (err *Error) Error() string { - return fmt.Sprintf("%s", err.Message) + return err.Message } // Options for the AuthProxy @@ -98,20 +98,12 @@ func New(options *Options) *AuthProxy { func (auth *AuthProxy) IsEnabled() bool { // Bail if the setting is not enabled - if !auth.enabled { - return false - } - - return true + return auth.enabled } // HasHeader checks if the we have specified header func (auth *AuthProxy) HasHeader() bool { - if len(auth.header) == 0 { - return false - } - - return true + return len(auth.header) != 0 } // IsAllowedIP compares presented IP with the whitelist one diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go index 9933ad5e5871..f85fa7f785e9 100644 --- a/pkg/services/alerting/notifiers/discord.go +++ b/pkg/services/alerting/notifiers/discord.go @@ -171,7 +171,7 @@ func (this *DiscordNotifier) embedImage(cmd *models.SendWebhookSync, imagePath s w.Close() - cmd.Body = string(b.Bytes()) + cmd.Body = b.String() cmd.ContentType = w.FormDataContentType() return nil diff --git a/pkg/services/alerting/notifiers/victorops.go b/pkg/services/alerting/notifiers/victorops.go index b1fd78d8df38..c118d811b9c4 100644 --- a/pkg/services/alerting/notifiers/victorops.go +++ b/pkg/services/alerting/notifiers/victorops.go @@ -92,7 +92,7 @@ func (vn *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { messageType = alertStateRecovery } - fields := make(map[string]interface{}, 0) + fields := make(map[string]interface{}) fieldLimitCount := 4 for index, evt := range evalContext.EvalMatches { fields[evt.Metric] = evt.Value diff --git a/pkg/services/sqlstore/login_attempt.go b/pkg/services/sqlstore/login_attempt.go index fe77dd7e9146..a9adbca5bfed 100644 --- a/pkg/services/sqlstore/login_attempt.go +++ b/pkg/services/sqlstore/login_attempt.go @@ -43,7 +43,7 @@ func DeleteOldLoginAttempts(cmd *m.DeleteOldLoginAttemptsCommand) error { if err != nil { return err } - + // nolint: gosimple if result == nil || len(result) == 0 || result[0] == nil { return nil } diff --git a/scripts/backend-lint.sh b/scripts/backend-lint.sh index b447071efa0e..6e7305364fdf 100755 --- a/scripts/backend-lint.sh +++ b/scripts/backend-lint.sh @@ -27,6 +27,7 @@ exit_if_fail gometalinter --enable-gc --vendor --deadline 10m --disable-all \ exit_if_fail golangci-lint run --deadline 10m --disable-all \ --enable=deadcode\ --enable=gofmt\ + --enable=gosimple\ --enable=ineffassign\ --enable=structcheck\ --enable=unconvert\ From b695dcd0eb276c0e235b321e2fe66d475ee2e085 Mon Sep 17 00:00:00 2001 From: Shavonn Brown Date: Tue, 21 May 2019 02:56:34 -0400 Subject: [PATCH 19/92] TablePanel: Check for table using keys (#17185) * Fix: the type is not set so can check for columns and rows to validate is table (#16996) * Fix: the type is not set so can check for columns and rows to validate is table (#16996) * Fix: fix no table check and tests (#16996) --- public/app/plugins/panel/table/specs/transformers.test.ts | 1 - public/app/plugins/panel/table/transformers.ts | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/panel/table/specs/transformers.test.ts b/public/app/plugins/panel/table/specs/transformers.test.ts index 49926aa00a8f..c82c9dd6cd09 100644 --- a/public/app/plugins/panel/table/specs/transformers.test.ts +++ b/public/app/plugins/panel/table/specs/transformers.test.ts @@ -108,7 +108,6 @@ describe('when transforming time series table', () => { { type: 'foo', columns: [{ text: 'Time' }, { text: 'Label Key 1' }, { text: 'Value' }], - rows: [[time, 'Label Value 1', 42]], }, ]; diff --git a/public/app/plugins/panel/table/transformers.ts b/public/app/plugins/panel/table/transformers.ts index dac63a935a09..ab3daf6c7be2 100644 --- a/public/app/plugins/panel/table/transformers.ts +++ b/public/app/plugins/panel/table/transformers.ts @@ -158,9 +158,8 @@ transformers['table'] = { if (!data || data.length === 0) { return; } - - const noTableIndex = _.findIndex(data, d => d.type !== 'table'); - if (noTableIndex > -1) { + const noTableIndex = _.findIndex(data, d => 'columns' in d && 'rows' in d); + if (noTableIndex < 0) { throw { message: `Result of query #${String.fromCharCode( 65 + noTableIndex From c82df97bb2b586b0faf93389bd08d2548db6d325 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 21 May 2019 09:20:57 +0200 Subject: [PATCH 20/92] testdata: scenarios returned are now sorted in a consistent way (#17181) This makes sure the scenarios returned from API are sorted in a consistent way and by that makes the values in scenario drop down always presented ordered instead of jumping around. --- pkg/api/metrics.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 6e5ae0f8761b..5b988686cfba 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -2,6 +2,7 @@ package api import ( "context" + "sort" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" @@ -66,7 +67,14 @@ func (hs *HTTPServer) QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) R func GetTestDataScenarios(c *m.ReqContext) Response { result := make([]interface{}, 0) - for _, scenario := range testdata.ScenarioRegistry { + scenarioIds := make([]string, 0) + for id := range testdata.ScenarioRegistry { + scenarioIds = append(scenarioIds, id) + } + sort.Strings(scenarioIds) + + for _, scenarioId := range scenarioIds { + scenario := testdata.ScenarioRegistry[scenarioId] result = append(result, map[string]interface{}{ "id": scenario.Id, "name": scenario.Name, From 3b008d06b42f83737a32264f183f0f0de78b64e9 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 21 May 2019 09:21:59 +0200 Subject: [PATCH 21/92] testdata: logs scenario (#17182) Adds logs scenario which is quite basic and not that smart to begin with. This will hopefully ease development of Explore and support for logs in Grafana. --- pkg/tsdb/testdata/scenarios.go | 79 +++++++++++++++++++ pkg/tsdb/testdata/utils.go | 22 ++++++ .../plugins/datasource/testdata/datasource.ts | 3 +- .../testdata/partials/query.editor.html | 53 ++++++++----- 4 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 pkg/tsdb/testdata/utils.go diff --git a/pkg/tsdb/testdata/scenarios.go b/pkg/tsdb/testdata/scenarios.go index 0a521894f430..3e7aa0541a8a 100644 --- a/pkg/tsdb/testdata/scenarios.go +++ b/pkg/tsdb/testdata/scenarios.go @@ -2,6 +2,7 @@ package testdata import ( "encoding/json" + "fmt" "math" "math/rand" "strconv" @@ -261,6 +262,84 @@ func init() { return queryRes }, }) + + registerScenario(&Scenario{ + Id: "logs", + Name: "Logs", + + Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult { + from := context.TimeRange.GetFromAsMsEpoch() + to := context.TimeRange.GetToAsMsEpoch() + lines := query.Model.Get("lines").MustInt64(10) + includeLevelColumn := query.Model.Get("levelColumn").MustBool(false) + + logLevelGenerator := newRandomStringProvider([]string{ + "emerg", + "alert", + "crit", + "critical", + "warn", + "warning", + "err", + "eror", + "error", + "info", + "notice", + "dbug", + "debug", + "trace", + "", + }) + containerIDGenerator := newRandomStringProvider([]string{ + "f36a9eaa6d34310686f2b851655212023a216de955cbcc764210cefa71179b1a", + "5a354a630364f3742c602f315132e16def594fe68b1e4a195b2fce628e24c97a", + }) + hostnameGenerator := newRandomStringProvider([]string{ + "srv-001", + "srv-002", + }) + + table := tsdb.Table{ + Columns: []tsdb.TableColumn{ + {Text: "time"}, + {Text: "message"}, + {Text: "container_id"}, + {Text: "hostname"}, + }, + Rows: []tsdb.RowValues{}, + } + + if includeLevelColumn { + table.Columns = append(table.Columns, tsdb.TableColumn{Text: "level"}) + } + + for i := int64(0); i < lines && to > from; i++ { + row := tsdb.RowValues{float64(to)} + + logLevel := logLevelGenerator.Next() + timeFormatted := time.Unix(to/1000, 0).Format(time.RFC3339) + lvlString := "" + if !includeLevelColumn { + lvlString = fmt.Sprintf("lvl=%s ", logLevel) + } + + row = append(row, fmt.Sprintf("t=%s %smsg=\"Request Completed\" logger=context userId=1 orgId=1 uname=admin method=GET path=/api/datasources/proxy/152/api/prom/label status=502 remote_addr=[::1] time_ms=1 size=0 referer=\"http://localhost:3000/explore?left=%%5B%%22now-6h%%22,%%22now%%22,%%22Prometheus%%202.x%%22,%%7B%%7D,%%7B%%22ui%%22:%%5Btrue,true,true,%%22none%%22%%5D%%7D%%5D\"", timeFormatted, lvlString)) + row = append(row, containerIDGenerator.Next()) + row = append(row, hostnameGenerator.Next()) + + if includeLevelColumn { + row = append(row, logLevel) + } + + table.Rows = append(table.Rows, row) + to -= query.IntervalMs + } + + queryRes := tsdb.NewQueryResult() + queryRes.Tables = append(queryRes.Tables, &table) + return queryRes + }, + }) } func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult { diff --git a/pkg/tsdb/testdata/utils.go b/pkg/tsdb/testdata/utils.go new file mode 100644 index 000000000000..85c02e8a296d --- /dev/null +++ b/pkg/tsdb/testdata/utils.go @@ -0,0 +1,22 @@ +package testdata + +import ( + "math/rand" + "time" +) + +type randomStringProvider struct { + r *rand.Rand + data []string +} + +func newRandomStringProvider(data []string) *randomStringProvider { + return &randomStringProvider{ + r: rand.New(rand.NewSource(time.Now().UnixNano())), + data: data, + } +} + +func (p *randomStringProvider) Next() string { + return p.data[p.r.Int31n(int32(len(p.data)))] +} diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index 0f69028f7848..c3b706153b3c 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -32,10 +32,11 @@ export class TestDataDatasource extends DataSourceApi { scenarioId: item.scenarioId, intervalMs: options.intervalMs, maxDataPoints: options.maxDataPoints, + datasourceId: this.id, stringInput: item.stringInput, points: item.points, alias: item.alias, - datasourceId: this.id, + ...item, }; }); diff --git a/public/app/plugins/datasource/testdata/partials/query.editor.html b/public/app/plugins/datasource/testdata/partials/query.editor.html index e982ce9fb61f..e70abb4d96de 100644 --- a/public/app/plugins/datasource/testdata/partials/query.editor.html +++ b/public/app/plugins/datasource/testdata/partials/query.editor.html @@ -40,8 +40,8 @@
-
-
-
-
+ +
+
+ + +
+
+ +
+
From 3b35e9d5e89c53d3b21bd01d792145e0673f6928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 21 May 2019 09:34:42 +0200 Subject: [PATCH 22/92] Fix: Fixes so new data is pushed during live tailing (#17189) Fixes: #17188 --- public/app/features/explore/state/reducers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 0d7bc298e156..7e8fbb9d6ef7 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -438,7 +438,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const { data } = action.payload; const live = isLive(refreshInterval); - if (live) { + if (!live) { return state; } From 5414a3110107f9d3a3d1bf3822e48b9ce40469d4 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 21 May 2019 09:37:31 +0200 Subject: [PATCH 23/92] explore: fix null checks (#17191) --- public/app/features/explore/Logs.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 3719ed0e88f5..f12830b63b78 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -175,7 +175,7 @@ export default class Logs extends PureComponent { const hasLabel = hasData && dedupedData.hasUniqueLabels; const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0); const showDuplicates = dedupStrategy !== LogsDedupStrategy.none && dedupCount > 0; - const meta = [...data.meta]; + const meta = data.meta ? [...data.meta] : []; if (dedupStrategy !== LogsDedupStrategy.none) { meta.push({ @@ -193,7 +193,9 @@ export default class Logs extends PureComponent { // React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead const getRows = () => processedRows; - const timeSeries = data.series.map(series => new TimeSeries(series)); + const timeSeries = data.series + ? data.series.map(series => new TimeSeries(series)) + : [new TimeSeries({ datapoints: [] })]; const absRange = { from: range.from.valueOf(), to: range.to.valueOf(), From f98095d6299e0363dbb4159cf12678da33188cb9 Mon Sep 17 00:00:00 2001 From: Brian Gann Date: Tue, 21 May 2019 02:58:07 -0500 Subject: [PATCH 24/92] MSI: Generate sha256sum during MSI build process in circleci (#17120) * build: generate sha256 during msi build --- .circleci/config.yml | 1 + scripts/build/ci-msi-build/ci-msi-build-oss.sh | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b713003a389..a484cd08e184 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -588,6 +588,7 @@ jobs: root: . paths: - dist/grafana-*.msi + - dist/grafana-*.msi.sha256 store-build-artifacts: docker: diff --git a/scripts/build/ci-msi-build/ci-msi-build-oss.sh b/scripts/build/ci-msi-build/ci-msi-build-oss.sh index 74ba4b001a3f..02f015dae234 100755 --- a/scripts/build/ci-msi-build/ci-msi-build-oss.sh +++ b/scripts/build/ci-msi-build/ci-msi-build-oss.sh @@ -24,5 +24,11 @@ python3 generator/build.py "$@" chmod a+x /tmp/scratch/*.msi echo "MSI: Copy to $WORKING_DIRECTORY/dist" cp /tmp/scratch/*.msi $WORKING_DIRECTORY/dist +echo "MSI: Generate SHA256" +MSI_FILE=`ls $WORKING_DIRECTORY/dist/*.msi` +SHA256SUM=`sha256sum $MSI_FILE | cut -f1 -d' '` +echo $SHA256SUM > $MSI_FILE.sha256 +echo "MSI: SHA256 file content:" +cat $MSI_FILE.sha256 echo "MSI: contents of $WORKING_DIRECTORY/dist" ls -al $WORKING_DIRECTORY/dist From 744682e6480f1cc8756ec0f70d5d70464dce4c53 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 21 May 2019 01:39:29 -0700 Subject: [PATCH 25/92] AppPlugin: add types for jsonData (#17177) * add types for App jsonData * add types for App jsonData * add value as possible type * make it extend {} --- packages/grafana-ui/src/types/app.ts | 16 ++++++++-------- packages/grafana-ui/src/types/plugin.ts | 10 ++++++---- .../plugins/app/example-app/ExampleRootPage.tsx | 5 +++-- public/app/plugins/app/example-app/module.ts | 3 ++- public/app/plugins/app/example-app/types.ts | 4 ++++ 5 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 public/app/plugins/app/example-app/types.ts diff --git a/packages/grafana-ui/src/types/app.ts b/packages/grafana-ui/src/types/app.ts index 16302feefd97..79d6fce50664 100644 --- a/packages/grafana-ui/src/types/app.ts +++ b/packages/grafana-ui/src/types/app.ts @@ -1,12 +1,12 @@ import { ComponentClass } from 'react'; import { NavModel } from './navModel'; -import { PluginMeta, PluginIncludeType, GrafanaPlugin } from './plugin'; +import { PluginMeta, PluginIncludeType, GrafanaPlugin, KeyValue } from './plugin'; -export interface AppRootProps { - meta: AppPluginMeta; +export interface AppRootProps { + meta: AppPluginMeta; path: string; // The URL path to this page - query: { [s: string]: any }; // The URL query parameters + query: KeyValue; // The URL query parameters /** * Pass the nav model to the container... is there a better way? @@ -14,13 +14,13 @@ export interface AppRootProps { onNavChanged: (nav: NavModel) => void; } -export interface AppPluginMeta extends PluginMeta { +export interface AppPluginMeta extends PluginMeta { // TODO anything specific to apps? } -export class AppPlugin extends GrafanaPlugin { +export class AppPlugin extends GrafanaPlugin> { // Content under: /a/${plugin-id}/* - root?: ComponentClass; + root?: ComponentClass>; rootNav?: NavModel; // Initial navigation model // Old style pages @@ -37,7 +37,7 @@ export class AppPlugin extends GrafanaPlugin { * Set the component displayed under: * /a/${plugin-id}/* */ - setRootPage(root: ComponentClass, rootNav?: NavModel) { + setRootPage(root: ComponentClass>, rootNav?: NavModel) { this.root = root; this.rootNav = rootNav; return this; diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 72e9d2453fd5..a9885e00cf82 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -11,7 +11,9 @@ export enum PluginType { app = 'app', } -export interface PluginMeta { +export type KeyValue = { [s: string]: T }; + +export interface PluginMeta { id: string; name: string; type: PluginType; @@ -27,8 +29,8 @@ export interface PluginMeta { dependencies?: PluginDependencies; // Filled in by the backend - jsonData?: { [str: string]: any }; - secureJsonData?: { [str: string]: any }; + jsonData?: T; + secureJsonData?: KeyValue; enabled?: boolean; defaultNavUrl?: string; hasUpdate?: boolean; @@ -93,7 +95,7 @@ export interface PluginMetaInfo { export interface PluginConfigPageProps { plugin: T; - query: { [s: string]: any }; // The URL query parameters + query: KeyValue; // The URL query parameters } export interface PluginConfigPage { diff --git a/public/app/plugins/app/example-app/ExampleRootPage.tsx b/public/app/plugins/app/example-app/ExampleRootPage.tsx index 488d3b511a11..565a9fa37d45 100644 --- a/public/app/plugins/app/example-app/ExampleRootPage.tsx +++ b/public/app/plugins/app/example-app/ExampleRootPage.tsx @@ -10,7 +10,7 @@ const TAB_ID_A = 'A'; const TAB_ID_B = 'B'; const TAB_ID_C = 'C'; -export class ExampleRootPage extends PureComponent { +export class ExampleRootPage extends PureComponent { constructor(props: Props) { super(props); } @@ -79,7 +79,7 @@ export class ExampleRootPage extends PureComponent { } render() { - const { path, query } = this.props; + const { path, query, meta } = this.props; return (
@@ -96,6 +96,7 @@ export class ExampleRootPage extends PureComponent { ZZZ +
{JSON.stringify(meta.jsonData)}
); } diff --git a/public/app/plugins/app/example-app/module.ts b/public/app/plugins/app/example-app/module.ts index f82f7faec08b..8b7ea7b42f47 100644 --- a/public/app/plugins/app/example-app/module.ts +++ b/public/app/plugins/app/example-app/module.ts @@ -5,6 +5,7 @@ import { AppPlugin } from '@grafana/ui'; import { ExamplePage1 } from './config/ExamplePage1'; import { ExamplePage2 } from './config/ExamplePage2'; import { ExampleRootPage } from './ExampleRootPage'; +import { ExampleAppSettings } from './types'; // Legacy exports just for testing export { @@ -12,7 +13,7 @@ export { AngularExamplePageCtrl, // Must match `pages.component` in plugin.json }; -export const plugin = new AppPlugin() +export const plugin = new AppPlugin() .setRootPage(ExampleRootPage) .addConfigPage({ title: 'Page 1', diff --git a/public/app/plugins/app/example-app/types.ts b/public/app/plugins/app/example-app/types.ts new file mode 100644 index 000000000000..c3c5bad5e767 --- /dev/null +++ b/public/app/plugins/app/example-app/types.ts @@ -0,0 +1,4 @@ +export interface ExampleAppSettings { + customText?: string; + customCheckbox?: boolean; +} From fa9ffe38d28e9ad0874e13ebeb143c516599e04f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 21 May 2019 12:28:30 +0200 Subject: [PATCH 26/92] Azuremonitor: multiple subscription support for alerting (#17195) * fix: azuremonitor adds multi-sub support to alerting * fix: AzureMonitor missing parameter in metadata func getMetricMetadata function when called in the query ctrl was missing a parameter for Subscription Id. Also, made some tweaks to what happens when a chained dropdown is changed to not reset all the fields that are dependent on it. --- .../azuremonitor/azuremonitor-datasource.go | 12 +++--- .../azuremonitor-datasource_test.go | 9 ++++- pkg/tsdb/azuremonitor/url-builder.go | 18 ++++++--- pkg/tsdb/azuremonitor/url-builder_test.go | 40 +++++++++++++------ .../query_ctrl.test.ts | 10 ++++- .../query_ctrl.ts | 5 +-- 6 files changed, 66 insertions(+), 28 deletions(-) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index cae8d8bfb73b..abbf6fd69b53 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -85,14 +85,17 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * azlog.Debug("AzureMonitor", "target", azureMonitorTarget) urlComponents := map[string]string{} + urlComponents["subscription"] = fmt.Sprintf("%v", query.Model.Get("subscription").MustString()) urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"]) urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) ub := urlBuilder{ - ResourceGroup: urlComponents["resourceGroup"], - MetricDefinition: urlComponents["metricDefinition"], - ResourceName: urlComponents["resourceName"], + DefaultSubscription: query.DataSource.JsonData.Get("subscriptionId").MustString(), + Subscription: urlComponents["subscription"], + ResourceGroup: urlComponents["resourceGroup"], + MetricDefinition: urlComponents["metricDefinition"], + ResourceName: urlComponents["resourceName"], } azureURL := ub.Build() @@ -199,8 +202,7 @@ func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *mode } cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor") - subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString() - proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID) + proxyPass := fmt.Sprintf("%s/subscriptions", cloudName) u, _ := url.Parse(dsInfo.Url) u.Path = path.Join(u.Path, "render") diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go index 39cfe3f76713..94c2aef6c03a 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" @@ -27,7 +28,13 @@ func TestAzureMonitorDatasource(t *testing.T) { }, Queries: []*tsdb.Query{ { + DataSource: &models.DataSource{ + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "subscriptionId": "default-subscription", + }), + }, Model: simplejson.NewFromAny(map[string]interface{}{ + "subscription": "12345678-aaaa-bbbb-cccc-123456789abc", "azureMonitor": map[string]interface{}{ "timeGrain": "PT1M", "aggregation": "Average", @@ -49,7 +56,7 @@ func TestAzureMonitorDatasource(t *testing.T) { So(len(queries), ShouldEqual, 1) So(queries[0].RefID, ShouldEqual, "A") - So(queries[0].URL, ShouldEqual, "resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics") + So(queries[0].URL, ShouldEqual, "12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics") So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z") So(len(queries[0].Params), ShouldEqual, 5) So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z") diff --git a/pkg/tsdb/azuremonitor/url-builder.go b/pkg/tsdb/azuremonitor/url-builder.go index c252048f5172..445f815e0cd6 100644 --- a/pkg/tsdb/azuremonitor/url-builder.go +++ b/pkg/tsdb/azuremonitor/url-builder.go @@ -7,22 +7,30 @@ import ( // urlBuilder builds the URL for calling the Azure Monitor API type urlBuilder struct { - ResourceGroup string - MetricDefinition string - ResourceName string + DefaultSubscription string + Subscription string + ResourceGroup string + MetricDefinition string + ResourceName string } // Build checks the metric definition property to see which form of the url // should be returned func (ub *urlBuilder) Build() string { + subscription := ub.Subscription + + if ub.Subscription == "" { + subscription = ub.DefaultSubscription + } + if strings.Count(ub.MetricDefinition, "/") > 1 { rn := strings.Split(ub.ResourceName, "/") lastIndex := strings.LastIndex(ub.MetricDefinition, "/") service := ub.MetricDefinition[lastIndex+1:] md := ub.MetricDefinition[0:lastIndex] - return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, md, rn[0], service, rn[1]) + return fmt.Sprintf("%s/resourceGroups/%s/providers/%s/%s/%s/%s/providers/microsoft.insights/metrics", subscription, ub.ResourceGroup, md, rn[0], service, rn[1]) } - return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, ub.MetricDefinition, ub.ResourceName) + return fmt.Sprintf("%s/resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", subscription, ub.ResourceGroup, ub.MetricDefinition, ub.ResourceName) } diff --git a/pkg/tsdb/azuremonitor/url-builder_test.go b/pkg/tsdb/azuremonitor/url-builder_test.go index 85c4f81bc835..2af784a7554a 100644 --- a/pkg/tsdb/azuremonitor/url-builder_test.go +++ b/pkg/tsdb/azuremonitor/url-builder_test.go @@ -11,35 +11,51 @@ func TestURLBuilder(t *testing.T) { Convey("when metric definition is in the short form", func() { ub := &urlBuilder{ - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Compute/virtualMachines", - ResourceName: "rn", + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Compute/virtualMachines", + ResourceName: "rn", } url := ub.Build() - So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") + So(url, ShouldEqual, "default-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") + }) + + Convey("when metric definition is in the short form and a subscription is defined", func() { + ub := &urlBuilder{ + DefaultSubscription: "default-sub", + Subscription: "specified-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Compute/virtualMachines", + ResourceName: "rn", + } + + url := ub.Build() + So(url, ShouldEqual, "specified-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") }) Convey("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func() { ub := &urlBuilder{ - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices", - ResourceName: "rn1/default", + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices", + ResourceName: "rn1/default", } url := ub.Build() - So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics") + So(url, ShouldEqual, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics") }) Convey("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func() { ub := &urlBuilder{ - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices", - ResourceName: "rn1/default", + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices", + ResourceName: "rn1/default", } url := ub.Build() - So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics") + So(url, ShouldEqual, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics") }) }) } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts index 015ba0df9f60..cbdd3f54aba9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.test.ts @@ -189,11 +189,19 @@ describe('AzureMonitorQueryCtrl', () => { }; beforeEach(() => { + queryCtrl.target.subscription = 'sub1'; queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines'; queryCtrl.target.azureMonitor.resourceName = 'test'; queryCtrl.target.azureMonitor.metricName = 'Percentage CPU'; - queryCtrl.datasource.getMetricMetadata = function(resourceGroup, metricDefinition, resourceName, metricName) { + queryCtrl.datasource.getMetricMetadata = function( + subscription, + resourceGroup, + metricDefinition, + resourceName, + metricName + ) { + expect(subscription).toBe('sub1'); expect(resourceGroup).toBe('test'); expect(metricDefinition).toBe('Microsoft.Compute/virtualMachines'); expect(resourceName).toBe('test'); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index 3d896f27a0a1..a6c95d6afde8 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -279,9 +279,6 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { } onResourceGroupChange() { - this.target.azureMonitor.metricDefinition = this.defaultDropdownValue; - this.target.azureMonitor.resourceName = this.defaultDropdownValue; - this.target.azureMonitor.metricName = this.defaultDropdownValue; this.target.azureMonitor.dimensions = []; this.target.azureMonitor.dimension = ''; } @@ -294,7 +291,6 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { } onResourceNameChange() { - this.target.azureMonitor.metricName = this.defaultDropdownValue; this.target.azureMonitor.dimensions = []; this.target.azureMonitor.dimension = ''; } @@ -306,6 +302,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { return this.datasource .getMetricMetadata( + this.replace(this.target.subscription), this.replace(this.target.azureMonitor.resourceGroup), this.replace(this.target.azureMonitor.metricDefinition), this.replace(this.target.azureMonitor.resourceName), From 874039992f3c6d8769af3dfb5f1dec66a64c7c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 21 May 2019 13:10:23 +0200 Subject: [PATCH 27/92] BarGauge: Fix for negative min values (#17192) --- devenv/dev-dashboards/home.json | 40 +- ...animated_demo.json => bar_gauge_demo.json} | 374 +++++--- .../panel-bargauge/gradient_demo.json | 376 -------- .../panel-bargauge/many_modes_demo.json | 405 --------- .../panel-bargauge/panel_tests_bar_gauge.json | 829 ++++++++++++++++++ .../panel-bargauge/retro_led_demo.json | 400 --------- .../src/components/BarGauge/BarGauge.test.tsx | 28 +- .../src/components/BarGauge/BarGauge.tsx | 42 +- 8 files changed, 1148 insertions(+), 1346 deletions(-) rename devenv/dev-dashboards/panel-bargauge/{animated_demo.json => bar_gauge_demo.json} (62%) delete mode 100644 devenv/dev-dashboards/panel-bargauge/gradient_demo.json delete mode 100644 devenv/dev-dashboards/panel-bargauge/many_modes_demo.json create mode 100644 devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json delete mode 100644 devenv/dev-dashboards/panel-bargauge/retro_led_demo.json diff --git a/devenv/dev-dashboards/home.json b/devenv/dev-dashboards/home.json index 8608b0b82647..f4cdfed9d53e 100644 --- a/devenv/dev-dashboards/home.json +++ b/devenv/dev-dashboards/home.json @@ -48,39 +48,39 @@ "y": 0 }, "headings": false, - "id": 8, + "id": 2, "limit": 1000, "links": [], "query": "", "recent": false, "search": true, "starred": false, - "tags": ["panel-demo"], + "tags": ["panel-tests"], "timeFrom": null, "timeShift": null, - "title": "tag: panel-demo", + "title": "tag: panel-tests", "type": "dashlist" }, { "folderId": null, "gridPos": { - "h": 13, + "h": 26, "w": 6, "x": 12, "y": 0 }, "headings": false, - "id": 2, + "id": 3, "limit": 1000, "links": [], "query": "", "recent": false, "search": true, "starred": false, - "tags": ["panel-tests"], + "tags": ["gdev", "demo"], "timeFrom": null, "timeShift": null, - "title": "tag: panel-tests", + "title": "tag: dashboard-demo", "type": "dashlist" }, { @@ -114,28 +114,6 @@ "y": 13 }, "headings": false, - "id": 3, - "limit": 1000, - "links": [], - "query": "", - "recent": false, - "search": true, - "starred": false, - "tags": ["gdev", "demo"], - "timeFrom": null, - "timeShift": null, - "title": "tag: dashboard-demo", - "type": "dashlist" - }, - { - "folderId": null, - "gridPos": { - "h": 13, - "w": 6, - "x": 12, - "y": 13 - }, - "headings": false, "id": 4, "limit": 1000, "links": [], @@ -146,7 +124,7 @@ "tags": ["templating", "gdev"], "timeFrom": null, "timeShift": null, - "title": "tag: templating", + "title": "tag: templating ", "type": "dashlist" } ], @@ -167,5 +145,5 @@ "timezone": "", "title": "Grafana Dev Overview & Home", "uid": "j6T00KRZz", - "version": 1 + "version": 2 } diff --git a/devenv/dev-dashboards/panel-bargauge/animated_demo.json b/devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json similarity index 62% rename from devenv/dev-dashboards/panel-bargauge/animated_demo.json rename to devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json index a061f5766d1f..dcbc7fd1343d 100644 --- a/devenv/dev-dashboards/panel-bargauge/animated_demo.json +++ b/devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json @@ -15,26 +15,27 @@ "editable": true, "gnetId": null, "graphTooltip": 0, + "id": 7501, "links": [], "panels": [ { + "datasource": "gdev-testdata", "gridPos": { "h": 7, - "w": 18, + "w": 24, "x": 0, "y": 0 }, - "id": 7, + "id": 2, "links": [], "options": { - "displayMode": "gradient", + "displayMode": "lcd", "fieldOptions": { "calcs": ["mean"], "defaults": { - "decimals": null, "max": 100, "min": 0, - "unit": "watt" + "unit": "decgbytes" }, "mappings": [], "override": {}, @@ -47,7 +48,7 @@ { "color": "orange", "index": 1, - "value": 40 + "value": 60 }, { "color": "red", @@ -59,92 +60,103 @@ }, "orientation": "vertical" }, - "pluginVersion": "6.2.0-pre", "targets": [ { + "alias": "sda1", "refId": "A", "scenarioId": "random_walk" }, { + "alias": "sda2", "refId": "B", "scenarioId": "random_walk" }, { + "alias": "sda3", "refId": "C", "scenarioId": "random_walk" }, { + "alias": "sda4", "refId": "D", "scenarioId": "random_walk" }, { + "alias": "sda5", "refId": "E", - "scenarioId": "csv_metric_values", - "stringInput": "10003,33333" + "scenarioId": "random_walk" }, { + "alias": "sda6", "refId": "F", "scenarioId": "random_walk" }, { + "alias": "sda7", "refId": "G", "scenarioId": "random_walk" }, { + "alias": "sda8", "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" + "scenarioId": "random_walk" }, { + "alias": "sda9", "refId": "I", "scenarioId": "random_walk" }, { + "alias": "sda10", "refId": "J", "scenarioId": "random_walk" }, { + "alias": "sda11", "refId": "K", "scenarioId": "random_walk" }, { + "alias": "sda12", "refId": "L", "scenarioId": "random_walk" }, { + "alias": "sda13", "refId": "M", "scenarioId": "random_walk" }, { + "alias": "sda14", "refId": "N", "scenarioId": "random_walk" }, { + "alias": "sda15", "refId": "O", "scenarioId": "random_walk" }, { + "alias": "sda16", "refId": "P", "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" } ], "timeFrom": null, "timeShift": null, - "title": "Usage", + "title": "", + "transparent": true, "type": "bargauge" }, { + "datasource": "gdev-testdata", "gridPos": { - "h": 22, - "w": 6, - "x": 18, - "y": 0 + "h": 10, + "w": 16, + "x": 0, + "y": 7 }, - "id": 8, + "id": 4, "links": [], "options": { "displayMode": "gradient", @@ -154,25 +166,30 @@ "decimals": null, "max": 100, "min": 0, - "unit": "watt" + "unit": "celsius" }, "mappings": [], "override": {}, "thresholds": [ { - "color": "green", + "color": "blue", "index": 0, "value": null }, { - "color": "orange", + "color": "green", "index": 1, - "value": 55 + "value": 20 }, { - "color": "red", + "color": "orange", "index": 2, - "value": 95 + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 } ], "values": false @@ -182,32 +199,94 @@ "pluginVersion": "6.2.0-pre", "targets": [ { - "refId": "E", - "scenarioId": "random_walk" - }, - { + "alias": "Inside", "refId": "H", "scenarioId": "csv_metric_values", "stringInput": "100,100,100" }, { + "alias": "Outhouse", "refId": "A", "scenarioId": "random_walk" }, { + "alias": "Area B", "refId": "B", "scenarioId": "random_walk" }, { + "alias": "Basement", "refId": "C", "scenarioId": "random_walk" }, { + "alias": "Garage", "refId": "D", "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Gradient mode", + "type": "bargauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 10, + "w": 6, + "x": 16, + "y": 7 + }, + "id": 6, + "links": [], + "options": { + "displayMode": "basic", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 42.5 + }, + { + "color": "orange", + "index": 2, + "value": 80 + }, + { + "color": "red", + "index": 3, + "value": 90 + } + ], + "values": false + }, + "orientation": "horizontal" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" }, { - "refId": "I", + "refId": "A", "scenarioId": "random_walk" }, { @@ -241,47 +320,78 @@ { "refId": "Q", "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Basic", + "type": "bargauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 22, + "w": 2, + "x": 22, + "y": 7 + }, + "id": 8, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "max": 100, + "min": 0 + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "red", + "index": 0, + "value": null + }, + { + "color": "red", + "index": 1, + "value": 90 + } + ], + "values": false }, + "orientation": "vertical" + }, + "targets": [ { - "refId": "F", - "scenarioId": "random_walk" - }, - { - "refId": "G", - "scenarioId": "random_walk" - }, - { - "refId": "R", - "scenarioId": "random_walk" - }, - { - "refId": "S", + "refId": "A", "scenarioId": "random_walk" } ], "timeFrom": null, "timeShift": null, - "title": "Usage", + "title": "Completion", "type": "bargauge" }, { + "datasource": "gdev-testdata", "gridPos": { - "h": 15, - "w": 11, + "h": 12, + "w": 22, "x": 0, - "y": 7 + "y": 17 }, - "id": 6, + "id": 10, "links": [], "options": { "displayMode": "gradient", "fieldOptions": { "calcs": ["mean"], "defaults": { - "decimals": null, "max": 100, "min": 0, - "unit": "celsius" + "unit": "decgbytes" }, "mappings": [], "override": {}, @@ -294,12 +404,12 @@ { "color": "green", "index": 1, - "value": 20 + "value": 30 }, { "color": "orange", "index": 2, - "value": 40 + "value": 60 }, { "color": "red", @@ -309,69 +419,113 @@ ], "values": false }, - "orientation": "horizontal" + "orientation": "vertical" }, - "pluginVersion": "6.2.0-pre", "targets": [ { - "alias": "Inside", - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "alias": "Outhouse", + "alias": "sda1", "refId": "A", "scenarioId": "random_walk" }, { - "alias": "Area B", + "alias": "sda2", "refId": "B", "scenarioId": "random_walk" }, { - "alias": "Basement", + "alias": "sda3", "refId": "C", "scenarioId": "random_walk" }, { - "alias": "Garage", + "alias": "sda4", "refId": "D", "scenarioId": "random_walk" }, { - "alias": "Attic", + "alias": "sda5", "refId": "E", "scenarioId": "random_walk" }, { + "alias": "sda6", "refId": "F", "scenarioId": "random_walk" + }, + { + "alias": "sda7", + "refId": "G", + "scenarioId": "random_walk" + }, + { + "alias": "sda8", + "refId": "H", + "scenarioId": "random_walk" + }, + { + "alias": "sda9", + "refId": "I", + "scenarioId": "random_walk" + }, + { + "alias": "sda10", + "refId": "J", + "scenarioId": "random_walk" + }, + { + "alias": "sda11", + "refId": "K", + "scenarioId": "random_walk" + }, + { + "alias": "sda12", + "refId": "L", + "scenarioId": "random_walk" + }, + { + "alias": "sda13", + "refId": "M", + "scenarioId": "random_walk" + }, + { + "alias": "sda14", + "refId": "N", + "scenarioId": "random_walk" + }, + { + "alias": "sda15", + "refId": "O", + "scenarioId": "random_walk" + }, + { + "alias": "sda16", + "refId": "P", + "scenarioId": "random_walk" } ], "timeFrom": null, "timeShift": null, - "title": "Temperature", + "title": "", "type": "bargauge" }, { + "datasource": "gdev-testdata", "gridPos": { - "h": 15, - "w": 7, - "x": 11, - "y": 7 + "h": 8, + "w": 24, + "x": 0, + "y": 29 }, - "id": 9, + "id": 11, "links": [], "options": { "displayMode": "basic", "fieldOptions": { "calcs": ["mean"], "defaults": { - "decimals": null, "max": 100, "min": 0, - "unit": "celsius" + "unit": "decgbytes" }, "mappings": [], "override": {}, @@ -384,12 +538,12 @@ { "color": "green", "index": 1, - "value": 20 + "value": 30 }, { "color": "orange", "index": 2, - "value": 40 + "value": 60 }, { "color": "red", @@ -399,89 +553,113 @@ ], "values": false }, - "orientation": "horizontal" + "orientation": "vertical" }, - "pluginVersion": "6.2.0-pre", "targets": [ { - "alias": "Inside", - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "alias": "Outhouse", + "alias": "sda1", "refId": "A", "scenarioId": "random_walk" }, { - "alias": "Area B", + "alias": "sda2", "refId": "B", "scenarioId": "random_walk" }, { - "alias": "Basement", + "alias": "sda3", "refId": "C", "scenarioId": "random_walk" }, { - "alias": "Garage", + "alias": "sda4", "refId": "D", "scenarioId": "random_walk" }, { - "alias": "Attic", + "alias": "sda5", "refId": "E", "scenarioId": "random_walk" }, { + "alias": "sda6", "refId": "F", "scenarioId": "random_walk" }, { + "alias": "sda7", "refId": "G", "scenarioId": "random_walk" }, { + "alias": "sda8", + "refId": "H", + "scenarioId": "random_walk" + }, + { + "alias": "sda9", "refId": "I", "scenarioId": "random_walk" }, { + "alias": "sda10", "refId": "J", "scenarioId": "random_walk" }, { + "alias": "sda11", "refId": "K", "scenarioId": "random_walk" }, { + "alias": "sda12", "refId": "L", "scenarioId": "random_walk" + }, + { + "alias": "sda13", + "refId": "M", + "scenarioId": "random_walk" + }, + { + "alias": "sda14", + "refId": "N", + "scenarioId": "random_walk" + }, + { + "alias": "sda15", + "refId": "O", + "scenarioId": "random_walk" + }, + { + "alias": "sda16", + "refId": "P", + "scenarioId": "random_walk" } ], "timeFrom": null, "timeShift": null, - "title": "Temperature", + "title": "", "type": "bargauge" } ], - "refresh": false, + "refresh": "10s", "schemaVersion": 18, "style": "dark", - "tags": ["gdev", "bargauge", "panel-demo"], + "tags": ["gdev", "demo"], "templating": { "list": [] }, "time": { - "from": "now-30m", + "from": "now-6h", "to": "now" }, "timepicker": { - "refresh_intervals": ["1s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "refresh_intervals": ["2s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] }, "timezone": "", - "title": "Bar Gauge Animated Demo", - "uid": "k5IUwQeikaa", - "version": 1 + "title": "Bar Gauge Demo", + "uid": "vmie2cmWz", + "version": 3 } diff --git a/devenv/dev-dashboards/panel-bargauge/gradient_demo.json b/devenv/dev-dashboards/panel-bargauge/gradient_demo.json deleted file mode 100644 index ffe68eb7a863..000000000000 --- a/devenv/dev-dashboards/panel-bargauge/gradient_demo.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "gridPos": { - "h": 7, - "w": 18, - "x": 0, - "y": 0 - }, - "id": 7, - "links": [], - "options": { - "displayMode": "gradient", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "watt" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "values": false - }, - "orientation": "vertical" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "A", - "scenarioId": "random_walk" - }, - { - "refId": "B", - "scenarioId": "random_walk" - }, - { - "refId": "C", - "scenarioId": "random_walk" - }, - { - "refId": "D", - "scenarioId": "random_walk" - }, - { - "refId": "E", - "scenarioId": "csv_metric_values", - "stringInput": "10003,33333" - }, - { - "refId": "F", - "scenarioId": "random_walk" - }, - { - "refId": "G", - "scenarioId": "random_walk" - }, - { - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "refId": "I", - "scenarioId": "random_walk" - }, - { - "refId": "J", - "scenarioId": "random_walk" - }, - { - "refId": "K", - "scenarioId": "random_walk" - }, - { - "refId": "L", - "scenarioId": "random_walk" - }, - { - "refId": "M", - "scenarioId": "random_walk" - }, - { - "refId": "N", - "scenarioId": "random_walk" - }, - { - "refId": "O", - "scenarioId": "random_walk" - }, - { - "refId": "P", - "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Usage", - "type": "bargauge" - }, - { - "gridPos": { - "h": 20, - "w": 6, - "x": 18, - "y": 0 - }, - "id": 8, - "links": [], - "options": { - "displayMode": "gradient", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "watt" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 65 - }, - { - "color": "red", - "index": 2, - "value": 95 - } - ], - "values": false - }, - "orientation": "horizontal" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "E", - "scenarioId": "random_walk" - }, - { - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "refId": "A", - "scenarioId": "random_walk" - }, - { - "refId": "B", - "scenarioId": "random_walk" - }, - { - "refId": "C", - "scenarioId": "random_walk" - }, - { - "refId": "D", - "scenarioId": "random_walk" - }, - { - "refId": "I", - "scenarioId": "random_walk" - }, - { - "refId": "J", - "scenarioId": "random_walk" - }, - { - "refId": "K", - "scenarioId": "random_walk" - }, - { - "refId": "L", - "scenarioId": "random_walk" - }, - { - "refId": "M", - "scenarioId": "random_walk" - }, - { - "refId": "N", - "scenarioId": "random_walk" - }, - { - "refId": "O", - "scenarioId": "random_walk" - }, - { - "refId": "P", - "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" - }, - { - "refId": "F", - "scenarioId": "random_walk" - }, - { - "refId": "G", - "scenarioId": "random_walk" - }, - { - "refId": "R", - "scenarioId": "random_walk" - }, - { - "refId": "S", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Usage", - "type": "bargauge" - }, - { - "gridPos": { - "h": 13, - "w": 18, - "x": 0, - "y": 7 - }, - "id": 6, - "links": [], - "options": { - "displayMode": "gradient", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "celsius" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "blue", - "index": 0, - "value": null - }, - { - "color": "green", - "index": 1, - "value": 20 - }, - { - "color": "orange", - "index": 2, - "value": 40 - }, - { - "color": "red", - "index": 3, - "value": 80 - } - ], - "values": false - }, - "orientation": "horizontal" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "alias": "Inside", - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "alias": "Outhouse", - "refId": "A", - "scenarioId": "random_walk" - }, - { - "alias": "Area B", - "refId": "B", - "scenarioId": "random_walk" - }, - { - "alias": "Basement", - "refId": "C", - "scenarioId": "random_walk" - }, - { - "alias": "Garage", - "refId": "D", - "scenarioId": "random_walk" - }, - { - "alias": "Attic", - "refId": "E", - "scenarioId": "random_walk" - }, - { - "refId": "F", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Temperature", - "type": "bargauge" - } - ], - "schemaVersion": 18, - "style": "dark", - "tags": ["gdev", "bargauge", "panel-demo"], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "", - "title": "Bar Gauge Gradient Demo", - "uid": "RndRQw6mz", - "version": 1 -} diff --git a/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json b/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json deleted file mode 100644 index 8b498e78ef15..000000000000 --- a/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json +++ /dev/null @@ -1,405 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "gridPos": { - "h": 7, - "w": 22, - "x": 0, - "y": 0 - }, - "id": 7, - "links": [], - "options": { - "displayMode": "lcd", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "watt" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "values": false - }, - "orientation": "vertical" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "A", - "scenarioId": "random_walk" - }, - { - "refId": "B", - "scenarioId": "random_walk" - }, - { - "refId": "C", - "scenarioId": "random_walk" - }, - { - "refId": "D", - "scenarioId": "random_walk" - }, - { - "refId": "E", - "scenarioId": "csv_metric_values", - "stringInput": "10003,33333" - }, - { - "refId": "F", - "scenarioId": "random_walk" - }, - { - "refId": "G", - "scenarioId": "random_walk" - }, - { - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "refId": "I", - "scenarioId": "random_walk" - }, - { - "refId": "J", - "scenarioId": "random_walk" - }, - { - "refId": "K", - "scenarioId": "random_walk" - }, - { - "refId": "L", - "scenarioId": "random_walk" - }, - { - "refId": "M", - "scenarioId": "random_walk" - }, - { - "refId": "N", - "scenarioId": "random_walk" - }, - { - "refId": "O", - "scenarioId": "random_walk" - }, - { - "refId": "P", - "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Usage", - "type": "bargauge" - }, - { - "gridPos": { - "h": 20, - "w": 2, - "x": 22, - "y": 0 - }, - "id": 11, - "links": [], - "options": { - "displayMode": "lcd", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "percent" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "red", - "index": 1, - "value": 80 - } - ], - "values": false - }, - "orientation": "vertical" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "A", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Progress", - "type": "bargauge" - }, - { - "gridPos": { - "h": 13, - "w": 10, - "x": 0, - "y": 7 - }, - "id": 6, - "links": [], - "options": { - "displayMode": "gradient", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "celsius" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "blue", - "index": 0, - "value": null - }, - { - "color": "green", - "index": 1, - "value": 20 - }, - { - "color": "orange", - "index": 2, - "value": 40 - }, - { - "color": "red", - "index": 3, - "value": 80 - } - ], - "values": false - }, - "orientation": "horizontal" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "alias": "Inside", - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "alias": "Outhouse", - "refId": "A", - "scenarioId": "random_walk" - }, - { - "alias": "Area B", - "refId": "B", - "scenarioId": "random_walk" - }, - { - "alias": "Basement", - "refId": "C", - "scenarioId": "random_walk" - }, - { - "alias": "Garage", - "refId": "D", - "scenarioId": "random_walk" - }, - { - "alias": "Attic", - "refId": "E", - "scenarioId": "random_walk" - }, - { - "refId": "F", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Temperature", - "type": "bargauge" - }, - { - "gridPos": { - "h": 13, - "w": 12, - "x": 10, - "y": 7 - }, - "id": 8, - "links": [], - "options": { - "displayMode": "basic", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "watt" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "purple", - "index": 1, - "value": 50 - }, - { - "color": "blue", - "index": 2, - "value": 70 - } - ], - "values": false - }, - "orientation": "horizontal" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "refId": "A", - "scenarioId": "random_walk" - }, - { - "refId": "B", - "scenarioId": "random_walk" - }, - { - "refId": "C", - "scenarioId": "random_walk" - }, - { - "refId": "D", - "scenarioId": "random_walk" - }, - { - "refId": "I", - "scenarioId": "random_walk" - }, - { - "refId": "J", - "scenarioId": "random_walk" - }, - { - "refId": "K", - "scenarioId": "random_walk" - }, - { - "refId": "L", - "scenarioId": "random_walk" - }, - { - "refId": "M", - "scenarioId": "random_walk" - }, - { - "refId": "N", - "scenarioId": "random_walk" - }, - { - "refId": "O", - "scenarioId": "random_walk" - }, - { - "refId": "P", - "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Usage", - "type": "bargauge" - } - ], - "schemaVersion": 18, - "style": "dark", - "tags": ["gdev", "bargauge", "panel-demo"], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "", - "title": "Bar Gauge All Modes Demo", - "uid": "zt2f6NgZzaa", - "version": 1 -} diff --git a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json new file mode 100644 index 000000000000..6230bb6c3055 --- /dev/null +++ b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json @@ -0,0 +1,829 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "links": [], + "options": { + "displayMode": "gradient", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "alias": "Inside", + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" + }, + { + "alias": "Outhouse", + "refId": "A", + "scenarioId": "random_walk" + }, + { + "refId": "F", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Title above bar", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 5, + "x": 6, + "y": 0 + }, + "id": 12, + "links": [], + "options": { + "displayMode": "gradient", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "alias": "Inside", + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" + }, + { + "alias": "Outhouse", + "refId": "A", + "scenarioId": "random_walk" + }, + { + "refId": "F", + "scenarioId": "random_walk" + }, + { + "refId": "B", + "scenarioId": "random_walk" + }, + { + "refId": "C", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Title to left of bar", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 7, + "x": 11, + "y": 0 + }, + "id": 13, + "links": [], + "options": { + "displayMode": "basic", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "alias": "Inside", + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" + }, + { + "alias": "Outhouse", + "refId": "A", + "scenarioId": "random_walk" + }, + { + "refId": "F", + "scenarioId": "random_walk" + }, + { + "refId": "B", + "scenarioId": "random_walk" + }, + { + "refId": "C", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Basic mode", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 14, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "alias": "Inside", + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" + }, + { + "alias": "Outhouse", + "refId": "A", + "scenarioId": "random_walk" + }, + { + "refId": "F", + "scenarioId": "random_walk" + }, + { + "refId": "B", + "scenarioId": "random_walk" + }, + { + "refId": "C", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "LED", + "type": "bargauge" + }, + { + "gridPos": { + "h": 9, + "w": 11, + "x": 0, + "y": 7 + }, + "id": 7, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "refId": "E", + "scenarioId": "csv_metric_values", + "stringInput": "10003,33333" + }, + { + "refId": "F", + "scenarioId": "random_walk" + }, + { + "refId": "G", + "scenarioId": "random_walk" + }, + { + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" + }, + { + "refId": "I", + "scenarioId": "random_walk" + }, + { + "refId": "J", + "scenarioId": "random_walk" + }, + { + "refId": "K", + "scenarioId": "random_walk" + }, + { + "refId": "L", + "scenarioId": "random_walk" + }, + { + "refId": "M", + "scenarioId": "random_walk" + }, + { + "refId": "N", + "scenarioId": "random_walk" + }, + { + "refId": "O", + "scenarioId": "random_walk" + }, + { + "refId": "P", + "scenarioId": "random_walk" + }, + { + "refId": "Q", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "LED Vertical", + "type": "bargauge" + }, + { + "gridPos": { + "h": 9, + "w": 13, + "x": 11, + "y": 7 + }, + "id": 8, + "links": [], + "options": { + "displayMode": "basic", + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "purple", + "index": 1, + "value": 50 + }, + { + "color": "blue", + "index": 2, + "value": 70 + } + ], + "values": false + }, + "orientation": "vertical" + }, + "pluginVersion": "6.2.0-pre", + "targets": [ + { + "refId": "H", + "scenarioId": "csv_metric_values", + "stringInput": "100,100,100" + }, + { + "refId": "A", + "scenarioId": "random_walk" + }, + { + "refId": "B", + "scenarioId": "random_walk" + }, + { + "refId": "C", + "scenarioId": "random_walk" + }, + { + "refId": "D", + "scenarioId": "random_walk" + }, + { + "refId": "I", + "scenarioId": "random_walk" + }, + { + "refId": "J", + "scenarioId": "random_walk" + }, + { + "refId": "K", + "scenarioId": "random_walk" + }, + { + "refId": "L", + "scenarioId": "random_walk" + }, + { + "refId": "M", + "scenarioId": "random_walk" + }, + { + "refId": "N", + "scenarioId": "random_walk" + }, + { + "refId": "O", + "scenarioId": "random_walk" + }, + { + "refId": "P", + "scenarioId": "random_walk" + }, + { + "refId": "Q", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Basic vertical ", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 11, + "x": 0, + "y": 16 + }, + "id": 16, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["last"], + "defaults": { + "max": 100, + "min": 0 + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "blue", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" + }, + "pluginVersion": "6.3.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0,-100" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Negative value below min", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 3, + "x": 11, + "y": 16 + }, + "id": 17, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["last"], + "defaults": { + "max": 100, + "min": 0 + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "blue", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" + }, + "pluginVersion": "6.3.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0,-100" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Negative value below min", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 3, + "x": 14, + "y": 16 + }, + "id": 18, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["last"], + "defaults": { + "max": 100, + "min": -10 + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "blue", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" + }, + "pluginVersion": "6.3.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,6" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Positive value above min", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 3, + "x": 17, + "y": 16 + }, + "id": 19, + "links": [], + "options": { + "displayMode": "lcd", + "fieldOptions": { + "calcs": ["last"], + "defaults": { + "max": 35, + "min": -20 + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 5 + }, + { + "color": "#EAB839", + "index": 2, + "value": 25 + }, + { + "color": "red", + "index": 3, + "value": 30 + } + ], + "values": false + }, + "orientation": "vertical" + }, + "pluginVersion": "6.3.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,6" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Negative min ", + "type": "bargauge" + }, + { + "gridPos": { + "h": 7, + "w": 4, + "x": 20, + "y": 16 + }, + "id": 20, + "links": [], + "options": { + "displayMode": "gradient", + "fieldOptions": { + "calcs": ["last"], + "defaults": { + "max": 35, + "min": -20 + }, + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 5 + }, + { + "color": "#EAB839", + "index": 2, + "value": 25 + }, + { + "color": "red", + "index": 3, + "value": 30 + } + ], + "values": false + }, + "orientation": "vertical" + }, + "pluginVersion": "6.3.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "30,30" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Negative min", + "type": "bargauge" + } + ], + "schemaVersion": 18, + "style": "dark", + "tags": ["gdev", "panel-tests"], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, + "timezone": "", + "title": "Panel Tests - Bar Gauge", + "uid": "O6f11TZWk", + "version": 12 +} diff --git a/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json b/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json deleted file mode 100644 index 3fe8272c4dfe..000000000000 --- a/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json +++ /dev/null @@ -1,400 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "gridPos": { - "h": 8, - "w": 22, - "x": 0, - "y": 0 - }, - "id": 7, - "links": [], - "options": { - "displayMode": "lcd", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "watt" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "values": false - }, - "orientation": "vertical" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "A", - "scenarioId": "random_walk" - }, - { - "refId": "B", - "scenarioId": "random_walk" - }, - { - "refId": "C", - "scenarioId": "random_walk" - }, - { - "refId": "D", - "scenarioId": "random_walk" - }, - { - "refId": "E", - "scenarioId": "csv_metric_values", - "stringInput": "10003,33333" - }, - { - "refId": "F", - "scenarioId": "random_walk" - }, - { - "refId": "G", - "scenarioId": "random_walk" - }, - { - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "refId": "I", - "scenarioId": "random_walk" - }, - { - "refId": "J", - "scenarioId": "random_walk" - }, - { - "refId": "K", - "scenarioId": "random_walk" - }, - { - "refId": "L", - "scenarioId": "random_walk" - }, - { - "refId": "M", - "scenarioId": "random_walk" - }, - { - "refId": "N", - "scenarioId": "random_walk" - }, - { - "refId": "O", - "scenarioId": "random_walk" - }, - { - "refId": "P", - "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Usage", - "type": "bargauge" - }, - { - "gridPos": { - "h": 21, - "w": 2, - "x": 22, - "y": 0 - }, - "id": 11, - "links": [], - "options": { - "displayMode": "lcd", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "percent" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "red", - "index": 1, - "value": 80 - } - ], - "values": false - }, - "orientation": "vertical" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "A", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Progress", - "type": "bargauge" - }, - { - "gridPos": { - "h": 13, - "w": 10, - "x": 0, - "y": 8 - }, - "id": 6, - "links": [], - "options": { - "displayMode": "lcd", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "celsius" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "values": false - }, - "orientation": "horizontal" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "alias": "Inside", - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "alias": "Outhouse", - "refId": "A", - "scenarioId": "random_walk" - }, - { - "alias": "Area B", - "refId": "B", - "scenarioId": "random_walk" - }, - { - "alias": "Basement", - "refId": "C", - "scenarioId": "random_walk" - }, - { - "alias": "Garage", - "refId": "D", - "scenarioId": "random_walk" - }, - { - "alias": "Attic", - "refId": "E", - "scenarioId": "random_walk" - }, - { - "refId": "F", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Temperature", - "type": "bargauge" - }, - { - "gridPos": { - "h": 13, - "w": 12, - "x": 10, - "y": 8 - }, - "id": 8, - "links": [], - "options": { - "displayMode": "lcd", - "fieldOptions": { - "calcs": ["mean"], - "defaults": { - "decimals": null, - "max": 100, - "min": 0, - "unit": "watt" - }, - "mappings": [], - "override": {}, - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 85 - }, - { - "color": "red", - "index": 2, - "value": 95 - } - ], - "values": false - }, - "orientation": "horizontal" - }, - "pluginVersion": "6.2.0-pre", - "targets": [ - { - "refId": "H", - "scenarioId": "csv_metric_values", - "stringInput": "100,100,100" - }, - { - "refId": "A", - "scenarioId": "random_walk" - }, - { - "refId": "B", - "scenarioId": "random_walk" - }, - { - "refId": "C", - "scenarioId": "random_walk" - }, - { - "refId": "D", - "scenarioId": "random_walk" - }, - { - "refId": "I", - "scenarioId": "random_walk" - }, - { - "refId": "J", - "scenarioId": "random_walk" - }, - { - "refId": "K", - "scenarioId": "random_walk" - }, - { - "refId": "L", - "scenarioId": "random_walk" - }, - { - "refId": "M", - "scenarioId": "random_walk" - }, - { - "refId": "N", - "scenarioId": "random_walk" - }, - { - "refId": "O", - "scenarioId": "random_walk" - }, - { - "refId": "P", - "scenarioId": "random_walk" - }, - { - "refId": "Q", - "scenarioId": "random_walk" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Usage", - "type": "bargauge" - } - ], - "schemaVersion": 18, - "style": "dark", - "tags": ["gdev", "bargauge", "panel-demo"], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "", - "title": "Bar Gauge LED Demo", - "uid": "0G3rbkqmkaa", - "version": 1 -} diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx index 620222315fa8..0dea5e2c9b52 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx @@ -1,6 +1,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { BarGauge, Props, getValueColor, getBasicAndGradientStyles, getBarGradient, getTitleStyles } from './BarGauge'; +import { + BarGauge, + Props, + getValueColor, + getBasicAndGradientStyles, + getBarGradient, + getTitleStyles, + getValuePercent, +} from './BarGauge'; import { VizOrientation, DisplayValue } from '../../types'; import { getTheme } from '../../themes'; @@ -63,6 +71,24 @@ describe('BarGauge', () => { }); }); + describe('Get value percent', () => { + it('0 to 100 and value 40', () => { + expect(getValuePercent(40, 0, 100)).toEqual(0.4); + }); + + it('50 to 100 and value 75', () => { + expect(getValuePercent(75, 50, 100)).toEqual(0.5); + }); + + it('-30 to 30 and value 0', () => { + expect(getValuePercent(0, -30, 30)).toEqual(0.5); + }); + + it('-30 to 30 and value 30', () => { + expect(getValuePercent(30, -30, 30)).toEqual(1); + }); + }); + describe('Vertical bar without title', () => { it('should not include title height in height', () => { const props = getProps({ diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index 9c5bf6563820..4351b6671d6e 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -161,7 +161,7 @@ export class BarGauge extends PureComponent { const cells: JSX.Element[] = []; for (let i = 0; i < cellCount; i++) { - const currentValue = (valueRange / cellCount) * i; + const currentValue = minValue + (valueRange / cellCount) * i; const cellColor = this.getCellColor(currentValue); const cellStyles: CSSProperties = { borderRadius: '2px', @@ -345,11 +345,6 @@ function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions { } } - // console.log('titleDim', titleDim); - // console.log('valueWidth', valueWidth); - // console.log('width', width); - // console.log('total', titleDim.width + maxBarWidth + valueWidth); - return { valueWidth, valueHeight, @@ -360,6 +355,10 @@ function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions { }; } +export function getValuePercent(value: number, minValue: number, maxValue: number): number { + return Math.min((value - minValue) / (maxValue - minValue), 1); +} + /** * Only exported to for unit test */ @@ -367,7 +366,7 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles const { displayMode, maxValue, minValue, value } = props; const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(props); - const valuePercent = Math.min(value.numeric / (maxValue - minValue), 1); + const valuePercent = getValuePercent(value.numeric, minValue, maxValue); const valueColor = getValueColor(props); const valueStyles = getValueStyles(value.text, valueColor, valueWidth, valueHeight); const isBasic = displayMode === 'basic'; @@ -450,7 +449,7 @@ export function getBarGradient(props: Props, maxSize: number): string { for (let i = 0; i < thresholds.length; i++) { const threshold = thresholds[i]; const color = getColorFromHexRgbOrName(threshold.color); - const valuePercent = Math.min(threshold.value / (maxValue - minValue), 1); + const valuePercent = getValuePercent(threshold.value, minValue, maxValue); const pos = valuePercent * maxSize; const offset = Math.round(pos - (pos - lastpos) / 2); @@ -499,30 +498,3 @@ function getValueStyles(value: string, color: string, width: number, height: num fontSize: fontSize.toFixed(2) + 'px', }; } - -// let canvasElement: HTMLCanvasElement | null = null; -// -// interface TextDimensions { -// width: number; -// height: number; -// } -// -// /** -// * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. -// * -// * @param {String} text The text to be rendered. -// * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). -// * -// * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 -// */ -// function getTextWidth(text: string): number { -// // re-use canvas object for better performance -// canvasElement = canvasElement || document.createElement('canvas'); -// const context = canvasElement.getContext('2d'); -// if (context) { -// context.font = 'normal 16px Roboto'; -// const metrics = context.measureText(text); -// return metrics.width; -// } -// return 16; -// } From 73e4178aef8f0bc6657229eb22adf2e05264bf64 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 21 May 2019 13:19:19 +0200 Subject: [PATCH 28/92] Panel: Apply option defaults on panel init and on save model retrieval (#17174) * Apply panel options defaults on panel init and on save model retrieval * Remove unnecessary argument, added tests * Make FieldPropertiesEditor statefull to enable onBlur changes * Remove unnecessary import * Post-review updates Fixes #17154 --- .../FieldPropertiesEditor.tsx | 178 ++++++++++-------- .../dashboard/dashgrid/PanelChrome.tsx | 2 +- .../panel_editor/VisualizationTab.tsx | 4 +- .../dashboard/state/PanelModel.test.ts | 100 ++++++---- .../features/dashboard/state/PanelModel.ts | 16 +- 5 files changed, 173 insertions(+), 127 deletions(-) diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx index 0c7858aef69d..8205599e9368 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx @@ -1,5 +1,5 @@ // Libraries -import React, { PureComponent, ChangeEvent } from 'react'; +import React, { ChangeEvent, useState, useCallback } from 'react'; // Components import { FormField } from '../FormField/FormField'; @@ -8,7 +8,7 @@ import { UnitPicker } from '../UnitPicker/UnitPicker'; // Types import { Field } from '../../types/data'; -import { toNumberString, toIntegerOrUndefined } from '../../utils'; +import { toIntegerOrUndefined } from '../../utils'; import { SelectOptionItem } from '../Select/Select'; import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay'; @@ -21,92 +21,108 @@ export interface Props { onChange: (value: Partial, event?: React.SyntheticEvent) => void; } -export class FieldPropertiesEditor extends PureComponent { - onTitleChange = (event: ChangeEvent) => - this.props.onChange({ ...this.props.value, title: event.target.value }); +export const FieldPropertiesEditor: React.FC = ({ value, onChange, showMinMax }) => { + const { unit, title } = value; - // @ts-ignore - onUnitChange = (unit: SelectOptionItem) => this.props.onChange({ ...this.props.value, unit: unit.value }); + const [decimals, setDecimals] = useState( + value.decimals !== undefined && value.decimals !== null ? value.decimals.toString() : '' + ); + const [min, setMin] = useState(value.min !== undefined && value.min !== null ? value.min.toString() : ''); + const [max, setMax] = useState(value.max !== undefined && value.max !== null ? value.max.toString() : ''); - onDecimalChange = (event: ChangeEvent) => { - this.props.onChange({ - ...this.props.value, - decimals: toIntegerOrUndefined(event.target.value), - }); + const onTitleChange = (event: ChangeEvent) => { + onChange({ ...value, title: event.target.value }); }; - onMinChange = (event: ChangeEvent) => { - this.props.onChange({ - ...this.props.value, - min: toIntegerOrUndefined(event.target.value), - }); + const onDecimalChange = useCallback( + (event: ChangeEvent) => { + setDecimals(event.target.value); + }, + [value.decimals, onChange] + ); + + const onMinChange = useCallback( + (event: ChangeEvent) => { + setMin(event.target.value); + }, + [value.min, onChange] + ); + + const onMaxChange = useCallback( + (event: ChangeEvent) => { + setMax(event.target.value); + }, + [value.max, onChange] + ); + + const onUnitChange = (unit: SelectOptionItem) => { + onChange({ ...value, unit: unit.value }); }; - onMaxChange = (event: ChangeEvent) => { - this.props.onChange({ - ...this.props.value, - max: toIntegerOrUndefined(event.target.value), + const commitChanges = useCallback(() => { + onChange({ + ...value, + decimals: toIntegerOrUndefined(decimals), + min: toIntegerOrUndefined(min), + max: toIntegerOrUndefined(max), }); - }; + }, [min, max, decimals]); - render() { - const { showMinMax } = this.props; - const { unit, decimals, min, max } = this.props.value; + const titleTooltip = ( +
+ Template Variables: +
+ {'$' + VAR_SERIES_NAME} +
+ {'$' + VAR_FIELD_NAME} +
+ {'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC} +
+ ); + return ( + <> + - const titleTooltip = ( -
- Template Variables: -
- {'$' + VAR_SERIES_NAME} -
- {'$' + VAR_FIELD_NAME} -
- {'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC} +
+ Unit +
- ); - - return ( - <> - - -
- Unit - -
- {showMinMax && ( - <> - - - - )} - - - ); - } -} + {showMinMax && ( + <> + + + + )} + + + ); +}; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index bbebbbf2f65c..e8a13cdeb711 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -253,7 +253,7 @@ export class PanelChrome extends PureComponent { id={panel.id} data={data} timeRange={data.request ? data.request.range : this.timeSrv.timeRange()} - options={panel.getOptions(plugin.defaults)} + options={panel.getOptions()} width={width - theme.panelPadding * 2} height={innerPanelHeight} renderCounter={renderCounter} diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index eb7bad652424..0eb352ca8061 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -53,8 +53,8 @@ export class VisualizationTab extends PureComponent { } getReactPanelOptions = () => { - const { panel, plugin } = this.props; - return panel.getOptions(plugin.defaults); + const { panel } = this.props; + return panel.getOptions(); }; renderPanelOptions() { diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index f4d9e8d1a667..3dcb5f5cb066 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -7,45 +7,70 @@ describe('PanelModel', () => { describe('when creating new panel model', () => { let model; let modelJson; + let persistedOptionsMock; + const defaultOptionsMock = { + fieldOptions: { + thresholds: [ + { + color: '#F2495C', + index: 1, + value: 50, + }, + { + color: '#73BF69', + index: 0, + value: null, + }, + ], + }, + showThresholds: true, + }; beforeEach(() => { + persistedOptionsMock = { + fieldOptions: { + thresholds: [ + { + color: '#F2495C', + index: 1, + value: 50, + }, + { + color: '#73BF69', + index: 0, + value: null, + }, + ], + }, + }; + modelJson = { type: 'table', showColumns: true, targets: [{ refId: 'A' }, { noRefId: true }], - options: { - fieldOptions: { - thresholds: [ - { - color: '#F2495C', - index: 1, - value: 50, - }, - { - color: '#73BF69', - index: 0, - value: null, - }, - ], - }, - }, + options: persistedOptionsMock, }; + model = new PanelModel(modelJson); - model.pluginLoaded( - getPanelPlugin( - { - id: 'table', - }, - null, // react - TablePanelCtrl // angular - ) + const panelPlugin = getPanelPlugin( + { + id: 'table', + }, + null, // react + TablePanelCtrl // angular ); + panelPlugin.setDefaults(defaultOptionsMock); + model.pluginLoaded(panelPlugin); }); it('should apply defaults', () => { expect(model.gridPos.h).toBe(3); }); + it('should apply option defaults', () => { + expect(model.getOptions().showThresholds).toBeTruthy(); + }); + it('should set model props on instance', () => { expect(model.showColumns).toBe(true); }); @@ -89,11 +114,22 @@ describe('PanelModel', () => { }); describe('when changing panel type', () => { + const newPanelPluginDefaults = { + showThresholdLabels: false, + }; + beforeEach(() => { - model.changePlugin(getPanelPlugin({ id: 'graph' })); + const newPlugin = getPanelPlugin({ id: 'graph' }); + newPlugin.setDefaults(newPanelPluginDefaults); + model.changePlugin(newPlugin); model.alert = { id: 2 }; }); + it('should apply next panel option defaults', () => { + expect(model.getOptions().showThresholdLabels).toBeFalsy(); + expect(model.getOptions().showThresholds).toBeUndefined(); + }); + it('should remove table properties but keep core props', () => { expect(model.showColumns).toBe(undefined); }); @@ -153,19 +189,5 @@ describe('PanelModel', () => { expect(panelQueryRunner).toBe(sameQueryRunner); }); }); - - describe('get panel options', () => { - it('should apply defaults', () => { - model.options = { existingProp: 10 }; - const options = model.getOptions({ - defaultProp: true, - existingProp: 0, - }); - - expect(options.defaultProp).toBe(true); - expect(options.existingProp).toBe(10); - expect(model.options).toBe(options); - }); - }); }); }); diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 156ae6dfa720..044d097e86ad 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -157,8 +157,8 @@ export class PanelModel { } } - getOptions(panelDefaults: any) { - return _.defaultsDeep(this.options || {}, panelDefaults); + getOptions() { + return this.options; } updateOptions(options: object) { @@ -179,7 +179,6 @@ export class PanelModel { model[property] = _.cloneDeep(this[property]); } - return model; } @@ -247,9 +246,18 @@ export class PanelModel { }); } + private applyPluginOptionDefaults(plugin: PanelPlugin) { + if (plugin.angularConfigCtrl) { + return; + } + this.options = _.defaultsDeep({}, this.options || {}, plugin.defaults); + } + pluginLoaded(plugin: PanelPlugin) { this.plugin = plugin; + this.applyPluginOptionDefaults(plugin); + if (plugin.panel && plugin.onPanelMigration) { const version = getPluginVersion(plugin); if (version !== this.pluginVersion) { @@ -284,7 +292,7 @@ export class PanelModel { // switch this.type = pluginId; this.plugin = newPlugin; - + this.applyPluginOptionDefaults(newPlugin); // Let panel plugins inspect options from previous panel and keep any that it can use if (newPlugin.onPanelTypeChanged) { this.options = this.options || {}; From 1033f0f905f2e50094be4e37f9b96ec375849f7c Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 21 May 2019 04:46:19 -0700 Subject: [PATCH 29/92] DataSourceMeta: add an option to get hidden queries (#17124) * add an option to get hidden queries * make sure you have meta * supportsHiddenQueries * remove spaces * DataSources: hidden queries flag --- packages/grafana-ui/src/types/datasource.ts | 7 +++++++ pkg/plugins/datasource_plugin.go | 21 ++++++++++--------- .../dashboard/state/PanelQueryRunner.ts | 9 ++++---- .../plugins/datasource/cloudwatch/plugin.json | 1 + .../plugins/datasource/graphite/plugin.json | 1 + 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 36506969117f..596e5851cf00 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -85,6 +85,13 @@ export interface DataSourcePluginMeta extends PluginMeta { queryOptions?: PluginMetaQueryOptions; sort?: number; supportsStreaming?: boolean; + + /** + * By default, hidden queries are not passed to the datasource + * Set this to true in plugin.json to have hidden queries passed to the + * DataSource query method + */ + hiddenQueries?: boolean; } interface PluginMetaQueryOptions { diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go index 57dacbe43b51..8c846839edaf 100644 --- a/pkg/plugins/datasource_plugin.go +++ b/pkg/plugins/datasource_plugin.go @@ -18,16 +18,17 @@ import ( // DataSourcePlugin contains all metadata about a datasource plugin type DataSourcePlugin struct { FrontendPluginBase - Annotations bool `json:"annotations"` - Metrics bool `json:"metrics"` - Alerting bool `json:"alerting"` - Explore bool `json:"explore"` - Table bool `json:"tables"` - Logs bool `json:"logs"` - QueryOptions map[string]bool `json:"queryOptions,omitempty"` - BuiltIn bool `json:"builtIn,omitempty"` - Mixed bool `json:"mixed,omitempty"` - Routes []*AppPluginRoute `json:"routes"` + Annotations bool `json:"annotations"` + Metrics bool `json:"metrics"` + Alerting bool `json:"alerting"` + Explore bool `json:"explore"` + Table bool `json:"tables"` + HiddenQueries bool `json:"hiddenQueries"` + Logs bool `json:"logs"` + QueryOptions map[string]bool `json:"queryOptions,omitempty"` + BuiltIn bool `json:"builtIn,omitempty"` + Mixed bool `json:"mixed,omitempty"` + Routes []*AppPluginRoute `json:"routes"` Backend bool `json:"backend,omitempty"` Executable string `json:"executable,omitempty"` diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index 00f6e3a00f02..b75a538f496e 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -108,9 +108,6 @@ export class PanelQueryRunner { delayStateNotification, } = options; - // filter out hidden queries & deep clone them - const clonedAndFilteredQueries = cloneDeep(queries.filter(q => !q.hide)); - const request: DataQueryRequest = { requestId: getNextRequestId(), timezone, @@ -120,7 +117,7 @@ export class PanelQueryRunner { timeInfo, interval: '', intervalMs: 0, - targets: clonedAndFilteredQueries, + targets: cloneDeep(queries), maxDataPoints: maxDataPoints || widthPixels, scopedVars: scopedVars || {}, cacheTimeout, @@ -135,6 +132,10 @@ export class PanelQueryRunner { try { const ds = await getDataSource(datasource, request.scopedVars); + if (ds.meta && !ds.meta.hiddenQueries) { + request.targets = request.targets.filter(q => !q.hide); + } + // Attach the datasource name to each query request.targets = request.targets.map(query => { if (!query.datasource) { diff --git a/public/app/plugins/datasource/cloudwatch/plugin.json b/public/app/plugins/datasource/cloudwatch/plugin.json index 212bb20a059f..2e71355975ce 100644 --- a/public/app/plugins/datasource/cloudwatch/plugin.json +++ b/public/app/plugins/datasource/cloudwatch/plugin.json @@ -4,6 +4,7 @@ "id": "cloudwatch", "category": "cloud", + "hiddenQueries": true, "metrics": true, "alerting": true, "annotations": true, diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json index f66c7fb202f6..01a95727ee79 100644 --- a/public/app/plugins/datasource/graphite/plugin.json +++ b/public/app/plugins/datasource/graphite/plugin.json @@ -6,6 +6,7 @@ "includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }], + "hiddenQueries": true, "metrics": true, "alerting": true, "annotations": true, From 8d1909c56debe39d4ede7fd44dbce71d21d6488d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 21 May 2019 13:50:57 +0200 Subject: [PATCH 30/92] docs: what's new in v6.2 fixes (#17193) --- docs/sources/guides/whats-new-in-v6-2.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/sources/guides/whats-new-in-v6-2.md b/docs/sources/guides/whats-new-in-v6-2.md index 00337c8ef2a2..8ee2e7a66c70 100644 --- a/docs/sources/guides/whats-new-in-v6-2.md +++ b/docs/sources/guides/whats-new-in-v6-2.md @@ -12,19 +12,21 @@ weight = -13 # What's New in Grafana v6.2 -> More content will be added to this guide before the stable release. - -Grafana v6.2 Beta is now [available for download](https://grafana.com/grafana/download/beta). - For all details please read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) If you use a password for your datasources please read the [upgrade notes](/installation/upgrading/#upgrading-to-v6-2). +Checkout the [demo dashboard](https://play.grafana.org/d/ZvPm55mWk/new-features-in-v6-2?orgId=1) of some the new features in v6.2. + ## Improved security -- Ensure encryption of datasource secrets -- Embedding Grafana not allowed per default -- Disable browser caching for full page requests +Datasources now store passwords and basic auth passwords in `secureJsonData` encrypted by default. Existing datasource with unencrypted passwords will keep working. +Read the [upgrade notes](/installation/upgrading/#upgrading-to-v6-2) on how to migrate existing datasources to use encrypted storage. + +To mitigate the risk of [Clickjacking](https://www.owasp.org/index.php/Clickjacking), embedding Grafana is no longer allowed per default. +Read the [upgrade notes](/installation/upgrading/#upgrading-to-v6-2) for further details of how this may affect you. + +To mitigate the risk of sensitive information being cached in browser after a user has logged out, browser caching is now disabled for full page requests. ## Provisioning @@ -79,10 +81,11 @@ This release contains a lot of small features and fixes: - Explore - Adds user time zone support, reconnect for failing datasources and a fix that prevents killing Prometheus instances when Histogram metrics are loaded. - Alerting - Adds support for configuring timeout durations and retries, see [configuration](/installation/configuration/#evaluation-timeout-seconds) for more information. +- Azure Monitor - Adds support for multiple subscriptions per datasource. - Elasticsearch - A small bug fix to properly display percentiles metrics in table panel. - InfluxDB - Support for POST HTTP verb. - CloudWatch - Important fix for default alias disappearing in v6.1. -- Search - Works in a scope of dashboard's folder by default when viewing dashboard +- Search - Works in a scope of dashboard's folder by default when viewing dashboard. Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list of new features, changes, and bug fixes. From 2d03815770b67950671df090c01f419fa045dc48 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 21 May 2019 14:52:49 +0300 Subject: [PATCH 31/92] Users: Disable users removed from LDAP (#16820) * Users: add is_disabled column * Users: disable users removed from LDAP * Auth: return ErrInvalidCredentials for failed LDAP auth * User: return isDisabled flag in user search api * User: mark disabled users at the server admin page * Chore: refactor according to review * Auth: prevent disabled user from login * Auth: re-enable user when it found in ldap * User: add api endpoint for disabling user * User: use separate endpoints to disable/enable user * User: disallow disabling external users * User: able do disable users from admin UI * Chore: refactor based on review * Chore: use more clear error check when disabling user * Fix login tests * Tests for disabling user during the LDAP login * Tests for disable user API * Tests for login with disabled user * Remove disable user UI stub * Sync with latest LDAP refactoring --- pkg/api/admin_users.go | 72 +++++++++++---- pkg/api/admin_users_test.go | 53 +++++++++++ pkg/api/api.go | 2 + pkg/api/login.go | 4 + pkg/login/auth.go | 9 +- pkg/login/auth_test.go | 4 +- pkg/login/grafana_login.go | 4 + pkg/login/grafana_login_test.go | 23 +++++ pkg/models/user.go | 8 ++ pkg/models/user_auth.go | 11 +++ pkg/services/ldap/ldap.go | 33 ++++++- pkg/services/ldap/ldap_login_test.go | 97 ++++++++++++++++++++ pkg/services/ldap/test.go | 46 ++++++++-- pkg/services/login/login.go | 47 ++++++---- pkg/services/sqlstore/migrations/user_mig.go | 6 ++ pkg/services/sqlstore/user.go | 16 +++- pkg/services/sqlstore/user_auth.go | 65 +++++++++---- 17 files changed, 428 insertions(+), 72 deletions(-) diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index 76193771eb91..dc056dbe2763 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -4,12 +4,12 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/metrics" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/util" ) -func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) { - cmd := m.CreateUserCommand{ +func AdminCreateUser(c *models.ReqContext, form dtos.AdminCreateUserForm) { + cmd := models.CreateUserCommand{ Login: form.Login, Email: form.Email, Password: form.Password, @@ -38,7 +38,7 @@ func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) { user := cmd.Result - result := m.UserIdDTO{ + result := models.UserIdDTO{ Message: "User created", Id: user.Id, } @@ -46,7 +46,7 @@ func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) { c.JSON(200, result) } -func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordForm) { +func AdminUpdateUserPassword(c *models.ReqContext, form dtos.AdminUpdateUserPasswordForm) { userID := c.ParamsInt64(":id") if len(form.Password) < 4 { @@ -54,7 +54,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF return } - userQuery := m.GetUserByIdQuery{Id: userID} + userQuery := models.GetUserByIdQuery{Id: userID} if err := bus.Dispatch(&userQuery); err != nil { c.JsonApiErr(500, "Could not read user from database", err) @@ -63,7 +63,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt) - cmd := m.ChangeUserPasswordCommand{ + cmd := models.ChangeUserPasswordCommand{ UserId: userID, NewPassword: passwordHashed, } @@ -77,17 +77,17 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF } // PUT /api/admin/users/:id/permissions -func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) { +func AdminUpdateUserPermissions(c *models.ReqContext, form dtos.AdminUpdateUserPermissionsForm) { userID := c.ParamsInt64(":id") - cmd := m.UpdateUserPermissionsCommand{ + cmd := models.UpdateUserPermissionsCommand{ UserId: userID, IsGrafanaAdmin: form.IsGrafanaAdmin, } if err := bus.Dispatch(&cmd); err != nil { - if err == m.ErrLastGrafanaAdmin { - c.JsonApiErr(400, m.ErrLastGrafanaAdmin.Error(), nil) + if err == models.ErrLastGrafanaAdmin { + c.JsonApiErr(400, models.ErrLastGrafanaAdmin.Error(), nil) return } @@ -98,10 +98,10 @@ func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermis c.JsonOK("User permissions updated") } -func AdminDeleteUser(c *m.ReqContext) { +func AdminDeleteUser(c *models.ReqContext) { userID := c.ParamsInt64(":id") - cmd := m.DeleteUserCommand{UserId: userID} + cmd := models.DeleteUserCommand{UserId: userID} if err := bus.Dispatch(&cmd); err != nil { c.JsonApiErr(500, "Failed to delete user", err) @@ -111,8 +111,48 @@ func AdminDeleteUser(c *m.ReqContext) { c.JsonOK("User deleted") } +// POST /api/admin/users/:id/disable +func AdminDisableUser(c *models.ReqContext) { + userID := c.ParamsInt64(":id") + + // External users shouldn't be disabled from API + authInfoQuery := &models.GetAuthInfoQuery{UserId: userID} + if err := bus.Dispatch(authInfoQuery); err != models.ErrUserNotFound { + c.JsonApiErr(500, "Could not disable external user", nil) + return + } + + disableCmd := models.DisableUserCommand{UserId: userID, IsDisabled: true} + if err := bus.Dispatch(&disableCmd); err != nil { + c.JsonApiErr(500, "Failed to disable user", err) + return + } + + c.JsonOK("User disabled") +} + +// POST /api/admin/users/:id/enable +func AdminEnableUser(c *models.ReqContext) { + userID := c.ParamsInt64(":id") + + // External users shouldn't be disabled from API + authInfoQuery := &models.GetAuthInfoQuery{UserId: userID} + if err := bus.Dispatch(authInfoQuery); err != models.ErrUserNotFound { + c.JsonApiErr(500, "Could not enable external user", nil) + return + } + + disableCmd := models.DisableUserCommand{UserId: userID, IsDisabled: false} + if err := bus.Dispatch(&disableCmd); err != nil { + c.JsonApiErr(500, "Failed to enable user", err) + return + } + + c.JsonOK("User enabled") +} + // POST /api/admin/users/:id/logout -func (server *HTTPServer) AdminLogoutUser(c *m.ReqContext) Response { +func (server *HTTPServer) AdminLogoutUser(c *models.ReqContext) Response { userID := c.ParamsInt64(":id") if c.UserId == userID { @@ -123,13 +163,13 @@ func (server *HTTPServer) AdminLogoutUser(c *m.ReqContext) Response { } // GET /api/admin/users/:id/auth-tokens -func (server *HTTPServer) AdminGetUserAuthTokens(c *m.ReqContext) Response { +func (server *HTTPServer) AdminGetUserAuthTokens(c *models.ReqContext) Response { userID := c.ParamsInt64(":id") return server.getUserAuthTokensInternal(c, userID) } // POST /api/admin/users/:id/revoke-auth-token -func (server *HTTPServer) AdminRevokeUserAuthToken(c *m.ReqContext, cmd m.RevokeAuthTokenCmd) Response { +func (server *HTTPServer) AdminRevokeUserAuthToken(c *models.ReqContext, cmd models.RevokeAuthTokenCmd) Response { userID := c.ParamsInt64(":id") return server.revokeUserAuthTokenInternal(c, userID, cmd) } diff --git a/pkg/api/admin_users_test.go b/pkg/api/admin_users_test.go index b94f09b0b753..1ec3000dc8be 100644 --- a/pkg/api/admin_users_test.go +++ b/pkg/api/admin_users_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/auth" @@ -84,6 +85,36 @@ func TestAdminApiEndpoint(t *testing.T) { So(userId, ShouldEqual, 200) }) }) + + Convey("When a server admin attempts to disable/enable external user", t, func() { + userId := int64(0) + bus.AddHandler("test", func(cmd *m.GetAuthInfoQuery) error { + userId = cmd.UserId + return nil + }) + + adminDisableUserScenario("Should return Could not disable external user error", "disable", "/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) { + sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() + So(sc.resp.Code, ShouldEqual, 500) + + respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) + So(err, ShouldBeNil) + So(respJSON.Get("message").MustString(), ShouldEqual, "Could not disable external user") + + So(userId, ShouldEqual, 42) + }) + + adminDisableUserScenario("Should return Could not enable external user error", "enable", "/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) { + sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() + So(sc.resp.Code, ShouldEqual, 500) + + respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) + So(err, ShouldBeNil) + So(respJSON.Get("message").MustString(), ShouldEqual, "Could not enable external user") + + So(userId, ShouldEqual, 42) + }) + }) } func putAdminScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) { @@ -186,3 +217,25 @@ func adminGetUserAuthTokensScenario(desc string, url string, routePattern string fn(sc) }) } + +func adminDisableUserScenario(desc string, action string, url string, routePattern string, fn scenarioFunc) { + Convey(desc+" "+url, func() { + defer bus.ClearBusHandlers() + + sc := setupScenarioContext(url) + sc.defaultHandler = Wrap(func(c *m.ReqContext) { + sc.context = c + sc.context.UserId = TestUserID + + if action == "enable" { + AdminEnableUser(c) + } else { + AdminDisableUser(c) + } + }) + + sc.m.Post(routePattern, sc.defaultHandler) + + fn(sc) + }) +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 9b5aae105cba..2facbf9351cb 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -381,6 +381,8 @@ func (hs *HTTPServer) registerRoutes() { adminRoute.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword) adminRoute.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions) adminRoute.Delete("/users/:id", AdminDeleteUser) + adminRoute.Post("/users/:id/disable", AdminDisableUser) + adminRoute.Post("/users/:id/enable", AdminEnableUser) adminRoute.Get("/users/:id/quotas", Wrap(GetUserQuotas)) adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), Wrap(UpdateUserQuota)) adminRoute.Get("/stats", AdminGetStats) diff --git a/pkg/api/login.go b/pkg/api/login.go index e7fe6db93477..fe4b8f5d1be2 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -105,6 +105,10 @@ func (hs *HTTPServer) LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response return Error(401, "Invalid username or password", err) } + if err == login.ErrUserDisabled { + return Error(401, "User is disabled", err) + } + return Error(500, "Error while trying to authenticate user", err) } diff --git a/pkg/login/auth.go b/pkg/login/auth.go index 51eda933c6e1..6d822c3244da 100644 --- a/pkg/login/auth.go +++ b/pkg/login/auth.go @@ -19,6 +19,7 @@ var ( ErrPasswordEmpty = errors.New("No password provided") ErrUsersQuotaReached = errors.New("Users quota reached") ErrGettingUserQuota = errors.New("Error getting user quota") + ErrUserDisabled = errors.New("User is disabled") ) func Init() { @@ -36,7 +37,7 @@ func AuthenticateUser(query *models.LoginUserQuery) error { } err := loginUsingGrafanaDB(query) - if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials) { + if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials && err != ErrUserDisabled) { return err } @@ -46,11 +47,14 @@ func AuthenticateUser(query *models.LoginUserQuery) error { return ldapErr } - err = ldapErr + if err != ErrUserDisabled || ldapErr != ldap.ErrInvalidCredentials { + err = ldapErr + } } if err == ErrInvalidCredentials || err == ldap.ErrInvalidCredentials { saveInvalidLoginAttempt(query) + return ErrInvalidCredentials } if err == models.ErrUserNotFound { @@ -59,6 +63,7 @@ func AuthenticateUser(query *models.LoginUserQuery) error { return err } + func validatePasswordSet(password string) error { if len(password) == 0 { return ErrPasswordEmpty diff --git a/pkg/login/auth_test.go b/pkg/login/auth_test.go index 658560b0ce1d..76e33ef47062 100644 --- a/pkg/login/auth_test.go +++ b/pkg/login/auth_test.go @@ -108,7 +108,7 @@ func TestAuthenticateUser(t *testing.T) { err := AuthenticateUser(sc.loginUserQuery) Convey("it should result in", func() { - So(err, ShouldEqual, ldap.ErrInvalidCredentials) + So(err, ShouldEqual, ErrInvalidCredentials) So(sc.loginAttemptValidationWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue) @@ -160,7 +160,7 @@ func TestAuthenticateUser(t *testing.T) { err := AuthenticateUser(sc.loginUserQuery) Convey("it should result in", func() { - So(err, ShouldEqual, ldap.ErrInvalidCredentials) + So(err, ShouldEqual, ErrInvalidCredentials) So(sc.loginAttemptValidationWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue) diff --git a/pkg/login/grafana_login.go b/pkg/login/grafana_login.go index e8594fdd190d..64bd4b201e7b 100644 --- a/pkg/login/grafana_login.go +++ b/pkg/login/grafana_login.go @@ -26,6 +26,10 @@ var loginUsingGrafanaDB = func(query *m.LoginUserQuery) error { user := userQuery.Result + if user.IsDisabled { + return ErrUserDisabled + } + if err := validatePassword(query.Password, user.Password, user.Salt); err != nil { return err } diff --git a/pkg/login/grafana_login_test.go b/pkg/login/grafana_login_test.go index 2c189ae00729..eddc6e12f5f6 100644 --- a/pkg/login/grafana_login_test.go +++ b/pkg/login/grafana_login_test.go @@ -63,6 +63,23 @@ func TestGrafanaLogin(t *testing.T) { So(sc.loginUserQuery.User.Password, ShouldEqual, sc.loginUserQuery.Password) }) }) + + grafanaLoginScenario("When login with disabled user", func(sc *grafanaLoginScenarioContext) { + sc.withDisabledUser() + err := loginUsingGrafanaDB(sc.loginUserQuery) + + Convey("it should return user is disabled error", func() { + So(err, ShouldEqual, ErrUserDisabled) + }) + + Convey("it should not call password validation", func() { + So(sc.validatePasswordCalled, ShouldBeFalse) + }) + + Convey("it should not pupulate user object", func() { + So(sc.loginUserQuery.User, ShouldBeNil) + }) + }) }) } @@ -138,3 +155,9 @@ func (sc *grafanaLoginScenarioContext) withInvalidPassword() { }) mockPasswordValidation(false, sc) } + +func (sc *grafanaLoginScenarioContext) withDisabledUser() { + sc.getUserByLoginQueryReturns(&m.User{ + IsDisabled: true, + }) +} diff --git a/pkg/models/user.go b/pkg/models/user.go index 69031e403386..5ced373f248f 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -30,6 +30,7 @@ type User struct { EmailVerified bool Theme string HelpFlags1 HelpFlags1 + IsDisabled bool IsAdmin bool OrgId int64 @@ -88,6 +89,11 @@ type UpdateUserPermissionsCommand struct { UserId int64 `json:"-"` } +type DisableUserCommand struct { + UserId int64 + IsDisabled bool +} + type DeleteUserCommand struct { UserId int64 } @@ -203,6 +209,7 @@ type UserProfileDTO struct { Theme string `json:"theme"` OrgId int64 `json:"orgId"` IsGrafanaAdmin bool `json:"isGrafanaAdmin"` + IsDisabled bool `json:"isDisabled"` } type UserSearchHitDTO struct { @@ -212,6 +219,7 @@ type UserSearchHitDTO struct { Email string `json:"email"` AvatarUrl string `json:"avatarUrl"` IsAdmin bool `json:"isAdmin"` + IsDisabled bool `json:"isDisabled"` LastSeenAt time.Time `json:"lastSeenAt"` LastSeenAtAge string `json:"lastSeenAtAge"` } diff --git a/pkg/models/user_auth.go b/pkg/models/user_auth.go index 11018c7fd716..e6f02212ebad 100644 --- a/pkg/models/user_auth.go +++ b/pkg/models/user_auth.go @@ -6,6 +6,10 @@ import ( "golang.org/x/oauth2" ) +const ( + AuthModuleLDAP = "ldap" +) + type UserAuth struct { Id int64 UserId int64 @@ -29,6 +33,7 @@ type ExternalUserInfo struct { Groups []string OrgRoles map[int64]RoleType IsGrafanaAdmin *bool // This is a pointer to know if we should sync this or not (nil = ignore sync) + IsDisabled bool } // --------------------- @@ -81,6 +86,12 @@ type GetUserByAuthInfoQuery struct { Result *User } +type GetExternalUserInfoByLoginQuery struct { + LoginOrEmail string + + Result *ExternalUserInfo +} + type GetAuthInfoQuery struct { UserId int64 AuthModule string diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index bd44b60c2bed..446a712477bf 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -10,6 +10,7 @@ import ( "gopkg.in/ldap.v3" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" ) @@ -48,6 +49,7 @@ var ( // ErrInvalidCredentials is returned if username and password do not match ErrInvalidCredentials = errors.New("Invalid Username or Password") + ErrLDAPUserNotFound = errors.New("LDAP user not found") ) var dial = func(network, addr string) (IConnection, error) { @@ -142,6 +144,7 @@ func (server *Server) Login(query *models.LoginUserQuery) ( // If we couldn't find the user - // we should show incorrect credentials err if len(users) == 0 { + server.disableExternalUser(query.Username) return nil, ErrInvalidCredentials } @@ -263,6 +266,34 @@ func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error { return nil } +// disableExternalUser marks external user as disabled in Grafana db +func (server *Server) disableExternalUser(username string) error { + // Check if external user exist in Grafana + userQuery := &models.GetExternalUserInfoByLoginQuery{ + LoginOrEmail: username, + } + + if err := bus.Dispatch(userQuery); err != nil { + return err + } + + userInfo := userQuery.Result + if !userInfo.IsDisabled { + server.log.Debug("Disabling external user", "user", userQuery.Result.Login) + // Mark user as disabled in grafana db + disableUserCmd := &models.DisableUserCommand{ + UserId: userQuery.Result.UserId, + IsDisabled: true, + } + + if err := bus.Dispatch(disableUserCmd); err != nil { + server.log.Debug("Error disabling external user", "user", userQuery.Result.Login, "message", err.Error()) + return err + } + } + return nil +} + // getSearchRequest returns LDAP search request for users func (server *Server) getSearchRequest( base string, @@ -305,7 +336,7 @@ func (server *Server) getSearchRequest( // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo { extUser := &models.ExternalUserInfo{ - AuthModule: "ldap", + AuthModule: models.AuthModuleLDAP, AuthId: user.DN, Name: strings.TrimSpace( fmt.Sprintf("%s %s", user.FirstName, user.LastName), diff --git a/pkg/services/ldap/ldap_login_test.go b/pkg/services/ldap/ldap_login_test.go index 9da82cc6e981..5bd0edc79cb1 100644 --- a/pkg/services/ldap/ldap_login_test.go +++ b/pkg/services/ldap/ldap_login_test.go @@ -148,5 +148,102 @@ func TestLDAPLogin(t *testing.T) { So(err, ShouldBeNil) So(resp.Login, ShouldEqual, "markelog") }) + + authScenario("When user not found in LDAP, but exist in Grafana", func(scenario *scenarioContext) { + connection := &mockConnection{} + result := ldap.SearchResult{Entries: []*ldap.Entry{}} + connection.setSearchResult(&result) + + externalUser := &models.ExternalUserInfo{UserId: 42, IsDisabled: false} + scenario.getExternalUserInfoByLoginQueryReturns(externalUser) + + connection.bindProvider = func(username, password string) error { + return nil + } + auth := &Server{ + config: &ServerConfig{ + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: connection, + log: log.New("test-logger"), + } + + _, err := auth.Login(scenario.loginUserQuery) + + Convey("it should disable user", func() { + So(scenario.disableExternalUserCalled, ShouldBeTrue) + So(scenario.disableUserCmd.IsDisabled, ShouldBeTrue) + So(scenario.disableUserCmd.UserId, ShouldEqual, 42) + }) + + Convey("it should return invalid credentials error", func() { + So(err, ShouldEqual, ErrInvalidCredentials) + }) + }) + + authScenario("When user not found in LDAP, and disabled in Grafana already", func(scenario *scenarioContext) { + connection := &mockConnection{} + result := ldap.SearchResult{Entries: []*ldap.Entry{}} + connection.setSearchResult(&result) + + externalUser := &models.ExternalUserInfo{UserId: 42, IsDisabled: true} + scenario.getExternalUserInfoByLoginQueryReturns(externalUser) + + connection.bindProvider = func(username, password string) error { + return nil + } + auth := &Server{ + config: &ServerConfig{ + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: connection, + log: log.New("test-logger"), + } + + _, err := auth.Login(scenario.loginUserQuery) + + Convey("it should't call disable function", func() { + So(scenario.disableExternalUserCalled, ShouldBeFalse) + }) + + Convey("it should return invalid credentials error", func() { + So(err, ShouldEqual, ErrInvalidCredentials) + }) + }) + + authScenario("When user found in LDAP, and disabled in Grafana", func(scenario *scenarioContext) { + connection := &mockConnection{} + entry := ldap.Entry{} + result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} + connection.setSearchResult(&result) + scenario.userQueryReturns(&models.User{Id: 42, IsDisabled: true}) + + connection.bindProvider = func(username, password string) error { + return nil + } + auth := &Server{ + config: &ServerConfig{ + SearchBaseDNs: []string{"BaseDNHere"}, + }, + connection: connection, + log: log.New("test-logger"), + } + + extUser, _ := auth.Login(scenario.loginUserQuery) + _, err := user.Upsert(&user.UpsertArgs{ + SignupAllowed: true, + ExternalUser: extUser, + }) + + Convey("it should re-enable user", func() { + So(scenario.disableExternalUserCalled, ShouldBeTrue) + So(scenario.disableUserCmd.IsDisabled, ShouldBeFalse) + So(scenario.disableUserCmd.UserId, ShouldEqual, 42) + }) + + Convey("it should not return error", func() { + So(err, ShouldBeNil) + }) + }) }) } diff --git a/pkg/services/ldap/test.go b/pkg/services/ldap/test.go index 07fd9c6317c5..daa6a3216641 100644 --- a/pkg/services/ldap/test.go +++ b/pkg/services/ldap/test.go @@ -115,6 +115,18 @@ func authScenario(desc string, fn scenarioFunc) { return nil }) + bus.AddHandler("test", func(cmd *models.GetExternalUserInfoByLoginQuery) error { + sc.getExternalUserInfoByLoginQuery = cmd + sc.getExternalUserInfoByLoginQuery.Result = &models.ExternalUserInfo{UserId: 42, IsDisabled: false} + return nil + }) + + bus.AddHandler("test", func(cmd *models.DisableUserCommand) error { + sc.disableExternalUserCalled = true + sc.disableUserCmd = cmd + return nil + }) + bus.AddHandler("test", func(cmd *models.AddOrgUserCommand) error { sc.addOrgUserCmd = cmd return nil @@ -145,16 +157,19 @@ func authScenario(desc string, fn scenarioFunc) { } type scenarioContext struct { - loginUserQuery *models.LoginUserQuery - getUserByAuthInfoQuery *models.GetUserByAuthInfoQuery - getUserOrgListQuery *models.GetUserOrgListQuery - createUserCmd *models.CreateUserCommand - addOrgUserCmd *models.AddOrgUserCommand - updateOrgUserCmd *models.UpdateOrgUserCommand - removeOrgUserCmd *models.RemoveOrgUserCommand - updateUserCmd *models.UpdateUserCommand - setUsingOrgCmd *models.SetUsingOrgCommand - updateUserPermissionsCmd *models.UpdateUserPermissionsCommand + loginUserQuery *models.LoginUserQuery + getUserByAuthInfoQuery *models.GetUserByAuthInfoQuery + getExternalUserInfoByLoginQuery *models.GetExternalUserInfoByLoginQuery + getUserOrgListQuery *models.GetUserOrgListQuery + createUserCmd *models.CreateUserCommand + disableUserCmd *models.DisableUserCommand + addOrgUserCmd *models.AddOrgUserCommand + updateOrgUserCmd *models.UpdateOrgUserCommand + removeOrgUserCmd *models.RemoveOrgUserCommand + updateUserCmd *models.UpdateUserCommand + setUsingOrgCmd *models.SetUsingOrgCommand + updateUserPermissionsCmd *models.UpdateUserPermissionsCommand + disableExternalUserCalled bool } func (sc *scenarioContext) userQueryReturns(user *models.User) { @@ -177,4 +192,15 @@ func (sc *scenarioContext) userOrgsQueryReturns(orgs []*models.UserOrgDTO) { }) } +func (sc *scenarioContext) getExternalUserInfoByLoginQueryReturns(externalUser *models.ExternalUserInfo) { + bus.AddHandler("test", func(cmd *models.GetExternalUserInfoByLoginQuery) error { + sc.getExternalUserInfoByLoginQuery = cmd + sc.getExternalUserInfoByLoginQuery.Result = &models.ExternalUserInfo{ + UserId: externalUser.UserId, + IsDisabled: externalUser.IsDisabled, + } + return nil + }) +} + type scenarioFunc func(c *scenarioContext) diff --git a/pkg/services/login/login.go b/pkg/services/login/login.go index 791b34e0b30a..cf7b8dd92c55 100644 --- a/pkg/services/login/login.go +++ b/pkg/services/login/login.go @@ -3,7 +3,7 @@ package login import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/quota" ) @@ -27,10 +27,10 @@ func (ls *LoginService) Init() error { return nil } -func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { +func (ls *LoginService) UpsertUser(cmd *models.UpsertUserCommand) error { extUser := cmd.ExternalUser - userQuery := &m.GetUserByAuthInfoQuery{ + userQuery := &models.GetUserByAuthInfoQuery{ AuthModule: extUser.AuthModule, AuthId: extUser.AuthId, UserId: extUser.UserId, @@ -39,7 +39,7 @@ func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { } err := bus.Dispatch(userQuery) - if err != m.ErrUserNotFound && err != nil { + if err != models.ErrUserNotFound && err != nil { return err } @@ -64,7 +64,7 @@ func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { } if extUser.AuthModule != "" { - cmd2 := &m.SetAuthInfoCommand{ + cmd2 := &models.SetAuthInfoCommand{ UserId: cmd.Result.Id, AuthModule: extUser.AuthModule, AuthId: extUser.AuthId, @@ -90,6 +90,13 @@ func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { return err } } + + if extUser.AuthModule == models.AuthModuleLDAP && userQuery.Result.IsDisabled { + // Re-enable user when it found in LDAP + if err := ls.Bus.Dispatch(&models.DisableUserCommand{UserId: cmd.Result.Id, IsDisabled: false}); err != nil { + return err + } + } } err = syncOrgRoles(cmd.Result, extUser) @@ -100,12 +107,12 @@ func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { // Sync isGrafanaAdmin permission if extUser.IsGrafanaAdmin != nil && *extUser.IsGrafanaAdmin != cmd.Result.IsAdmin { - if err := ls.Bus.Dispatch(&m.UpdateUserPermissionsCommand{UserId: cmd.Result.Id, IsGrafanaAdmin: *extUser.IsGrafanaAdmin}); err != nil { + if err := ls.Bus.Dispatch(&models.UpdateUserPermissionsCommand{UserId: cmd.Result.Id, IsGrafanaAdmin: *extUser.IsGrafanaAdmin}); err != nil { return err } } - err = ls.Bus.Dispatch(&m.SyncTeamsCommand{ + err = ls.Bus.Dispatch(&models.SyncTeamsCommand{ User: cmd.Result, ExternalUser: extUser, }) @@ -117,8 +124,8 @@ func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { return err } -func createUser(extUser *m.ExternalUserInfo) (*m.User, error) { - cmd := &m.CreateUserCommand{ +func createUser(extUser *models.ExternalUserInfo) (*models.User, error) { + cmd := &models.CreateUserCommand{ Login: extUser.Login, Email: extUser.Email, Name: extUser.Name, @@ -132,9 +139,9 @@ func createUser(extUser *m.ExternalUserInfo) (*m.User, error) { return &cmd.Result, nil } -func updateUser(user *m.User, extUser *m.ExternalUserInfo) error { +func updateUser(user *models.User, extUser *models.ExternalUserInfo) error { // sync user info - updateCmd := &m.UpdateUserCommand{ + updateCmd := &models.UpdateUserCommand{ UserId: user.Id, } @@ -165,8 +172,8 @@ func updateUser(user *m.User, extUser *m.ExternalUserInfo) error { return bus.Dispatch(updateCmd) } -func updateUserAuth(user *m.User, extUser *m.ExternalUserInfo) error { - updateCmd := &m.UpdateAuthInfoCommand{ +func updateUserAuth(user *models.User, extUser *models.ExternalUserInfo) error { + updateCmd := &models.UpdateAuthInfoCommand{ AuthModule: extUser.AuthModule, AuthId: extUser.AuthId, UserId: user.Id, @@ -177,13 +184,13 @@ func updateUserAuth(user *m.User, extUser *m.ExternalUserInfo) error { return bus.Dispatch(updateCmd) } -func syncOrgRoles(user *m.User, extUser *m.ExternalUserInfo) error { +func syncOrgRoles(user *models.User, extUser *models.ExternalUserInfo) error { // don't sync org roles if none are specified if len(extUser.OrgRoles) == 0 { return nil } - orgsQuery := &m.GetUserOrgListQuery{UserId: user.Id} + orgsQuery := &models.GetUserOrgListQuery{UserId: user.Id} if err := bus.Dispatch(orgsQuery); err != nil { return err } @@ -199,7 +206,7 @@ func syncOrgRoles(user *m.User, extUser *m.ExternalUserInfo) error { deleteOrgIds = append(deleteOrgIds, org.OrgId) } else if extUser.OrgRoles[org.OrgId] != org.Role { // update role - cmd := &m.UpdateOrgUserCommand{OrgId: org.OrgId, UserId: user.Id, Role: extUser.OrgRoles[org.OrgId]} + cmd := &models.UpdateOrgUserCommand{OrgId: org.OrgId, UserId: user.Id, Role: extUser.OrgRoles[org.OrgId]} if err := bus.Dispatch(cmd); err != nil { return err } @@ -213,16 +220,16 @@ func syncOrgRoles(user *m.User, extUser *m.ExternalUserInfo) error { } // add role - cmd := &m.AddOrgUserCommand{UserId: user.Id, Role: orgRole, OrgId: orgId} + cmd := &models.AddOrgUserCommand{UserId: user.Id, Role: orgRole, OrgId: orgId} err := bus.Dispatch(cmd) - if err != nil && err != m.ErrOrgNotFound { + if err != nil && err != models.ErrOrgNotFound { return err } } // delete any removed org roles for _, orgId := range deleteOrgIds { - cmd := &m.RemoveOrgUserCommand{OrgId: orgId, UserId: user.Id} + cmd := &models.RemoveOrgUserCommand{OrgId: orgId, UserId: user.Id} if err := bus.Dispatch(cmd); err != nil { return err } @@ -235,7 +242,7 @@ func syncOrgRoles(user *m.User, extUser *m.ExternalUserInfo) error { break } - return bus.Dispatch(&m.SetUsingOrgCommand{ + return bus.Dispatch(&models.SetUsingOrgCommand{ UserId: user.Id, OrgId: user.OrgId, }) diff --git a/pkg/services/sqlstore/migrations/user_mig.go b/pkg/services/sqlstore/migrations/user_mig.go index e273cb7d5424..8202dfc1cbbc 100644 --- a/pkg/services/sqlstore/migrations/user_mig.go +++ b/pkg/services/sqlstore/migrations/user_mig.go @@ -116,6 +116,12 @@ func addUserMigrations(mg *Migrator) { // Adds salt & rands for old users who used ldap or oauth mg.AddMigration("Add missing user data", &AddMissingUserSaltAndRandsMigration{}) + + // is_disabled indicates whether user disabled or not. Disabled user should not be able to log in. + // This field used in couple with LDAP auth to disable users removed from LDAP rather than delete it immediately. + mg.AddMigration("Add is_disabled column to user", NewAddColumnMigration(userV2, &Column{ + Name: "is_disabled", Type: DB_Bool, Nullable: false, Default: "0", + })) } type AddMissingUserSaltAndRandsMigration struct { diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 311081af4f8e..3c94e0617f08 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -27,6 +27,7 @@ func (ss *SqlStore) addUserQueryAndCommandHandlers() { bus.AddHandler("sql", GetUserProfile) bus.AddHandler("sql", SearchUsers) bus.AddHandler("sql", GetUserOrgList) + bus.AddHandler("sql", DisableUser) bus.AddHandler("sql", DeleteUser) bus.AddHandler("sql", UpdateUserPermissions) bus.AddHandler("sql", SetUserHelpFlag) @@ -326,6 +327,7 @@ func GetUserProfile(query *m.GetUserProfileQuery) error { Login: user.Login, Theme: user.Theme, IsGrafanaAdmin: user.IsAdmin, + IsDisabled: user.IsDisabled, OrgId: user.OrgId, } @@ -450,7 +452,7 @@ func SearchUsers(query *m.SearchUsersQuery) error { offset := query.Limit * (query.Page - 1) sess.Limit(query.Limit, offset) - sess.Cols("id", "email", "name", "login", "is_admin", "last_seen_at") + sess.Cols("id", "email", "name", "login", "is_admin", "is_disabled", "last_seen_at") if err := sess.Find(&query.Result.Users); err != nil { return err } @@ -473,6 +475,18 @@ func SearchUsers(query *m.SearchUsersQuery) error { return err } +func DisableUser(cmd *m.DisableUserCommand) error { + user := m.User{} + sess := x.Table("user") + sess.ID(cmd.UserId).Get(&user) + + user.IsDisabled = cmd.IsDisabled + sess.UseBool("is_disabled") + + _, err := sess.ID(cmd.UserId).Update(&user) + return err +} + func DeleteUser(cmd *m.DeleteUserCommand) error { return inTransaction(func(sess *DBSession) error { return deleteUserInTransaction(sess, cmd) diff --git a/pkg/services/sqlstore/user_auth.go b/pkg/services/sqlstore/user_auth.go index fd8ec3d057f6..19287b8a8668 100644 --- a/pkg/services/sqlstore/user_auth.go +++ b/pkg/services/sqlstore/user_auth.go @@ -5,7 +5,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -14,17 +14,18 @@ var getTime = time.Now func init() { bus.AddHandler("sql", GetUserByAuthInfo) + bus.AddHandler("sql", GetExternalUserInfoByLogin) bus.AddHandler("sql", GetAuthInfo) bus.AddHandler("sql", SetAuthInfo) bus.AddHandler("sql", UpdateAuthInfo) bus.AddHandler("sql", DeleteAuthInfo) } -func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { - user := &m.User{} +func GetUserByAuthInfo(query *models.GetUserByAuthInfoQuery) error { + user := &models.User{} has := false var err error - authQuery := &m.GetAuthInfoQuery{} + authQuery := &models.GetAuthInfoQuery{} // Try to find the user by auth module and id first if query.AuthModule != "" && query.AuthId != "" { @@ -32,14 +33,14 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { authQuery.AuthId = query.AuthId err = GetAuthInfo(authQuery) - if err != m.ErrUserNotFound { + if err != models.ErrUserNotFound { if err != nil { return err } // if user id was specified and doesn't match the user_auth entry, remove it if query.UserId != 0 && query.UserId != authQuery.Result.UserId { - err = DeleteAuthInfo(&m.DeleteAuthInfoCommand{ + err = DeleteAuthInfo(&models.DeleteAuthInfoCommand{ UserAuth: authQuery.Result, }) if err != nil { @@ -55,7 +56,7 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { if !has { // if the user has been deleted then remove the entry - err = DeleteAuthInfo(&m.DeleteAuthInfoCommand{ + err = DeleteAuthInfo(&models.DeleteAuthInfoCommand{ UserAuth: authQuery.Result, }) if err != nil { @@ -78,7 +79,7 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { // If not found, try to find the user by email address if !has && query.Email != "" { - user = &m.User{Email: query.Email} + user = &models.User{Email: query.Email} has, err = x.Get(user) if err != nil { return err @@ -87,7 +88,7 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { // If not found, try to find the user by login if !has && query.Login != "" { - user = &m.User{Login: query.Login} + user = &models.User{Login: query.Login} has, err = x.Get(user) if err != nil { return err @@ -96,12 +97,12 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { // No user found if !has { - return m.ErrUserNotFound + return models.ErrUserNotFound } // create authInfo record to link accounts if authQuery.Result == nil && query.AuthModule != "" { - cmd2 := &m.SetAuthInfoCommand{ + cmd2 := &models.SetAuthInfoCommand{ UserId: user.Id, AuthModule: query.AuthModule, AuthId: query.AuthId, @@ -115,8 +116,32 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { return nil } -func GetAuthInfo(query *m.GetAuthInfoQuery) error { - userAuth := &m.UserAuth{ +func GetExternalUserInfoByLogin(query *models.GetExternalUserInfoByLoginQuery) error { + userQuery := models.GetUserByLoginQuery{LoginOrEmail: query.LoginOrEmail} + err := bus.Dispatch(&userQuery) + if err != nil { + return err + } + + authInfoQuery := &models.GetAuthInfoQuery{UserId: userQuery.Result.Id} + if err := bus.Dispatch(authInfoQuery); err != nil { + return err + } + + query.Result = &models.ExternalUserInfo{ + UserId: userQuery.Result.Id, + Login: userQuery.Result.Login, + Email: userQuery.Result.Email, + Name: userQuery.Result.Name, + IsDisabled: userQuery.Result.IsDisabled, + AuthModule: authInfoQuery.Result.AuthModule, + AuthId: authInfoQuery.Result.AuthId, + } + return nil +} + +func GetAuthInfo(query *models.GetAuthInfoQuery) error { + userAuth := &models.UserAuth{ UserId: query.UserId, AuthModule: query.AuthModule, AuthId: query.AuthId, @@ -126,7 +151,7 @@ func GetAuthInfo(query *m.GetAuthInfoQuery) error { return err } if !has { - return m.ErrUserNotFound + return models.ErrUserNotFound } secretAccessToken, err := decodeAndDecrypt(userAuth.OAuthAccessToken) @@ -149,9 +174,9 @@ func GetAuthInfo(query *m.GetAuthInfoQuery) error { return nil } -func SetAuthInfo(cmd *m.SetAuthInfoCommand) error { +func SetAuthInfo(cmd *models.SetAuthInfoCommand) error { return inTransaction(func(sess *DBSession) error { - authUser := &m.UserAuth{ + authUser := &models.UserAuth{ UserId: cmd.UserId, AuthModule: cmd.AuthModule, AuthId: cmd.AuthId, @@ -183,9 +208,9 @@ func SetAuthInfo(cmd *m.SetAuthInfoCommand) error { }) } -func UpdateAuthInfo(cmd *m.UpdateAuthInfoCommand) error { +func UpdateAuthInfo(cmd *models.UpdateAuthInfoCommand) error { return inTransaction(func(sess *DBSession) error { - authUser := &m.UserAuth{ + authUser := &models.UserAuth{ UserId: cmd.UserId, AuthModule: cmd.AuthModule, AuthId: cmd.AuthId, @@ -212,7 +237,7 @@ func UpdateAuthInfo(cmd *m.UpdateAuthInfoCommand) error { authUser.OAuthExpiry = cmd.OAuthToken.Expiry } - cond := &m.UserAuth{ + cond := &models.UserAuth{ UserId: cmd.UserId, AuthModule: cmd.AuthModule, } @@ -222,7 +247,7 @@ func UpdateAuthInfo(cmd *m.UpdateAuthInfoCommand) error { }) } -func DeleteAuthInfo(cmd *m.DeleteAuthInfoCommand) error { +func DeleteAuthInfo(cmd *models.DeleteAuthInfoCommand) error { return inTransaction(func(sess *DBSession) error { _, err := sess.Delete(cmd.UserAuth) return err From 9cdf0f4f9cc61a9830ac5e5e7f155e788d155e92 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 21 May 2019 13:58:14 +0200 Subject: [PATCH 32/92] Docs: Add guidelines for PR/commit messages (#17190) --- CONTRIBUTING.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 272e53787601..db02d4134948 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,8 @@ To setup a local development environment we recommend reading [Building Grafana * Add tests relevant to the fixed bug or new feature. +* Follow [PR and commit messages guidelines](#PR-and-commit-messages-guidelines) + ### Pull requests with new features Commits should be as small as possible, while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). @@ -38,12 +40,12 @@ Make sure to include `Closes #` or `Fixes #` in the ### Pull requests with bug fixes Please make all changes in one commit if possible. Include `Closes #` in bottom of the commit message. -A commit message for a bug fix should look something like this. +A commit message for a bug fix should look something like this: ``` -avoid infinite loop in the dashboard provisioner +Dashboard: Avoid infinite loop in the dashboard provisioner -if one dashboard with an uid is refered to by two +If one dashboard with an uid is refered to by two provsioners each provisioner overwrite each other. filling up dashboard_versions quite fast if using default settings. @@ -51,6 +53,8 @@ default settings. Closes #12864 ``` +For more details about PR naming and commit messages please see [PR and commit messages guidelines](#PR-and-commit-messages-guidelines) + If the pull request needs changes before its merged the new commits should be rebased into one commit before its merged. ## Backend dependency management @@ -80,3 +84,22 @@ GO111MODULE=on go mod vendor ``` You have to commit the changes to `go.mod`, `go.sum` and the `vendor/` directory before submitting the pull request. + +## PR and commit messages guidelines +PR title and squash commit messages should follow guidelines below: + +``` +Area of changes: Message + +Detailed description +``` + +The `Area of changes` is related either to functional domain (i.e. Build, Release) or feature domain (i.e. Explore, Plugins, BarGaugePanel). + + +`Message` should be concise, written in present tense and start with capitalised verb. Detailed description should be provided as commit message body, by entering a blank line between commit title and the description. + +### Examples of good PR titles/commit messages: +- `Explore: Adds Live option for supported datasources` +- `GraphPanel: Don't sort series when legend table & sort column is not visible` +- `Build: Support publishing MSI to grafana.com` From 3ff3e0f2acb37388f6a01cca97dbde631052ca38 Mon Sep 17 00:00:00 2001 From: CodeLingo Bot Date: Wed, 22 May 2019 18:48:47 +1200 Subject: [PATCH 33/92] Defer closing of files (#17213) Signed-off-by: CodeLingo Bot --- pkg/api/dashboard.go | 1 + pkg/components/imguploader/azureblobuploader.go | 3 ++- pkg/components/imguploader/gcsuploader.go | 1 + pkg/components/imguploader/s3uploader.go | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index ed153c02efe3..27f70d4d388a 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -316,6 +316,7 @@ func GetHomeDashboard(c *m.ReqContext) Response { if err != nil { return Error(500, "Failed to load home dashboard", err) } + defer file.Close() dash := dtos.DashboardFullWithMeta{} dash.Meta.IsHome = true diff --git a/pkg/components/imguploader/azureblobuploader.go b/pkg/components/imguploader/azureblobuploader.go index bfcb901dd0c2..fd39f70678b0 100644 --- a/pkg/components/imguploader/azureblobuploader.go +++ b/pkg/components/imguploader/azureblobuploader.go @@ -46,10 +46,11 @@ func (az *AzureBlobUploader) Upload(ctx context.Context, imageDiskPath string) ( blob := NewStorageClient(az.account_name, az.account_key) file, err := os.Open(imageDiskPath) - if err != nil { return "", err } + defer file.Close() + randomFileName := util.GetRandomString(30) + ".png" // upload image az.log.Debug("Uploading image to azure_blob", "container_name", az.container_name, "blob_name", randomFileName) diff --git a/pkg/components/imguploader/gcsuploader.go b/pkg/components/imguploader/gcsuploader.go index 8932e96e59eb..443534ff76ad 100644 --- a/pkg/components/imguploader/gcsuploader.go +++ b/pkg/components/imguploader/gcsuploader.go @@ -67,6 +67,7 @@ func (u *GCSUploader) uploadFile(client *http.Client, imageDiskPath, key string) if err != nil { return err } + defer fileReader.Close() reqUrl := fmt.Sprintf(uploadUrl, u.bucket, key) u.log.Debug("Request URL: ", reqUrl) diff --git a/pkg/components/imguploader/s3uploader.go b/pkg/components/imguploader/s3uploader.go index d690f629f577..7ad8a0f7bebc 100644 --- a/pkg/components/imguploader/s3uploader.go +++ b/pkg/components/imguploader/s3uploader.go @@ -69,6 +69,7 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string, if err != nil { return "", err } + defer file.Close() sess, err = session.NewSession(cfg) if err != nil { From cd9517c58b904558be68b5b4da68b4f8fd233930 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 22 May 2019 11:22:55 +0200 Subject: [PATCH 34/92] AzureMonitor: docs for multiple subscriptions (#17194) * AzureMonitor: docs for multiple subscriptions Also, links to images in Grafana.org instead of the images in the plugin GitHub repo. * Update docs/sources/features/datasources/azuremonitor.md Co-Authored-By: Marcus Efraimsson * Update docs/sources/features/datasources/azuremonitor.md Co-Authored-By: Marcus Efraimsson * docs: fix auto format errors VS code automatically escapes the dollar sign. --- .../features/datasources/azuremonitor.md | 98 +++++++++++-------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/docs/sources/features/datasources/azuremonitor.md b/docs/sources/features/datasources/azuremonitor.md index d72f5273925f..ee40248fe001 100644 --- a/docs/sources/features/datasources/azuremonitor.md +++ b/docs/sources/features/datasources/azuremonitor.md @@ -31,34 +31,37 @@ The datasource can access metrics from four different services. You can configur - [Guide to setting up an Azure Active Directory Application for Azure Log Analytics.](https://dev.loganalytics.io/documentation/Authorization/AAD-Setup) - [Quickstart Guide for Application Insights.](https://dev.applicationinsights.io/quickstart/) -1. Accessed from the Grafana main menu, newly installed data sources can be added immediately within the Data Sources section. Next, click the "Add data source" button in the upper right. The data source will be available for selection in the Type select box. +1. Accessed from the Grafana main menu, newly installed data sources can be added immediately within the Data Sources section. Next, click the "Add data source" button in the upper right. The Azure Monitor data source will be available for selection in the Cloud section in the list of data sources. -2. Select Azure Monitor from the Type dropdown:
-![Data Source Type](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_1_select_type.png) -3. In the name field, fill in a name for the data source. It can be anything. Some suggestions are Azure Monitor or App Insights. +2. In the name field, Grafana will automatically fill in a name for the data source - `Azure Monitor` or something like `Azure Monitor - 3`. If you are going to configure multiple data sources then change the name to something more informative. -4. If you are using Azure Monitor, then you need 4 pieces of information from the Azure portal (see link above for detailed instructions): - - **Tenant Id** (Azure Active Directory -> Properties -> Directory ID) - - **Subscription Id** (Subscriptions -> Choose subscription -> Overview -> Subscription ID) - - **Client Id** (Azure Active Directory -> App Registrations -> Choose your app -> Application ID) - - **Client Secret** ( Azure Active Directory -> App Registrations -> Choose your app -> Keys) +3. If you are using Azure Monitor, you need 4 pieces of information from the Azure portal (see link above for detailed instructions): -5. Paste these four items into the fields in the Azure Monitor API Details section:
-![Azure Monitor API Details](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_2_azure_monitor_api_details.png) + - **Tenant Id** (Azure Active Directory -> Properties -> Directory ID) + - **Client Id** (Azure Active Directory -> App Registrations -> Choose your app -> Application ID) + - **Client Secret** ( Azure Active Directory -> App Registrations -> Choose your app -> Keys) + - **Default Subscription Id** (Subscriptions -> Choose subscription -> Overview -> Subscription ID) -6. If you are also using the Azure Log Analytics service, then you need to specify these two config values (or you can reuse the Client Id and Secret from the previous step). - - Client Id (Azure Active Directory -> App Registrations -> Choose your app -> Application ID) - - Client Secret ( Azure Active Directory -> App Registrations -> Choose your app -> Keys -> Create a key -> Use client secret) +4. Paste these four items into the fields in the Azure Monitor API Details section: + {{< docs-imagebox img="/img/docs/v62/config_1_azure_monitor_details.png" class="docs-image--no-shadow" caption="Azure Monitor Configuration Details" >}} -7. If you are are using Application Insights, then you need two pieces of information from the Azure Portal (see link above for detailed instructions): - - Application ID - - API Key + - The Subscription Id can be changed per query. Save the datasource and refresh the page to see the list of subscriptions available for the specified Client Id. -8. Paste these two items into the appropriate fields in the Application Insights API Details section:
-![Application Insights API Details](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_3_app_insights_api_details.png) +5. If you are also using the Azure Log Analytics service, then you need to specify these two config values (or you can reuse the Client Id and Secret from the previous step). -9. Test that the configuration details are correct by clicking on the "Save & Test" button:
-![Azure Monitor API Details](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_4_save_and_test.png) + - Client Id (Azure Active Directory -> App Registrations -> Choose your app -> Application ID) + - Client Secret (Azure Active Directory -> App Registrations -> Choose your app -> Keys -> Create a key -> Use client secret) + +6. If you are using Application Insights, then you need two pieces of information from the Azure Portal (see link above for detailed instructions): + + - Application ID + - API Key + +7. Paste these two items into the appropriate fields in the Application Insights API Details section: + {{< docs-imagebox img="/img/docs/v62/config_2_app_insights_api_details.png" class="docs-image--no-shadow" caption="Application Insights Configuration Details" >}} + +8. Test that the configuration details are correct by clicking on the "Save & Test" button: + {{< docs-imagebox img="/img/docs/v62/config_3_save_and_test.png" class="docs-image--no-shadow" caption="Save and Test" >}} Alternatively on step 4 if creating a new Azure Active Directory App, use the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest): @@ -68,17 +71,25 @@ az ad sp create-for-rbac -n "http://localhost:3000" ## Choose a Service -In the query editor for a panel, after choosing your Azure Monitor datasource, the first option is to choose a service. There are three options here: Azure Monitor, Application Insights and Azure Log Analytics. The query editor will change depending on which one you pick. Azure Monitor is the default. +In the query editor for a panel, after choosing your Azure Monitor datasource, the first option is to choose a service. There are three options here: + +- `Azure Monitor` +- `Application Insights` +- `Azure Log Analytics` + +The query editor will change depending on which one you pick. Azure Monitor is the default. ## Querying the Azure Monitor Service The Azure Monitor service provides metrics for all the Azure services that you have running. It helps you understand how your applications on Azure are performing and to proactively find issues affecting your applications. +If your Azure Monitor credentials give you access to multiple subscriptions then choose the appropriate subscription first. + Examples of metrics that you can get from the service are: -- Microsoft.Compute/virtualMachines - Percentage CPU -- Microsoft.Network/networkInterfaces - Bytes sent -- Microsoft.Storage/storageAccounts - Used Capacity +- `Microsoft.Compute/virtualMachines - Percentage CPU` +- `Microsoft.Network/networkInterfaces - Bytes sent` +- `Microsoft.Storage/storageAccounts - Used Capacity` {{< docs-imagebox img="/img/docs/v60/azuremonitor-service-query-editor.png" class="docs-image--no-shadow" caption="Azure Monitor Query Editor" >}} @@ -112,12 +123,17 @@ Note that the Azure Monitor service does not support multiple values yet. If you The Azure Monitor Datasource Plugin provides the following queries you can specify in the `Query` field in the Variable edit view. They allow you to fill a variable's options list. -| Name | Description | -| -------------------------------------------------------- | -------------------------------------------------------------- | -| *ResourceGroups()* | Returns a list of resource groups. | -| *Namespaces(aResourceGroup)* | Returns a list of namespaces for the specified resource group. | -| *ResourceNames(aResourceGroup, aNamespace)* | Returns a list of resource names. | -| *MetricNames(aResourceGroup, aNamespace, aResourceName)* | Returns a list of metric names. | +| Name | Description | +| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| _Subscriptions()_ | Returns a list of subscriptions. | +| _ResourceGroups()_ | Returns a list of resource groups. | +| _ResourceGroups(12345678-aaaa-bbbb-cccc-123456789aaa)_ | Returns a list of resource groups for a specified subscription. | +| _Namespaces(aResourceGroup)_ | Returns a list of namespaces for the specified resource group. | +| _Namespaces(12345678-aaaa-bbbb-cccc-123456789aaa, aResourceGroup)_ | Returns a list of namespaces for the specified resource group and subscription. | +| _ResourceNames(aResourceGroup, aNamespace)_ | Returns a list of resource names. | +| _ResourceNames(12345678-aaaa-bbbb-cccc-123456789aaaaResourceGroup, aNamespace)_ | Returns a list of resource names for a specified subscription. | +| _MetricNames(aResourceGroup, aNamespace, aResourceName)_ | Returns a list of metric names. | +| _MetricNames(12345678-aaaa-bbbb-cccc-123456789aaaaResourceGroup, aNamespace, aResourceName)_ | Returns a list of metric names for a specified subscription. | Examples: @@ -185,8 +201,8 @@ types of template variables. | Name | Description | | ---------------------------------- | ---------------------------------------------------------- | -| *AppInsightsMetricNames()* | Returns a list of metric names. | -| *AppInsightsGroupBys(aMetricName)* | Returns a list of group bys for the specified metric name. | +| _AppInsightsMetricNames()_ | Returns a list of metric names. | +| _AppInsightsGroupBys(aMetricName)_ | Returns a list of group bys for the specified metric name. | Examples: @@ -222,6 +238,8 @@ AzureActivity | order by TimeGenerated desc ``` +If your credentials give you access to multiple subscriptions then choose the appropriate subscription first. + {{< docs-imagebox img="/img/docs/v60/azureloganalytics-service-query-editor.png" class="docs-image--no-shadow" caption="Azure Log Analytics Query Editor" >}} ### Azure Log Analytics Macros @@ -229,18 +247,18 @@ AzureActivity To make writing queries easier there are several Grafana macros that can be used in the where clause of a query: - `$__timeFilter()` - Expands to - `TimeGenerated ≥ datetime(2018-06-05T18:09:58.907Z) and` - `TimeGenerated ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. + `TimeGenerated ≥ datetime(2018-06-05T18:09:58.907Z) and` + `TimeGenerated ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. - `$__timeFilter(datetimeColumn)` - Expands to - `datetimeColumn ≥ datetime(2018-06-05T18:09:58.907Z) and` - `datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. + `datetimeColumn ≥ datetime(2018-06-05T18:09:58.907Z) and` + `datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. -- `$__escapeMulti($myVar)` - is to be used with multi-value template variables that contains illegal characters. If $myVar has the value `'\\grafana-vm\Network(eth0)\Total','\\hello!'`, it expands to: `@'\\grafana-vm\Network(eth0)\Total', @'\\hello!'`. If using single value variables there no need for this macro, simply escape the variable inline instead - `@'\$myVar'` +- `$__escapeMulti($myVar)` - is to be used with multi-value template variables that contain illegal characters. If `$myVar` has the following two values as a string `'\\grafana-vm\Network(eth0)\Total','\\hello!'`, then it expands to: `@'\\grafana-vm\Network(eth0)\Total', @'\\hello!'`. If using single value variables there is no need for this macro, simply escape the variable inline instead - `@'\$myVar'`. -- `$__contains(colName, $myVar)` - is to be used with multi-value template variables. If $myVar has the value `'value1','value2'`, it expands to: `colName in ('value1','value2')`. +- `$__contains(colName, $myVar)` - is to be used with multi-value template variables. If `$myVar` has the value `'value1','value2'`, it expands to: `colName in ('value1','value2')`. - If using the `All` option, then check the `Include All Option` checkbox and in the `Custom all value` field type in the following value: `all`. If $myVar has value `all` then the macro will instead expand to `1 == 1`. For template variables with a lot of options, this will increase the query performance by not building a large where..in clause. + If using the `All` option, then check the `Include All Option` checkbox and in the `Custom all value` field type in the following value: `all`. If `$myVar` has value `all` then the macro will instead expand to `1 == 1`. For template variables with a lot of options, this will increase the query performance by not building a large where..in clause. ### Azure Log Analytics Builtin Variables From a96b522a427f6f49e0def4bcad7604b395013d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 22 May 2019 11:28:11 +0200 Subject: [PATCH 35/92] Search: changed how search filter on current folder works (#17219) --- .../dashboard/components/DashNav/DashNav.tsx | 36 +++++++++++-------- public/sass/components/_navbar.scss | 32 +++++++++++------ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index d366c5f18317..f95e34d2d2e1 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -60,17 +60,14 @@ export class DashNav extends PureComponent { } } - onOpenSearch = () => { - const { dashboard } = this.props; - const haveFolder = dashboard.meta.folderId > 0; - appEvents.emit( - 'show-dash-search', - haveFolder - ? { - query: 'folder:current', - } - : null - ); + onDahboardNameClick = () => { + appEvents.emit('show-dash-search'); + }; + + onFolderNameClick = () => { + appEvents.emit('show-dash-search', { + query: 'folder:current', + }); }; onClose = () => { @@ -148,11 +145,20 @@ export class DashNav extends PureComponent { return ( <> {this.isSettings &&  / Settings}
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 8c950f728590..67071bb61c86 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -67,11 +67,6 @@ min-height: $navbarHeight; line-height: $navbarHeight; - .fa-caret-down { - font-size: 60%; - padding-left: 6px; - } - .gicon { top: -2px; position: relative; @@ -85,17 +80,32 @@ display: inline-block; } } +} - &--folder { - color: $text-color-weak; - display: none; +.navbar-page-btn__folder { + color: $text-color-weak; + display: none; - @include media-breakpoint-up(lg) { - display: inline-block; - } + @include media-breakpoint-up(lg) { + display: inline-block; } } +// element is needed here to override font-awesome specificity +i.navbar-page-btn__folder-icon { + font-size: $font-size-sm; + color: $text-color-weak; + padding: 0 $space-sm; + position: relative; + top: -1px; +} + +// element is needed here to override font-awesome specificity +i.navbar-page-btn__search { + font-size: $font-size-xs; + padding: 0 $space-xs; +} + .navbar-buttons { // height: $navbarHeight; display: flex; From 577beebcca1c279e5631a127169b0514d3ddda97 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 22 May 2019 11:34:54 +0200 Subject: [PATCH 36/92] azuremonitor: revert to clearing chained dropdowns (#17212) * azuremonitor: revert to clearing chained dropdowns After feedback from users, changing back to clearing dropdowns to the right in the chain. E.g. if the user changes the subscription dropdown which is first in the chain then all the dependent dropdowns to the right should be cleared (reset to default values). Also, now triggers getting subscriptions every time the dropdown menu is shown rather than just the first time. It is apparently common to add subscriptions while building queries. --- .../partials/query.editor.html | 2 +- .../query_ctrl.ts | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html index 6761502dd2a2..4690bc5be26c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html @@ -10,7 +10,7 @@
+ get-options="ctrl.getSubscriptions()" on-change="ctrl.onSubscriptionChange()" css-class="min-width-12">
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index a6c95d6afde8..cc623d8df981 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -197,6 +197,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { if (!this.target.subscription && this.subscriptions.length > 0) { this.target.subscription = this.subscriptions[0].value; } + + return this.subscriptions; }); } @@ -204,6 +206,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { if (this.target.queryType === 'Azure Log Analytics') { return this.getWorkspaces(); } + + if (this.target.queryType === 'Azure Monitor') { + this.target.azureMonitor.resourceGroup = this.defaultDropdownValue; + this.target.azureMonitor.metricDefinition = this.defaultDropdownValue; + this.target.azureMonitor.resourceName = this.defaultDropdownValue; + this.target.azureMonitor.metricName = this.defaultDropdownValue; + this.target.azureMonitor.aggregation = ''; + this.target.azureMonitor.timeGrains = []; + this.target.azureMonitor.timeGrain = ''; + this.target.azureMonitor.dimensions = []; + this.target.azureMonitor.dimension = ''; + } } /* Azure Monitor Section */ @@ -279,6 +293,12 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { } onResourceGroupChange() { + this.target.azureMonitor.metricDefinition = this.defaultDropdownValue; + this.target.azureMonitor.resourceName = this.defaultDropdownValue; + this.target.azureMonitor.metricName = this.defaultDropdownValue; + this.target.azureMonitor.aggregation = ''; + this.target.azureMonitor.timeGrains = []; + this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.dimensions = []; this.target.azureMonitor.dimension = ''; } @@ -286,11 +306,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { onMetricDefinitionChange() { this.target.azureMonitor.resourceName = this.defaultDropdownValue; this.target.azureMonitor.metricName = this.defaultDropdownValue; + this.target.azureMonitor.aggregation = ''; + this.target.azureMonitor.timeGrains = []; + this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.dimensions = []; this.target.azureMonitor.dimension = ''; } onResourceNameChange() { + this.target.azureMonitor.metricName = this.defaultDropdownValue; + this.target.azureMonitor.aggregation = ''; + this.target.azureMonitor.timeGrains = []; + this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.dimensions = []; this.target.azureMonitor.dimension = ''; } @@ -312,6 +339,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { this.target.azureMonitor.aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType]; this.target.azureMonitor.aggregation = metadata.primaryAggType; this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains); + this.target.azureMonitor.timeGrain = 'auto'; this.target.azureMonitor.dimensions = metadata.dimensions; if (metadata.dimensions.length > 0) { From e4217239925fc4a8d7428ceb65235b96bee3c111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 22 May 2019 12:23:30 +0200 Subject: [PATCH 37/92] Search: removed old not working search shortcuts (#17226) --- public/app/core/components/help/help.ts | 2 -- public/app/core/components/search/search.ts | 15 ++++++++------- public/app/core/services/keybindingSrv.ts | 10 ---------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/public/app/core/components/help/help.ts b/public/app/core/components/help/help.ts index 7ef54339f497..63be172a5deb 100644 --- a/public/app/core/components/help/help.ts +++ b/public/app/core/components/help/help.ts @@ -13,8 +13,6 @@ export class HelpCtrl { { keys: ['g', 'h'], description: 'Go to Home Dashboard' }, { keys: ['g', 'p'], description: 'Go to Profile' }, { keys: ['s', 'o'], description: 'Open search' }, - { keys: ['s', 's'], description: 'Open search with starred filter' }, - { keys: ['s', 't'], description: 'Open search in tags view' }, { keys: ['esc'], description: 'Exit edit/setting views' }, ], Dashboard: [ diff --git a/public/app/core/components/search/search.ts b/public/app/core/components/search/search.ts index 8728cbb1f348..fbeb90922986 100644 --- a/public/app/core/components/search/search.ts +++ b/public/app/core/components/search/search.ts @@ -38,6 +38,10 @@ interface SelectedIndicies { folderIndex?: number; } +interface OpenSearchParams { + query?: string; +} + export class SearchCtrl { isOpen: boolean; query: SearchQuery; @@ -94,7 +98,7 @@ export class SearchCtrl { appEvents.emit('search-query'); } - openSearch(evt: any, payload: any) { + openSearch(payload: OpenSearchParams = {}) { if (this.isOpen) { this.closeSearch(); return; @@ -105,19 +109,16 @@ export class SearchCtrl { this.selectedIndex = -1; this.results = []; this.query = { - query: evt ? `${evt.query} ` : '', - parsedQuery: this.queryParser.parse(evt && evt.query), + query: payload.query ? `${payload.query} ` : '', + parsedQuery: this.queryParser.parse(payload.query), tags: [], starred: false, }; + this.currentSearchId = 0; this.ignoreClose = true; this.isLoading = true; - if (payload && payload.starred) { - this.query.starred = true; - } - this.$timeout(() => { this.ignoreClose = false; this.giveSearchFocus = true; diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 6fb50dd4ec29..0a4bd1c028ca 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -43,21 +43,11 @@ export class KeybindingSrv { this.bind('g h', this.goToHome); this.bind('g a', this.openAlerting); this.bind('g p', this.goToProfile); - this.bind('s s', this.openSearchStarred); this.bind('s o', this.openSearch); - this.bind('s t', this.openSearchTags); this.bind('f', this.openSearch); this.bindGlobal('esc', this.exit); } - openSearchStarred() { - appEvents.emit('show-dash-search', { starred: true }); - } - - openSearchTags() { - appEvents.emit('show-dash-search', { tagsMode: true }); - } - openSearch() { appEvents.emit('show-dash-search'); } From 350190e66929846e4767bc1445f41abc95ffbefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 22 May 2019 13:40:37 +0200 Subject: [PATCH 38/92] Release: updated changelog with v6.2 entries (#17201) * Release: updated changelog with v6.2 entries * Release: Updated changelog --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e8da7947b7b..b868254dc210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ -# 6.2.0 (unreleased) +# 6.2.0 (2019-05-22) + +### Bug Fixes +* **BarGauge**: Fix for negative min values. [#17192](https://github.com/grafana/grafana/pull/17192), [@torkelo](https://github.com/torkelo) +* **Gauge/BarGauge**: Fix for issues editing min & max options. [#17174](https://github.com/grafana/grafana/pull/17174) + +### Breaking Changes +* **Plugins**: Data source plugins that process hidden queries need to add a "hiddenQueries: true" attribute in plugin.json. [#17124](https://github.com/grafana/grafana/pull/17124), [@ryantxu](https://github.com/ryantxu) + +### Removal of old deprecated package repository + +5 months ago we deprecated our old package cloud repository and [replaced it](https://grafana.com/blog/2019/01/05/moving-to-packages.grafana.com/) with our own. We will remove the old depreciated +repo on July 1st. Make sure you have switched to the new repo by then. The new repository has all our old releases so you are not required to upgrade just to switch package repository. # 6.2.0-beta2 (2019-05-15) From 7df4d635e3009d526d2bc6f9418e3b7e49b6e183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 22 May 2019 13:44:45 +0200 Subject: [PATCH 39/92] Release: Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b868254dc210..9224bb241aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ### Bug Fixes * **BarGauge**: Fix for negative min values. [#17192](https://github.com/grafana/grafana/pull/17192), [@torkelo](https://github.com/torkelo) * **Gauge/BarGauge**: Fix for issues editing min & max options. [#17174](https://github.com/grafana/grafana/pull/17174) +* **Search**: Make only folder name only open search with current folder filter. [#17226](https://github.com/grafana/grafana/pull/17226) +* **AzureMonitor**: Revert to clearing chained dropdowns. [#17212](https://github.com/grafana/grafana/pull/17212) ### Breaking Changes * **Plugins**: Data source plugins that process hidden queries need to add a "hiddenQueries: true" attribute in plugin.json. [#17124](https://github.com/grafana/grafana/pull/17124), [@ryantxu](https://github.com/ryantxu) From 23a941e880bbc9310ff5ca95e5140e8c0c8843cb Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Wed, 22 May 2019 14:53:33 +0300 Subject: [PATCH 40/92] Docs: Example for multiple LDAP servers (#17216) Fixes #16898 --- docs/sources/auth/ldap.md | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/sources/auth/ldap.md b/docs/sources/auth/ldap.md index d0ae9094d66a..a3a01e6b6c67 100644 --- a/docs/sources/auth/ldap.md +++ b/docs/sources/auth/ldap.md @@ -215,6 +215,67 @@ email = "email" # [[servers.group_mappings]] omitted for clarity ``` +### Multiple LDAP servers + +Grafana does support receiving information from multiple LDAP servers. + +**LDAP specific configuration file (ldap.toml):** +```bash +# --- First LDAP Server --- + +[[servers]] +host = "10.0.0.1" +port = 389 +use_ssl = false +start_tls = false +ssl_skip_verify = false +bind_dn = "cn=admin,dc=grafana,dc=org" +bind_password = 'grafana' +search_filter = "(cn=%s)" +search_base_dns = ["ou=users,dc=grafana,dc=org"] + +[servers.attributes] +name = "givenName" +surname = "sn" +username = "cn" +member_of = "memberOf" +email = "email" + +[[servers.group_mappings]] +group_dn = "cn=admins,ou=groups,dc=grafana,dc=org" +org_role = "Admin" +grafana_admin = true + +# --- Second LDAP Server --- + +[[servers]] +host = "10.0.0.2" +port = 389 +use_ssl = false +start_tls = false +ssl_skip_verify = false + +bind_dn = "cn=admin,dc=grafana,dc=org" +bind_password = 'grafana' +search_filter = "(cn=%s)" +search_base_dns = ["ou=users,dc=grafana,dc=org"] + +[servers.attributes] +name = "givenName" +surname = "sn" +username = "cn" +member_of = "memberOf" +email = "email" + +[[servers.group_mappings]] +group_dn = "cn=editors,ou=groups,dc=grafana,dc=org" +org_role = "Editor" + +[[servers.group_mappings]] +group_dn = "*" +org_role = "Viewer" +``` + ### Active Directory [Active Directory](https://technet.microsoft.com/en-us/library/hh831484(v=ws.11).aspx) is a directory service which is commonly used in Windows environments. @@ -247,6 +308,8 @@ email = "mail" # [[servers.group_mappings]] omitted for clarity ``` + + #### Port requirements In above example SSL is enabled and an encrypted port have been configured. If your Active Directory don't support SSL please change `enable_ssl = false` and `port = 389`. From c31aaa1a254b62e794d7cc6c9ca3c850bed69992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 22 May 2019 13:55:20 +0200 Subject: [PATCH 41/92] Docs: Updated versions selector --- docs/versions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/versions.json b/docs/versions.json index a0857a7f3afa..e4960ff98ebe 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,5 +1,6 @@ [ - { "version": "v6.1", "path": "/", "archived": false, "current": true }, + { "version": "v6.2", "path": "/", "archived": false, "current": true }, + { "version": "v6.1", "path": "/v6.1", "archived": true }, { "version": "v6.0", "path": "/v6.0", "archived": true }, { "version": "v5.4", "path": "/v5.4", "archived": true }, { "version": "v5.3", "path": "/v5.3", "archived": true }, From e9130210ccae77f3cc3a3258d1912c7560b5e423 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 22 May 2019 14:10:13 +0200 Subject: [PATCH 42/92] Build: Fix final prompt for @grafana/ui npm publish confirmation Fixes issue of the final confirmation prompt (the one to confirm the actual npm publish) being invisible, making it impossible to release by anyone but me. Also, before the release is created I'm assuring previous release bundle is remove (otherwise tests and checks would fail) --- scripts/cli/tasks/grafanaui.build.ts | 2 +- scripts/cli/tasks/grafanaui.release.ts | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/scripts/cli/tasks/grafanaui.build.ts b/scripts/cli/tasks/grafanaui.build.ts index 6fce809bef91..1a48bb1a7243 100644 --- a/scripts/cli/tasks/grafanaui.build.ts +++ b/scripts/cli/tasks/grafanaui.build.ts @@ -7,7 +7,7 @@ import { Task, TaskRunner } from './task'; let distDir, cwd; -const clean = useSpinner('Cleaning', async () => await execa('npm', ['run', 'clean'])); +export const clean = useSpinner('Cleaning', async () => await execa('npm', ['run', 'clean'])); const compile = useSpinner('Compiling sources', () => execa('tsc', ['-p', './tsconfig.build.json'])); diff --git a/scripts/cli/tasks/grafanaui.release.ts b/scripts/cli/tasks/grafanaui.release.ts index f0e53a4e7bad..03bcdd0219e5 100644 --- a/scripts/cli/tasks/grafanaui.release.ts +++ b/scripts/cli/tasks/grafanaui.release.ts @@ -1,11 +1,11 @@ import execa from 'execa'; import { execTask } from '../utils/execTask'; -import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi } from '../utils/cwd'; +import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd'; import semver from 'semver'; import inquirer from 'inquirer'; import chalk from 'chalk'; import { useSpinner } from '../utils/useSpinner'; -import { savePackage, buildTask } from './grafanaui.build'; +import { savePackage, buildTask, clean } from './grafanaui.build'; import { TaskRunner, Task } from './task'; type VersionBumpType = 'prerelease' | 'patch' | 'minor' | 'major'; @@ -81,12 +81,6 @@ const bumpVersion = (version: string) => const publishPackage = (name: string, version: string) => useSpinner(`Publishing ${name} @ ${version} to npm registry...`, async () => { changeCwdToGrafanaUiDist(); - console.log(chalk.yellowBright.bold(`\nReview dist package.json before proceeding!\n`)); - const { confirmed } = await promptConfirm('Are you ready to publish to npm?'); - - if (!confirmed) { - process.exit(); - } await execa('npm', ['publish', '--access', 'public']); })(); @@ -111,13 +105,18 @@ const releaseTaskRunner: TaskRunner = async ({ usePackageJsonVersion, createVersionCommit, }) => { - await runChecksAndTests(); + changeCwdToGrafanaUi(); + await clean(); // Clean previous build if exists + restoreCwd(); + if (publishToNpm) { // TODO: Ensure release branch // When need to update this when we star keeping @grafana/ui releases in sync with core await ensureMasterBranch(); } + runChecksAndTests(); + await execTask(buildTask)(); let releaseConfirmed = false; @@ -169,6 +168,13 @@ const releaseTaskRunner: TaskRunner = async ({ } if (publishToNpm) { + console.log(chalk.yellowBright.bold(`\nReview dist package.json before proceeding!\n`)); + const { confirmed } = await promptConfirm('Are you ready to publish to npm?'); + + if (!confirmed) { + process.exit(); + } + await publishPackage(pkg.name, nextVersion); console.log(chalk.green(`\nVersion ${nextVersion} of ${pkg.name} succesfully released!`)); console.log(chalk.yellow(`\nUpdated @grafana/ui/package.json with version bump created.`)); From 87688930a259c305d1f28271b426d862bfd5d938 Mon Sep 17 00:00:00 2001 From: Filip Barl Date: Wed, 22 May 2019 14:21:43 +0200 Subject: [PATCH 43/92] Panels: Show Drilldown links in top-left corner of custom React panels (#17142) --- .../dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx | 6 +++--- public/sass/components/_panel_header.scss | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 12f3b95ca21f..9d057d5338ea 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -48,7 +48,7 @@ export class PanelHeaderCorner extends Component { const remarkableInterpolatedMarkdown = new Remarkable().render(interpolatedMarkdown); return ( -
+
{panel.links && panel.links.length > 0 && (
    @@ -71,7 +71,7 @@ export class PanelHeaderCorner extends Component { renderCornerType(infoMode: InfoMode, content: string | JSX.Element) { const theme = infoMode === InfoMode.Error ? 'error' : 'info'; return ( - +
    @@ -91,7 +91,7 @@ export class PanelHeaderCorner extends Component { return this.renderCornerType(infoMode, this.props.error); } - if (infoMode === InfoMode.Info) { + if (infoMode === InfoMode.Info || infoMode === InfoMode.Links) { return this.renderCornerType(infoMode, this.getInfoContent()); } diff --git a/public/sass/components/_panel_header.scss b/public/sass/components/_panel_header.scss index 40afc2cd3aae..d9882d213511 100644 --- a/public/sass/components/_panel_header.scss +++ b/public/sass/components/_panel_header.scss @@ -167,6 +167,15 @@ $panel-header-no-title-zindex: 1; } } +.panel-info-content { + a { + color: $white; + &:hover { + color: darken($white, 10%); + } + } +} + .panel-time-info { font-weight: $font-weight-semi-bold; float: right; From 9b7f9dd9be40d007de471ef06641cf07c2d532fe Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Wed, 22 May 2019 15:30:03 +0300 Subject: [PATCH 44/92] LDAP: consistently name the LDAP entities (#17203) --- pkg/api/admin_ldap.go | 4 ++-- pkg/api/api.go | 2 +- pkg/api/frontendsettings.go | 2 +- pkg/api/password.go | 2 +- pkg/api/team_members.go | 2 +- pkg/api/user.go | 2 +- pkg/infra/usagestats/usage_stats.go | 2 +- pkg/infra/usagestats/usage_stats_test.go | 2 +- pkg/login/auth.go | 2 +- pkg/login/auth_test.go | 26 ++++++++++---------- pkg/login/ldap_login.go | 6 ++--- pkg/login/ldap_login_test.go | 10 ++++---- pkg/middleware/auth_proxy/auth_proxy.go | 8 +++---- pkg/middleware/middleware_test.go | 10 ++++---- pkg/services/ldap/ldap.go | 30 ++++++++++++------------ pkg/services/ldap/settings.go | 8 +++---- pkg/setting/setting.go | 24 +++++++++---------- 17 files changed, 71 insertions(+), 71 deletions(-) diff --git a/pkg/api/admin_ldap.go b/pkg/api/admin_ldap.go index 6e1b40bfa3e8..8aca63c5394f 100644 --- a/pkg/api/admin_ldap.go +++ b/pkg/api/admin_ldap.go @@ -4,7 +4,7 @@ import ( "github.com/grafana/grafana/pkg/services/ldap" ) -func (server *HTTPServer) ReloadLdapCfg() Response { +func (server *HTTPServer) ReloadLDAPCfg() Response { if !ldap.IsEnabled() { return Error(400, "LDAP is not enabled", nil) } @@ -13,5 +13,5 @@ func (server *HTTPServer) ReloadLdapCfg() Response { if err != nil { return Error(500, "Failed to reload ldap config.", err) } - return Success("Ldap config reloaded") + return Success("LDAP config reloaded") } diff --git a/pkg/api/api.go b/pkg/api/api.go index 2facbf9351cb..70f6263686a5 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -395,7 +395,7 @@ func (hs *HTTPServer) registerRoutes() { adminRoute.Post("/provisioning/dashboards/reload", Wrap(hs.AdminProvisioningReloadDasboards)) adminRoute.Post("/provisioning/datasources/reload", Wrap(hs.AdminProvisioningReloadDatasources)) adminRoute.Post("/provisioning/notifications/reload", Wrap(hs.AdminProvisioningReloadNotifications)) - adminRoute.Post("/ldap/reload", Wrap(hs.ReloadLdapCfg)) + adminRoute.Post("/ldap/reload", Wrap(hs.ReloadLDAPCfg)) }, reqGrafanaAdmin) // rendering diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index e881fc1539de..643a53d18fbd 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -176,7 +176,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf "appSubUrl": setting.AppSubUrl, "allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin, "authProxyEnabled": setting.AuthProxyEnabled, - "ldapEnabled": setting.LdapEnabled, + "ldapEnabled": setting.LDAPEnabled, "alertingEnabled": setting.AlertingEnabled, "alertingErrorOrTimeout": setting.AlertingErrorOrTimeout, "alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues, diff --git a/pkg/api/password.go b/pkg/api/password.go index 4776c6a30649..278408ee9afe 100644 --- a/pkg/api/password.go +++ b/pkg/api/password.go @@ -9,7 +9,7 @@ import ( ) func SendResetPasswordEmail(c *m.ReqContext, form dtos.SendResetPasswordEmailForm) Response { - if setting.LdapEnabled || setting.AuthProxyEnabled { + if setting.LDAPEnabled || setting.AuthProxyEnabled { return Error(401, "Not allowed to reset password when LDAP or Auth Proxy is enabled", nil) } if setting.DisableLoginForm { diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index 54a4d8220e5a..bc622f8662ae 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -21,7 +21,7 @@ func GetTeamMembers(c *m.ReqContext) Response { member.AvatarUrl = dtos.GetGravatarUrl(member.Email) member.Labels = []string{} - if setting.IsEnterprise && setting.LdapEnabled && member.External { + if setting.IsEnterprise && setting.LDAPEnabled && member.External { member.Labels = append(member.Labels, "LDAP") } } diff --git a/pkg/api/user.go b/pkg/api/user.go index 9eb7e75eab0e..cde5b7b92fdd 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -202,7 +202,7 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) { } func ChangeUserPassword(c *m.ReqContext, cmd m.ChangeUserPasswordCommand) Response { - if setting.LdapEnabled || setting.AuthProxyEnabled { + if setting.LDAPEnabled || setting.AuthProxyEnabled { return Error(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil) } diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go index 8b20391cbb43..e5b9e8fd8cb7 100644 --- a/pkg/infra/usagestats/usage_stats.go +++ b/pkg/infra/usagestats/usage_stats.go @@ -132,7 +132,7 @@ func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { authTypes := map[string]bool{} authTypes["anonymous"] = setting.AnonymousEnabled authTypes["basic_auth"] = setting.BasicAuthEnabled - authTypes["ldap"] = setting.LdapEnabled + authTypes["ldap"] = setting.LDAPEnabled authTypes["auth_proxy"] = setting.AuthProxyEnabled for provider, enabled := range oauthProviders { diff --git a/pkg/infra/usagestats/usage_stats_test.go b/pkg/infra/usagestats/usage_stats_test.go index ea5b95d6ef08..07f2df221924 100644 --- a/pkg/infra/usagestats/usage_stats_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -182,7 +182,7 @@ func TestMetrics(t *testing.T) { setting.BuildVersion = "5.0.0" setting.AnonymousEnabled = true setting.BasicAuthEnabled = true - setting.LdapEnabled = true + setting.LDAPEnabled = true setting.AuthProxyEnabled = true setting.Packaging = "deb" diff --git a/pkg/login/auth.go b/pkg/login/auth.go index 6d822c3244da..ca5b572deb83 100644 --- a/pkg/login/auth.go +++ b/pkg/login/auth.go @@ -41,7 +41,7 @@ func AuthenticateUser(query *models.LoginUserQuery) error { return err } - ldapEnabled, ldapErr := loginUsingLdap(query) + ldapEnabled, ldapErr := loginUsingLDAP(query) if ldapEnabled { if ldapErr == nil || ldapErr != ldap.ErrInvalidCredentials { return ldapErr diff --git a/pkg/login/auth_test.go b/pkg/login/auth_test.go index 76e33ef47062..06a78cc6d5df 100644 --- a/pkg/login/auth_test.go +++ b/pkg/login/auth_test.go @@ -15,7 +15,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a user authenticates without setting a password", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(nil, sc) - mockLoginUsingLdap(false, nil, sc) + mockLoginUsingLDAP(false, nil, sc) loginQuery := models.LoginUserQuery{ Username: "user", @@ -33,7 +33,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a user authenticates having too many login attempts", func(sc *authScenarioContext) { mockLoginAttemptValidation(ErrTooManyLoginAttempts, sc) mockLoginUsingGrafanaDB(nil, sc) - mockLoginUsingLdap(true, nil, sc) + mockLoginUsingLDAP(true, nil, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -50,7 +50,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When grafana user authenticate with valid credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(nil, sc) - mockLoginUsingLdap(true, ErrInvalidCredentials, sc) + mockLoginUsingLDAP(true, ErrInvalidCredentials, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -68,7 +68,7 @@ func TestAuthenticateUser(t *testing.T) { customErr := errors.New("custom") mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(customErr, sc) - mockLoginUsingLdap(true, ErrInvalidCredentials, sc) + mockLoginUsingLDAP(true, ErrInvalidCredentials, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -85,7 +85,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) - mockLoginUsingLdap(false, nil, sc) + mockLoginUsingLDAP(false, nil, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -102,7 +102,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) - mockLoginUsingLdap(true, ldap.ErrInvalidCredentials, sc) + mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -119,7 +119,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) - mockLoginUsingLdap(true, nil, sc) + mockLoginUsingLDAP(true, nil, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -137,7 +137,7 @@ func TestAuthenticateUser(t *testing.T) { customErr := errors.New("custom") mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc) - mockLoginUsingLdap(true, customErr, sc) + mockLoginUsingLDAP(true, customErr, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -154,7 +154,7 @@ func TestAuthenticateUser(t *testing.T) { authScenario("When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) { mockLoginAttemptValidation(nil, sc) mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc) - mockLoginUsingLdap(true, ldap.ErrInvalidCredentials, sc) + mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc) mockSaveInvalidLoginAttempt(sc) err := AuthenticateUser(sc.loginUserQuery) @@ -187,8 +187,8 @@ func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) { } } -func mockLoginUsingLdap(enabled bool, err error, sc *authScenarioContext) { - loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) { +func mockLoginUsingLDAP(enabled bool, err error, sc *authScenarioContext) { + loginUsingLDAP = func(query *models.LoginUserQuery) (bool, error) { sc.ldapLoginWasCalled = true return enabled, err } @@ -210,7 +210,7 @@ func mockSaveInvalidLoginAttempt(sc *authScenarioContext) { func authScenario(desc string, fn authScenarioFunc) { Convey(desc, func() { origLoginUsingGrafanaDB := loginUsingGrafanaDB - origLoginUsingLdap := loginUsingLdap + origLoginUsingLDAP := loginUsingLDAP origValidateLoginAttempts := validateLoginAttempts origSaveInvalidLoginAttempt := saveInvalidLoginAttempt @@ -224,7 +224,7 @@ func authScenario(desc string, fn authScenarioFunc) { defer func() { loginUsingGrafanaDB = origLoginUsingGrafanaDB - loginUsingLdap = origLoginUsingLdap + loginUsingLDAP = origLoginUsingLDAP validateLoginAttempts = origValidateLoginAttempts saveInvalidLoginAttempt = origSaveInvalidLoginAttempt }() diff --git a/pkg/login/ldap_login.go b/pkg/login/ldap_login.go index 0b41e612a2ee..33693212ac7f 100644 --- a/pkg/login/ldap_login.go +++ b/pkg/login/ldap_login.go @@ -17,9 +17,9 @@ var isLDAPEnabled = multildap.IsEnabled // newLDAP creates multiple LDAP instance var newLDAP = multildap.New -// loginUsingLdap logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be +// loginUsingLDAP logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be // populated with the logged in user if successful. -var loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) { +var loginUsingLDAP = func(query *models.LoginUserQuery) (bool, error) { enabled := isLDAPEnabled() if !enabled { @@ -38,7 +38,7 @@ var loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) { login, err := user.Upsert(&user.UpsertArgs{ ExternalUser: externalUser, - SignupAllowed: setting.LdapAllowSignup, + SignupAllowed: setting.LDAPAllowSignup, }) if err != nil { return true, err diff --git a/pkg/login/ldap_login_test.go b/pkg/login/ldap_login_test.go index b93a4890bc31..ac5ba49e2f1d 100644 --- a/pkg/login/ldap_login_test.go +++ b/pkg/login/ldap_login_test.go @@ -14,10 +14,10 @@ import ( var errTest = errors.New("Test error") -func TestLdapLogin(t *testing.T) { +func TestLDAPLogin(t *testing.T) { Convey("Login using ldap", t, func() { Convey("Given ldap enabled and no server configured", func() { - setting.LdapEnabled = true + setting.LDAPEnabled = true LDAPLoginScenario("When login", func(sc *LDAPLoginScenarioContext) { sc.withLoginResult(false) @@ -29,7 +29,7 @@ func TestLdapLogin(t *testing.T) { return config, nil } - enabled, err := loginUsingLdap(sc.loginUserQuery) + enabled, err := loginUsingLDAP(sc.loginUserQuery) Convey("it should return true", func() { So(enabled, ShouldBeTrue) @@ -46,11 +46,11 @@ func TestLdapLogin(t *testing.T) { }) Convey("Given ldap disabled", func() { - setting.LdapEnabled = false + setting.LDAPEnabled = false LDAPLoginScenario("When login", func(sc *LDAPLoginScenarioContext) { sc.withLoginResult(false) - enabled, err := loginUsingLdap(&models.LoginUserQuery{ + enabled, err := loginUsingLDAP(&models.LoginUserQuery{ Username: "user", Password: "pwd", }) diff --git a/pkg/middleware/auth_proxy/auth_proxy.go b/pkg/middleware/auth_proxy/auth_proxy.go index 2e1e72b82f6c..e078aaf84251 100644 --- a/pkg/middleware/auth_proxy/auth_proxy.go +++ b/pkg/middleware/auth_proxy/auth_proxy.go @@ -40,7 +40,7 @@ type AuthProxy struct { header string enabled bool - LdapAllowSignup bool + LDAPAllowSignup bool AuthProxyAutoSignUp bool whitelistIP string headerType string @@ -88,8 +88,8 @@ func New(options *Options) *AuthProxy { headerType: setting.AuthProxyHeaderProperty, headers: setting.AuthProxyHeaders, whitelistIP: setting.AuthProxyWhitelist, - cacheTTL: setting.AuthProxyLdapSyncTtl, - LdapAllowSignup: setting.LdapAllowSignup, + cacheTTL: setting.AuthProxyLDAPSyncTtl, + LDAPAllowSignup: setting.LDAPAllowSignup, AuthProxyAutoSignUp: setting.AuthProxyAutoSignUp, } } @@ -213,7 +213,7 @@ func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) { // Have to sync grafana and LDAP user during log in user, err := user.Upsert(&user.UpsertArgs{ ReqContext: auth.ctx, - SignupAllowed: auth.LdapAllowSignup, + SignupAllowed: auth.LDAPAllowSignup, ExternalUser: extUser, }) if err != nil { diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 694ecef14f41..c50960569b63 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -300,7 +300,7 @@ func TestMiddlewareContext(t *testing.T) { setting.AuthProxyEnabled = true setting.AuthProxyWhitelist = "" setting.AuthProxyAutoSignUp = true - setting.LdapEnabled = true + setting.LDAPEnabled = true setting.AuthProxyHeaderName = "X-WEBAUTH-USER" setting.AuthProxyHeaderProperty = "username" name := "markelog" @@ -326,7 +326,7 @@ func TestMiddlewareContext(t *testing.T) { }) middlewareScenario(t, "should create an user from a header", func(sc *scenarioContext) { - setting.LdapEnabled = false + setting.LDAPEnabled = false setting.AuthProxyAutoSignUp = true bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { @@ -354,7 +354,7 @@ func TestMiddlewareContext(t *testing.T) { }) middlewareScenario(t, "should get an existing user from header", func(sc *scenarioContext) { - setting.LdapEnabled = false + setting.LDAPEnabled = false bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { query.Result = &m.SignedInUser{OrgId: 2, UserId: 12} @@ -379,7 +379,7 @@ func TestMiddlewareContext(t *testing.T) { middlewareScenario(t, "should allow the request from whitelist IP", func(sc *scenarioContext) { setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120" - setting.LdapEnabled = false + setting.LDAPEnabled = false bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} @@ -405,7 +405,7 @@ func TestMiddlewareContext(t *testing.T) { middlewareScenario(t, "should not allow the request from whitelist IP", func(sc *scenarioContext) { setting.AuthProxyWhitelist = "8.8.8.8" - setting.LdapEnabled = false + setting.LDAPEnabled = false bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index 446a712477bf..9fa680d1e19b 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -454,9 +454,9 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string for _, groupSearchBase := range server.config.GroupSearchBaseDNs { var filterReplace string if server.config.GroupSearchFilterUserAttribute == "" { - filterReplace = getLdapAttr(server.config.Attr.Username, searchResult) + filterReplace = getLDAPAttr(server.config.Attr.Username, searchResult) } else { - filterReplace = getLdapAttr(server.config.GroupSearchFilterUserAttribute, searchResult) + filterReplace = getLDAPAttr(server.config.GroupSearchFilterUserAttribute, searchResult) } filter := strings.Replace( @@ -489,7 +489,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string if len(groupSearchResult.Entries) > 0 { for i := range groupSearchResult.Entries { - memberOf = append(memberOf, getLdapAttrN(groupIDAttribute, groupSearchResult, i)) + memberOf = append(memberOf, getLDAPAttrN(groupIDAttribute, groupSearchResult, i)) } break } @@ -512,27 +512,27 @@ func (server *Server) serializeUsers( } userInfo := &UserInfo{ - DN: getLdapAttrN( + DN: getLDAPAttrN( "dn", users, index, ), - LastName: getLdapAttrN( + LastName: getLDAPAttrN( server.config.Attr.Surname, users, index, ), - FirstName: getLdapAttrN( + FirstName: getLDAPAttrN( server.config.Attr.Name, users, index, ), - Username: getLdapAttrN( + Username: getLDAPAttrN( server.config.Attr.Username, users, index, ), - Email: getLdapAttrN( + Email: getLDAPAttrN( server.config.Attr.Email, users, index, @@ -554,7 +554,7 @@ func (server *Server) getMemberOf(search *ldap.SearchResult) ( []string, error, ) { if server.config.GroupSearchFilter == "" { - memberOf := getLdapAttrArray(server.config.Attr.MemberOf, search) + memberOf := getLDAPAttrArray(server.config.Attr.MemberOf, search) return memberOf, nil } @@ -576,11 +576,11 @@ func appendIfNotEmpty(slice []string, values ...string) []string { return slice } -func getLdapAttr(name string, result *ldap.SearchResult) string { - return getLdapAttrN(name, result, 0) +func getLDAPAttr(name string, result *ldap.SearchResult) string { + return getLDAPAttrN(name, result, 0) } -func getLdapAttrN(name string, result *ldap.SearchResult, n int) string { +func getLDAPAttrN(name string, result *ldap.SearchResult, n int) string { if strings.ToLower(name) == "dn" { return result.Entries[n].DN } @@ -594,11 +594,11 @@ func getLdapAttrN(name string, result *ldap.SearchResult, n int) string { return "" } -func getLdapAttrArray(name string, result *ldap.SearchResult) []string { - return getLdapAttrArrayN(name, result, 0) +func getLDAPAttrArray(name string, result *ldap.SearchResult) []string { + return getLDAPAttrArrayN(name, result, 0) } -func getLdapAttrArrayN(name string, result *ldap.SearchResult, n int) []string { +func getLDAPAttrArrayN(name string, result *ldap.SearchResult, n int) []string { for _, attr := range result.Entries[n].Attributes { if attr.Name == name { return attr.Values diff --git a/pkg/services/ldap/settings.go b/pkg/services/ldap/settings.go index 15f1583fea71..df63f10a6fa6 100644 --- a/pkg/services/ldap/settings.go +++ b/pkg/services/ldap/settings.go @@ -65,7 +65,7 @@ var loadingMutex = &sync.Mutex{} // IsEnabled checks if ldap is enabled func IsEnabled() bool { - return setting.LdapEnabled + return setting.LDAPEnabled } // ReloadConfig reads the config from the disc and caches it. @@ -78,7 +78,7 @@ func ReloadConfig() error { defer loadingMutex.Unlock() var err error - config, err = readConfig(setting.LdapConfigFile) + config, err = readConfig(setting.LDAPConfigFile) return err } @@ -98,7 +98,7 @@ func GetConfig() (*Config, error) { defer loadingMutex.Unlock() var err error - config, err = readConfig(setting.LdapConfigFile) + config, err = readConfig(setting.LDAPConfigFile) return config, err } @@ -106,7 +106,7 @@ func GetConfig() (*Config, error) { func readConfig(configFile string) (*Config, error) { result := &Config{} - logger.Info("Ldap enabled, reading config file", "file", configFile) + logger.Info("LDAP enabled, reading config file", "file", configFile) _, err := toml.DecodeFile(configFile, result) if err != nil { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 194617f3bcb5..65e4e0e25021 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -138,7 +138,7 @@ var ( AuthProxyHeaderName string AuthProxyHeaderProperty string AuthProxyAutoSignUp bool - AuthProxyLdapSyncTtl int + AuthProxyLDAPSyncTtl int AuthProxyWhitelist string AuthProxyHeaders map[string]string @@ -165,11 +165,11 @@ var ( GoogleTagManagerId string // LDAP - LdapEnabled bool - LdapConfigFile string - LdapSyncCron string - LdapAllowSignup bool - LdapActiveSyncEnabled bool + LDAPEnabled bool + LDAPConfigFile string + LDAPSyncCron string + LDAPAllowSignup bool + LDAPActiveSyncEnabled bool // QUOTA Quota QuotaSettings @@ -815,7 +815,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { return err } AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true) - AuthProxyLdapSyncTtl = authProxy.Key("ldap_sync_ttl").MustInt() + AuthProxyLDAPSyncTtl = authProxy.Key("ldap_sync_ttl").MustInt() AuthProxyWhitelist, err = valueAsString(authProxy, "whitelist", "") if err != nil { return err @@ -978,11 +978,11 @@ type RemoteCacheOptions struct { func (cfg *Cfg) readLDAPConfig() { ldapSec := cfg.Raw.Section("auth.ldap") - LdapConfigFile = ldapSec.Key("config_file").String() - LdapSyncCron = ldapSec.Key("sync_cron").String() - LdapEnabled = ldapSec.Key("enabled").MustBool(false) - LdapActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false) - LdapAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true) + LDAPConfigFile = ldapSec.Key("config_file").String() + LDAPSyncCron = ldapSec.Key("sync_cron").String() + LDAPEnabled = ldapSec.Key("enabled").MustBool(false) + LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false) + LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true) } func (cfg *Cfg) readSessionConfig() { From 874957497538fb59ee0becb2a731efc5eeaa42c4 Mon Sep 17 00:00:00 2001 From: bendem Date: Wed, 22 May 2019 17:54:08 +0200 Subject: [PATCH 45/92] Docs: Fix grammar in docs (#17233) Use of singular and added an article for consistency with other titles. --- docs/sources/http_api/data_source.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/http_api/data_source.md b/docs/sources/http_api/data_source.md index 364b55b0cfcb..4ddeb5a520ed 100644 --- a/docs/sources/http_api/data_source.md +++ b/docs/sources/http_api/data_source.md @@ -51,7 +51,7 @@ Content-Type: application/json ] ``` -## Get a single data sources by Id +## Get a single data source by Id `GET /api/datasources/:datasourceId` @@ -149,7 +149,7 @@ Content-Type: application/json } ``` -## Create data source +## Create a data source `POST /api/datasources` From a9c94ec93bc772c086e8d7eab29b37b6bc82c1ee Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 22 May 2019 23:10:05 +0200 Subject: [PATCH 46/92] Explore: Update the way Loki retrieve log context (#17204) * Move log's typings into grafana/ui * Update the way context is retrieved for Loki Major changes: 1. getLogRowContext expects row to be of LogRowModel type 2. getLogRowContext accepts generic options object, specific to a datasource of interest. limit option has been removed, and now it's a part of Loki's context query options (see below) 3. LogRowContextProvider performs two queries now. Before, it was Loki ds that performed queries in both directions when getLogRowContext. 4. Loki's getLogRowContext accepts options object of a type: interface LokiContextQueryOptions { direction?: 'BACKWARD' | 'FORWARD'; limit?: number; } This will enable querying in either direction independently and also slightly simplifies the way query errors are handled. LogRowContextProvider maps the results to a Loki specific context types, basically string[][], as raw log lines are displayed in first version. --- package.json | 1 + packages/grafana-ui/src/types/datasource.ts | 10 +- packages/grafana-ui/src/types/logs.ts | 84 +++++++++++++++ public/app/core/logs_model.ts | 90 ++-------------- public/app/core/specs/logs_model.test.ts | 7 +- public/app/core/utils/explore.test.ts | 3 +- public/app/core/utils/explore.ts | 5 +- public/app/features/explore/LiveLogs.tsx | 11 +- public/app/features/explore/LogLabel.tsx | 3 +- public/app/features/explore/LogLabelStats.tsx | 2 +- public/app/features/explore/LogLabels.tsx | 3 +- public/app/features/explore/LogRow.tsx | 14 ++- public/app/features/explore/LogRowContext.tsx | 2 +- .../explore/LogRowContextProvider.tsx | 69 +++++++----- public/app/features/explore/Logs.tsx | 18 +++- public/app/features/explore/LogsContainer.tsx | 9 +- .../features/explore/state/actions.test.ts | 3 +- public/app/features/explore/state/actions.ts | 4 +- .../features/explore/state/reducers.test.ts | 3 +- public/app/features/explore/state/reducers.ts | 4 +- .../features/explore/state/selectors.test.ts | 2 +- .../app/plugins/datasource/loki/datasource.ts | 100 ++++++++---------- public/app/types/explore.ts | 3 +- 23 files changed, 245 insertions(+), 205 deletions(-) diff --git a/package.json b/package.json index 30721ddaf6af..e2153dcf3419 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "storybook:build": "cd packages/grafana-ui && yarn storybook:build", "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", + "prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write", "gui:tslint": "tslint -c ./packages/grafana-ui/tslint.json --project ./packages/grafana-ui/tsconfig.json", "gui:build": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:build", "gui:releasePrepare": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release", diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 596e5851cf00..dd364a7d9c9e 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -3,6 +3,7 @@ import { TimeRange } from './time'; import { PluginMeta, GrafanaPlugin } from './plugin'; import { TableData, TimeSeries, SeriesData, LoadingState } from './data'; import { PanelData } from './panel'; +import { LogRowModel } from './logs'; // NOTE: this seems more general than just DataSource export interface DataSourcePluginOptionsEditorProps { @@ -187,7 +188,10 @@ export abstract class DataSourceApi< /** * Retrieve context for a given log row */ - getLogRowContext?(row: any, limit?: number): Promise; + getLogRowContext?: ( + row: LogRowModel, + options?: TContextQueryOptions + ) => Promise; /** * Set after constructor call, as the data source instance is the most common thing to pass around @@ -299,10 +303,6 @@ export interface DataQueryResponse { data: DataQueryResponseData[]; } -export interface LogRowContextQueryResponse { - data: Array>; -} - export interface DataQuery { /** * A - Z diff --git a/packages/grafana-ui/src/types/logs.ts b/packages/grafana-ui/src/types/logs.ts index 63f264fff4f3..3d8cc0951b83 100644 --- a/packages/grafana-ui/src/types/logs.ts +++ b/packages/grafana-ui/src/types/logs.ts @@ -1,3 +1,5 @@ +import { Labels, TimeSeries } from './data'; + /** * Mapping of log level abbreviation to canonical log level. * Supported levels are reduce to limit color variation. @@ -19,3 +21,85 @@ export enum LogLevel { trace = 'trace', unknown = 'unknown', } + +export enum LogsMetaKind { + Number, + String, + LabelsMap, +} + +export interface LogsMetaItem { + label: string; + value: string | number | Labels; + kind: LogsMetaKind; +} + +export interface LogRowModel { + duplicates?: number; + entry: string; + hasAnsi: boolean; + labels: Labels; + logLevel: LogLevel; + raw: string; + searchWords?: string[]; + timestamp: string; // ISO with nanosec precision + timeFromNow: string; + timeEpochMs: number; + timeLocal: string; + uniqueLabels?: Labels; +} + +export interface LogsModel { + hasUniqueLabels: boolean; + meta?: LogsMetaItem[]; + rows: LogRowModel[]; + series?: TimeSeries[]; +} + +export interface LogSearchMatch { + start: number; + length: number; + text: string; +} + +export interface LogLabelStatsModel { + active?: boolean; + count: number; + proportion: number; + value: string; +} + +export enum LogsDedupStrategy { + none = 'none', + exact = 'exact', + numbers = 'numbers', + signature = 'signature', +} + +export interface LogsParser { + /** + * Value-agnostic matcher for a field label. + * Used to filter rows, and first capture group contains the value. + */ + buildMatcher: (label: string) => RegExp; + + /** + * Returns all parsable substrings from a line, used for highlighting + */ + getFields: (line: string) => string[]; + + /** + * Gets the label name from a parsable substring of a line + */ + getLabelFromField: (field: string) => string; + + /** + * Gets the label value from a parsable substring of a line + */ + getValueFromField: (field: string) => string; + /** + * Function to verify if this is a valid parser for the given line. + * The parser accepts the line unless it returns undefined. + */ + test: (line: string) => any; +} diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index fd7e5c638a94..d2a4780b62a7 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -13,6 +13,13 @@ import { toLegacyResponseData, FieldCache, FieldType, + LogRowModel, + LogsModel, + LogsMetaItem, + LogsMetaKind, + LogsParser, + LogLabelStatsModel, + LogsDedupStrategy, } from '@grafana/ui'; import { getThemeColor } from 'app/core/utils/colors'; import { hasAnsiCodes } from 'app/core/utils/text'; @@ -28,95 +35,12 @@ export const LogLevelColor = { [LogLevel.unknown]: getThemeColor('#8e8e8e', '#dde4ed'), }; -export interface LogSearchMatch { - start: number; - length: number; - text: string; -} - -export interface LogRowModel { - duplicates?: number; - entry: string; - hasAnsi: boolean; - labels: Labels; - logLevel: LogLevel; - raw: string; - searchWords?: string[]; - timestamp: string; // ISO with nanosec precision - timeFromNow: string; - timeEpochMs: number; - timeLocal: string; - uniqueLabels?: Labels; -} - -export interface LogLabelStatsModel { - active?: boolean; - count: number; - proportion: number; - value: string; -} - -export enum LogsMetaKind { - Number, - String, - LabelsMap, -} - -export interface LogsMetaItem { - label: string; - value: string | number | Labels; - kind: LogsMetaKind; -} - -export interface LogsModel { - hasUniqueLabels: boolean; - meta?: LogsMetaItem[]; - rows: LogRowModel[]; - series?: TimeSeries[]; -} - export enum LogsDedupDescription { none = 'No de-duplication', exact = 'De-duplication of successive lines that are identical, ignoring ISO datetimes.', numbers = 'De-duplication of successive lines that are identical when ignoring numbers, e.g., IP addresses, latencies.', signature = 'De-duplication of successive lines that have identical punctuation and whitespace.', } - -export enum LogsDedupStrategy { - none = 'none', - exact = 'exact', - numbers = 'numbers', - signature = 'signature', -} - -export interface LogsParser { - /** - * Value-agnostic matcher for a field label. - * Used to filter rows, and first capture group contains the value. - */ - buildMatcher: (label: string) => RegExp; - - /** - * Returns all parsable substrings from a line, used for highlighting - */ - getFields: (line: string) => string[]; - - /** - * Gets the label name from a parsable substring of a line - */ - getLabelFromField: (field: string) => string; - - /** - * Gets the label value from a parsable substring of a line - */ - getValueFromField: (field: string) => string; - /** - * Function to verify if this is a valid parser for the given line. - * The parser accepts the line unless it returns undefined. - */ - test: (line: string) => any; -} - const LOGFMT_REGEXP = /(?:^|\s)(\w+)=("[^"]*"|\S+)/; export const LogsParsers: { [name: string]: LogsParser } = { diff --git a/public/app/core/specs/logs_model.test.ts b/public/app/core/specs/logs_model.test.ts index c30a3ebfa391..a2d47412bd06 100644 --- a/public/app/core/specs/logs_model.test.ts +++ b/public/app/core/specs/logs_model.test.ts @@ -1,15 +1,12 @@ +import { SeriesData, FieldType, LogsModel, LogsMetaKind, LogsDedupStrategy } from '@grafana/ui'; import { + dedupLogRows, calculateFieldStats, calculateLogsLabelStats, - dedupLogRows, getParser, - LogsDedupStrategy, - LogsModel, LogsParsers, seriesDataToLogsModel, - LogsMetaKind, } from '../logs_model'; -import { SeriesData, FieldType } from '@grafana/ui'; describe('dedupLogRows()', () => { test('should return rows as is when dedup is set to none', () => { diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 3a0752d2a5ed..9e11fddd6299 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -12,8 +12,7 @@ import { } from './explore'; import { ExploreUrlState } from 'app/types/explore'; import store from 'app/core/store'; -import { LogsDedupStrategy } from 'app/core/logs_model'; -import { DataQueryError } from '@grafana/ui'; +import { DataQueryError, LogsDedupStrategy } from '@grafana/ui'; const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: null, diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index e82ba9c34094..99e168b8590f 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -22,6 +22,9 @@ import { guessFieldTypes, TimeFragment, DataQueryError, + LogRowModel, + LogsModel, + LogsDedupStrategy, } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; import { @@ -33,7 +36,7 @@ import { QueryOptions, ResultGetter, } from 'app/types/explore'; -import { LogsDedupStrategy, seriesDataToLogsModel, LogsModel, LogRowModel } from 'app/core/logs_model'; +import { seriesDataToLogsModel } from 'app/core/logs_model'; import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; diff --git a/public/app/features/explore/LiveLogs.tsx b/public/app/features/explore/LiveLogs.tsx index 120b136b127c..040beae2aec8 100644 --- a/public/app/features/explore/LiveLogs.tsx +++ b/public/app/features/explore/LiveLogs.tsx @@ -1,8 +1,15 @@ import React, { PureComponent } from 'react'; import { css, cx } from 'emotion'; -import { Themeable, withTheme, GrafanaTheme, selectThemeVariant, LinkButton } from '@grafana/ui'; +import { + Themeable, + withTheme, + GrafanaTheme, + selectThemeVariant, + LinkButton, + LogsModel, + LogRowModel, +} from '@grafana/ui'; -import { LogsModel, LogRowModel } from 'app/core/logs_model'; import ElapsedTime from './ElapsedTime'; import { ButtonSize, ButtonVariant } from '@grafana/ui/src/components/Button/AbstractButton'; diff --git a/public/app/features/explore/LogLabel.tsx b/public/app/features/explore/LogLabel.tsx index b4570f10c826..1794d60b6891 100644 --- a/public/app/features/explore/LogLabel.tsx +++ b/public/app/features/explore/LogLabel.tsx @@ -1,7 +1,8 @@ import React, { PureComponent } from 'react'; -import { calculateLogsLabelStats, LogLabelStatsModel, LogRowModel } from 'app/core/logs_model'; import { LogLabelStats } from './LogLabelStats'; +import { LogRowModel, LogLabelStatsModel } from '@grafana/ui'; +import { calculateLogsLabelStats } from 'app/core/logs_model'; interface Props { getRows?: () => LogRowModel[]; diff --git a/public/app/features/explore/LogLabelStats.tsx b/public/app/features/explore/LogLabelStats.tsx index 466cc050e43b..07f33d8aff71 100644 --- a/public/app/features/explore/LogLabelStats.tsx +++ b/public/app/features/explore/LogLabelStats.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import classnames from 'classnames'; -import { LogLabelStatsModel } from 'app/core/logs_model'; +import { LogLabelStatsModel } from '@grafana/ui'; function LogLabelStatsRow(logLabelStatsModel: LogLabelStatsModel) { const { active, count, proportion, value } = logLabelStatsModel; diff --git a/public/app/features/explore/LogLabels.tsx b/public/app/features/explore/LogLabels.tsx index f89836055c53..9dffca38a66a 100644 --- a/public/app/features/explore/LogLabels.tsx +++ b/public/app/features/explore/LogLabels.tsx @@ -1,8 +1,7 @@ import React, { PureComponent } from 'react'; -import { LogRowModel } from 'app/core/logs_model'; import { LogLabel } from './LogLabel'; -import { Labels } from '@grafana/ui'; +import { Labels, LogRowModel } from '@grafana/ui'; interface Props { getRows?: () => LogRowModel[]; diff --git a/public/app/features/explore/LogRow.tsx b/public/app/features/explore/LogRow.tsx index 719c4eba816b..ad2aced592f4 100644 --- a/public/app/features/explore/LogRow.tsx +++ b/public/app/features/explore/LogRow.tsx @@ -3,7 +3,7 @@ import _ from 'lodash'; import Highlighter from 'react-highlight-words'; import classnames from 'classnames'; -import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model'; +import { calculateFieldStats, getParser } from 'app/core/logs_model'; import { LogLabels } from './LogLabels'; import { findHighlightChunksInText } from 'app/core/utils/text'; import { LogLabelStats } from './LogLabelStats'; @@ -15,7 +15,15 @@ import { HasMoreContextRows, LogRowContextQueryErrors, } from './LogRowContextProvider'; -import { ThemeContext, selectThemeVariant, GrafanaTheme, DataQueryResponse } from '@grafana/ui'; +import { + ThemeContext, + selectThemeVariant, + GrafanaTheme, + DataQueryResponse, + LogRowModel, + LogLabelStatsModel, + LogsParser, +} from '@grafana/ui'; import { LogRowContext } from './LogRowContext'; import tinycolor from 'tinycolor2'; @@ -29,7 +37,7 @@ interface Props { getRows: () => LogRowModel[]; onClickLabel?: (label: string, value: string) => void; onContextClick?: () => void; - getRowContext?: (row: LogRowModel, limit: number) => Promise; + getRowContext?: (row: LogRowModel, options?: any) => Promise; className?: string; } diff --git a/public/app/features/explore/LogRowContext.tsx b/public/app/features/explore/LogRowContext.tsx index f873b50958b5..da9c3ec48123 100644 --- a/public/app/features/explore/LogRowContext.tsx +++ b/public/app/features/explore/LogRowContext.tsx @@ -7,10 +7,10 @@ import { ClickOutsideWrapper, CustomScrollbar, DataQueryError, + LogRowModel, } from '@grafana/ui'; import { css, cx } from 'emotion'; import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider'; -import { LogRowModel } from 'app/core/logs_model'; import { Alert } from './Error'; interface LogRowContextProps { diff --git a/public/app/features/explore/LogRowContextProvider.tsx b/public/app/features/explore/LogRowContextProvider.tsx index d3fa0bfdcc83..a43d982e2b48 100644 --- a/public/app/features/explore/LogRowContextProvider.tsx +++ b/public/app/features/explore/LogRowContextProvider.tsx @@ -1,11 +1,11 @@ -import { LogRowModel } from 'app/core/logs_model'; -import { LogRowContextQueryResponse, SeriesData, DataQueryResponse, DataQueryError } from '@grafana/ui'; +import { DataQueryResponse, DataQueryError, LogRowModel } from '@grafana/ui'; import { useState, useEffect } from 'react'; +import flatten from 'lodash/flatten'; import useAsync from 'react-use/lib/useAsync'; export interface LogRowContextRows { - before?: Array; - after?: Array; + before?: string[]; + after?: string[]; } export interface LogRowContextQueryErrors { before?: string; @@ -19,7 +19,7 @@ export interface HasMoreContextRows { interface LogRowContextProviderProps { row: LogRowModel; - getRowContext: (row: LogRowModel, limit: number) => Promise; + getRowContext: (row: LogRowModel, options?: any) => Promise; children: (props: { result: LogRowContextRows; errors: LogRowContextQueryErrors; @@ -34,23 +34,44 @@ export const LogRowContextProvider: React.FunctionComponent { const [limit, setLimit] = useState(10); - const [result, setResult] = useState(null); - const [errors, setErrors] = useState(null); + const [result, setResult] = useState<{ + data: string[][]; + errors: string[]; + }>(null); const [hasMoreContextRows, setHasMoreContextRows] = useState({ before: true, after: true, }); const { value } = useAsync(async () => { - const context = await getRowContext(row, limit); + const promises = [ + getRowContext(row, { + limit, + }), + getRowContext(row, { + limit, + direction: 'FORWARD', + }), + ]; + + const results: Array = await Promise.all(promises.map(p => p.catch(e => e))); + return { - data: context.data.map(series => { - if ((series as SeriesData).rows) { - return (series as SeriesData).rows.map(row => row[1]); + data: results.map(result => { + if ((result as DataQueryResponse).data) { + return (result as DataQueryResponse).data.map(series => { + return series.rows.map(row => row[1]); + }); + } else { + return []; + } + }), + errors: results.map(result => { + if ((result as DataQueryError).message) { + return (result as DataQueryError).message; } else { - return [series]; + return null; } - return []; }), }; }, [limit]); @@ -60,7 +81,6 @@ export const LogRowContextProvider: React.FunctionComponent { let hasMoreLogsBefore = true, hasMoreLogsAfter = true; - let beforeContextError, afterContextError; if (currentResult && currentResult.data[0].length === value.data[0].length) { hasMoreLogsBefore = false; @@ -70,23 +90,11 @@ export const LogRowContextProvider: React.FunctionComponent 0 && value.data[0][0].message) { - beforeContextError = value.data[0][0].message; - } - if (value.data[1] && value.data[1].length > 0 && value.data[1][0].message) { - afterContextError = value.data[1][0].message; - } - setHasMoreContextRows({ before: hasMoreLogsBefore, after: hasMoreLogsAfter, }); - setErrors({ - before: beforeContextError, - after: afterContextError, - }); - return value; }); } @@ -94,10 +102,13 @@ export const LogRowContextProvider: React.FunctionComponent setLimit(limit + 10), }); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index f12830b63b78..b6ee5a7f7312 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -2,16 +2,26 @@ import _ from 'lodash'; import React, { PureComponent } from 'react'; import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; -import { RawTimeRange, Switch, LogLevel, TimeZone, TimeRange, AbsoluteTimeRange } from '@grafana/ui'; +import { + RawTimeRange, + Switch, + LogLevel, + TimeZone, + TimeRange, + AbsoluteTimeRange, + LogsMetaKind, + LogsModel, + LogsDedupStrategy, + LogRowModel, +} from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; -import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind, LogRowModel } from 'app/core/logs_model'; - import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; import Graph from './Graph'; import { LogLabels } from './LogLabels'; import { LogRow } from './LogRow'; +import { LogsDedupDescription } from 'app/core/logs_model'; const PREVIEW_LIMIT = 100; @@ -60,7 +70,7 @@ interface Props { onStopScanning?: () => void; onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; onToggleLogLevel: (hiddenLogLevels: Set) => void; - getRowContext?: (row: LogRowModel, limit: number) => Promise; + getRowContext?: (row: LogRowModel, options?: any) => Promise; } interface State { diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 484dd993cecd..d8d85efcc135 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -10,10 +10,12 @@ import { toUtc, dateTime, DataSourceApi, + LogsModel, + LogRowModel, + LogsDedupStrategy, } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { LogsModel, LogsDedupStrategy, LogRowModel } from 'app/core/logs_model'; import { StoreState } from 'app/types'; import { changeDedupStrategy, changeTime } from './state/actions'; @@ -29,6 +31,7 @@ interface LogsContainerProps { datasourceInstance: DataSourceApi | null; exploreId: ExploreId; loading: boolean; + logsHighlighterExpressions?: string[]; logsResult?: LogsModel; dedupedResult?: LogsModel; @@ -77,11 +80,11 @@ export class LogsContainer extends PureComponent { }); }; - getLogRowContext = async (row: LogRowModel, limit: number) => { + getLogRowContext = async (row: LogRowModel, options?: any) => { const { datasourceInstance } = this.props; if (datasourceInstance) { - return datasourceInstance.getLogRowContext(row, limit); + return datasourceInstance.getLogRowContext(row, options); } return []; diff --git a/public/app/features/explore/state/actions.test.ts b/public/app/features/explore/state/actions.test.ts index 0bae4dc1d958..ba096602c050 100644 --- a/public/app/features/explore/state/actions.test.ts +++ b/public/app/features/explore/state/actions.test.ts @@ -1,7 +1,6 @@ import { refreshExplore, testDatasource, loadDatasource } from './actions'; import { ExploreId, ExploreUrlState, ExploreUpdateState } from 'app/types'; import { thunkTester } from 'test/core/thunk/thunkTester'; -import { LogsDedupStrategy } from 'app/core/logs_model'; import { initializeExploreAction, InitializeExplorePayload, @@ -18,7 +17,7 @@ import { Emitter } from 'app/core/core'; import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { makeInitialUpdateState } from './reducers'; import { DataQuery } from '@grafana/ui/src/types/datasource'; -import { DefaultTimeZone, RawTimeRange } from '@grafana/ui'; +import { DefaultTimeZone, RawTimeRange, LogsDedupStrategy } from '@grafana/ui'; import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; jest.mock('app/features/plugins/datasource_srv', () => ({ diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index e06218988265..09f950905ae9 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -35,7 +35,8 @@ import { DataSourceSelectItem, QueryFixAction, TimeRange, -} from '@grafana/ui/src/types'; + LogsDedupStrategy, +} from '@grafana/ui'; import { ExploreId, ExploreUrlState, @@ -87,7 +88,6 @@ import { changeModeAction, } from './actionTypes'; import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; -import { LogsDedupStrategy } from 'app/core/logs_model'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; import { toDataQueryError } from 'app/features/dashboard/state/PanelQueryState'; diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 5af71c29f8df..da9bdfabe261 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -29,10 +29,9 @@ import { import { Reducer } from 'redux'; import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { updateLocation } from 'app/core/actions/location'; -import { LogsDedupStrategy, LogsModel } from 'app/core/logs_model'; import { serializeStateToUrlParam } from 'app/core/utils/explore'; import TableModel from 'app/core/table_model'; -import { DataSourceApi, DataQuery } from '@grafana/ui'; +import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy } from '@grafana/ui'; describe('Explore item reducer', () => { describe('scanning', () => { diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 7e8fbb9d6ef7..1291f3d749bb 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -10,7 +10,7 @@ import { sortLogsResult, } from 'app/core/utils/explore'; import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore'; -import { DataQuery } from '@grafana/ui/src/types'; +import { DataQuery, LogsModel } from '@grafana/ui'; import { HigherOrderAction, ActionTypes, @@ -58,7 +58,7 @@ import { LocationUpdate } from 'app/types'; import TableModel from 'app/core/table_model'; import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; import { subscriptionDataReceivedAction, startSubscriptionAction } from './epics'; -import { LogsModel, seriesDataToLogsModel } from 'app/core/logs_model'; +import { seriesDataToLogsModel } from 'app/core/logs_model'; export const DEFAULT_RANGE = { from: 'now-6h', diff --git a/public/app/features/explore/state/selectors.test.ts b/public/app/features/explore/state/selectors.test.ts index 3a1fa5102ae3..52f8d27811dc 100644 --- a/public/app/features/explore/state/selectors.test.ts +++ b/public/app/features/explore/state/selectors.test.ts @@ -1,5 +1,5 @@ import { deduplicatedLogsSelector } from './selectors'; -import { LogsDedupStrategy } from 'app/core/logs_model'; +import { LogsDedupStrategy } from '@grafana/ui'; import { ExploreItemState } from 'app/types'; const state = { diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 932c9c0a6e2b..d86e5fe1922a 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -16,12 +16,12 @@ import { DataSourceApi, DataSourceInstanceSettings, DataQueryError, -} from '@grafana/ui/src/types'; + LogRowModel, +} from '@grafana/ui'; import { LokiQuery, LokiOptions } from './types'; import { BackendSrv } from 'app/core/services/backend_srv'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { safeStringifyValue } from 'app/core/utils/explore'; -import { LogRowModel } from 'app/core/logs_model'; export const DEFAULT_MAX_LINES = 1000; @@ -41,6 +41,11 @@ function serializeParams(data: any) { .join('&'); } +interface LokiContextQueryOptions { + direction?: 'BACKWARD' | 'FORWARD'; + limit?: number; +} + export class LokiDatasource extends DataSourceApi { languageProvider: LanguageProvider; maxLines: number; @@ -224,7 +229,7 @@ export class LokiDatasource extends DataSourceApi { return Math.ceil(date.valueOf() * 1e6); } - prepareLogRowContextQueryTargets = (row: LogRowModel, limit: number) => { + prepareLogRowContextQueryTarget = (row: LogRowModel, limit: number, direction: 'BACKWARD' | 'FORWARD') => { const query = Object.keys(row.labels) .map(label => { return `${label}="${row.labels[label]}"`; @@ -236,69 +241,58 @@ export class LokiDatasource extends DataSourceApi { const commontTargetOptons = { limit, query: `{${query}}`, + direction, }; - return [ - // Target for "before" context - { + + if (direction === 'BACKWARD') { + return { ...commontTargetOptons, start: timeEpochNs - contextTimeBuffer, end: timeEpochNs, - direction: 'BACKWARD', - }, - // Target for "after" context - { + direction, + }; + } else { + return { ...commontTargetOptons, start: timeEpochNs, // TODO: We should add 1ns here for the original row not no be included in the result end: timeEpochNs + contextTimeBuffer, - direction: 'FORWARD', - }, - ]; + }; + } }; - getLogRowContext = (row: LogRowModel, limit?: number) => { - // Preparing two targets, for preceeding and following log queries - const targets = this.prepareLogRowContextQueryTargets(row, limit || 10); - - return Promise.all( - targets.map(target => { - return this._request('/api/prom/query', target).catch(e => { - const error: DataQueryError = { - message: 'Error during context query. Please check JS console logs.', - status: e.status, - statusText: e.statusText, - }; - return error; - }); - }) - ).then((results: any[]) => { - const series: Array> = []; - const emptySeries = { - fields: [], - rows: [], - } as SeriesData; - - for (let i = 0; i < results.length; i++) { - const result = results[i]; - series[i] = []; - if (result.data) { - for (const stream of result.data.streams || []) { - const seriesData = logStreamToSeriesData(stream); - series[i].push(seriesData); - } - } else { - series[i].push(result); + getLogRowContext = async (row: LogRowModel, options?: LokiContextQueryOptions) => { + const target = this.prepareLogRowContextQueryTarget( + row, + (options && options.limit) || 10, + (options && options.direction) || 'BACKWARD' + ); + const series: SeriesData[] = []; + + try { + const result = await this._request('/api/prom/query', target); + if (result.data) { + for (const stream of result.data.streams || []) { + const seriesData = logStreamToSeriesData(stream); + series.push(seriesData); } } - - // Following context logs are requested in "forward" direction. - // This means, that we need to reverse those to make them sorted - // in descending order (by timestamp) - if (series[1][0] && (series[1][0] as SeriesData).rows) { - (series[1][0] as SeriesData).rows.reverse(); + if (options && options.direction === 'FORWARD') { + if (series[0] && series[0].rows) { + series[0].rows.reverse(); + } } - return { data: [series[0][0] || emptySeries, series[1][0] || emptySeries] }; - }); + return { + data: series, + }; + } catch (e) { + const error: DataQueryError = { + message: 'Error during context query. Please check JS console logs.', + status: e.status, + statusText: e.statusText, + }; + throw error; + } }; testDatasource() { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 7bb8695e1da3..c852d92ee29a 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -11,10 +11,11 @@ import { LogLevel, TimeRange, DataQueryError, + LogsModel, + LogsDedupStrategy, } from '@grafana/ui'; import { Emitter, TimeSeries } from 'app/core/core'; -import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import TableModel from 'app/core/table_model'; export enum ExploreMode { From ca6151e23fcc85ab3f67662f4d67e71dc1008b3e Mon Sep 17 00:00:00 2001 From: Abhilash Gnan Date: Thu, 23 May 2019 08:31:02 +0200 Subject: [PATCH 47/92] Alerting: Support for configuring content field for Discord alert notifier (#17017) --- pkg/services/alerting/notifiers/discord.go | 24 ++++++++++++++++--- .../alerting/notifiers/discord_test.go | 4 +++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go index f85fa7f785e9..160c76528dd9 100644 --- a/pkg/services/alerting/notifiers/discord.go +++ b/pkg/services/alerting/notifiers/discord.go @@ -24,15 +24,27 @@ func init() { Factory: NewDiscordNotifier, OptionsTemplate: `

    Discord settings

    -
    - Webhook URL - +
    + Message Content + + + + Mention a group using @ or a user using <@ID> when notifying in a channel + +
    +
    + Webhook URL +
    `, }) } func NewDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, error) { + content := model.Settings.Get("content").MustString() url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find webhook url property in settings"} @@ -40,6 +52,7 @@ func NewDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, err return &DiscordNotifier{ NotifierBase: NewNotifierBase(model), + Content: content, WebhookURL: url, log: log.New("alerting.notifier.discord"), }, nil @@ -47,6 +60,7 @@ func NewDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, err type DiscordNotifier struct { NotifierBase + Content string WebhookURL string log log.Logger } @@ -63,6 +77,10 @@ func (this *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON := simplejson.New() bodyJSON.Set("username", "Grafana") + if this.Content != "" { + bodyJSON.Set("content", this.Content) + } + fields := make([]map[string]interface{}, 0) for _, evt := range evalContext.EvalMatches { diff --git a/pkg/services/alerting/notifiers/discord_test.go b/pkg/services/alerting/notifiers/discord_test.go index dfc6bbe9aee0..5fe700245e23 100644 --- a/pkg/services/alerting/notifiers/discord_test.go +++ b/pkg/services/alerting/notifiers/discord_test.go @@ -29,7 +29,8 @@ func TestDiscordNotifier(t *testing.T) { Convey("settings should trigger incident", func() { json := ` { - "url": "https://web.hook/" + "content": "@everyone Please check this notification", + "url": "https://web.hook/" }` settingsJSON, _ := simplejson.NewJson([]byte(json)) @@ -45,6 +46,7 @@ func TestDiscordNotifier(t *testing.T) { So(err, ShouldBeNil) So(discordNotifier.Name, ShouldEqual, "discord_testing") So(discordNotifier.Type, ShouldEqual, "discord") + So(discordNotifier.Content, ShouldEqual, "@everyone Please check this notification") So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/") }) }) From 9e7fa74682423774a3962cbac8fa8cfdd819559d Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 23 May 2019 02:56:00 -0700 Subject: [PATCH 48/92] DataSourcePlugin: Avoid anuglar injector if only one parameter (#17239) --- public/app/features/plugins/datasource_srv.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 6143a5e182ba..1a84355ac028 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -61,9 +61,13 @@ export class DatasourceSrv { return; } - const instance: DataSourceApi = this.$injector.instantiate(dsPlugin.DataSourceClass, { - instanceSettings: dsConfig, - }); + // If there is only one constructor argument it is instanceSettings + const useAngular = dsPlugin.DataSourceClass.length !== 1; + const instance: DataSourceApi = useAngular + ? this.$injector.instantiate(dsPlugin.DataSourceClass, { + instanceSettings: dsConfig, + }) + : new dsPlugin.DataSourceClass(dsConfig); instance.components = dsPlugin.components; instance.meta = dsConfig.meta; From 972df40af8b2af356de17ba08b6df628c61797ac Mon Sep 17 00:00:00 2001 From: Brian Gann Date: Thu, 23 May 2019 04:56:34 -0500 Subject: [PATCH 49/92] Build: Fix filter for building msi during release (#17236) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a484cd08e184..91ace22d33b9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -723,7 +723,7 @@ workflows: - backend-lint - mysql-integration-test - postgres-integration-test - filters: *filter-only-master + filters: *filter-only-release build-branches-and-prs: jobs: From a04bf0cdedc82b46ebd770138cc0b11cdcd14f63 Mon Sep 17 00:00:00 2001 From: Filip Barl Date: Thu, 23 May 2019 11:58:51 +0200 Subject: [PATCH 50/92] Panel: Pass transparency prop down to React panels. (#17235) --- packages/grafana-ui/src/types/panel.ts | 1 + public/app/features/dashboard/dashgrid/PanelChrome.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index a1756ff02fea..aedb5b30a877 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -31,6 +31,7 @@ export interface PanelProps { options: T; onOptionsChange: (options: T) => void; renderCounter: number; + transparent: boolean; width: number; height: number; replaceVariables: InterpolateFunction; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index e8a13cdeb711..485c409220c6 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -254,6 +254,7 @@ export class PanelChrome extends PureComponent { data={data} timeRange={data.request ? data.request.range : this.timeSrv.timeRange()} options={panel.getOptions()} + transparent={panel.transparent} width={width - theme.panelPadding * 2} height={innerPanelHeight} renderCounter={renderCounter} From 8e434ad1635000983ebe0bc9428349613626114a Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Thu, 23 May 2019 03:02:19 -0700 Subject: [PATCH 51/92] CloudWatch: Made region visible for AWS Cloudwatch Expressions (#17243) Fixes #14933 --- .../cloudwatch/partials/query.parameter.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html b/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html index 1b252e1dda3e..ce272bb4d64a 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html @@ -1,8 +1,18 @@ +
    +
    + + +
    + +
    +
    +
    +
    +
    -
    From c87b2c9913c03c6543e5965a3c7884f4217506ce Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Thu, 23 May 2019 13:06:34 +0200 Subject: [PATCH 52/92] docs: fixes typo in provisioning docs (#17248) closes #17196 --- docs/sources/administration/provisioning.md | 2 +- pkg/services/provisioning/notifiers/config_reader_test.go | 2 ++ .../test-configs/correct-properties/correct-properties.yaml | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index da95a311635b..9b1f8a6c70f6 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -274,7 +274,7 @@ notifiers: # or org_name: Main Org. is_default: true - send_reminders: true + send_reminder: true frequency: 1h disable_resolve_message: false # See `Supported Settings` section for settings supporter for each diff --git a/pkg/services/provisioning/notifiers/config_reader_test.go b/pkg/services/provisioning/notifiers/config_reader_test.go index e2ffb5aa75f8..e09531774252 100644 --- a/pkg/services/provisioning/notifiers/config_reader_test.go +++ b/pkg/services/provisioning/notifiers/config_reader_test.go @@ -66,6 +66,8 @@ func TestNotificationAsConfig(t *testing.T) { So(nt.Settings, ShouldResemble, map[string]interface{}{ "recipient": "XXX", "token": "xoxb", "uploadImage": true, "url": "https://slack.com", }) + So(nt.SendReminder, ShouldBeTrue) + So(nt.Frequency, ShouldEqual, "1h") nt = nts[1] So(nt.Name, ShouldEqual, "another-not-default-notification") diff --git a/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml b/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml index 1d846f64473b..5c71e0229335 100644 --- a/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml +++ b/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml @@ -3,8 +3,9 @@ notifiers: type: slack uid: notifier1 org_id: 2 - uid: "notifier1" is_default: true + send_reminder: true + frequency: 1h settings: recipient: "XXX" token: "xoxb" From a3a7916597a4a1778eb685ddcd51651b05619400 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 23 May 2019 15:54:47 +0300 Subject: [PATCH 53/92] Auth: Logout disabled user (#17166) * Feature: revoke user token when disabled * Chore: fix linter error --- pkg/api/admin_users.go | 25 +++++++++++++------------ pkg/api/admin_users_test.go | 15 +++++++++++---- pkg/api/api.go | 4 ++-- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index dc056dbe2763..9fedbc4ddf52 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -112,43 +112,44 @@ func AdminDeleteUser(c *models.ReqContext) { } // POST /api/admin/users/:id/disable -func AdminDisableUser(c *models.ReqContext) { +func (server *HTTPServer) AdminDisableUser(c *models.ReqContext) Response { userID := c.ParamsInt64(":id") // External users shouldn't be disabled from API authInfoQuery := &models.GetAuthInfoQuery{UserId: userID} if err := bus.Dispatch(authInfoQuery); err != models.ErrUserNotFound { - c.JsonApiErr(500, "Could not disable external user", nil) - return + return Error(500, "Could not disable external user", nil) } disableCmd := models.DisableUserCommand{UserId: userID, IsDisabled: true} if err := bus.Dispatch(&disableCmd); err != nil { - c.JsonApiErr(500, "Failed to disable user", err) - return + return Error(500, "Failed to disable user", err) + } + + err := server.AuthTokenService.RevokeAllUserTokens(c.Req.Context(), userID) + if err != nil { + return Error(500, "Failed to disable user", err) } - c.JsonOK("User disabled") + return Success("User disabled") } // POST /api/admin/users/:id/enable -func AdminEnableUser(c *models.ReqContext) { +func AdminEnableUser(c *models.ReqContext) Response { userID := c.ParamsInt64(":id") // External users shouldn't be disabled from API authInfoQuery := &models.GetAuthInfoQuery{UserId: userID} if err := bus.Dispatch(authInfoQuery); err != models.ErrUserNotFound { - c.JsonApiErr(500, "Could not enable external user", nil) - return + return Error(500, "Could not enable external user", nil) } disableCmd := models.DisableUserCommand{UserId: userID, IsDisabled: false} if err := bus.Dispatch(&disableCmd); err != nil { - c.JsonApiErr(500, "Failed to enable user", err) - return + return Error(500, "Failed to enable user", err) } - c.JsonOK("User enabled") + return Success("User enabled") } // POST /api/admin/users/:id/logout diff --git a/pkg/api/admin_users_test.go b/pkg/api/admin_users_test.go index 1ec3000dc8be..4d858183545f 100644 --- a/pkg/api/admin_users_test.go +++ b/pkg/api/admin_users_test.go @@ -222,16 +222,23 @@ func adminDisableUserScenario(desc string, action string, url string, routePatte Convey(desc+" "+url, func() { defer bus.ClearBusHandlers() + fakeAuthTokenService := auth.NewFakeUserAuthTokenService() + + hs := HTTPServer{ + Bus: bus.GetBus(), + AuthTokenService: fakeAuthTokenService, + } + sc := setupScenarioContext(url) - sc.defaultHandler = Wrap(func(c *m.ReqContext) { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID if action == "enable" { - AdminEnableUser(c) - } else { - AdminDisableUser(c) + return AdminEnableUser(c) } + + return hs.AdminDisableUser(c) }) sc.m.Post(routePattern, sc.defaultHandler) diff --git a/pkg/api/api.go b/pkg/api/api.go index 70f6263686a5..9f80f1cb4fba 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -381,8 +381,8 @@ func (hs *HTTPServer) registerRoutes() { adminRoute.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword) adminRoute.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions) adminRoute.Delete("/users/:id", AdminDeleteUser) - adminRoute.Post("/users/:id/disable", AdminDisableUser) - adminRoute.Post("/users/:id/enable", AdminEnableUser) + adminRoute.Post("/users/:id/disable", Wrap(hs.AdminDisableUser)) + adminRoute.Post("/users/:id/enable", Wrap(AdminEnableUser)) adminRoute.Get("/users/:id/quotas", Wrap(GetUserQuotas)) adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), Wrap(UpdateUserQuota)) adminRoute.Get("/stats", AdminGetStats) From 0d6c9d371f9462a28a9c0135e6eab58b32e8bb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 23 May 2019 19:56:16 +0200 Subject: [PATCH 54/92] Release: Updated latest.json --- latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latest.json b/latest.json index ff5fac8e6d55..18770c57cce2 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "6.1.6", - "testing": "6.2.0-beta1" + "stable": "6.2.0", + "testing": "6.2.0" } From b0ea3a8c885a8ed71425e22fbc93fad4fe77cbe2 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 23 May 2019 11:03:10 -0700 Subject: [PATCH 55/92] Chore: bump grafana-ui version (#17256) --- packages/grafana-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index a62de9ca003e..46b402c92f97 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -1,6 +1,6 @@ { "name": "@grafana/ui", - "version": "6.2.0-alpha.0", + "version": "6.3.0-alpha.0", "description": "Grafana Components Library", "keywords": [ "typescript", From a7aaed49b5f0903ddc8be01903fcb24ab57ee3a3 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 24 May 2019 08:09:51 +0200 Subject: [PATCH 56/92] Devenv: Update Graphite port in dev datasources (#17255) --- devenv/datasources.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index e0f63bef2999..d5664d7136c0 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -1,10 +1,11 @@ + apiVersion: 1 datasources: - name: gdev-graphite type: graphite access: proxy - url: http://localhost:8080 + url: http://localhost:8180 jsonData: graphiteVersion: "1.1" From 6acc7d37dad262a5f0a2442b2ec80c29fcdd1151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 24 May 2019 08:52:47 +0200 Subject: [PATCH 57/92] Singlestat: fixes issue with value placement and line wraps (#17249) Fixes #17237 --- public/app/plugins/panel/singlestat/module.ts | 7 +++++-- public/sass/components/_panel_singlestat.scss | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index d0fa3c3183eb..3272323c3540 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -11,6 +11,8 @@ import TimeSeries from 'app/core/time_series2'; import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName, isTableData } from '@grafana/ui'; +const BASE_FONT_SIZE = 38; + class SingleStatCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -384,10 +386,11 @@ class SingleStatCtrl extends MetricsPanelCtrl { return valueString; } - function getSpan(className, fontSize, applyColoring, value) { + function getSpan(className, fontSizePercent, applyColoring, value) { value = $sanitize(templateSrv.replace(value, data.scopedVars)); value = applyColoring ? applyColoringThresholds(value) : value; - return '' + value + ''; + const pixelSize = (parseInt(fontSizePercent, 10) / 100) * BASE_FONT_SIZE; + return '' + value + ''; } function getBigValueHtml() { diff --git a/public/sass/components/_panel_singlestat.scss b/public/sass/components/_panel_singlestat.scss index 7854ac2093fa..df8cbd0c037b 100644 --- a/public/sass/components/_panel_singlestat.scss +++ b/public/sass/components/_panel_singlestat.scss @@ -6,17 +6,13 @@ } .singlestat-panel-value-container { - // line-height 0 is imporant here as the font-size is on this - // level but overriden one level deeper and but the line-height: is still - // based on the base font size on this level. Using line-height: 0 fixes that - line-height: 0; display: table-cell; vertical-align: middle; text-align: center; position: relative; z-index: 1; font-weight: $font-weight-semi-bold; - font-size: 38px; + line-height: 1; } // Helps From 2a52dbfb74a70f18fa51d643ef2294bd6929cd05 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 24 May 2019 08:49:17 -0700 Subject: [PATCH 58/92] Table: various minor fixes (alpha panel) (#17258) --- .../src/components/Table/Table.test.tsx | 48 +++++++++++++++++++ .../grafana-ui/src/components/Table/Table.tsx | 14 +++--- .../src/components/Table/TableCellBuilder.tsx | 5 +- .../src/components/Table/_Table.scss | 1 + 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 packages/grafana-ui/src/components/Table/Table.test.tsx diff --git a/packages/grafana-ui/src/components/Table/Table.test.tsx b/packages/grafana-ui/src/components/Table/Table.test.tsx new file mode 100644 index 000000000000..43947c9455f8 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/Table.test.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { readCSV } from '../../utils/csv'; +import { Table, Props } from './Table'; +import { getTheme } from '../../themes/index'; +import { GrafanaThemeType } from '../../types/theme'; +import renderer from 'react-test-renderer'; + +const series = readCSV('a,b,c\n1,2,3\n4,5,6')[0]; +const setup = (propOverrides?: object) => { + const props: Props = { + data: series, + + minColumnWidth: 100, + showHeader: true, + fixedHeader: true, + fixedColumns: 0, + rotate: false, + styles: [], + replaceVariables: (value: string) => value, + width: 600, + height: 800, + + theme: getTheme(GrafanaThemeType.Dark), + }; // partial + + Object.assign(props, propOverrides); + + const tree = renderer.create(
); + const instance = (tree.getInstance() as unknown) as Table; + + return { + tree, + instance, + }; +}; + +describe('Table', () => { + it('ignore invalid properties', () => { + const { tree, instance } = setup(); + expect(tree.toJSON() + '').toEqual( + setup({ + id: 3, // Don't pass invalid parameters to MultiGrid + }).tree.toJSON() + '' + ); + expect(instance.measurer.has(0, 0)).toBeTruthy(); + }); +}); diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 72b7d6392b93..71e96b486fd4 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -282,14 +282,16 @@ export class Table extends Component { this.scrollToTop = false; } + // Force MultiGrid to rerender if these options change + // See: https://github.com/bvaughn/react-virtualized#pass-thru-props + const refreshKeys = { + ...this.state, // Includes data and sort parameters + d1: this.props.data, + s0: this.props.styles, + }; return ( { const { thresholds, colors } = this.style; diff --git a/packages/grafana-ui/src/components/Table/_Table.scss b/packages/grafana-ui/src/components/Table/_Table.scss index d9fb2dafe6c7..bb82d021b4b5 100644 --- a/packages/grafana-ui/src/components/Table/_Table.scss +++ b/packages/grafana-ui/src/components/Table/_Table.scss @@ -70,6 +70,7 @@ text-overflow: ellipsis; white-space: nowrap; + overflow: hidden; border-right: 2px solid $body-bg; border-bottom: 2px solid $body-bg; From bc3a718107e009d3d245ee764b83a52c750ba967 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 24 May 2019 17:51:10 +0200 Subject: [PATCH 59/92] devenv: adds auth proxy load test (#17271) --- devenv/docker/loadtest/README.md | 10 ++++ devenv/docker/loadtest/auth_proxy_test.js | 56 +++++++++++++++++++++++ devenv/docker/loadtest/run.sh | 8 +++- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 devenv/docker/loadtest/auth_proxy_test.js diff --git a/devenv/docker/loadtest/README.md b/devenv/docker/loadtest/README.md index ca70a77dc74f..95f5c38dc0eb 100644 --- a/devenv/docker/loadtest/README.md +++ b/devenv/docker/loadtest/README.md @@ -6,6 +6,9 @@ Runs load tests and checks using [k6](https://k6.io/). Docker +To run the auth proxy test you'll need to setup nginx proxy from docker block and +enable auth proxy together with configuring Grafana for auth proxy. + ## Run Run load test for 15 minutes using 2 virtual users and targeting http://localhost:3000. @@ -32,6 +35,13 @@ Run load test for 10 virtual users: $ ./run.sh -v 10 ``` +Run auth proxy test: + +```bash +$ ./run.sh -c auth_proxy_test +``` + + Example output: ```bash diff --git a/devenv/docker/loadtest/auth_proxy_test.js b/devenv/docker/loadtest/auth_proxy_test.js new file mode 100644 index 000000000000..f506701e44e0 --- /dev/null +++ b/devenv/docker/loadtest/auth_proxy_test.js @@ -0,0 +1,56 @@ +import { sleep, check, group } from 'k6'; +import { createBasicAuthClient } from './modules/client.js'; + +export let options = { + noCookiesReset: true +}; + +let endpoint = __ENV.URL || 'http://localhost:10080/grafana'; +const client = createBasicAuthClient(endpoint, 'user1', 'grafana'); +client.withOrgId(1); + +export const setup = () => { + const adminClient = createBasicAuthClient(endpoint, 'admin', 'admin'); + let res = adminClient.datasources.getByName('gdev-prometheus'); + if (res.status !== 200) { + throw new Error('Expected 200 response status when creating datasource'); + } + + return { + datasourceId: res.json().id, + }; +} + +export default (data) => { + group("auth proxy test", () => { + group("batch proxy requests", () => { + const d = new Date(); + const batchCount = 300; + const requests = []; + const query = encodeURI('topk(5, max(scrape_duration_seconds) by (job))'); + const start = (d.getTime() / 1000) - 3600; + const end = (d.getTime() / 1000); + const step = 20; + + requests.push({ method: 'GET', url: '/api/annotations?dashboardId=8&from=1558670300607&to=1558691900607' }); + + for (let n = 0; n < batchCount; n++) { + requests.push({ + method: 'GET', + url: `/api/datasources/proxy/${data.datasourceId}/api/v1/query_range?query=${query}&start=${start}&end=${end}&step=${step}`, + }); + } + + let responses = client.batch(requests); + for (let n = 0; n < batchCount; n++) { + check(responses[n], { + 'response status is 200': (r) => r.status === 200, + }); + } + }); + }); + + sleep(5) +} + +export const teardown = (data) => {} diff --git a/devenv/docker/loadtest/run.sh b/devenv/docker/loadtest/run.sh index 9517edf5d743..0d5d9cc441c8 100755 --- a/devenv/docker/loadtest/run.sh +++ b/devenv/docker/loadtest/run.sh @@ -6,8 +6,9 @@ run() { duration='15m' url='http://localhost:3000' vus='2' + testcase='auth_token_test' - while getopts ":d:u:v:" o; do + while getopts ":d:u:v:c:" o; do case "${o}" in d) duration=${OPTARG} @@ -18,11 +19,14 @@ run() { v) vus=${OPTARG} ;; + c) + testcase=${OPTARG} + ;; esac done shift $((OPTIND-1)) - docker run -t --network=host -v $PWD:/src -e URL=$url --rm -i loadimpact/k6:master run --vus $vus --duration $duration src/auth_token_test.js + docker run -t --network=host -v $PWD:/src -e URL=$url --rm -i loadimpact/k6:master run --vus $vus --duration $duration src/$testcase.js } run "$@" From baa55ab6ae14ae23864f650cec2bb180b36fdabe Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Sat, 25 May 2019 04:38:01 +0300 Subject: [PATCH 60/92] Feature: do dev environment via makefile (#17136) Simplifies dev environment creation. I also planing to utilize this logic for the LDAP benchmarking --- Makefile | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8cfc7ce26812..9f56e3da8f1a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -include local/Makefile -.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go test-go test-js test run clean gosec revive +.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go test-go test-js test run clean gosec revive devenv devenv-down GO := GO111MODULE=on go GO_FILES := ./pkg/... @@ -84,6 +84,19 @@ revive: scripts/go/bin/revive -config ./scripts/go/configs/revive.toml \ $(GO_FILES) +# create docker-compose file with provided sources and start them +# example: make devenv sources=postgres,openldap +devenv: devenv-down + $(eval targets := $(shell echo '$(sources)' | tr "," " ")) + + @cd devenv; \ + ./create_docker_compose.sh $(targets); \ + docker-compose up -d + +# drop down the envs +devenv-down: + @cd devenv; docker-compose down; + # TODO recheck the rules and leave only necessary exclusions gosec: scripts/go/bin/gosec @scripts/go/bin/gosec -quiet \ From 07d37b4a929cb0d35376d0294cb268d88cb1da73 Mon Sep 17 00:00:00 2001 From: olcbean Date: Sat, 25 May 2019 20:41:25 +0200 Subject: [PATCH 61/92] fix typo in basic_concepts.md (#17285) --- docs/sources/guides/basic_concepts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/guides/basic_concepts.md b/docs/sources/guides/basic_concepts.md index d3f8dd0ba633..3c610e21ce23 100644 --- a/docs/sources/guides/basic_concepts.md +++ b/docs/sources/guides/basic_concepts.md @@ -66,7 +66,7 @@ There are a wide variety of styling and formatting options that each Panel expos Panels can be dragged and dropped and rearranged on the Dashboard. They can also be resized. -There are currently four Panel types: [Graph](/reference/graph/), [Singlestat](/reference/singlestat/), [Dashlist](/reference/dashlist/), [Table](/reference/table_panel/),and [Text](/reference/text/). +There are currently five Panel types: [Graph](/reference/graph/), [Singlestat](/reference/singlestat/), [Dashlist](/reference/dashlist/), [Table](/reference/table_panel/), and [Text](/reference/text/). Panels like the [Graph](/reference/graph/) panel allow you to graph as many metrics and series as you want. Other panels like [Singlestat](/reference/singlestat/) require a reduction of a single query into a single number. [Dashlist](/reference/dashlist/) and [Text](/reference/text/) are special panels that do not connect to any Data Source. From df6a4914c4eca3ae87068df3ca29ad9eb101f03c Mon Sep 17 00:00:00 2001 From: Tim Butler Date: Mon, 27 May 2019 15:11:30 +1000 Subject: [PATCH 62/92] Tech: Update jQuery to 3.4.1 (#17290) Fixes #17289 Special notes for your reviewer: Updates jQuery to 3.4.1 (from 3.4.0) to fix the jQuery bug: https://blog.jquery.com/2019/05/01/jquery-3-4-1-triggering-focus-events-in-ie-and-finding-root-elements-in-ios-10/ --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e2153dcf3419..e084f705b024 100644 --- a/package.json +++ b/package.json @@ -206,7 +206,7 @@ "fast-text-encoding": "^1.0.0", "file-saver": "1.3.8", "immutable": "3.8.2", - "jquery": "3.4.0", + "jquery": "3.4.1", "lodash": "4.17.11", "moment": "2.24.0", "mousetrap": "1.6.3", diff --git a/yarn.lock b/yarn.lock index e092d5045691..f48cba0ec948 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10138,10 +10138,10 @@ jest@24.8.0: import-local "^2.0.0" jest-cli "^24.8.0" -jquery@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.0.tgz#8de513fa0fa4b2c7d2e48a530e26f0596936efdf" - integrity sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ== +jquery@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== js-base64@^2.1.8, js-base64@^2.1.9: version "2.5.1" From 5884e235fcf8cdbb4c42a94bdafe19881832bc54 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 27 May 2019 09:24:15 +0200 Subject: [PATCH 63/92] database: retry transaction if sqlite returns database is locked error (#17276) Adds an additional sqlite error code 5 (SQLITE_BUSY) to the transaction retry handler to add retries when sqlite returns database is locked error. More info: https://www.sqlite.org/rescode.html#busy Ref #17247 #16638 --- pkg/services/sqlstore/transactions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/sqlstore/transactions.go b/pkg/services/sqlstore/transactions.go index a0f648043399..9b744fd32884 100644 --- a/pkg/services/sqlstore/transactions.go +++ b/pkg/services/sqlstore/transactions.go @@ -40,12 +40,12 @@ func inTransactionWithRetryCtx(ctx context.Context, engine *xorm.Engine, callbac err = callback(sess) - // special handling of database locked errors for sqlite, then we can retry 3 times + // special handling of database locked errors for sqlite, then we can retry 5 times if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 { - if sqlError.Code == sqlite3.ErrLocked { + if sqlError.Code == sqlite3.ErrLocked || sqlError.Code == sqlite3.ErrBusy { sess.Rollback() time.Sleep(time.Millisecond * time.Duration(10)) - sqlog.Info("Database table locked, sleeping then retrying", "retry", retry) + sqlog.Info("Database locked, sleeping then retrying", "error", err, "retry", retry) return inTransactionWithRetry(callback, retry+1) } } From de92c360a1ad2882f8cc7acac0d2ebbe7847c257 Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Mon, 27 May 2019 10:36:49 +0300 Subject: [PATCH 64/92] LDAP: reduce API and allow its extension (#17209) * Removes Add/Remove methods * Publicise necessary fields and methods so we could extend it * Publicise mock API * More comments and additional simplifications * Sync with master Still having low coverage :/ - should be addressed in #17208 --- pkg/services/ldap/ldap.go | 159 ++++++++------------- pkg/services/ldap/ldap_helpers_test.go | 91 ++---------- pkg/services/ldap/ldap_login_test.go | 74 +++++----- pkg/services/ldap/ldap_test.go | 183 ++++++++++--------------- pkg/services/ldap/test.go | 58 ++++---- pkg/services/multildap/multildap.go | 52 ------- 6 files changed, 209 insertions(+), 408 deletions(-) diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index 9fa680d1e19b..c64533d34922 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -29,18 +29,17 @@ type IConnection interface { // IServer is interface for LDAP authorization type IServer interface { Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error) - Add(string, map[string][]string) error - Remove(string) error Users([]string) ([]*models.ExternalUserInfo, error) ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error) + InitialBind(string, string) error Dial() error Close() } // Server is basic struct of LDAP authorization type Server struct { - config *ServerConfig - connection IConnection + Config *ServerConfig + Connection IConnection requireSecondBind bool log log.Logger } @@ -49,7 +48,6 @@ var ( // ErrInvalidCredentials is returned if username and password do not match ErrInvalidCredentials = errors.New("Invalid Username or Password") - ErrLDAPUserNotFound = errors.New("LDAP user not found") ) var dial = func(network, addr string) (IConnection, error) { @@ -59,7 +57,7 @@ var dial = func(network, addr string) (IConnection, error) { // New creates the new LDAP auth func New(config *ServerConfig) IServer { return &Server{ - config: config, + Config: config, log: log.New("ldap"), } } @@ -68,9 +66,9 @@ func New(config *ServerConfig) IServer { func (server *Server) Dial() error { var err error var certPool *x509.CertPool - if server.config.RootCACert != "" { + if server.Config.RootCACert != "" { certPool = x509.NewCertPool() - for _, caCertFile := range strings.Split(server.config.RootCACert, " ") { + for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") { pem, err := ioutil.ReadFile(caCertFile) if err != nil { return err @@ -81,35 +79,35 @@ func (server *Server) Dial() error { } } var clientCert tls.Certificate - if server.config.ClientCert != "" && server.config.ClientKey != "" { - clientCert, err = tls.LoadX509KeyPair(server.config.ClientCert, server.config.ClientKey) + if server.Config.ClientCert != "" && server.Config.ClientKey != "" { + clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey) if err != nil { return err } } - for _, host := range strings.Split(server.config.Host, " ") { - address := fmt.Sprintf("%s:%d", host, server.config.Port) - if server.config.UseSSL { + for _, host := range strings.Split(server.Config.Host, " ") { + address := fmt.Sprintf("%s:%d", host, server.Config.Port) + if server.Config.UseSSL { tlsCfg := &tls.Config{ - InsecureSkipVerify: server.config.SkipVerifySSL, + InsecureSkipVerify: server.Config.SkipVerifySSL, ServerName: host, RootCAs: certPool, } if len(clientCert.Certificate) > 0 { tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert) } - if server.config.StartTLS { - server.connection, err = dial("tcp", address) + if server.Config.StartTLS { + server.Connection, err = dial("tcp", address) if err == nil { - if err = server.connection.StartTLS(tlsCfg); err == nil { + if err = server.Connection.StartTLS(tlsCfg); err == nil { return nil } } } else { - server.connection, err = ldap.DialTLS("tcp", address, tlsCfg) + server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg) } } else { - server.connection, err = dial("tcp", address) + server.Connection, err = dial("tcp", address) } if err == nil { @@ -121,16 +119,16 @@ func (server *Server) Dial() error { // Close closes the LDAP connection func (server *Server) Close() { - server.connection.Close() + server.Connection.Close() } -// Log in user by searching and serializing it +// Login user by searching and serializing it func (server *Server) Login(query *models.LoginUserQuery) ( *models.ExternalUserInfo, error, ) { // Perform initial authentication - err := server.initialBind(query.Username, query.Password) + err := server.InitialBind(query.Username, query.Password) if err != nil { return nil, err } @@ -160,56 +158,6 @@ func (server *Server) Login(query *models.LoginUserQuery) ( return user, nil } -// Add adds stuff to LDAP -func (server *Server) Add(dn string, values map[string][]string) error { - err := server.initialBind( - server.config.BindDN, - server.config.BindPassword, - ) - if err != nil { - return err - } - - attributes := make([]ldap.Attribute, 0) - for key, value := range values { - attributes = append(attributes, ldap.Attribute{ - Type: key, - Vals: value, - }) - } - - request := &ldap.AddRequest{ - DN: dn, - Attributes: attributes, - } - - err = server.connection.Add(request) - if err != nil { - return err - } - - return nil -} - -// Remove removes stuff from LDAP -func (server *Server) Remove(dn string) error { - err := server.initialBind( - server.config.BindDN, - server.config.BindPassword, - ) - if err != nil { - return err - } - - request := ldap.NewDelRequest(dn, nil) - err = server.connection.Del(request) - if err != nil { - return err - } - - return nil -} - // Users gets LDAP users func (server *Server) Users(logins []string) ( []*models.ExternalUserInfo, @@ -217,10 +165,10 @@ func (server *Server) Users(logins []string) ( ) { var result *ldap.SearchResult var err error - var config = server.config + var Config = server.Config - for _, base := range config.SearchBaseDNs { - result, err = server.connection.Search( + for _, base := range Config.SearchBaseDNs { + result, err = server.Connection.Search( server.getSearchRequest(base, logins), ) if err != nil { @@ -254,7 +202,7 @@ func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserIn // If there are no ldap group mappings access is true // otherwise a single group must match func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error { - if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 { + if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 { server.log.Error( "user does not belong in any of the specified LDAP groups", "username", user.Login, @@ -301,7 +249,7 @@ func (server *Server) getSearchRequest( ) *ldap.SearchRequest { attributes := []string{} - inputs := server.config.Attr + inputs := server.Config.Attr attributes = appendIfNotEmpty( attributes, inputs.Username, @@ -314,7 +262,7 @@ func (server *Server) getSearchRequest( search := "" for _, login := range logins { query := strings.Replace( - server.config.SearchFilter, + server.Config.SearchFilter, "%s", ldap.EscapeFilter(login), -1, ) @@ -347,7 +295,7 @@ func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo OrgRoles: map[int64]models.RoleType{}, } - for _, group := range server.config.Groups { + for _, group := range server.Config.Groups { // only use the first match for each org if extUser.OrgRoles[group.OrgId] != "" { continue @@ -366,15 +314,15 @@ func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo func (server *Server) serverBind() error { bindFn := func() error { - return server.connection.Bind( - server.config.BindDN, - server.config.BindPassword, + return server.Connection.Bind( + server.Config.BindDN, + server.Config.BindPassword, ) } - if server.config.BindPassword == "" { + if server.Config.BindPassword == "" { bindFn = func() error { - return server.connection.UnauthenticatedBind(server.config.BindDN) + return server.Connection.UnauthenticatedBind(server.Config.BindDN) } } @@ -397,7 +345,7 @@ func (server *Server) secondBind( user *models.ExternalUserInfo, userPassword string, ) error { - err := server.connection.Bind(user.AuthId, userPassword) + err := server.Connection.Bind(user.AuthId, userPassword) if err != nil { server.log.Info("Second bind failed", "error", err) @@ -412,24 +360,25 @@ func (server *Server) secondBind( return nil } -func (server *Server) initialBind(username, userPassword string) error { - if server.config.BindPassword != "" || server.config.BindDN == "" { - userPassword = server.config.BindPassword +// InitialBind intiates first bind to LDAP server +func (server *Server) InitialBind(username, userPassword string) error { + if server.Config.BindPassword != "" || server.Config.BindDN == "" { + userPassword = server.Config.BindPassword server.requireSecondBind = true } - bindPath := server.config.BindDN + bindPath := server.Config.BindDN if strings.Contains(bindPath, "%s") { - bindPath = fmt.Sprintf(server.config.BindDN, username) + bindPath = fmt.Sprintf(server.Config.BindDN, username) } bindFn := func() error { - return server.connection.Bind(bindPath, userPassword) + return server.Connection.Bind(bindPath, userPassword) } if userPassword == "" { bindFn = func() error { - return server.connection.UnauthenticatedBind(bindPath) + return server.Connection.UnauthenticatedBind(bindPath) } } @@ -451,16 +400,16 @@ func (server *Server) initialBind(username, userPassword string) error { func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) { var memberOf []string - for _, groupSearchBase := range server.config.GroupSearchBaseDNs { + for _, groupSearchBase := range server.Config.GroupSearchBaseDNs { var filterReplace string - if server.config.GroupSearchFilterUserAttribute == "" { - filterReplace = getLDAPAttr(server.config.Attr.Username, searchResult) + if server.Config.GroupSearchFilterUserAttribute == "" { + filterReplace = getLDAPAttr(server.Config.Attr.Username, searchResult) } else { - filterReplace = getLDAPAttr(server.config.GroupSearchFilterUserAttribute, searchResult) + filterReplace = getLDAPAttr(server.Config.GroupSearchFilterUserAttribute, searchResult) } filter := strings.Replace( - server.config.GroupSearchFilter, "%s", + server.Config.GroupSearchFilter, "%s", ldap.EscapeFilter(filterReplace), -1, ) @@ -468,7 +417,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string server.log.Info("Searching for user's groups", "filter", filter) // support old way of reading settings - groupIDAttribute := server.config.Attr.MemberOf + groupIDAttribute := server.Config.Attr.MemberOf // but prefer dn attribute if default settings are used if groupIDAttribute == "" || groupIDAttribute == "memberOf" { groupIDAttribute = "dn" @@ -482,7 +431,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string Filter: filter, } - groupSearchResult, err := server.connection.Search(&groupSearchReq) + groupSearchResult, err := server.Connection.Search(&groupSearchReq) if err != nil { return nil, err } @@ -518,22 +467,22 @@ func (server *Server) serializeUsers( index, ), LastName: getLDAPAttrN( - server.config.Attr.Surname, + server.Config.Attr.Surname, users, index, ), FirstName: getLDAPAttrN( - server.config.Attr.Name, + server.Config.Attr.Name, users, index, ), Username: getLDAPAttrN( - server.config.Attr.Username, + server.Config.Attr.Username, users, index, ), Email: getLDAPAttrN( - server.config.Attr.Email, + server.Config.Attr.Email, users, index, ), @@ -553,8 +502,8 @@ func (server *Server) serializeUsers( func (server *Server) getMemberOf(search *ldap.SearchResult) ( []string, error, ) { - if server.config.GroupSearchFilter == "" { - memberOf := getLDAPAttrArray(server.config.Attr.MemberOf, search) + if server.Config.GroupSearchFilter == "" { + memberOf := getLDAPAttrArray(server.Config.Attr.MemberOf, search) return memberOf, nil } diff --git a/pkg/services/ldap/ldap_helpers_test.go b/pkg/services/ldap/ldap_helpers_test.go index 3f25633460c6..48e6bce8b5ba 100644 --- a/pkg/services/ldap/ldap_helpers_test.go +++ b/pkg/services/ldap/ldap_helpers_test.go @@ -13,7 +13,7 @@ func TestLDAPHelpers(t *testing.T) { Convey("serializeUsers()", t, func() { Convey("simple case", func() { server := &Server{ - config: &ServerConfig{ + Config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -22,7 +22,7 @@ func TestLDAPHelpers(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: &mockConnection{}, + Connection: &MockConnection{}, log: log.New("test-logger"), } @@ -46,7 +46,7 @@ func TestLDAPHelpers(t *testing.T) { Convey("without lastname", func() { server := &Server{ - config: &ServerConfig{ + Config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -55,7 +55,7 @@ func TestLDAPHelpers(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: &mockConnection{}, + Connection: &MockConnection{}, log: log.New("test-logger"), } @@ -75,74 +75,9 @@ func TestLDAPHelpers(t *testing.T) { }) }) - Convey("initialBind", t, func() { - Convey("Given bind dn and password configured", func() { - connection := &mockConnection{} - var actualUsername, actualPassword string - connection.bindProvider = func(username, password string) error { - actualUsername = username - actualPassword = password - return nil - } - server := &Server{ - connection: connection, - config: &ServerConfig{ - BindDN: "cn=%s,o=users,dc=grafana,dc=org", - BindPassword: "bindpwd", - }, - } - err := server.initialBind("user", "pwd") - So(err, ShouldBeNil) - So(server.requireSecondBind, ShouldBeTrue) - So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") - So(actualPassword, ShouldEqual, "bindpwd") - }) - - Convey("Given bind dn configured", func() { - connection := &mockConnection{} - var actualUsername, actualPassword string - connection.bindProvider = func(username, password string) error { - actualUsername = username - actualPassword = password - return nil - } - server := &Server{ - connection: connection, - config: &ServerConfig{ - BindDN: "cn=%s,o=users,dc=grafana,dc=org", - }, - } - err := server.initialBind("user", "pwd") - So(err, ShouldBeNil) - So(server.requireSecondBind, ShouldBeFalse) - So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") - So(actualPassword, ShouldEqual, "pwd") - }) - - Convey("Given empty bind dn and password", func() { - connection := &mockConnection{} - unauthenticatedBindWasCalled := false - var actualUsername string - connection.unauthenticatedBindProvider = func(username string) error { - unauthenticatedBindWasCalled = true - actualUsername = username - return nil - } - server := &Server{ - connection: connection, - config: &ServerConfig{}, - } - err := server.initialBind("user", "pwd") - So(err, ShouldBeNil) - So(server.requireSecondBind, ShouldBeTrue) - So(unauthenticatedBindWasCalled, ShouldBeTrue) - So(actualUsername, ShouldBeEmpty) - }) - }) - Convey("serverBind()", t, func() { Convey("Given bind dn and password configured", func() { - connection := &mockConnection{} + connection := &MockConnection{} var actualUsername, actualPassword string connection.bindProvider = func(username, password string) error { actualUsername = username @@ -150,8 +85,8 @@ func TestLDAPHelpers(t *testing.T) { return nil } server := &Server{ - connection: connection, - config: &ServerConfig{ + Connection: connection, + Config: &ServerConfig{ BindDN: "o=users,dc=grafana,dc=org", BindPassword: "bindpwd", }, @@ -163,7 +98,7 @@ func TestLDAPHelpers(t *testing.T) { }) Convey("Given bind dn configured", func() { - connection := &mockConnection{} + connection := &MockConnection{} unauthenticatedBindWasCalled := false var actualUsername string connection.unauthenticatedBindProvider = func(username string) error { @@ -172,8 +107,8 @@ func TestLDAPHelpers(t *testing.T) { return nil } server := &Server{ - connection: connection, - config: &ServerConfig{ + Connection: connection, + Config: &ServerConfig{ BindDN: "o=users,dc=grafana,dc=org", }, } @@ -184,7 +119,7 @@ func TestLDAPHelpers(t *testing.T) { }) Convey("Given empty bind dn and password", func() { - connection := &mockConnection{} + connection := &MockConnection{} unauthenticatedBindWasCalled := false var actualUsername string connection.unauthenticatedBindProvider = func(username string) error { @@ -193,8 +128,8 @@ func TestLDAPHelpers(t *testing.T) { return nil } server := &Server{ - connection: connection, - config: &ServerConfig{}, + Connection: connection, + Config: &ServerConfig{}, } err := server.serverBind() So(err, ShouldBeNil) diff --git a/pkg/services/ldap/ldap_login_test.go b/pkg/services/ldap/ldap_login_test.go index 5bd0edc79cb1..573a9a560e84 100644 --- a/pkg/services/ldap/ldap_login_test.go +++ b/pkg/services/ldap/ldap_login_test.go @@ -13,12 +13,12 @@ import ( func TestLDAPLogin(t *testing.T) { Convey("Login()", t, func() { - authScenario("When user is log in and updated", func(sc *scenarioContext) { + serverScenario("When user is log in and updated", func(sc *scenarioContext) { // arrange - mockConnection := &mockConnection{} + mockConnection := &MockConnection{} - auth := &Server{ - config: &ServerConfig{ + server := &Server{ + Config: &ServerConfig{ Host: "", RootCACert: "", Groups: []*GroupToOrgRole{ @@ -33,7 +33,7 @@ func TestLDAPLogin(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: mockConnection, + Connection: mockConnection, log: log.New("test-logger"), } @@ -61,7 +61,7 @@ func TestLDAPLogin(t *testing.T) { sc.userOrgsQueryReturns([]*models.UserOrgDTO{}) // act - extUser, _ := auth.Login(query) + extUser, _ := server.Login(query) userInfo, err := user.Upsert(&user.UpsertArgs{ SignupAllowed: true, ExternalUser: extUser, @@ -73,7 +73,7 @@ func TestLDAPLogin(t *testing.T) { So(err, ShouldBeNil) // User should be searched in ldap - So(mockConnection.searchCalled, ShouldBeTrue) + So(mockConnection.SearchCalled, ShouldBeTrue) // Info should be updated (email differs) So(userInfo.Email, ShouldEqual, "roel@test.com") @@ -82,8 +82,8 @@ func TestLDAPLogin(t *testing.T) { So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin") }) - authScenario("When login with invalid credentials", func(scenario *scenarioContext) { - connection := &mockConnection{} + serverScenario("When login with invalid credentials", func(scenario *scenarioContext) { + connection := &MockConnection{} entry := ldap.Entry{} result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} connection.setSearchResult(&result) @@ -93,8 +93,8 @@ func TestLDAPLogin(t *testing.T) { ResultCode: 49, } } - auth := &Server{ - config: &ServerConfig{ + server := &Server{ + Config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -102,19 +102,19 @@ func TestLDAPLogin(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: connection, + Connection: connection, log: log.New("test-logger"), } - _, err := auth.Login(scenario.loginUserQuery) + _, err := server.Login(scenario.loginUserQuery) Convey("it should return invalid credentials error", func() { So(err, ShouldEqual, ErrInvalidCredentials) }) }) - authScenario("When login with valid credentials", func(scenario *scenarioContext) { - connection := &mockConnection{} + serverScenario("When login with valid credentials", func(scenario *scenarioContext) { + connection := &MockConnection{} entry := ldap.Entry{ DN: "dn", Attributes: []*ldap.EntryAttribute{ {Name: "username", Values: []string{"markelog"}}, @@ -130,8 +130,8 @@ func TestLDAPLogin(t *testing.T) { connection.bindProvider = func(username, password string) error { return nil } - auth := &Server{ - config: &ServerConfig{ + server := &Server{ + Config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -139,18 +139,18 @@ func TestLDAPLogin(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: connection, + Connection: connection, log: log.New("test-logger"), } - resp, err := auth.Login(scenario.loginUserQuery) + resp, err := server.Login(scenario.loginUserQuery) So(err, ShouldBeNil) So(resp.Login, ShouldEqual, "markelog") }) - authScenario("When user not found in LDAP, but exist in Grafana", func(scenario *scenarioContext) { - connection := &mockConnection{} + serverScenario("When user not found in LDAP, but exist in Grafana", func(scenario *scenarioContext) { + connection := &MockConnection{} result := ldap.SearchResult{Entries: []*ldap.Entry{}} connection.setSearchResult(&result) @@ -160,15 +160,15 @@ func TestLDAPLogin(t *testing.T) { connection.bindProvider = func(username, password string) error { return nil } - auth := &Server{ - config: &ServerConfig{ + server := &Server{ + Config: &ServerConfig{ SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: connection, + Connection: connection, log: log.New("test-logger"), } - _, err := auth.Login(scenario.loginUserQuery) + _, err := server.Login(scenario.loginUserQuery) Convey("it should disable user", func() { So(scenario.disableExternalUserCalled, ShouldBeTrue) @@ -181,8 +181,8 @@ func TestLDAPLogin(t *testing.T) { }) }) - authScenario("When user not found in LDAP, and disabled in Grafana already", func(scenario *scenarioContext) { - connection := &mockConnection{} + serverScenario("When user not found in LDAP, and disabled in Grafana already", func(scenario *scenarioContext) { + connection := &MockConnection{} result := ldap.SearchResult{Entries: []*ldap.Entry{}} connection.setSearchResult(&result) @@ -192,15 +192,15 @@ func TestLDAPLogin(t *testing.T) { connection.bindProvider = func(username, password string) error { return nil } - auth := &Server{ - config: &ServerConfig{ + server := &Server{ + Config: &ServerConfig{ SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: connection, + Connection: connection, log: log.New("test-logger"), } - _, err := auth.Login(scenario.loginUserQuery) + _, err := server.Login(scenario.loginUserQuery) Convey("it should't call disable function", func() { So(scenario.disableExternalUserCalled, ShouldBeFalse) @@ -211,8 +211,8 @@ func TestLDAPLogin(t *testing.T) { }) }) - authScenario("When user found in LDAP, and disabled in Grafana", func(scenario *scenarioContext) { - connection := &mockConnection{} + serverScenario("When user found in LDAP, and disabled in Grafana", func(scenario *scenarioContext) { + connection := &MockConnection{} entry := ldap.Entry{} result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} connection.setSearchResult(&result) @@ -221,15 +221,15 @@ func TestLDAPLogin(t *testing.T) { connection.bindProvider = func(username, password string) error { return nil } - auth := &Server{ - config: &ServerConfig{ + server := &Server{ + Config: &ServerConfig{ SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: connection, + Connection: connection, log: log.New("test-logger"), } - extUser, _ := auth.Login(scenario.loginUserQuery) + extUser, _ := server.Login(scenario.loginUserQuery) _, err := user.Upsert(&user.UpsertArgs{ SignupAllowed: true, ExternalUser: extUser, diff --git a/pkg/services/ldap/ldap_test.go b/pkg/services/ldap/ldap_test.go index 266fe22a4fc7..98b15ec44576 100644 --- a/pkg/services/ldap/ldap_test.go +++ b/pkg/services/ldap/ldap_test.go @@ -9,114 +9,10 @@ import ( "github.com/grafana/grafana/pkg/infra/log" ) -func TestAuth(t *testing.T) { - Convey("Add()", t, func() { - connection := &mockConnection{} - - auth := &Server{ - config: &ServerConfig{ - SearchBaseDNs: []string{"BaseDNHere"}, - }, - connection: connection, - log: log.New("test-logger"), - } - - Convey("Adds user", func() { - err := auth.Add( - "cn=ldap-tuz,ou=users,dc=grafana,dc=org", - map[string][]string{ - "mail": {"ldap-viewer@grafana.com"}, - "userPassword": {"grafana"}, - "objectClass": { - "person", - "top", - "inetOrgPerson", - "organizationalPerson", - }, - "sn": {"ldap-tuz"}, - "cn": {"ldap-tuz"}, - }, - ) - - hasMail := false - hasUserPassword := false - hasObjectClass := false - hasSN := false - hasCN := false - - So(err, ShouldBeNil) - So(connection.addParams.Controls, ShouldBeNil) - So(connection.addCalled, ShouldBeTrue) - So( - connection.addParams.DN, - ShouldEqual, - "cn=ldap-tuz,ou=users,dc=grafana,dc=org", - ) - - attrs := connection.addParams.Attributes - for _, value := range attrs { - if value.Type == "mail" { - So(value.Vals, ShouldContain, "ldap-viewer@grafana.com") - hasMail = true - } - - if value.Type == "userPassword" { - hasUserPassword = true - So(value.Vals, ShouldContain, "grafana") - } - - if value.Type == "objectClass" { - hasObjectClass = true - So(value.Vals, ShouldContain, "person") - So(value.Vals, ShouldContain, "top") - So(value.Vals, ShouldContain, "inetOrgPerson") - So(value.Vals, ShouldContain, "organizationalPerson") - } - - if value.Type == "sn" { - hasSN = true - So(value.Vals, ShouldContain, "ldap-tuz") - } - - if value.Type == "cn" { - hasCN = true - So(value.Vals, ShouldContain, "ldap-tuz") - } - } - - So(hasMail, ShouldBeTrue) - So(hasUserPassword, ShouldBeTrue) - So(hasObjectClass, ShouldBeTrue) - So(hasSN, ShouldBeTrue) - So(hasCN, ShouldBeTrue) - }) - }) - - Convey("Remove()", t, func() { - connection := &mockConnection{} - - auth := &Server{ - config: &ServerConfig{ - SearchBaseDNs: []string{"BaseDNHere"}, - }, - connection: connection, - log: log.New("test-logger"), - } - - Convey("Removes the user", func() { - dn := "cn=ldap-tuz,ou=users,dc=grafana,dc=org" - err := auth.Remove(dn) - - So(err, ShouldBeNil) - So(connection.delCalled, ShouldBeTrue) - So(connection.delParams.Controls, ShouldBeNil) - So(connection.delParams.DN, ShouldEqual, dn) - }) - }) - +func TestPublicAPI(t *testing.T) { Convey("Users()", t, func() { Convey("find one user", func() { - mockConnection := &mockConnection{} + MockConnection := &MockConnection{} entry := ldap.Entry{ DN: "dn", Attributes: []*ldap.EntryAttribute{ {Name: "username", Values: []string{"roelgerrits"}}, @@ -126,11 +22,11 @@ func TestAuth(t *testing.T) { {Name: "memberof", Values: []string{"admins"}}, }} result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} - mockConnection.setSearchResult(&result) + MockConnection.setSearchResult(&result) // Set up attribute map without surname and email server := &Server{ - config: &ServerConfig{ + Config: &ServerConfig{ Attr: AttributeMap{ Username: "username", Name: "name", @@ -138,7 +34,7 @@ func TestAuth(t *testing.T) { }, SearchBaseDNs: []string{"BaseDNHere"}, }, - connection: mockConnection, + Connection: MockConnection, log: log.New("test-logger"), } @@ -148,10 +44,75 @@ func TestAuth(t *testing.T) { So(searchResult, ShouldNotBeNil) // User should be searched in ldap - So(mockConnection.searchCalled, ShouldBeTrue) + So(MockConnection.SearchCalled, ShouldBeTrue) // No empty attributes should be added to the search request - So(len(mockConnection.searchAttributes), ShouldEqual, 3) + So(len(MockConnection.SearchAttributes), ShouldEqual, 3) + }) + }) + + Convey("InitialBind", t, func() { + Convey("Given bind dn and password configured", func() { + connection := &MockConnection{} + var actualUsername, actualPassword string + connection.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + server := &Server{ + Connection: connection, + Config: &ServerConfig{ + BindDN: "cn=%s,o=users,dc=grafana,dc=org", + BindPassword: "bindpwd", + }, + } + err := server.InitialBind("user", "pwd") + So(err, ShouldBeNil) + So(server.requireSecondBind, ShouldBeTrue) + So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "bindpwd") + }) + + Convey("Given bind dn configured", func() { + connection := &MockConnection{} + var actualUsername, actualPassword string + connection.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + server := &Server{ + Connection: connection, + Config: &ServerConfig{ + BindDN: "cn=%s,o=users,dc=grafana,dc=org", + }, + } + err := server.InitialBind("user", "pwd") + So(err, ShouldBeNil) + So(server.requireSecondBind, ShouldBeFalse) + So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "pwd") + }) + + Convey("Given empty bind dn and password", func() { + connection := &MockConnection{} + unauthenticatedBindWasCalled := false + var actualUsername string + connection.unauthenticatedBindProvider = func(username string) error { + unauthenticatedBindWasCalled = true + actualUsername = username + return nil + } + server := &Server{ + Connection: connection, + Config: &ServerConfig{}, + } + err := server.InitialBind("user", "pwd") + So(err, ShouldBeNil) + So(server.requireSecondBind, ShouldBeTrue) + So(unauthenticatedBindWasCalled, ShouldBeTrue) + So(actualUsername, ShouldBeEmpty) }) }) } diff --git a/pkg/services/ldap/test.go b/pkg/services/ldap/test.go index daa6a3216641..6319cddd2807 100644 --- a/pkg/services/ldap/test.go +++ b/pkg/services/ldap/test.go @@ -12,22 +12,24 @@ import ( "github.com/grafana/grafana/pkg/services/login" ) -type mockConnection struct { - searchResult *ldap.SearchResult - searchCalled bool - searchAttributes []string +// MockConnection struct for testing +type MockConnection struct { + SearchResult *ldap.SearchResult + SearchCalled bool + SearchAttributes []string - addParams *ldap.AddRequest - addCalled bool + AddParams *ldap.AddRequest + AddCalled bool - delParams *ldap.DelRequest - delCalled bool + DelParams *ldap.DelRequest + DelCalled bool bindProvider func(username, password string) error unauthenticatedBindProvider func(username string) error } -func (c *mockConnection) Bind(username, password string) error { +// Bind mocks Bind connection function +func (c *MockConnection) Bind(username, password string) error { if c.bindProvider != nil { return c.bindProvider(username, password) } @@ -35,7 +37,8 @@ func (c *mockConnection) Bind(username, password string) error { return nil } -func (c *mockConnection) UnauthenticatedBind(username string) error { +// UnauthenticatedBind mocks UnauthenticatedBind connection function +func (c *MockConnection) UnauthenticatedBind(username string) error { if c.unauthenticatedBindProvider != nil { return c.unauthenticatedBindProvider(username) } @@ -43,35 +46,40 @@ func (c *mockConnection) UnauthenticatedBind(username string) error { return nil } -func (c *mockConnection) Close() {} +// Close mocks Close connection function +func (c *MockConnection) Close() {} -func (c *mockConnection) setSearchResult(result *ldap.SearchResult) { - c.searchResult = result +func (c *MockConnection) setSearchResult(result *ldap.SearchResult) { + c.SearchResult = result } -func (c *mockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) { - c.searchCalled = true - c.searchAttributes = sr.Attributes - return c.searchResult, nil +// Search mocks Search connection function +func (c *MockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) { + c.SearchCalled = true + c.SearchAttributes = sr.Attributes + return c.SearchResult, nil } -func (c *mockConnection) Add(request *ldap.AddRequest) error { - c.addCalled = true - c.addParams = request +// Add mocks Add connection function +func (c *MockConnection) Add(request *ldap.AddRequest) error { + c.AddCalled = true + c.AddParams = request return nil } -func (c *mockConnection) Del(request *ldap.DelRequest) error { - c.delCalled = true - c.delParams = request +// Del mocks Del connection function +func (c *MockConnection) Del(request *ldap.DelRequest) error { + c.DelCalled = true + c.DelParams = request return nil } -func (c *mockConnection) StartTLS(*tls.Config) error { +// StartTLS mocks StartTLS connection function +func (c *MockConnection) StartTLS(*tls.Config) error { return nil } -func authScenario(desc string, fn scenarioFunc) { +func serverScenario(desc string, fn scenarioFunc) { Convey(desc, func() { defer bus.ClearBusHandlers() diff --git a/pkg/services/multildap/multildap.go b/pkg/services/multildap/multildap.go index 1b309c646e17..6c2baf1671af 100644 --- a/pkg/services/multildap/multildap.go +++ b/pkg/services/multildap/multildap.go @@ -35,9 +35,6 @@ type IMultiLDAP interface { User(login string) ( *models.ExternalUserInfo, error, ) - - Add(dn string, values map[string][]string) error - Remove(dn string) error } // MultiLDAP is basic struct of LDAP authorization @@ -52,55 +49,6 @@ func New(configs []*ldap.ServerConfig) IMultiLDAP { } } -// Add adds user to the *first* defined LDAP -func (multiples *MultiLDAP) Add( - dn string, - values map[string][]string, -) error { - if len(multiples.configs) == 0 { - return ErrNoLDAPServers - } - - config := multiples.configs[0] - ldap := ldap.New(config) - - if err := ldap.Dial(); err != nil { - return err - } - - defer ldap.Close() - - err := ldap.Add(dn, values) - if err != nil { - return err - } - - return nil -} - -// Remove removes user from the *first* defined LDAP -func (multiples *MultiLDAP) Remove(dn string) error { - if len(multiples.configs) == 0 { - return ErrNoLDAPServers - } - - config := multiples.configs[0] - ldap := ldap.New(config) - - if err := ldap.Dial(); err != nil { - return err - } - - defer ldap.Close() - - err := ldap.Remove(dn) - if err != nil { - return err - } - - return nil -} - // Login tries to log in the user in multiples LDAP func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) ( *models.ExternalUserInfo, error, From 1a2841e24418df89b39ffaa308f158197c13de4f Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 27 May 2019 09:55:58 +0200 Subject: [PATCH 65/92] devenv: metricbeat and kibana for elasticsearch 7 block (#17262) --- devenv/datasources.yaml | 11 ++++++ .../blocks/elastic7/docker-compose.yaml | 16 ++++++++ devenv/docker/blocks/elastic7/metricbeat.yml | 38 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 devenv/docker/blocks/elastic7/metricbeat.yml diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index d5664d7136c0..33dde611bdcd 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -143,6 +143,17 @@ datasources: timeField: "@timestamp" esVersion: 70 + - name: gdev-elasticsearch-v7-metricbeat + type: elasticsearch + access: proxy + database: "[metricbeat-]YYYY.MM.DD" + url: http://localhost:12200 + jsonData: + interval: Daily + timeField: "@timestamp" + esVersion: 70 + timeInterval: "10s" + - name: gdev-mysql type: mysql url: localhost:3306 diff --git a/devenv/docker/blocks/elastic7/docker-compose.yaml b/devenv/docker/blocks/elastic7/docker-compose.yaml index 3ef922c890c3..45e2836f870e 100644 --- a/devenv/docker/blocks/elastic7/docker-compose.yaml +++ b/devenv/docker/blocks/elastic7/docker-compose.yaml @@ -21,3 +21,19 @@ - ./docker/blocks/elastic7/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro - /var/log:/var/log:ro - ../data/log:/var/log/grafana:ro + + metricbeat7: + image: docker.elastic.co/beats/metricbeat-oss:7.0.0 + network_mode: host + command: metricbeat -e -strict.perms=false + user: root + volumes: + - ./docker/blocks/elastic7/metricbeat.yml:/usr/share/metricbeat/metricbeat.yml:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + + kibana7: + image: docker.elastic.co/kibana/kibana-oss:7.0.0 + ports: + - "5601:5601" + environment: + ELASTICSEARCH_HOSTS: http://elasticsearch7:9200 diff --git a/devenv/docker/blocks/elastic7/metricbeat.yml b/devenv/docker/blocks/elastic7/metricbeat.yml new file mode 100644 index 000000000000..4788c0cdd9a5 --- /dev/null +++ b/devenv/docker/blocks/elastic7/metricbeat.yml @@ -0,0 +1,38 @@ +metricbeat.config: + modules: + path: ${path.config}/modules.d/*.yml + # Reload module configs as they change: + reload.enabled: false + +metricbeat.autodiscover: + providers: + - type: docker + hints.enabled: true + +metricbeat.modules: +- module: docker + metricsets: + - "container" + - "cpu" + - "diskio" + - "healthcheck" + - "info" + #- "image" + - "memory" + - "network" + hosts: ["unix:///var/run/docker.sock"] + period: 10s + enabled: true + +processors: + - add_cloud_metadata: ~ + +output.elasticsearch: + hosts: ["localhost:12200"] + index: "metricbeat-%{+yyyy.MM.dd}" + +setup.template.name: "metricbeat" +setup.template.pattern: "metricbeat-*" +setup.template.settings: + index.number_of_shards: 1 + index.number_of_replicas: 1 \ No newline at end of file From b9181df21285dff20f25397ede06198f0d024d8d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 27 May 2019 10:38:17 +0200 Subject: [PATCH 66/92] Auth Proxy: Log any error in middleware (#17275) Fixes so that errors happening in auth proxy middleware is logged. Ref #17247 --- pkg/middleware/auth_proxy.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index 890fd5e4f24b..9ec5852b73dc 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -31,6 +31,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, // Check if allowed to continue with this IP if result, err := auth.IsAllowedIP(); !result { + ctx.Logger.Error("auth proxy: failed to check whitelisted ip addresses", "message", err.Error(), "error", err.DetailsError) ctx.Handle(407, err.Error(), err.DetailsError) return true } @@ -38,6 +39,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, // Try to log in user from various providers id, err := auth.Login() if err != nil { + ctx.Logger.Error("auth proxy: failed to login", "message", err.Error(), "error", err.DetailsError) ctx.Handle(500, err.Error(), err.DetailsError) return true } @@ -45,6 +47,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, // Get full user info user, err := auth.GetSignedUser(id) if err != nil { + ctx.Logger.Error("auth proxy: failed to get signed in user", "message", err.Error(), "error", err.DetailsError) ctx.Handle(500, err.Error(), err.DetailsError) return true } @@ -55,6 +58,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, // Remember user data it in cache if err := auth.Remember(id); err != nil { + ctx.Logger.Error("auth proxy: failed to store user in cache", "message", err.Error(), "error", err.DetailsError) ctx.Handle(500, err.Error(), err.DetailsError) return true } From 151b24b95fb52a777533c9fd76db48ae8967a74e Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Mon, 27 May 2019 10:47:21 +0200 Subject: [PATCH 67/92] CLI: Add command to migrate all datasources to use encrypted password fields (#17118) closes: #17107 --- pkg/cmd/grafana-cli/commands/commands.go | 23 +++- .../encrypt_datasource_passwords.go | 126 ++++++++++++++++++ .../encrypt_datasource_passwords_test.go | 67 ++++++++++ .../grafana-cli/commands/install_command.go | 7 +- .../commands/listremote_command.go | 3 +- .../commands/listversions_command.go | 5 +- pkg/cmd/grafana-cli/commands/ls_command.go | 3 +- .../grafana-cli/commands/remove_command.go | 5 +- .../commands/reset_password_command.go | 4 +- .../commands/upgrade_all_command.go | 3 +- .../grafana-cli/commands/upgrade_command.go | 3 +- .../{commands => utils}/command_line.go | 16 +-- pkg/util/strings.go | 17 +++ pkg/util/strings_test.go | 9 ++ 14 files changed, 266 insertions(+), 25 deletions(-) create mode 100644 pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go create mode 100644 pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go rename pkg/cmd/grafana-cli/{commands => utils}/command_line.go (64%) diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index d5add2b71684..ebaee5573482 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/pkg/cmd/grafana-cli/commands/commands.go @@ -7,14 +7,16 @@ import ( "github.com/codegangsta/cli" "github.com/fatih/color" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/datamigrations" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" ) -func runDbCommand(command func(commandLine CommandLine) error) func(context *cli.Context) { +func runDbCommand(command func(commandLine utils.CommandLine, sqlStore *sqlstore.SqlStore) error) func(context *cli.Context) { return func(context *cli.Context) { - cmd := &contextCommandLine{context} + cmd := &utils.ContextCommandLine{Context: context} cfg := setting.NewCfg() cfg.Load(&setting.CommandLineArgs{ @@ -28,7 +30,7 @@ func runDbCommand(command func(commandLine CommandLine) error) func(context *cli engine.Bus = bus.GetBus() engine.Init() - if err := command(cmd); err != nil { + if err := command(cmd, engine); err != nil { logger.Errorf("\n%s: ", color.RedString("Error")) logger.Errorf("%s\n\n", err) @@ -40,10 +42,10 @@ func runDbCommand(command func(commandLine CommandLine) error) func(context *cli } } -func runPluginCommand(command func(commandLine CommandLine) error) func(context *cli.Context) { +func runPluginCommand(command func(commandLine utils.CommandLine) error) func(context *cli.Context) { return func(context *cli.Context) { - cmd := &contextCommandLine{context} + cmd := &utils.ContextCommandLine{Context: context} if err := command(cmd); err != nil { logger.Errorf("\n%s: ", color.RedString("Error")) logger.Errorf("%s %s\n\n", color.RedString("✗"), err) @@ -107,6 +109,17 @@ var adminCommands = []cli.Command{ }, }, }, + { + Name: "data-migration", + Usage: "Runs a script that migrates or cleanups data in your db", + Subcommands: []cli.Command{ + { + Name: "encrypt-datasource-passwords", + Usage: "Migrates passwords from unsecured fields to secure_json_data field. Return ok unless there is an error. Safe to execute multiple times.", + Action: runDbCommand(datamigrations.EncryptDatasourcePaswords), + }, + }, + }, } var Commands = []cli.Command{ diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go new file mode 100644 index 000000000000..e55fa2d70b88 --- /dev/null +++ b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go @@ -0,0 +1,126 @@ +package datamigrations + +import ( + "context" + "encoding/json" + + "github.com/fatih/color" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" + + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/errutil" +) + +var ( + datasourceTypes = []string{ + "mysql", + "influxdb", + "elasticsearch", + "graphite", + "prometheus", + "opentsdb", + } +) + +// EncryptDatasourcePaswords migrates un-encrypted secrets on datasources +// to the secureJson Column. +func EncryptDatasourcePaswords(c utils.CommandLine, sqlStore *sqlstore.SqlStore) error { + return sqlStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error { + passwordsUpdated, err := migrateColumn(session, "password") + if err != nil { + return err + } + + basicAuthUpdated, err := migrateColumn(session, "basic_auth_password") + if err != nil { + return err + } + + logger.Info("\n") + if passwordsUpdated > 0 { + logger.Infof("%s Encrypted password field for %d datasources \n", color.GreenString("✔"), passwordsUpdated) + } + + if basicAuthUpdated > 0 { + logger.Infof("%s Encrypted basic_auth_password field for %d datasources \n", color.GreenString("✔"), basicAuthUpdated) + } + + if passwordsUpdated == 0 && basicAuthUpdated == 0 { + logger.Infof("%s All datasources secrets are allready encrypted\n", color.GreenString("✔")) + } + + logger.Info("\n") + + logger.Warn("Warning: Datasource provisioning files need to be manually changed to prevent overwriting of " + + "the data during provisioning. See https://grafana.com/docs/installation/upgrading/#upgrading-to-v6-2 for " + + "details") + return nil + }) +} + +func migrateColumn(session *sqlstore.DBSession, column string) (int, error) { + var rows []map[string]string + + session.Cols("id", column, "secure_json_data") + session.Table("data_source") + session.In("type", datasourceTypes) + session.Where(column + " IS NOT NULL AND " + column + " != ''") + err := session.Find(&rows) + + if err != nil { + return 0, errutil.Wrapf(err, "failed to select column: %s", column) + } + + rowsUpdated, err := updateRows(session, rows, column) + return rowsUpdated, errutil.Wrapf(err, "failed to update column: %s", column) +} + +func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordFieldName string) (int, error) { + var rowsUpdated int + + for _, row := range rows { + newSecureJSONData, err := getUpdatedSecureJSONData(row, passwordFieldName) + if err != nil { + return 0, err + } + + data, err := json.Marshal(newSecureJSONData) + if err != nil { + return 0, errutil.Wrap("marshaling newSecureJsonData failed", err) + } + + newRow := map[string]interface{}{"secure_json_data": data, passwordFieldName: ""} + session.Table("data_source") + session.Where("id = ?", row["id"]) + // Setting both columns while having value only for secure_json_data should clear the [passwordFieldName] column + session.Cols("secure_json_data", passwordFieldName) + + _, err = session.Update(newRow) + if err != nil { + return 0, err + } + + rowsUpdated++ + } + return rowsUpdated, nil +} + +func getUpdatedSecureJSONData(row map[string]string, passwordFieldName string) (map[string]interface{}, error) { + encryptedPassword, err := util.Encrypt([]byte(row[passwordFieldName]), setting.SecretKey) + if err != nil { + return nil, err + } + + var secureJSONData map[string]interface{} + + if err := json.Unmarshal([]byte(row["secure_json_data"]), &secureJSONData); err != nil { + return nil, err + } + + jsonFieldName := util.ToCamelCase(passwordFieldName) + secureJSONData[jsonFieldName] = encryptedPassword + return secureJSONData, nil +} diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go new file mode 100644 index 000000000000..64987423decd --- /dev/null +++ b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go @@ -0,0 +1,67 @@ +package datamigrations + +import ( + "testing" + "time" + + "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest" + "github.com/grafana/grafana/pkg/components/securejsondata" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/stretchr/testify/assert" +) + +func TestPasswordMigrationCommand(t *testing.T) { + //setup datasources with password, basic_auth and none + sqlstore := sqlstore.InitTestDB(t) + session := sqlstore.NewSession() + defer session.Close() + + datasources := []*models.DataSource{ + {Type: "influxdb", Name: "influxdb", Password: "foobar"}, + {Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"}, + {Type: "prometheus", Name: "prometheus", SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{})}, + } + + // set required default values + for _, ds := range datasources { + ds.Created = time.Now() + ds.Updated = time.Now() + ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{}) + } + + _, err := session.Insert(&datasources) + assert.Nil(t, err) + + //run migration + err = EncryptDatasourcePaswords(&commandstest.FakeCommandLine{}, sqlstore) + assert.Nil(t, err) + + //verify that no datasources still have password or basic_auth + var dss []*models.DataSource + err = session.SQL("select * from data_source").Find(&dss) + assert.Nil(t, err) + assert.Equal(t, len(dss), 3) + + for _, ds := range dss { + sj := ds.SecureJsonData.Decrypt() + + if ds.Name == "influxdb" { + assert.Equal(t, ds.Password, "") + v, exist := sj["password"] + assert.True(t, exist) + assert.Equal(t, v, "foobar", "expected password to be moved to securejson") + } + + if ds.Name == "graphite" { + assert.Equal(t, ds.BasicAuthPassword, "") + v, exist := sj["basicAuthPassword"] + assert.True(t, exist) + assert.Equal(t, v, "foobar", "expected basic_auth_password to be moved to securejson") + } + + if ds.Name == "prometheus" { + assert.Equal(t, len(sj), 0) + } + } +} diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 99cef15e50e3..db3907682638 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -14,13 +14,14 @@ import ( "strings" "github.com/fatih/color" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" ) -func validateInput(c CommandLine, pluginFolder string) error { +func validateInput(c utils.CommandLine, pluginFolder string) error { arg := c.Args().First() if arg == "" { return errors.New("please specify plugin to install") @@ -46,7 +47,7 @@ func validateInput(c CommandLine, pluginFolder string) error { return nil } -func installCommand(c CommandLine) error { +func installCommand(c utils.CommandLine) error { pluginFolder := c.PluginDirectory() if err := validateInput(c, pluginFolder); err != nil { return err @@ -60,7 +61,7 @@ func installCommand(c CommandLine) error { // InstallPlugin downloads the plugin code as a zip file from the Grafana.com API // and then extracts the zip into the plugins directory. -func InstallPlugin(pluginName, version string, c CommandLine) error { +func InstallPlugin(pluginName, version string, c utils.CommandLine) error { pluginFolder := c.PluginDirectory() downloadURL := c.PluginURL() if downloadURL == "" { diff --git a/pkg/cmd/grafana-cli/commands/listremote_command.go b/pkg/cmd/grafana-cli/commands/listremote_command.go index 4798369def11..7351ee58a371 100644 --- a/pkg/cmd/grafana-cli/commands/listremote_command.go +++ b/pkg/cmd/grafana-cli/commands/listremote_command.go @@ -3,9 +3,10 @@ package commands import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) -func listremoteCommand(c CommandLine) error { +func listremoteCommand(c utils.CommandLine) error { plugin, err := s.ListAllPlugins(c.RepoDirectory()) if err != nil { diff --git a/pkg/cmd/grafana-cli/commands/listversions_command.go b/pkg/cmd/grafana-cli/commands/listversions_command.go index 95c536e94f0a..78d681c06a3a 100644 --- a/pkg/cmd/grafana-cli/commands/listversions_command.go +++ b/pkg/cmd/grafana-cli/commands/listversions_command.go @@ -5,9 +5,10 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) -func validateVersionInput(c CommandLine) error { +func validateVersionInput(c utils.CommandLine) error { arg := c.Args().First() if arg == "" { return errors.New("please specify plugin to list versions for") @@ -16,7 +17,7 @@ func validateVersionInput(c CommandLine) error { return nil } -func listversionsCommand(c CommandLine) error { +func listversionsCommand(c utils.CommandLine) error { if err := validateVersionInput(c); err != nil { return err } diff --git a/pkg/cmd/grafana-cli/commands/ls_command.go b/pkg/cmd/grafana-cli/commands/ls_command.go index 30745ce3172d..63492d732e98 100644 --- a/pkg/cmd/grafana-cli/commands/ls_command.go +++ b/pkg/cmd/grafana-cli/commands/ls_command.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) var ls_getPlugins func(path string) []m.InstalledPlugin = s.GetLocalPlugins @@ -31,7 +32,7 @@ var validateLsCommand = func(pluginDir string) error { return nil } -func lsCommand(c CommandLine) error { +func lsCommand(c utils.CommandLine) error { pluginDir := c.PluginDirectory() if err := validateLsCommand(pluginDir); err != nil { return err diff --git a/pkg/cmd/grafana-cli/commands/remove_command.go b/pkg/cmd/grafana-cli/commands/remove_command.go index e51929dc95cb..eb536d7b8c7a 100644 --- a/pkg/cmd/grafana-cli/commands/remove_command.go +++ b/pkg/cmd/grafana-cli/commands/remove_command.go @@ -5,12 +5,13 @@ import ( "fmt" "strings" - services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin -func removeCommand(c CommandLine) error { +func removeCommand(c utils.CommandLine) error { pluginPath := c.PluginDirectory() plugin := c.Args().First() diff --git a/pkg/cmd/grafana-cli/commands/reset_password_command.go b/pkg/cmd/grafana-cli/commands/reset_password_command.go index af2b8b3f89ae..4a6a4b674f2e 100644 --- a/pkg/cmd/grafana-cli/commands/reset_password_command.go +++ b/pkg/cmd/grafana-cli/commands/reset_password_command.go @@ -6,13 +6,15 @@ import ( "github.com/fatih/color" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" ) const AdminUserId = 1 -func resetPasswordCommand(c CommandLine) error { +func resetPasswordCommand(c utils.CommandLine, sqlStore *sqlstore.SqlStore) error { newPassword := c.Args().First() password := models.Password(newPassword) diff --git a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go index e01df2dab602..a5aadbbb0c23 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go @@ -4,6 +4,7 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/hashicorp/go-version" ) @@ -27,7 +28,7 @@ func ShouldUpgrade(installed string, remote m.Plugin) bool { return false } -func upgradeAllCommand(c CommandLine) error { +func upgradeAllCommand(c utils.CommandLine) error { pluginsDir := c.PluginDirectory() localPlugins := s.GetLocalPlugins(pluginsDir) diff --git a/pkg/cmd/grafana-cli/commands/upgrade_command.go b/pkg/cmd/grafana-cli/commands/upgrade_command.go index 396371d35772..f32961ce5895 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_command.go @@ -4,9 +4,10 @@ import ( "github.com/fatih/color" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) -func upgradeCommand(c CommandLine) error { +func upgradeCommand(c utils.CommandLine) error { pluginsDir := c.PluginDirectory() pluginName := c.Args().First() diff --git a/pkg/cmd/grafana-cli/commands/command_line.go b/pkg/cmd/grafana-cli/utils/command_line.go similarity index 64% rename from pkg/cmd/grafana-cli/commands/command_line.go rename to pkg/cmd/grafana-cli/utils/command_line.go index d487aff8aaaa..d3142d0f195e 100644 --- a/pkg/cmd/grafana-cli/commands/command_line.go +++ b/pkg/cmd/grafana-cli/utils/command_line.go @@ -1,4 +1,4 @@ -package commands +package utils import ( "github.com/codegangsta/cli" @@ -22,30 +22,30 @@ type CommandLine interface { PluginURL() string } -type contextCommandLine struct { +type ContextCommandLine struct { *cli.Context } -func (c *contextCommandLine) ShowHelp() { +func (c *ContextCommandLine) ShowHelp() { cli.ShowCommandHelp(c.Context, c.Command.Name) } -func (c *contextCommandLine) ShowVersion() { +func (c *ContextCommandLine) ShowVersion() { cli.ShowVersion(c.Context) } -func (c *contextCommandLine) Application() *cli.App { +func (c *ContextCommandLine) Application() *cli.App { return c.App } -func (c *contextCommandLine) PluginDirectory() string { +func (c *ContextCommandLine) PluginDirectory() string { return c.GlobalString("pluginsDir") } -func (c *contextCommandLine) RepoDirectory() string { +func (c *ContextCommandLine) RepoDirectory() string { return c.GlobalString("repo") } -func (c *contextCommandLine) PluginURL() string { +func (c *ContextCommandLine) PluginURL() string { return c.GlobalString("pluginUrl") } diff --git a/pkg/util/strings.go b/pkg/util/strings.go index 9eaa141edbfb..9ce5d03e126c 100644 --- a/pkg/util/strings.go +++ b/pkg/util/strings.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "regexp" + "strings" "time" ) @@ -66,3 +67,19 @@ func GetAgeString(t time.Time) string { return "< 1m" } + +// ToCamelCase changes kebab case, snake case or mixed strings to camel case. See unit test for examples. +func ToCamelCase(str string) string { + var finalParts []string + parts := strings.Split(str, "_") + + for _, part := range parts { + finalParts = append(finalParts, strings.Split(part, "-")...) + } + + for index, part := range finalParts[1:] { + finalParts[index+1] = strings.Title(part) + } + + return strings.Join(finalParts, "") +} diff --git a/pkg/util/strings_test.go b/pkg/util/strings_test.go index 0cc1905baff8..4bc52ee75217 100644 --- a/pkg/util/strings_test.go +++ b/pkg/util/strings_test.go @@ -37,3 +37,12 @@ func TestDateAge(t *testing.T) { So(GetAgeString(time.Now().Add(-time.Hour*24*409)), ShouldEqual, "1y") }) } + +func TestToCamelCase(t *testing.T) { + Convey("ToCamelCase", t, func() { + So(ToCamelCase("kebab-case-string"), ShouldEqual, "kebabCaseString") + So(ToCamelCase("snake_case_string"), ShouldEqual, "snakeCaseString") + So(ToCamelCase("mixed-case_string"), ShouldEqual, "mixedCaseString") + So(ToCamelCase("alreadyCamelCase"), ShouldEqual, "alreadyCamelCase") + }) +} From 3dda812f12653479084c567cbf63fa83be2a0659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 27 May 2019 11:48:17 +0200 Subject: [PATCH 68/92] Chore: Update jquery to 3.4.1 in grafana ui (#17295) --- packages/grafana-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 46b402c92f97..1e672eb46ebf 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -23,7 +23,7 @@ "@types/react-color": "2.17.0", "classnames": "2.2.6", "d3": "5.9.1", - "jquery": "3.4.0", + "jquery": "3.4.1", "lodash": "4.17.11", "moment": "2.24.0", "papaparse": "4.6.3", From 5358c5fe6b0a966dcfe0495b47b06d18155ed647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 27 May 2019 12:13:08 +0200 Subject: [PATCH 69/92] Gauge/BarGauge: font size improvements (#17292) --- .../src/components/BarGauge/BarGauge.tsx | 24 +++++++++++++------ .../__snapshots__/BarGauge.test.tsx.snap | 3 ++- .../grafana-ui/src/components/Gauge/Gauge.tsx | 5 ++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index 4351b6671d6e..cb08d1b15aba 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -11,8 +11,9 @@ import { DisplayValue, Themeable, TimeSeriesValue, Threshold, VizOrientation } f const MIN_VALUE_HEIGHT = 18; const MAX_VALUE_HEIGHT = 50; const MIN_VALUE_WIDTH = 50; -const MAX_VALUE_WIDTH = 100; -const LINE_HEIGHT = 1.5; +const MAX_VALUE_WIDTH = 150; +const TITLE_LINE_HEIGHT = 1.5; +const VALUE_LINE_HEIGHT = 1; export interface Props extends Themeable { height: number; @@ -227,7 +228,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions { return { fontSize: 14, width: width, - height: 14 * LINE_HEIGHT, + height: 14 * TITLE_LINE_HEIGHT, placement: 'below', }; } @@ -238,7 +239,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions { const titleHeight = Math.max(Math.min(height * maxTitleHeightRatio, MAX_VALUE_HEIGHT), 17); return { - fontSize: titleHeight / LINE_HEIGHT, + fontSize: titleHeight / TITLE_LINE_HEIGHT, width: 0, height: titleHeight, placement: 'above', @@ -251,7 +252,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions { const titleHeight = Math.max(height * maxTitleHeightRatio, MIN_VALUE_HEIGHT); return { - fontSize: titleHeight / LINE_HEIGHT, + fontSize: titleHeight / TITLE_LINE_HEIGHT, height: 0, width: Math.min(Math.max(width * maxTitleWidthRatio, 50), 200), placement: 'left', @@ -485,7 +486,7 @@ export function getValueColor(props: Props): string { * Only exported to for unit test */ function getValueStyles(value: string, color: string, width: number, height: number): CSSProperties { - const heightFont = height / LINE_HEIGHT; + const heightFont = height / VALUE_LINE_HEIGHT; const guess = width / (value.length * 1.1); const fontSize = Math.min(Math.max(guess, 14), heightFont); @@ -495,6 +496,15 @@ function getValueStyles(value: string, color: string, width: number, height: num width: `${width}px`, display: 'flex', alignItems: 'center', - fontSize: fontSize.toFixed(2) + 'px', + lineHeight: VALUE_LINE_HEIGHT, + fontSize: fontSize.toFixed(4) + 'px', }; } + +// function getTextWidth(text: string): number { +// const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas")); +// var context = canvas.getContext("2d"); +// context.font = "'Roboto', 'Helvetica Neue', Arial, sans-serif"; +// var metrics = context.measureText(text); +// return metrics.width; +// } diff --git a/packages/grafana-ui/src/components/BarGauge/__snapshots__/BarGauge.test.tsx.snap b/packages/grafana-ui/src/components/BarGauge/__snapshots__/BarGauge.test.tsx.snap index 4bb9395dd96e..1d341a9b0d4c 100644 --- a/packages/grafana-ui/src/components/BarGauge/__snapshots__/BarGauge.test.tsx.snap +++ b/packages/grafana-ui/src/components/BarGauge/__snapshots__/BarGauge.test.tsx.snap @@ -18,8 +18,9 @@ exports[`BarGauge Render with basic options should render 1`] = ` "alignItems": "center", "color": "#73BF69", "display": "flex", - "fontSize": "27.27px", + "fontSize": "27.2727px", "height": "300px", + "lineHeight": 1, "paddingLeft": "10px", "width": "60px", } diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index eb49891d298e..0a0495c4848d 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -58,7 +58,7 @@ export class Gauge extends PureComponent { if (length > 12) { return FONT_SCALE - (length * 5) / 110; } - return FONT_SCALE - (length * 5) / 100; + return FONT_SCALE - (length * 5) / 101; } draw() { @@ -78,7 +78,8 @@ export class Gauge extends PureComponent { const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1; const gaugeWidth = Math.min(dimension / 5.5, 40) / gaugeWidthReduceRatio; const thresholdMarkersWidth = gaugeWidth / 5; - const fontSize = Math.min(dimension / 5.5, 100) * (value.text !== null ? this.getFontScale(value.text.length) : 1); + const fontSize = Math.min(dimension / 4, 100) * (value.text !== null ? this.getFontScale(value.text.length) : 1); + const thresholdLabelFontSize = fontSize / 2.5; const options: any = { From db32c7dcf51e1b508933e6e2c2ce97ca4daa9c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 27 May 2019 12:22:43 +0200 Subject: [PATCH 70/92] Build: Enables end-to-end tests in build-master workflow (#17268) * Fix: Adds back necessary aria-labels for e2e tests * Build: Adds end-to-end-tests job to build-master workflow * Build: Changes grafana image used --- .circleci/config.yml | 9 ++++++--- .../datasources/settings/DataSourceSettingsPage.tsx | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 91ace22d33b9..e90af0509225 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,7 +73,7 @@ jobs: end-to-end-test: docker: - image: circleci/node:8-browsers - - image: grafana/grafana:master + - image: grafana/grafana-dev:master-$CIRCLE_SHA1 steps: - run: dockerize -wait tcp://127.0.0.1:3000 -timeout 120s - checkout @@ -629,7 +629,7 @@ workflows: - mysql-integration-test - postgres-integration-test - build-oss-msi - filters: *filter-only-master + filters: *filter-only-master - grafana-docker-master: requires: - build-all @@ -662,7 +662,10 @@ workflows: - mysql-integration-test - postgres-integration-test filters: *filter-only-master - + - end-to-end-test: + requires: + - grafana-docker-master + filters: *filter-only-master release: jobs: - build-all: diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index 5c31b946149c..30d4d6ea38ca 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -276,7 +276,7 @@ export class DataSourceSettingsPage extends PureComponent {
{testingMessage && ( -
+
{testingStatus === 'error' ? ( @@ -285,7 +285,9 @@ export class DataSourceSettingsPage extends PureComponent { )}
-
{testingMessage}
+
+ {testingMessage} +
)} From 2146f837cfe07931fa0943036292a83a4258bc3c Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Mon, 27 May 2019 14:05:32 +0300 Subject: [PATCH 71/92] Build(devenv): correct the context issue (#17291) With the previous configuration `docker-compose build` was always failing. This moves the dockerfiles in the parent dir and changes paths as a result. Ref moby/moby#2745 --- .../Dockerfile => admins-ldap-server.Dockerfile} | 8 ++++---- .../docker/blocks/multiple-openldap/docker-compose.yaml | 8 ++++++-- .../Dockerfile => ldap-server.Dockerfile} | 8 ++++---- 3 files changed, 14 insertions(+), 10 deletions(-) rename devenv/docker/blocks/multiple-openldap/{ldap-server/Dockerfile => admins-ldap-server.Dockerfile} (74%) rename devenv/docker/blocks/multiple-openldap/{admins-ldap-server/Dockerfile => ldap-server.Dockerfile} (75%) diff --git a/devenv/docker/blocks/multiple-openldap/ldap-server/Dockerfile b/devenv/docker/blocks/multiple-openldap/admins-ldap-server.Dockerfile similarity index 74% rename from devenv/docker/blocks/multiple-openldap/ldap-server/Dockerfile rename to devenv/docker/blocks/multiple-openldap/admins-ldap-server.Dockerfile index 979d01c7dad4..29e581d2b137 100644 --- a/devenv/docker/blocks/multiple-openldap/ldap-server/Dockerfile +++ b/devenv/docker/blocks/multiple-openldap/admins-ldap-server.Dockerfile @@ -19,11 +19,11 @@ EXPOSE 389 VOLUME ["/etc/ldap", "/var/lib/ldap"] -COPY modules/ /etc/ldap.dist/modules -COPY prepopulate/ /etc/ldap.dist/prepopulate +COPY admins-ldap-server/modules/ /etc/ldap.dist/modules +COPY admins-ldap-server/prepopulate/ /etc/ldap.dist/prepopulate -COPY ../entrypoint.sh /entrypoint.sh -COPY ../prepopulate.sh /prepopulate.sh +COPY ./entrypoint.sh /entrypoint.sh +COPY ./prepopulate.sh /prepopulate.sh ENTRYPOINT ["/entrypoint.sh"] diff --git a/devenv/docker/blocks/multiple-openldap/docker-compose.yaml b/devenv/docker/blocks/multiple-openldap/docker-compose.yaml index 74f5d29a90ff..7ed0ca2e840a 100644 --- a/devenv/docker/blocks/multiple-openldap/docker-compose.yaml +++ b/devenv/docker/blocks/multiple-openldap/docker-compose.yaml @@ -1,5 +1,7 @@ admins-openldap: - build: docker/blocks/multiple-openldap/admins-ldap-server + build: + context: docker/blocks/multiple-openldap + dockerfile: ./admins-ldap-server.Dockerfile environment: SLAPD_PASSWORD: grafana SLAPD_DOMAIN: grafana.org @@ -8,7 +10,9 @@ - "389:389" openldap: - build: docker/blocks/multiple-openldap/ldap-server + build: + context: docker/blocks/multiple-openldap + dockerfile: ./ldap-server.Dockerfile environment: SLAPD_PASSWORD: grafana SLAPD_DOMAIN: grafana.org diff --git a/devenv/docker/blocks/multiple-openldap/admins-ldap-server/Dockerfile b/devenv/docker/blocks/multiple-openldap/ldap-server.Dockerfile similarity index 75% rename from devenv/docker/blocks/multiple-openldap/admins-ldap-server/Dockerfile rename to devenv/docker/blocks/multiple-openldap/ldap-server.Dockerfile index 979d01c7dad4..7604d1118a3f 100644 --- a/devenv/docker/blocks/multiple-openldap/admins-ldap-server/Dockerfile +++ b/devenv/docker/blocks/multiple-openldap/ldap-server.Dockerfile @@ -19,11 +19,11 @@ EXPOSE 389 VOLUME ["/etc/ldap", "/var/lib/ldap"] -COPY modules/ /etc/ldap.dist/modules -COPY prepopulate/ /etc/ldap.dist/prepopulate +COPY ldap-server/modules/ /etc/ldap.dist/modules +COPY ldap-server/prepopulate/ /etc/ldap.dist/prepopulate -COPY ../entrypoint.sh /entrypoint.sh -COPY ../prepopulate.sh /prepopulate.sh +COPY ./entrypoint.sh /entrypoint.sh +COPY ./prepopulate.sh /prepopulate.sh ENTRYPOINT ["/entrypoint.sh"] From 4965e10db1941e061adf41fb06b7f8c2d16543ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 27 May 2019 13:16:33 +0200 Subject: [PATCH 72/92] Build: Removes e2e-tests from Grafana master workflow (#17301) --- .circleci/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e90af0509225..42a4d1bb2b91 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -662,10 +662,6 @@ workflows: - mysql-integration-test - postgres-integration-test filters: *filter-only-master - - end-to-end-test: - requires: - - grafana-docker-master - filters: *filter-only-master release: jobs: - build-all: From 0b3768e551c771bc05d155184ea8cf8d5f63fd75 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 27 May 2019 14:59:42 +0200 Subject: [PATCH 73/92] release: v6.2.1 changelog update (#17303) --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9224bb241aeb..a649bf0cc229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# 6.3.0 (unreleased) + +# 6.2.1 (2019-05-27) + +### Features / Enhancements + * **CLI**: Add command to migrate all datasources to use encrypted password fields . [#17118](https://github.com/grafana/grafana/pull/17118), [@aocenas](https://github.com/aocenas) + * **Gauge/BarGauge**: Improvements to auto value font size . [#17292](https://github.com/grafana/grafana/pull/17292), [@torkelo](https://github.com/torkelo) + +### Bug Fixes + * **Auth Proxy**: Resolve database is locked errors. [#17274](https://github.com/grafana/grafana/pull/17274), [@marefr](https://github.com/marefr) + * **Database**: Retry transaction if sqlite returns database is locked error. [#17276](https://github.com/grafana/grafana/pull/17276), [@marefr](https://github.com/marefr) + * **Explore**: Fixes so clicking in a Prometheus Table the query is filtered by clicked value. [#17083](https://github.com/grafana/grafana/pull/17083), [@hugohaggmark](https://github.com/hugohaggmark) + * **Singlestat**: Fixes issue with value placement and line wraps. [#17249](https://github.com/grafana/grafana/pull/17249), [@torkelo](https://github.com/torkelo) + * **Tech**: Update jQuery to 3.4.1 to fix issue on iOS 10 based browers as well as Chrome 53.x . [#17290](https://github.com/grafana/grafana/pull/17290), [@timbutler](https://github.com/timbutler) + # 6.2.0 (2019-05-22) ### Bug Fixes From b547a0cb34615d9eb3d280a9cf8dd50eb3ae6ccb Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 27 May 2019 16:15:25 +0200 Subject: [PATCH 74/92] update latest.json to latest stable version (#17306) --- latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latest.json b/latest.json index 18770c57cce2..97724801882c 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "6.2.0", - "testing": "6.2.0" + "stable": "6.2.1", + "testing": "6.2.1" } From 04d473b3e533db0cc1b9abed28b9953941da499e Mon Sep 17 00:00:00 2001 From: Abhilash Gnan Date: Mon, 27 May 2019 17:47:29 +0200 Subject: [PATCH 75/92] HTTP Server: Serve Grafana with a custom URL path prefix (#17048) Adds a new [server] setting `serve_from_sub_path`. By enabling this setting and using a subpath in `root_url` setting, e.g. `root_url = http://localhost:3000/grafana`, Grafana will be accessible on `http://localhost:3000/grafana`. By default it is set to `false` for compatibility reasons. Closes #16623 --- conf/defaults.ini | 3 +++ conf/sample.ini | 3 +++ docs/sources/installation/configuration.md | 9 +++++++++ pkg/api/http_server.go | 6 +++++- pkg/setting/setting.go | 17 +++++++++++------ 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index ca49f1212698..ec83a5ea1a5a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -47,6 +47,9 @@ enforce_domain = false # The full public facing url root_url = %(protocol)s://%(domain)s:%(http_port)s/ +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +serve_from_sub_path = false + # Log web requests router_logging = false diff --git a/conf/sample.ini b/conf/sample.ini index de684bc98f67..dc7d5bc54677 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -48,6 +48,9 @@ # If you use reverse proxy and sub path specify full url (with sub path) ;root_url = http://localhost:3000 +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +;serve_from_sub_path = false + # Log web requests ;router_logging = false diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index a865234ebeba..af0261032d4b 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -154,6 +154,15 @@ callback URL to be correct). > in front of Grafana that exposes it through a subpath. In that > case add the subpath to the end of this URL setting. +### serve_from_sub_path + +Serve Grafana from subpath specified in `root_url` setting. By +default it is set to `false` for compatibility reasons. + +By enabling this setting and using a subpath in `root_url` above, e.g. +`root_url = http://localhost:3000/grafana`, Grafana will be accessible on +`http://localhost:3000/grafana`. + ### static_root_path The path to the directory where the front end files (HTML, JS, and CSS diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 7ec4fbaa3b3c..d2094b33cb1a 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -30,7 +30,7 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "gopkg.in/macaron.v1" + macaron "gopkg.in/macaron.v1" ) func init() { @@ -227,6 +227,10 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { m.Use(middleware.AddDefaultResponseHeaders()) + if setting.ServeFromSubPath && setting.AppSubUrl != "" { + m.SetURLPrefix(setting.AppSubUrl) + } + m.Use(macaron.Renderer(macaron.RenderOptions{ Directory: path.Join(setting.StaticRootPath, "views"), IndentJSON: macaron.Env != macaron.PROD, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 65e4e0e25021..a6c07d232e10 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -47,10 +47,11 @@ var ( var ( // App settings. - Env = DEV - AppUrl string - AppSubUrl string - InstanceName string + Env = DEV + AppUrl string + AppSubUrl string + ServeFromSubPath bool + InstanceName string // build BuildVersion string @@ -205,8 +206,9 @@ type Cfg struct { Logger log.Logger // HTTP Server Settings - AppUrl string - AppSubUrl string + AppUrl string + AppSubUrl string + ServeFromSubPath bool // Paths ProvisioningPath string @@ -610,8 +612,11 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { if err != nil { return err } + ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false) + cfg.AppUrl = AppUrl cfg.AppSubUrl = AppSubUrl + cfg.ServeFromSubPath = ServeFromSubPath Protocol = HTTP protocolStr, err := valueAsString(server, "protocol", "http") From a07296bf24856bb66b2103929af179ee627f8985 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 27 May 2019 18:43:04 +0200 Subject: [PATCH 76/92] explore: don't parse log levels if provided by field or label (#17180) If a field or a label named level is returned from datasource that is used as log level for the logs result instead of parsing the log level from the message. Closes #17122 --- packages/grafana-ui/src/utils/logs.ts | 9 +++++++++ public/app/core/logs_model.ts | 13 ++++++++++++- public/app/core/specs/logs_model.test.ts | 18 ++++++++++++------ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/grafana-ui/src/utils/logs.ts b/packages/grafana-ui/src/utils/logs.ts index fb8c7977e2ad..b5c45b635daf 100644 --- a/packages/grafana-ui/src/utils/logs.ts +++ b/packages/grafana-ui/src/utils/logs.ts @@ -23,6 +23,15 @@ export function getLogLevel(line: string): LogLevel { return LogLevel.unknown; } +export function getLogLevelFromKey(key: string): LogLevel { + const level = (LogLevel as any)[key]; + if (level) { + return level; + } + + return LogLevel.unknown; +} + export function addLogLevelToSeries(series: SeriesData, lineIndex: number): SeriesData { return { ...series, // Keeps Tags, RefID etc diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index d2a4780b62a7..5fe95a182d07 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -13,6 +13,7 @@ import { toLegacyResponseData, FieldCache, FieldType, + getLogLevelFromKey, LogRowModel, LogsModel, LogsMetaItem, @@ -368,7 +369,17 @@ export function processLogSeriesRow( const timeEpochMs = time.valueOf(); const timeFromNow = time.fromNow(); const timeLocal = time.format('YYYY-MM-DD HH:mm:ss'); - const logLevel = getLogLevel(message); + + let logLevel = LogLevel.unknown; + const logLevelField = fieldCache.getFieldByName('level'); + + if (logLevelField) { + logLevel = getLogLevelFromKey(row[logLevelField.index]); + } else if (series.labels && Object.keys(series.labels).indexOf('level') !== -1) { + logLevel = getLogLevelFromKey(series.labels['level']); + } else { + logLevel = getLogLevel(message); + } const hasAnsi = hasAnsiCodes(message); const searchWords = series.meta && series.meta.searchWords ? series.meta.searchWords : []; diff --git a/public/app/core/specs/logs_model.test.ts b/public/app/core/specs/logs_model.test.ts index a2d47412bd06..c83f0ce6c1c0 100644 --- a/public/app/core/specs/logs_model.test.ts +++ b/public/app/core/specs/logs_model.test.ts @@ -1,4 +1,4 @@ -import { SeriesData, FieldType, LogsModel, LogsMetaKind, LogsDedupStrategy } from '@grafana/ui'; +import { SeriesData, FieldType, LogsModel, LogsMetaKind, LogsDedupStrategy, LogLevel } from '@grafana/ui'; import { dedupLogRows, calculateFieldStats, @@ -460,8 +460,12 @@ describe('seriesDataToLogsModel', () => { name: 'message', type: FieldType.string, }, + { + name: 'level', + type: FieldType.string, + }, ], - rows: [['1970-01-01T00:00:01Z', 'WARN boooo']], + rows: [['1970-01-01T00:00:01Z', 'WARN boooo', 'dbug']], }, ]; const logsModel = seriesDataToLogsModel(series, 0); @@ -470,7 +474,7 @@ describe('seriesDataToLogsModel', () => { { entry: 'WARN boooo', labels: undefined, - logLevel: 'warning', + logLevel: LogLevel.debug, uniqueLabels: {}, }, ]); @@ -482,6 +486,7 @@ describe('seriesDataToLogsModel', () => { labels: { foo: 'bar', baz: '1', + level: 'dbug', }, fields: [ { @@ -500,6 +505,7 @@ describe('seriesDataToLogsModel', () => { labels: { foo: 'bar', baz: '2', + level: 'err', }, fields: [ { @@ -521,19 +527,19 @@ describe('seriesDataToLogsModel', () => { { entry: 'INFO 2', labels: { foo: 'bar', baz: '2' }, - logLevel: 'info', + logLevel: LogLevel.error, uniqueLabels: { baz: '2' }, }, { entry: 'WARN boooo', labels: { foo: 'bar', baz: '1' }, - logLevel: 'warning', + logLevel: LogLevel.debug, uniqueLabels: { baz: '1' }, }, { entry: 'INFO 1', labels: { foo: 'bar', baz: '2' }, - logLevel: 'info', + logLevel: LogLevel.error, uniqueLabels: { baz: '2' }, }, ]); From 83187fd8eae9b8bb54121083761c849d0adbdcde Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 28 May 2019 09:06:30 +0200 Subject: [PATCH 77/92] update v6.2-beta1 changelog with missing pr (#17308) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a649bf0cc229..64d8d41d5f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ repo on July 1st. Make sure you have switched to the new repo by then. The new r * **Provisioning**: Add API endpoint to reload provisioning configs. [#16579](https://github.com/grafana/grafana/pull/16579), [@aocenas](https://github.com/aocenas) * **Provisioning**: Do not allow deletion of provisioned dashboards. [#16211](https://github.com/grafana/grafana/pull/16211), [@aocenas](https://github.com/aocenas) * **Provisioning**: Interpolate env vars in provisioning files. [#16499](https://github.com/grafana/grafana/pull/16499), [@aocenas](https://github.com/aocenas) + * **Provisioning**: Support FolderUid in Dashboard Provisioning Config. [#16559](https://github.com/grafana/grafana/pull/16559), [@swtch1](https://github.com/swtch1) * **Security**: Add new setting allow_embedding. [#16853](https://github.com/grafana/grafana/pull/16853), [@marefr](https://github.com/marefr) * **Security**: Store datasource passwords encrypted in secureJsonData. [#16175](https://github.com/grafana/grafana/pull/16175), [@aocenas](https://github.com/aocenas) * **UX**: Improve Grafana usage for smaller screens. [#16783](https://github.com/grafana/grafana/pull/16783), [@torkelo](https://github.com/torkelo) From 83af1bdff3322331e89d6ba7f10fb483e524922e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Tue, 28 May 2019 10:13:49 +0200 Subject: [PATCH 78/92] Frontend/utils: Add missing type (#17312) --- public/app/core/utils/kbn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index d747fa37f579..47b753f91233 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -133,7 +133,7 @@ kbn.secondsToHms = seconds => { }; kbn.secondsToHhmmss = seconds => { - const strings = []; + const strings: string[] = []; const numhours = Math.floor(seconds / 3600); const numminutes = Math.floor((seconds % 3600) / 60); const numseconds = Math.floor((seconds % 3600) % 60); From 4b0ad174ff43ba8b629a1c6b3c75051ad7f7c6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Tue, 28 May 2019 10:17:03 +0200 Subject: [PATCH 79/92] Frontend/utils: Import has only from lodash (#17311) --- public/app/core/utils/kbn.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index 47b753f91233..1a1cf6f56ba1 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { has } from 'lodash'; import { getValueFormat, getValueFormatterIndex, getValueFormats, stringToJsRegex } from '@grafana/ui'; import deprecationWarning from '@grafana/ui/src/utils/deprecationWarning'; @@ -193,7 +193,7 @@ kbn.calculateInterval = (range, resolution, lowLimitInterval) => { kbn.describe_interval = str => { const matches = str.match(kbn.interval_regex); - if (!matches || !_.has(kbn.intervals_in_seconds, matches[2])) { + if (!matches || !has(kbn.intervals_in_seconds, matches[2])) { throw new Error('Invalid interval string, expecting a number followed by one of "Mwdhmsy"'); } else { return { From 382ebd6362c5001a4c59e30e624fbb36325d4539 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 28 May 2019 01:18:09 -0700 Subject: [PATCH 80/92] Frontend/SeriesData: Fix for convert SeriesData to Table format (#17314) --- .../src/utils/processSeriesData.test.ts | 23 ++++++++++++++++++- .../grafana-ui/src/utils/processSeriesData.ts | 14 +++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/grafana-ui/src/utils/processSeriesData.test.ts b/packages/grafana-ui/src/utils/processSeriesData.test.ts index 96afa79aa8c0..ea582e89b3a8 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.test.ts +++ b/packages/grafana-ui/src/utils/processSeriesData.test.ts @@ -6,7 +6,7 @@ import { guessFieldTypes, guessFieldTypeFromValue, } from './processSeriesData'; -import { FieldType, TimeSeries } from '../types/data'; +import { FieldType, TimeSeries, SeriesData, TableData } from '../types/data'; import { dateTime } from './moment_wrapper'; describe('toSeriesData', () => { @@ -99,4 +99,25 @@ describe('SerisData backwards compatibility', () => { expect(isTableData(roundtrip)).toBeTruthy(); expect(roundtrip).toMatchObject(table); }); + + it('converts SeriesData to TableData to series and back again', () => { + const series: SeriesData = { + refId: 'Z', + meta: { + somethign: 8, + }, + fields: [ + { name: 'T', type: FieldType.time }, // first + { name: 'N', type: FieldType.number, filterable: true }, + { name: 'S', type: FieldType.string, filterable: true }, + ], + rows: [[1, 100, '1'], [2, 200, '2'], [3, 300, '3']], + }; + const table = toLegacyResponseData(series) as TableData; + expect(table.meta).toBe(series.meta); + expect(table.refId).toBe(series.refId); + + const names = table.columns.map(c => c.text); + expect(names).toEqual(['T', 'N', 'S']); + }); }); diff --git a/packages/grafana-ui/src/utils/processSeriesData.ts b/packages/grafana-ui/src/utils/processSeriesData.ts index 1ba31e3bbdee..84aadcc9f655 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.ts +++ b/packages/grafana-ui/src/utils/processSeriesData.ts @@ -4,7 +4,7 @@ import isString from 'lodash/isString'; import isBoolean from 'lodash/isBoolean'; // Types -import { SeriesData, Field, TimeSeries, FieldType, TableData } from '../types/index'; +import { SeriesData, Field, TimeSeries, FieldType, TableData, Column } from '../types/index'; import { isDateTime } from './moment_wrapper'; function convertTableToSeriesData(table: TableData): SeriesData { @@ -171,14 +171,12 @@ export const toLegacyResponseData = (series: SeriesData): TimeSeries | TableData return { columns: fields.map(f => { - return { - text: f.name, - filterable: f.filterable, - unit: f.unit, - refId: series.refId, - meta: series.meta, - }; + const { name, ...column } = f; + (column as Column).text = name; + return column as Column; }), + refId: series.refId, + meta: series.meta, rows, }; }; From 27874a18810ca276a562556ffbac7e9c4de062cf Mon Sep 17 00:00:00 2001 From: Shavonn Brown Date: Tue, 28 May 2019 09:04:42 -0400 Subject: [PATCH 81/92] 16365 change clashing variable names (#17140) * Fix: change and to and so not clashing with grafana vars (#16365) * Fix: change and to and so not clashing with grafana vars (#16365) * Fix: update now to datetime (#16365) * Fix: test should look for datetime instead of now (#16365) * Fix: _az suffix to datasource convention (#16365) * Fix: convert vars to macro, update doc (#16365) * Fix: convert vars to macro, update doc (#16365) * Fix: remove support for time vars (#16365) * Fix: confilct from master * add migration on editor open * fix migration var name --- docs/sources/features/datasources/azuremonitor.md | 6 ++++-- .../editor/kusto/kusto.ts | 10 ++++++++++ .../log_analytics/querystring_builder.test.ts | 14 +++++++------- .../log_analytics/querystring_builder.ts | 13 ++++++++++--- .../partials/annotations.editor.html | 4 ++-- .../partials/query.editor.html | 6 +++--- .../grafana-azure-monitor-datasource/query_ctrl.ts | 7 +++++++ 7 files changed, 43 insertions(+), 17 deletions(-) diff --git a/docs/sources/features/datasources/azuremonitor.md b/docs/sources/features/datasources/azuremonitor.md index ee40248fe001..114187499f17 100644 --- a/docs/sources/features/datasources/azuremonitor.md +++ b/docs/sources/features/datasources/azuremonitor.md @@ -254,6 +254,10 @@ To make writing queries easier there are several Grafana macros that can be used `datetimeColumn ≥ datetime(2018-06-05T18:09:58.907Z) and` `datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. +- `$__timeFrom()` - Returns the From datetime from the Grafana picker. Example: `datetime(2018-06-05T18:09:58.907Z)`. + +- `$__timeTo()` - Returns the From datetime from the Grafana picker. Example: `datetime(2018-06-05T20:09:58.907Z)`. + - `$__escapeMulti($myVar)` - is to be used with multi-value template variables that contain illegal characters. If `$myVar` has the following two values as a string `'\\grafana-vm\Network(eth0)\Total','\\hello!'`, then it expands to: `@'\\grafana-vm\Network(eth0)\Total', @'\\hello!'`. If using single value variables there is no need for this macro, simply escape the variable inline instead - `@'\$myVar'`. - `$__contains(colName, $myVar)` - is to be used with multi-value template variables. If `$myVar` has the value `'value1','value2'`, it expands to: `colName in ('value1','value2')`. @@ -264,8 +268,6 @@ To make writing queries easier there are several Grafana macros that can be used There are also some Grafana variables that can be used in Azure Log Analytics queries: -- `$__from` - Returns the From datetime from the Grafana picker. Example: `datetime(2018-06-05T18:09:58.907Z)`. -- `$__to` - Returns the From datetime from the Grafana picker. Example: `datetime(2018-06-05T20:09:58.907Z)`. - `$__interval` - Grafana calculates the minimum time grain that can be used to group by time in queries. More details on how it works [here]({{< relref "reference/templating.md#interval-variables" >}}). It returns a time grain like `5m` or `1h` that can be used in the bin function. E.g. `summarize count() by bin(TimeGenerated, $__interval)` ### Azure Log Analytics Alerting diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts index 5bf7dfb19eb5..172aa5ee077a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts @@ -649,6 +649,16 @@ export const grafanaMacros = [ display: '$__timeFilter()', hint: 'Macro that uses the selected timerange in Grafana to filter the query.', }, + { + text: '$__timeTo', + display: '$__timeTo()', + hint: 'Returns the From datetime from the Grafana picker. Example: datetime(2018-06-05T20:09:58.907Z).', + }, + { + text: '$__timeFrom', + display: '$__timeFrom()', + hint: 'Returns the From datetime from the Grafana picker. Example: datetime(2018-06-05T18:09:58.907Z).', + }, { text: '$__escapeMulti', display: '$__escapeMulti()', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts index fab268a34401..186c78743f8c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts @@ -90,27 +90,27 @@ describe('LogAnalyticsDatasource', () => { }); }); - describe('when using $__from and $__to is in the query and range is until now', () => { + describe('when using $__timeFrom and $__timeTo is in the query and range is until now', () => { beforeEach(() => { - builder.rawQueryString = 'query=Tablename | where myTime >= $__from and myTime <= $__to'; + builder.rawQueryString = 'query=Tablename | where myTime >= $__timeFrom() and myTime <= $__timeTo()'; }); - it('should replace $__from and $__to with a datetime and the now() function', () => { + it('should replace $__timeFrom and $__timeTo with a datetime and the now() function', () => { const query = builder.generate().uriString; expect(query).toContain('where%20myTime%20%3E%3D%20datetime('); - expect(query).toContain('myTime%20%3C%3D%20now()'); + expect(query).toContain('myTime%20%3C%3D%20datetime('); }); }); - describe('when using $__from and $__to is in the query and range is a specific interval', () => { + describe('when using $__timeFrom and $__timeTo is in the query and range is a specific interval', () => { beforeEach(() => { - builder.rawQueryString = 'query=Tablename | where myTime >= $__from and myTime <= $__to'; + builder.rawQueryString = 'query=Tablename | where myTime >= $__timeFrom() and myTime <= $__timeTo()'; builder.options.range.to = dateTime().subtract(1, 'hour'); builder.options.rangeRaw.to = 'now-1h'; }); - it('should replace $__from and $__to with datetimes', () => { + it('should replace $__timeFrom and $__timeTo with datetimes', () => { const query = builder.generate().uriString; expect(query).toContain('where%20myTime%20%3E%3D%20datetime('); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.ts index afb64da8f4c6..ad72c4eb2eb9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.ts @@ -21,12 +21,16 @@ export default class LogAnalyticsQuerystringBuilder { if (p1 === 'timeFilter') { return this.getTimeFilter(p2, this.options); } + if (p1 === 'timeFrom') { + return this.getFrom(this.options); + } + if (p1 === 'timeTo') { + return this.getUntil(this.options); + } return match; }); queryString = queryString.replace(/\$__interval/gi, this.options.interval); - queryString = queryString.replace(/\$__from/gi, this.getFrom(this.options)); - queryString = queryString.replace(/\$__to/gi, this.getUntil(this.options)); } const rawQuery = queryString; queryString = encodeURIComponent(queryString); @@ -44,7 +48,10 @@ export default class LogAnalyticsQuerystringBuilder { getUntil(options) { if (options.rangeRaw.to === 'now') { - return 'now()'; + const now = Date.now(); + return `datetime(${dateTime(now) + .startOf('minute') + .toISOString()})`; } else { const until = options.range.to; return `datetime(${dateTime(until) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html index a5b2b2adc5be..7a855a10b44b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html @@ -67,8 +67,8 @@ - $__timeFilter(datetimeColumn) -> datetimeColumn ≥ datetime(2018-06-05T18:09:58.907Z) and datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z) Or build your own conditionals using these built-in variables which just return the values: - - $__from -> datetime(2018-06-05T18:09:58.907Z) - - $__to -> datetime(2018-06-05T20:09:58.907Z) + - $__timeFrom -> datetime(2018-06-05T18:09:58.907Z) + - $__timeTo -> datetime(2018-06-05T20:09:58.907Z) - $__interval -> 5m
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html index 4690bc5be26c..1c2b14f366ed 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html @@ -189,13 +189,13 @@ If using the All option, then check the Include All Option checkbox and in the Custom all value field type in: all. If All is chosen -> 1 == 1 Or build your own conditionals using these built-in variables which just return the values: - - $__from -> datetime(2018-06-05T18:09:58.907Z) - - $__to -> datetime(2018-06-05T20:09:58.907Z) + - $__timeFrom -> datetime(2018-06-05T18:09:58.907Z) + - $__timeTo -> datetime(2018-06-05T20:09:58.907Z) - $__interval -> 5m Examples: - ¡ where $__timeFilter - - | where TimeGenerated ≥ $__from and TimeGenerated ≤ $__to + - | where TimeGenerated ≥ $__timeFrom and TimeGenerated ≤ $__timeTo - | summarize count() by Category, bin(TimeGenerated, $__interval)
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index cc623d8df981..9fc12e9b9169 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -110,6 +110,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { this.migrateTimeGrains(); + this.migrateToFromTimes(); + this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }]; @@ -171,6 +173,11 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { } } + migrateToFromTimes() { + this.target.azureLogAnalytics.query = this.target.azureLogAnalytics.query.replace(/\$__from\s/gi, '$__timeFrom() '); + this.target.azureLogAnalytics.query = this.target.azureLogAnalytics.query.replace(/\$__to\s/gi, '$__timeTo() '); + } + replace(variable: string) { return this.templateSrv.replace(variable, this.panelCtrl.panel.scopedVars); } From 2dc660d533088e52378d0eb5ceff6aba927c290b Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Tue, 28 May 2019 16:02:14 +0200 Subject: [PATCH 82/92] docs: remove my email from docs examples (#17325) I dont want emails from companies who test alert notifiers :) --- .../alerting_notification_channels.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/sources/http_api/alerting_notification_channels.md b/docs/sources/http_api/alerting_notification_channels.md index b8db1595aaa1..aa6e7297ac2d 100644 --- a/docs/sources/http_api/alerting_notification_channels.md +++ b/docs/sources/http_api/alerting_notification_channels.md @@ -54,7 +54,7 @@ Content-Type: application/json "sendReminder": false, "disableResolveMessage": false, "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" }, "created": "2018-04-23T14:44:09+02:00", "updated": "2018-08-20T15:47:49+02:00" @@ -93,7 +93,7 @@ Content-Type: application/json "sendReminder": false, "disableResolveMessage": false, "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" }, "created": "2018-04-23T14:44:09+02:00", "updated": "2018-08-20T15:47:49+02:00" @@ -130,7 +130,7 @@ Content-Type: application/json "sendReminder": false, "disableResolveMessage": false, "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" }, "created": "2018-04-23T14:44:09+02:00", "updated": "2018-08-20T15:47:49+02:00" @@ -158,7 +158,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk "isDefault": false, "sendReminder": false, "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" } } ``` @@ -177,7 +177,7 @@ Content-Type: application/json "isDefault": false, "sendReminder": false, "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" }, "created": "2018-04-23T14:44:09+02:00", "updated": "2018-08-20T15:47:49+02:00" @@ -206,7 +206,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk "sendReminder": true, "frequency": "15m", "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" } } ``` @@ -226,7 +226,7 @@ Content-Type: application/json "sendReminder": true, "frequency": "15m", "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" }, "created": "2017-01-01 12:34", "updated": "2017-01-01 12:34" @@ -256,7 +256,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk "sendReminder": true, "frequency": "15m", "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" } } ``` @@ -276,7 +276,7 @@ Content-Type: application/json "sendReminder": true, "frequency": "15m", "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" }, "created": "2017-01-01 12:34", "updated": "2017-01-01 12:34" @@ -353,7 +353,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk { "type": "email", "settings": { - "addresses": "carl@grafana.com;dev@grafana.com" + "addresses": "dev@grafana.com" } } ``` From 1e508d7288d63e9322ff0f8b1b4cee9646d9b930 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 28 May 2019 16:34:28 +0200 Subject: [PATCH 83/92] adds auth example for the cli cherrypick task (#17307) --- scripts/cli/tasks/cherrypick.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/cli/tasks/cherrypick.ts b/scripts/cli/tasks/cherrypick.ts index 966781951828..ac92f223a7eb 100644 --- a/scripts/cli/tasks/cherrypick.ts +++ b/scripts/cli/tasks/cherrypick.ts @@ -7,6 +7,10 @@ const cherryPickRunner: TaskRunner = async () => { let client = axios.create({ baseURL: 'https://api.github.com/repos/grafana/grafana', timeout: 10000, + // auth: { + // username: '', + // password: '', + // }, }); const res = await client.get('/issues', { From 9ff44b5037e909bc0952cdd556ab85901b05cccb Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Tue, 28 May 2019 19:32:14 +0300 Subject: [PATCH 84/92] Build(makefile): improve error handling (#17281) * Build(makefile): improve error handling Ref baa55ab6ae14ae23864f650cec2bb180b36fdabe --- Makefile | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9f56e3da8f1a..7e5a3798f70c 100644 --- a/Makefile +++ b/Makefile @@ -86,16 +86,25 @@ revive: scripts/go/bin/revive # create docker-compose file with provided sources and start them # example: make devenv sources=postgres,openldap +ifeq ($(sources),) +devenv: + @printf 'You have to define sources for this command \nexample: make devenv sources=postgres,openldap\n' +else devenv: devenv-down $(eval targets := $(shell echo '$(sources)' | tr "," " ")) @cd devenv; \ - ./create_docker_compose.sh $(targets); \ + ./create_docker_compose.sh $(targets) || \ + (rm -rf docker-compose.yaml; exit 1) + + @cd devenv; \ docker-compose up -d +endif # drop down the envs devenv-down: - @cd devenv; docker-compose down; + @cd devenv; \ + docker-compose down; # TODO recheck the rules and leave only necessary exclusions gosec: scripts/go/bin/gosec From a1a498f96cec2fe9c7ecb8d89bd9e7f6d49eae13 Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Tue, 28 May 2019 20:08:27 +0300 Subject: [PATCH 85/92] Build: ignore absence of docker-compose (#17331) If devenv/docker-compose.yaml file is missing, `devenv-down` and subsequently `devenv` is not going to work --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7e5a3798f70c..5b89b178e974 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,8 @@ endif # drop down the envs devenv-down: @cd devenv; \ - docker-compose down; + test -f docker-compose.yaml && \ + docker-compose down || exit 0; # TODO recheck the rules and leave only necessary exclusions gosec: scripts/go/bin/gosec From 5fa5d4bdd54cc493d344b41510515115169f6cb9 Mon Sep 17 00:00:00 2001 From: Joshua Piccari Date: Wed, 29 May 2019 00:10:09 -0700 Subject: [PATCH 86/92] CloudWatch: Avoid exception while accessing results (#17283) When accessing the `series` property of query results, if a query is hidden, an exception is thrown. This is caused by lack of checks to verify that the query result exists before accessing the `series` property. Closes #17112 --- .../app/plugins/datasource/cloudwatch/datasource.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index bd2bd67248ca..5a92122f221c 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -149,12 +149,14 @@ export default class CloudWatchDatasource extends DataSourceApi if (res.results) { for (const query of request.queries) { const queryRes = res.results[query.refId]; - for (const series of queryRes.series) { - const s = { target: series.name, datapoints: series.points } as any; - if (queryRes.meta.unit) { - s.unit = queryRes.meta.unit; + if (queryRes) { + for (const series of queryRes.series) { + const s = { target: series.name, datapoints: series.points } as any; + if (queryRes.meta.unit) { + s.unit = queryRes.meta.unit; + } + data.push(s); } - data.push(s); } } } From afbdfe7cb4540f1027050f46d93cccc093c6256c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 29 May 2019 09:37:29 +0200 Subject: [PATCH 87/92] NewDataSourcePage: Add Grafana Cloud link (#17324) * NewDataSource: adding initial grafana cloud link * Minor update * Updated --- .../datasources/NewDataSourcePage.tsx | 44 ++++++++++++++++--- public/sass/components/_add_data_source.scss | 4 ++ public/sass/components/_buttons.scss | 1 + 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index 6420225f2afe..bd16ba6e8840 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -6,7 +6,7 @@ import { StoreState } from 'app/types'; import { addDataSource, loadDataSourceTypes, setDataSourceTypeSearchQuery } from './state/actions'; import { getDataSourceTypes } from './state/selectors'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; -import { NavModel, DataSourcePluginMeta, List } from '@grafana/ui'; +import { NavModel, DataSourcePluginMeta, List, PluginType } from '@grafana/ui'; export interface Props { navModel: NavModel; @@ -43,6 +43,7 @@ class NewDataSourcePage extends PureComponent { loki: 90, mysql: 80, postgres: 79, + gcloud: -1, }; componentDidMount() { @@ -114,6 +115,8 @@ class NewDataSourcePage extends PureComponent { {} as DataSourceCategories ); + categories['cloud'].push(getGrafanaCloudPhantomPlugin()); + return ( <> {this.categoryInfoList.map(category => ( @@ -174,7 +177,9 @@ interface DataSourceTypeCardProps { } const DataSourceTypeCard: FC = props => { - const { plugin, onClick, onLearnMoreClick } = props; + const { plugin, onLearnMoreClick } = props; + const canSelect = plugin.id !== 'gcloud'; + const onClick = canSelect ? props.onClick : () => {}; // find first plugin info link const learnMoreLink = plugin.info.links && plugin.info.links.length > 0 ? plugin.info.links[0].url : null; @@ -188,16 +193,45 @@ const DataSourceTypeCard: FC = props => {
{learnMoreLink && ( - - Learn more + + Learn more )} - + {canSelect && }
); }; +function getGrafanaCloudPhantomPlugin(): DataSourcePluginMeta { + return { + id: 'gcloud', + name: 'Grafana Cloud', + type: PluginType.datasource, + module: '', + baseUrl: '', + info: { + description: 'Hosted Graphite, Prometheus and Loki', + logos: { small: 'public/img/grafana_icon.svg', large: 'asd' }, + author: { name: 'Grafana Labs' }, + links: [ + { + url: 'https://grafana.com/cloud', + name: 'Learn more', + }, + ], + screenshots: [], + updated: '2019-05-10', + version: '1.0.0', + }, + }; +} + export function getNavModel(): NavModel { const main = { icon: 'gicon gicon-add-datasources', diff --git a/public/sass/components/_add_data_source.scss b/public/sass/components/_add_data_source.scss index c14455d35c14..9e1bcb6ed778 100644 --- a/public/sass/components/_add_data_source.scss +++ b/public/sass/components/_add_data_source.scss @@ -77,6 +77,10 @@ } } +.add-datasource-item-actions__btn-icon { + margin-left: $space-sm; +} + .add-data-source-more { text-align: center; margin: $space-xl; diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index 1a9936bceab0..254ac5906abb 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -70,6 +70,7 @@ @include button-size($btn-padding-y-lg, $space-lg, $font-size-lg, $border-radius-sm); font-weight: normal; height: $height-lg; + .gicon { //font-size: 31px; margin-right: $space-sm; From fd741cbea41fbdd7c77c9c9691276acab877efd0 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Wed, 29 May 2019 10:27:57 +0200 Subject: [PATCH 88/92] Chore: upgrade webpack analyser (#17340) * webpack: upgrade webpack analyser * yarn.lock update --- package.json | 2 +- yarn.lock | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e084f705b024..6c101939295d 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "tslint-react": "3.6.0", "typescript": "3.4.1", "webpack": "4.29.6", - "webpack-bundle-analyzer": "3.1.0", + "webpack-bundle-analyzer": "3.3.2", "webpack-cleanup-plugin": "0.5.1", "webpack-cli": "3.2.3", "webpack-dev-server": "3.2.1", diff --git a/yarn.lock b/yarn.lock index f48cba0ec948..1936ceb817f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2284,10 +2284,10 @@ resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-1.10.35.tgz#4e5c2b1e5b3bf0b863efb8c5e70081f52e6c9518" integrity sha512-SVtqEcudm7yjkTwoRA1gC6CNMhGDdMx4Pg8BPdiqI7bXXdCn1BPmtxgeWYQOgDxrq53/5YTlhq5ULxBEAlWIBg== -"@types/lodash@4.14.119", "@types/lodash@4.14.123": - version "4.14.119" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39" - integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw== +"@types/lodash@4.14.123": + version "4.14.123" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" + integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== "@types/minimatch@*": version "3.0.3" @@ -4470,6 +4470,11 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" +caniuse-db@1.0.30000772: + version "1.0.30000772" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" + integrity sha1-UarokXaChureSj2DGep21qAbUSs= + caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000957, caniuse-lite@^1.0.30000963: version "1.0.30000966" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000966.tgz#f3c6fefacfbfbfb981df6dfa68f2aae7bff41b64" @@ -17029,6 +17034,11 @@ tryor@~0.1.2: resolved "https://registry.yarnpkg.com/tryor/-/tryor-0.1.2.tgz#8145e4ca7caff40acde3ccf946e8b8bb75b4172b" integrity sha1-gUXkynyv9ArN48z5Rui4u3W0Fys= +ts-easing@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" + integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== + ts-jest@24.0.2: version "24.0.2" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.2.tgz#8dde6cece97c31c03e80e474c749753ffd27194d" @@ -17696,10 +17706,10 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-bundle-analyzer@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.1.0.tgz#2f19cbb87bb6d4f3cb4e59cb67c837bd9436e89d" - integrity sha512-nyDyWEs7C6DZlgvu1pR1zzJfIWSiGPbtaByZr8q+Fd2xp70FuM/8ngCJzj3Er1TYRLSFmp1F1OInbEm4DZH8NA== +webpack-bundle-analyzer@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.3.2.tgz#3da733a900f515914e729fcebcd4c40dde71fc6f" + integrity sha512-7qvJLPKB4rRWZGjVp5U1KEjwutbDHSKboAl0IfafnrdXMrgC0tOtZbQD6Rw0u4cmpgRN4O02Fc0t8eAT+FgGzA== dependencies: acorn "^6.0.7" acorn-walk "^6.1.1" From 0a92de623da2deac6b0aa37ebbd6c7546da604b9 Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Wed, 29 May 2019 11:54:07 +0300 Subject: [PATCH 89/92] Build(package.json): improve npm commands (#17022) Remove some of the repetitions in the npm commands --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 6c101939295d..962c443727e6 100644 --- a/package.json +++ b/package.json @@ -140,9 +140,9 @@ }, "scripts": { "dev": "webpack --progress --colors --mode development --config scripts/webpack/webpack.dev.js", - "start": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts core:start --watchTheme", - "start:hot": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts core:start --hot --watchTheme", - "start:ignoreTheme": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts core:start --hot", + "start": "npm run cli -- core:start --watchTheme", + "start:hot": "npm run cli -- core:start --hot --watchTheme", + "start:ignoreTheme": "npm run cli -- core:start --hot", "watch": "yarn start -d watch,start core:start --watchTheme ", "build": "grunt build", "test": "grunt test", @@ -156,13 +156,13 @@ "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", "prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write", + "cli": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts", "gui:tslint": "tslint -c ./packages/grafana-ui/tslint.json --project ./packages/grafana-ui/tsconfig.json", - "gui:build": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:build", - "gui:releasePrepare": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release", + "gui:build": "npm run cli -- gui:build", + "gui:releasePrepare": "npm run cli -- gui:release", "gui:publish": "cd packages/grafana-ui/dist && npm publish --access public", - "gui:release": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release -p --createVersionCommit", - "precommit": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts precommit", - "cli": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts" + "gui:release": "npm run cli -- gui:release -p --createVersionCommit", + "precommit": "npm run cli -- precommit" }, "husky": { "hooks": { From e951e71843e88a8cede85a137e6701d57d91edbd Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 29 May 2019 13:47:05 +0200 Subject: [PATCH 90/92] Explore: Update time range before running queries (#17349) This makes sure that refresh/update/run query are parsing a relative time range to get proper epoch time range before running queries. Fixes #17322 --- public/app/features/explore/state/actionTypes.ts | 1 + public/app/features/explore/state/actions.ts | 6 +++++- public/app/features/explore/state/reducers.test.ts | 11 +++++++++-- public/app/features/explore/state/reducers.ts | 6 ++++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index ff7fdcb55dec..b572b6ca041b 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -230,6 +230,7 @@ export interface LoadExploreDataSourcesPayload { export interface RunQueriesPayload { exploreId: ExploreId; + range: TimeRange; } export interface ResetQueryErrorPayload { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 09f950905ae9..bfeb96aef35b 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -521,6 +521,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false, replaceU datasourceError, containerWidth, mode, + range, } = getState().explore[exploreId]; if (datasourceError) { @@ -538,7 +539,10 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false, replaceU // but we're using the datasource interval limit for now const interval = datasourceInstance.interval; - dispatch(runQueriesAction({ exploreId })); + const timeZone = getTimeZone(getState().user); + const updatedRange = getTimeRange(timeZone, range.raw); + + dispatch(runQueriesAction({ exploreId, range: updatedRange })); // Keep table queries first since they need to return quickly if ((ignoreUIState || showingTable) && mode === ExploreMode.Metrics) { dispatch( diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index da9bdfabe261..e7127eb71592 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -4,6 +4,7 @@ import { exploreReducer, makeInitialUpdateState, initialExploreState, + DEFAULT_RANGE, } from './reducers'; import { ExploreId, @@ -31,7 +32,7 @@ import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { updateLocation } from 'app/core/actions/location'; import { serializeStateToUrlParam } from 'app/core/utils/explore'; import TableModel from 'app/core/table_model'; -import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy } from '@grafana/ui'; +import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy, dateTime } from '@grafana/ui'; describe('Explore item reducer', () => { describe('scanning', () => { @@ -193,6 +194,7 @@ describe('Explore item reducer', () => { it('then it should set correct state', () => { const initalState: Partial = { showingStartPage: true, + range: null, }; const expectedState = { queryIntervals: { @@ -200,11 +202,16 @@ describe('Explore item reducer', () => { intervalMs: 1000, }, showingStartPage: false, + range: { + from: dateTime(), + to: dateTime(), + raw: DEFAULT_RANGE, + }, }; reducerTester() .givenReducer(itemReducer, initalState) - .whenActionIsDispatched(runQueriesAction({ exploreId: ExploreId.left })) + .whenActionIsDispatched(runQueriesAction({ exploreId: ExploreId.left, range: expectedState.range })) .thenStateShouldEqual(expectedState); }); }); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 1291f3d749bb..208825c9c19a 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -599,8 +599,9 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }) .addMapper({ filter: runQueriesAction, - mapper: (state): ExploreItemState => { - const { range, datasourceInstance, containerWidth } = state; + mapper: (state, action): ExploreItemState => { + const { range } = action.payload; + const { datasourceInstance, containerWidth } = state; let interval = '1s'; if (datasourceInstance && datasourceInstance.interval) { interval = datasourceInstance.interval; @@ -608,6 +609,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const queryIntervals = getIntervals(range, interval, containerWidth); return { ...state, + range, queryIntervals, showingStartPage: false, }; From d4ef19737eb64924c7e4802c0241442382f5490d Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Wed, 29 May 2019 15:55:51 +0300 Subject: [PATCH 91/92] Enterprise: remove gofakeit dep (#17344) * Enterprise: remove gofakeit dep Since we decided to use our uuid generation inside util module * Enterprise: result of execution of `go mod ...` --- go.mod | 2 - go.sum | 2 - pkg/extensions/main.go | 1 - .../brianvoe/gofakeit/BENCHMARKS.md | 134 --------- .../brianvoe/gofakeit/CODE_OF_CONDUCT.md | 46 ---- .../brianvoe/gofakeit/CONTRIBUTING.md | 1 - .../github.com/brianvoe/gofakeit/LICENSE.txt | 20 -- vendor/github.com/brianvoe/gofakeit/README.md | 254 ------------------ vendor/github.com/brianvoe/gofakeit/TODO.txt | 3 - .../github.com/brianvoe/gofakeit/address.go | 131 --------- vendor/github.com/brianvoe/gofakeit/beer.go | 45 ---- vendor/github.com/brianvoe/gofakeit/bool.go | 10 - vendor/github.com/brianvoe/gofakeit/color.go | 44 --- .../github.com/brianvoe/gofakeit/company.go | 30 --- .../github.com/brianvoe/gofakeit/contact.go | 40 --- .../github.com/brianvoe/gofakeit/currency.go | 38 --- .../brianvoe/gofakeit/data/address.go | 15 -- .../github.com/brianvoe/gofakeit/data/beer.go | 10 - .../brianvoe/gofakeit/data/colors.go | 7 - .../brianvoe/gofakeit/data/company.go | 9 - .../brianvoe/gofakeit/data/computer.go | 8 - .../brianvoe/gofakeit/data/contact.go | 6 - .../brianvoe/gofakeit/data/currency.go | 7 - .../github.com/brianvoe/gofakeit/data/data.go | 28 -- .../brianvoe/gofakeit/data/datetime.go | 9 - .../brianvoe/gofakeit/data/files.go | 7 - .../brianvoe/gofakeit/data/hacker.go | 20 -- .../brianvoe/gofakeit/data/hipster.go | 6 - .../brianvoe/gofakeit/data/internet.go | 8 - .../github.com/brianvoe/gofakeit/data/job.go | 8 - .../brianvoe/gofakeit/data/log_level.go | 8 - .../brianvoe/gofakeit/data/lorem.go | 6 - .../brianvoe/gofakeit/data/payment.go | 20 -- .../brianvoe/gofakeit/data/person.go | 9 - .../brianvoe/gofakeit/data/status_code.go | 7 - .../brianvoe/gofakeit/data/vehicle.go | 10 - .../github.com/brianvoe/gofakeit/datetime.go | 77 ------ vendor/github.com/brianvoe/gofakeit/doc.go | 10 - vendor/github.com/brianvoe/gofakeit/faker.go | 15 -- vendor/github.com/brianvoe/gofakeit/file.go | 11 - .../github.com/brianvoe/gofakeit/generate.go | 41 --- vendor/github.com/brianvoe/gofakeit/hacker.go | 35 --- .../github.com/brianvoe/gofakeit/hipster.go | 20 -- vendor/github.com/brianvoe/gofakeit/image.go | 8 - .../github.com/brianvoe/gofakeit/internet.go | 55 ---- vendor/github.com/brianvoe/gofakeit/job.go | 34 --- .../github.com/brianvoe/gofakeit/log_level.go | 15 -- vendor/github.com/brianvoe/gofakeit/logo.png | Bin 36022 -> 0 bytes vendor/github.com/brianvoe/gofakeit/misc.go | 132 --------- vendor/github.com/brianvoe/gofakeit/name.go | 26 -- vendor/github.com/brianvoe/gofakeit/number.go | 84 ------ .../github.com/brianvoe/gofakeit/password.go | 68 ----- .../github.com/brianvoe/gofakeit/payment.go | 81 ------ vendor/github.com/brianvoe/gofakeit/person.go | 45 ---- .../brianvoe/gofakeit/status_code.go | 11 - vendor/github.com/brianvoe/gofakeit/string.go | 48 ---- vendor/github.com/brianvoe/gofakeit/struct.go | 87 ------ vendor/github.com/brianvoe/gofakeit/unique.go | 34 --- .../brianvoe/gofakeit/user_agent.go | 92 ------- .../github.com/brianvoe/gofakeit/vehicle.go | 55 ---- vendor/github.com/brianvoe/gofakeit/words.go | 100 ------- vendor/github.com/robfig/cron/README.md | 2 +- vendor/github.com/robfig/cron/doc.go | 2 +- vendor/modules.txt | 3 - 64 files changed, 2 insertions(+), 2198 deletions(-) delete mode 100644 vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md delete mode 100644 vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md delete mode 100644 vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md delete mode 100644 vendor/github.com/brianvoe/gofakeit/LICENSE.txt delete mode 100644 vendor/github.com/brianvoe/gofakeit/README.md delete mode 100644 vendor/github.com/brianvoe/gofakeit/TODO.txt delete mode 100644 vendor/github.com/brianvoe/gofakeit/address.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/beer.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/bool.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/color.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/company.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/contact.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/currency.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/address.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/beer.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/colors.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/company.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/computer.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/contact.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/currency.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/data.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/datetime.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/files.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/hacker.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/hipster.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/internet.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/job.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/log_level.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/lorem.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/payment.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/person.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/status_code.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/data/vehicle.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/datetime.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/doc.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/faker.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/file.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/generate.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/hacker.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/hipster.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/image.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/internet.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/job.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/log_level.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/logo.png delete mode 100644 vendor/github.com/brianvoe/gofakeit/misc.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/name.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/number.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/password.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/payment.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/person.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/status_code.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/string.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/struct.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/unique.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/user_agent.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/vehicle.go delete mode 100644 vendor/github.com/brianvoe/gofakeit/words.go diff --git a/go.mod b/go.mod index 619f5183a5c7..1730235d6173 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,8 @@ require ( github.com/aws/aws-sdk-go v1.18.5 github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 - github.com/brianvoe/gofakeit v3.17.0+incompatible github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/codegangsta/cli v1.20.0 - github.com/davecgh/go-spew v1.1.1 github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect github.com/facebookgo/inject v0.0.0-20180706035515-f23751cae28b diff --git a/go.sum b/go.sum index 3c77812fbf7b..55223ecbc74e 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= -github.com/brianvoe/gofakeit v3.17.0+incompatible h1:C1+30+c0GtjgGDtRC+iePZeP1WMiwsWCELNJhmc7aIc= -github.com/brianvoe/gofakeit v3.17.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index cbe9ec2b7b07..6ee742a4d8e3 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -1,7 +1,6 @@ package extensions import ( - _ "github.com/brianvoe/gofakeit" _ "github.com/gobwas/glob" _ "github.com/robfig/cron" _ "gopkg.in/square/go-jose.v2" diff --git a/vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md b/vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md deleted file mode 100644 index ec6e6d7a3767..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md +++ /dev/null @@ -1,134 +0,0 @@ -go test -bench=. -benchmem -goos: darwin -goarch: amd64 -pkg: github.com/brianvoe/gofakeit -Table generated with tablesgenerator.com/markdown_tables - -| Benchmark | Ops | CPU | MEM | MEM alloc | -|---------------------------------|-----------|-------------|------------|--------------| -| BenchmarkAddress-4 | 1000000 | 1998 ns/op | 248 B/op | 7 allocs/op | -| BenchmarkStreet-4 | 1000000 | 1278 ns/op | 62 B/op | 3 allocs/op | -| BenchmarkStreetNumber-4 | 5000000 | 344 ns/op | 36 B/op | 2 allocs/op | -| BenchmarkStreetPrefix-4 | 10000000 | 121 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkStreetName-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkStreetSuffix-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkCity-4 | 5000000 | 326 ns/op | 15 B/op | 1 allocs/op | -| BenchmarkState-4 | 10000000 | 120 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkStateAbr-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkZip-4 | 5000000 | 315 ns/op | 5 B/op | 1 allocs/op | -| BenchmarkCountry-4 | 10000000 | 126 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkCountryAbr-4 | 10000000 | 123 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLatitude-4 | 100000000 | 23.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLongitude-4 | 100000000 | 23.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLatitudeInRange-4 | 50000000 | 27.7 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLongitudeInRange-4 | 50000000 | 27.8 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBeerName-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBeerStyle-4 | 10000000 | 119 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBeerHop-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBeerYeast-4 | 20000000 | 106 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBeerMalt-4 | 20000000 | 114 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBeerIbu-4 | 20000000 | 71.0 ns/op | 8 B/op | 1 allocs/op | -| BenchmarkBeerAlcohol-4 | 5000000 | 335 ns/op | 40 B/op | 3 allocs/op | -| BenchmarkBeerBlg-4 | 5000000 | 338 ns/op | 48 B/op | 3 allocs/op | -| BenchmarkBool-4 | 50000000 | 34.2 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkColor-4 | 20000000 | 112 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkSafeColor-4 | 20000000 | 102 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHexColor-4 | 3000000 | 491 ns/op | 24 B/op | 3 allocs/op | -| BenchmarkRGBColor-4 | 20000000 | 103 ns/op | 32 B/op | 1 allocs/op | -| BenchmarkCompany-4 | 5000000 | 353 ns/op | 22 B/op | 1 allocs/op | -| BenchmarkCompanySuffix-4 | 20000000 | 89.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBuzzWord-4 | 20000000 | 99.0 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkBS-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkContact-4 | 1000000 | 1121 ns/op | 178 B/op | 7 allocs/op | -| BenchmarkPhone-4 | 5000000 | 346 ns/op | 16 B/op | 1 allocs/op | -| BenchmarkPhoneFormatted-4 | 3000000 | 456 ns/op | 16 B/op | 1 allocs/op | -| BenchmarkEmail-4 | 2000000 | 715 ns/op | 130 B/op | 5 allocs/op | -| BenchmarkCurrency-4 | 10000000 | 125 ns/op | 32 B/op | 1 allocs/op | -| BenchmarkCurrencyShort-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkCurrencyLong-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkPrice-4 | 50000000 | 27.2 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkDate-4 | 5000000 | 371 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkDateRange-4 | 10000000 | 238 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkMonth-4 | 30000000 | 44.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkDay-4 | 50000000 | 39.2 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkWeekDay-4 | 30000000 | 44.7 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkYear-4 | 20000000 | 115 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHour-4 | 30000000 | 39.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkMinute-4 | 50000000 | 40.4 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkSecond-4 | 30000000 | 40.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkNanoSecond-4 | 30000000 | 42.2 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkTimeZone-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkTimeZoneFull-4 | 20000000 | 118 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkTimeZoneAbv-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkTimeZoneOffset-4 | 10000000 | 147 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkMimeType-4 | 20000000 | 99.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkExtension-4 | 20000000 | 109 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkGenerate-4 | 1000000 | 1588 ns/op | 414 B/op | 11 allocs/op | -| BenchmarkHackerPhrase-4 | 300000 | 4576 ns/op | 2295 B/op | 26 allocs/op | -| BenchmarkHackerAbbreviation-4 | 20000000 | 101 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHackerAdjective-4 | 20000000 | 101 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHackerNoun-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHackerVerb-4 | 20000000 | 113 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHackerIngverb-4 | 20000000 | 98.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHipsterWord-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkHipsterSentence-4 | 1000000 | 1636 ns/op | 353 B/op | 3 allocs/op | -| BenchmarkHipsterParagraph-4 | 50000 | 31677 ns/op | 12351 B/op | 64 allocs/op | -| BenchmarkImageURL-4 | 20000000 | 108 ns/op | 38 B/op | 3 allocs/op | -| BenchmarkDomainName-4 | 3000000 | 491 ns/op | 76 B/op | 3 allocs/op | -| BenchmarkDomainSuffix-4 | 20000000 | 99.4 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkURL-4 | 1000000 | 1201 ns/op | 278 B/op | 8 allocs/op | -| BenchmarkHTTPMethod-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkIPv4Address-4 | 3000000 | 407 ns/op | 48 B/op | 5 allocs/op | -| BenchmarkIPv6Address-4 | 3000000 | 552 ns/op | 96 B/op | 7 allocs/op | -| BenchmarkUsername-4 | 5000000 | 307 ns/op | 16 B/op | 2 allocs/op | -| BenchmarkJob-4 | 2000000 | 726 ns/op | 86 B/op | 2 allocs/op | -| BenchmarkJobTitle-4 | 20000000 | 98.7 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkJobDescriptor-4 | 20000000 | 98.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkJobLevel-4 | 20000000 | 110 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLogLevel-4 | 20000000 | 107 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkReplaceWithNumbers-4 | 3000000 | 570 ns/op | 32 B/op | 1 allocs/op | -| BenchmarkName-4 | 5000000 | 285 ns/op | 17 B/op | 1 allocs/op | -| BenchmarkFirstName-4 | 20000000 | 102 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLastName-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkNamePrefix-4 | 20000000 | 98.0 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkNameSuffix-4 | 20000000 | 109 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkNumber-4 | 50000000 | 34.5 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkUint8-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkUint16-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkUint32-4 | 50000000 | 27.0 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkUint64-4 | 50000000 | 34.6 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkInt8-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkInt16-4 | 50000000 | 28.4 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkInt32-4 | 50000000 | 27.0 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkInt64-4 | 50000000 | 34.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkFloat32-4 | 50000000 | 27.7 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkFloat32Range-4 | 50000000 | 27.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkFloat64-4 | 50000000 | 25.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkFloat64Range-4 | 50000000 | 26.5 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkNumerify-4 | 5000000 | 354 ns/op | 16 B/op | 1 allocs/op | -| BenchmarkShuffleInts-4 | 10000000 | 226 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkPassword-4 | 2000000 | 655 ns/op | 304 B/op | 6 allocs/op | -| BenchmarkCreditCard-4 | 2000000 | 997 ns/op | 88 B/op | 4 allocs/op | -| BenchmarkCreditCardType-4 | 20000000 | 92.7 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkCreditCardNumber-4 | 3000000 | 572 ns/op | 16 B/op | 1 allocs/op | -| BenchmarkCreditCardNumberLuhn-4 | 300000 | 5815 ns/op | 159 B/op | 9 allocs/op | -| BenchmarkCreditCardExp-4 | 10000000 | 129 ns/op | 5 B/op | 1 allocs/op | -| BenchmarkCreditCardCvv-4 | 10000000 | 128 ns/op | 3 B/op | 1 allocs/op | -| BenchmarkSSN-4 | 20000000 | 84.2 ns/op | 16 B/op | 1 allocs/op | -| BenchmarkGender-4 | 50000000 | 38.0 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkPerson-4 | 300000 | 5563 ns/op | 805 B/op | 26 allocs/op | -| BenchmarkSimpleStatusCode-4 | 20000000 | 72.9 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkStatusCode-4 | 20000000 | 75.8 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLetter-4 | 50000000 | 38.4 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkDigit-4 | 50000000 | 38.2 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkLexify-4 | 10000000 | 222 ns/op | 8 B/op | 1 allocs/op | -| BenchmarkShuffleStrings-4 | 10000000 | 197 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkUUID-4 | 20000000 | 106 ns/op | 48 B/op | 1 allocs/op | -| BenchmarkUserAgent-4 | 1000000 | 1236 ns/op | 305 B/op | 5 allocs/op | -| BenchmarkChromeUserAgent-4 | 2000000 | 881 ns/op | 188 B/op | 5 allocs/op | -| BenchmarkFirefoxUserAgent-4 | 1000000 | 1595 ns/op | 386 B/op | 7 allocs/op | -| BenchmarkSafariUserAgent-4 | 1000000 | 1396 ns/op | 551 B/op | 7 allocs/op | -| BenchmarkOperaUserAgent-4 | 2000000 | 950 ns/op | 216 B/op | 5 allocs/op | -| BenchmarkWord-4 | 20000000 | 99.1 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkSentence-4 | 1000000 | 1540 ns/op | 277 B/op | 2 allocs/op | -| BenchmarkParagraph-4 | 50000 | 30978 ns/op | 11006 B/op | 61 allocs/op | \ No newline at end of file diff --git a/vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md b/vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md deleted file mode 100644 index 99d12c90fecf..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at brian@webiswhatido.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md b/vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md deleted file mode 100644 index 5a4812c28ee8..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -# Make a pull request and submit it and ill take a look at it. Thanks! diff --git a/vendor/github.com/brianvoe/gofakeit/LICENSE.txt b/vendor/github.com/brianvoe/gofakeit/LICENSE.txt deleted file mode 100644 index 21984c9d5eaa..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) [year] [fullname] - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/brianvoe/gofakeit/README.md b/vendor/github.com/brianvoe/gofakeit/README.md deleted file mode 100644 index 4e3723fd5117..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/README.md +++ /dev/null @@ -1,254 +0,0 @@ -![alt text](https://raw.githubusercontent.com/brianvoe/gofakeit/master/logo.png) - -# gofakeit [![Go Report Card](https://goreportcard.com/badge/github.com/brianvoe/gofakeit)](https://goreportcard.com/report/github.com/brianvoe/gofakeit) [![Build Status](https://travis-ci.org/brianvoe/gofakeit.svg?branch=master)](https://travis-ci.org/brianvoe/gofakeit) [![codecov.io](https://codecov.io/github/brianvoe/gofakeit/branch/master/graph/badge.svg)](https://codecov.io/github/brianvoe/gofakeit) [![GoDoc](https://godoc.org/github.com/brianvoe/gofakeit?status.svg)](https://godoc.org/github.com/brianvoe/gofakeit) [![license](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/brianvoe/gofakeit/master/LICENSE.txt) -Random data generator written in go - -Buy Me A Coffee - -### Features -- Every function has an example and a benchmark, -[see benchmarks](https://github.com/brianvoe/gofakeit/blob/master/BENCHMARKS.md) -- Zero dependencies -- Randomizes user defined structs -- Numerous functions for regular use - -### 120+ Functions!!! -If there is something that is generic enough missing from this package [add an issue](https://github.com/brianvoe/gofakeit/issues) and let me know what you need. -Most of the time i'll add it! - -## Person -```go -Person() *PersonInfo -Name() string -NamePrefix() string -NameSuffix() string -FirstName() string -LastName() string -Gender() string -SSN() string -Contact() *ContactInfo -Email() string -Phone() string -PhoneFormatted() string -Username() string -Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string -``` - -## Address -```go -Address() *AddressInfo -City() string -Country() string -CountryAbr() string -State() string -StateAbr() string -StatusCode() string -Street() string -StreetName() string -StreetNumber() string -StreetPrefix() string -StreetSuffix() string -Zip() string -Latitude() float64 -LatitudeInRange() (float64, error) -Longitude() float64 -LongitudeInRange() (float64, error) -``` - -## Beer -```go -BeerAlcohol() string -BeerBlg() string -BeerHop() string -BeerIbu() string -BeerMalt() string -BeerName() string -BeerStyle() string -BeerYeast() string -``` - -## Cars -```go -Vehicle() *VehicleInfo -CarMaker() string -CarModel() string -VehicleType() string -FuelType() string -TransmissionGearType() string -``` - -## Words -```go -Word() string -Sentence(wordCount int) string -Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string -Question() string -Quote() string -``` - -## Misc -```go -Struct(v interface{}) -Generate() string -Bool() bool -UUID() string -``` - -## Colors -```go -Color() string -HexColor() string -RGBColor() string -SafeColor() string -``` - -## Internet -```go -URL() string -ImageURL(width int, height int) string -DomainName() string -DomainSuffix() string -IPv4Address() string -IPv6Address() string -SimpleStatusCode() int -LogLevel(logType string) string -HTTPMethod() string -UserAgent() string -ChromeUserAgent() string -FirefoxUserAgent() string -OperaUserAgent() string -SafariUserAgent() string -``` - -## Date/Time -```go -Date() time.Time -DateRange(start, end time.Time) time.Time -NanoSecond() int -Second() int -Minute() int -Hour() int -Month() string -Day() int -WeekDay() string -Year() int -TimeZone() string -TimeZoneAbv() string -TimeZoneFull() string -TimeZoneOffset() float32 -``` - -## Payment -```go -Price(min, max float64) float64 -CreditCard() *CreditCardInfo -CreditCardCvv() string -CreditCardExp() string -CreditCardNumber() int -CreditCardNumberLuhn() int -CreditCardType() string -Currency() *CurrencyInfo -CurrencyLong() string -CurrencyShort() string -``` - -## Company -```go -BS() string -BuzzWord() string -Company() string -CompanySuffix() string -Job() *JobInfo -JobDescriptor() string -JobLevel() string -JobTitle() string -``` - -## Hacker -```go -HackerAbbreviation() string -HackerAdjective() string -HackerIngverb() string -HackerNoun() string -HackerPhrase() string -HackerVerb() string -``` - -## Hipster -```go -HipsterWord() string -HipsterSentence(wordCount int) string -HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string -``` - -## File -```go -Extension() string -MimeType() string -``` - -## Numbers -```go -Number(min int, max int) int -Numerify(str string) string -Int8() int8 -Int16() int16 -Int32() int32 -Int64() int64 -Uint8() uint8 -Uint16() uint16 -Uint32() uint32 -Uint64() uint64 -Float32() float32 -Float32Range(min, max float32) float32 -Float64() float64 -Float64Range(min, max float64) float64 -ShuffleInts(a []int) -``` - -## String -```go -Digit() string -Letter() string -Lexify(str string) string -RandString(a []string) string -ShuffleStrings(a []string) -``` - -## Documentation -[![GoDoc](https://godoc.org/github.com/brianvoe/gofakeit?status.svg)](https://godoc.org/github.com/brianvoe/gofakeit) - -## Example -```go -import "github.com/brianvoe/gofakeit" - -gofakeit.Name() // Markus Moen -gofakeit.Email() // alaynawuckert@kozey.biz -gofakeit.Phone() // (570)245-7485 -gofakeit.BS() // front-end -gofakeit.BeerName() // Duvel -gofakeit.Color() // MediumOrchid -gofakeit.Company() // Moen, Pagac and Wuckert -gofakeit.CreditCardNumber() // 4287271570245748 -gofakeit.HackerPhrase() // Connecting the array won't do anything, we need to generate the haptic COM driver! -gofakeit.JobTitle() // Director -gofakeit.Password(true, true, true, true, true, 32) // WV10MzLxq2DX79w1omH97_0ga59j8!kj -gofakeit.CurrencyShort() // USD -// 120+ more!!! - -// Create structs with random injected data -type Foo struct { - Bar string - Baz string - Int int - Pointer *int - Skip *string `fake:"skip"` // Set to "skip" to not generate data for -} -var f Foo -gofakeit.Struct(&f) -fmt.Printf("f.Bar:%s\n", f.Bar) // f.Bar:hrukpttuezptneuvunh -fmt.Printf("f.Baz:%s\n", f.Baz) // f.Baz:uksqvgzadxlgghejkmv -fmt.Printf("f.Int:%d\n", f.Int) // f.Int:-7825289004089916589 -fmt.Printf("f.Pointer:%d\n", *f.Pointer) // f.Pointer:-343806609094473732 -fmt.Printf("f.Skip:%v\n", f.Skip) // f.Skip: -``` diff --git a/vendor/github.com/brianvoe/gofakeit/TODO.txt b/vendor/github.com/brianvoe/gofakeit/TODO.txt deleted file mode 100644 index 7a492842136b..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/TODO.txt +++ /dev/null @@ -1,3 +0,0 @@ -* Take a look at [chance.js](http://chancejs.com/) and see if i missed anything. -* Look into [National Baby Name List](http://www.ssa.gov/oact/babynames/limits.html) and see if that makes sense to replace over what we currently have. -* Look at [data list](https://github.com/dariusk/corpora/tree/master/data) and see if it makes sense to add that data in or if it seems unncessary. diff --git a/vendor/github.com/brianvoe/gofakeit/address.go b/vendor/github.com/brianvoe/gofakeit/address.go deleted file mode 100644 index 82fc6b00e191..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/address.go +++ /dev/null @@ -1,131 +0,0 @@ -package gofakeit - -import ( - "errors" - "math/rand" - "strings" -) - -// AddressInfo is a struct full of address information -type AddressInfo struct { - Address string - Street string - City string - State string - Zip string - Country string - Latitude float64 - Longitude float64 -} - -// Address will generate a struct of address information -func Address() *AddressInfo { - street := Street() - city := City() - state := State() - zip := Zip() - - return &AddressInfo{ - Address: street + ", " + city + ", " + state + " " + zip, - Street: street, - City: city, - State: state, - Zip: zip, - Country: Country(), - Latitude: Latitude(), - Longitude: Longitude(), - } -} - -// Street will generate a random address street string -func Street() (street string) { - switch randInt := randIntRange(1, 2); randInt { - case 1: - street = StreetNumber() + " " + StreetPrefix() + " " + StreetName() + StreetSuffix() - case 2: - street = StreetNumber() + " " + StreetName() + StreetSuffix() - } - - return -} - -// StreetNumber will generate a random address street number string -func StreetNumber() string { - return strings.TrimLeft(replaceWithNumbers(getRandValue([]string{"address", "number"})), "0") -} - -// StreetPrefix will generate a random address street prefix string -func StreetPrefix() string { - return getRandValue([]string{"address", "street_prefix"}) -} - -// StreetName will generate a random address street name string -func StreetName() string { - return getRandValue([]string{"address", "street_name"}) -} - -// StreetSuffix will generate a random address street suffix string -func StreetSuffix() string { - return getRandValue([]string{"address", "street_suffix"}) -} - -// City will generate a random city string -func City() (city string) { - switch randInt := randIntRange(1, 3); randInt { - case 1: - city = FirstName() + StreetSuffix() - case 2: - city = LastName() + StreetSuffix() - case 3: - city = StreetPrefix() + " " + LastName() - } - - return -} - -// State will generate a random state string -func State() string { - return getRandValue([]string{"address", "state"}) -} - -// StateAbr will generate a random abbreviated state string -func StateAbr() string { - return getRandValue([]string{"address", "state_abr"}) -} - -// Zip will generate a random Zip code string -func Zip() string { - return replaceWithNumbers(getRandValue([]string{"address", "zip"})) -} - -// Country will generate a random country string -func Country() string { - return getRandValue([]string{"address", "country"}) -} - -// CountryAbr will generate a random abbreviated country string -func CountryAbr() string { - return getRandValue([]string{"address", "country_abr"}) -} - -// Latitude will generate a random latitude float64 -func Latitude() float64 { return (rand.Float64() * 180) - 90 } - -// LatitudeInRange will generate a random latitude within the input range -func LatitudeInRange(min, max float64) (float64, error) { - if min > max || min < -90 || min > 90 || max < -90 || max > 90 { - return 0, errors.New("input range is invalid") - } - return randFloat64Range(min, max), nil -} - -// Longitude will generate a random longitude float64 -func Longitude() float64 { return (rand.Float64() * 360) - 180 } - -// LongitudeInRange will generate a random longitude within the input range -func LongitudeInRange(min, max float64) (float64, error) { - if min > max || min < -180 || min > 180 || max < -180 || max > 180 { - return 0, errors.New("input range is invalid") - } - return randFloat64Range(min, max), nil -} diff --git a/vendor/github.com/brianvoe/gofakeit/beer.go b/vendor/github.com/brianvoe/gofakeit/beer.go deleted file mode 100644 index 53297d537809..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/beer.go +++ /dev/null @@ -1,45 +0,0 @@ -package gofakeit - -import "strconv" - -// Faker::Beer.blg #=> "18.5°Blg" - -// BeerName will return a random beer name -func BeerName() string { - return getRandValue([]string{"beer", "name"}) -} - -// BeerStyle will return a random beer style -func BeerStyle() string { - return getRandValue([]string{"beer", "style"}) -} - -// BeerHop will return a random beer hop -func BeerHop() string { - return getRandValue([]string{"beer", "hop"}) -} - -// BeerYeast will return a random beer yeast -func BeerYeast() string { - return getRandValue([]string{"beer", "yeast"}) -} - -// BeerMalt will return a random beer malt -func BeerMalt() string { - return getRandValue([]string{"beer", "malt"}) -} - -// BeerIbu will return a random beer ibu value between 10 and 100 -func BeerIbu() string { - return strconv.Itoa(randIntRange(10, 100)) + " IBU" -} - -// BeerAlcohol will return a random beer alcohol level between 2.0 and 10.0 -func BeerAlcohol() string { - return strconv.FormatFloat(randFloat64Range(2.0, 10.0), 'f', 1, 64) + "%" -} - -// BeerBlg will return a random beer blg between 5.0 and 20.0 -func BeerBlg() string { - return strconv.FormatFloat(randFloat64Range(5.0, 20.0), 'f', 1, 64) + "°Blg" -} diff --git a/vendor/github.com/brianvoe/gofakeit/bool.go b/vendor/github.com/brianvoe/gofakeit/bool.go deleted file mode 100644 index f63eeedd3241..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/bool.go +++ /dev/null @@ -1,10 +0,0 @@ -package gofakeit - -// Bool will generate a random boolean value -func Bool() bool { - if randIntRange(0, 1) == 1 { - return true - } - - return false -} diff --git a/vendor/github.com/brianvoe/gofakeit/color.go b/vendor/github.com/brianvoe/gofakeit/color.go deleted file mode 100644 index 63a737e99a62..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/color.go +++ /dev/null @@ -1,44 +0,0 @@ -package gofakeit - -import "math/rand" - -// Color will generate a random color string -func Color() string { - return getRandValue([]string{"color", "full"}) -} - -// SafeColor will generate a random safe color string -func SafeColor() string { - return getRandValue([]string{"color", "safe"}) -} - -// HexColor will generate a random hexadecimal color string -func HexColor() string { - color := make([]byte, 6) - hashQuestion := []byte("?#") - for i := 0; i < 6; i++ { - color[i] = hashQuestion[rand.Intn(2)] - } - - return "#" + replaceWithLetters(replaceWithNumbers(string(color))) - - // color := "" - // for i := 1; i <= 6; i++ { - // color += RandString([]string{"?", "#"}) - // } - - // // Replace # with number - // color = replaceWithNumbers(color) - - // // Replace ? with letter - // for strings.Count(color, "?") > 0 { - // color = strings.Replace(color, "?", RandString(letters), 1) - // } - - // return "#" + color -} - -// RGBColor will generate a random int slice color -func RGBColor() []int { - return []int{randIntRange(0, 255), randIntRange(0, 255), randIntRange(0, 255)} -} diff --git a/vendor/github.com/brianvoe/gofakeit/company.go b/vendor/github.com/brianvoe/gofakeit/company.go deleted file mode 100644 index abdb2aa698f1..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/company.go +++ /dev/null @@ -1,30 +0,0 @@ -package gofakeit - -// Company will generate a random company name string -func Company() (company string) { - switch randInt := randIntRange(1, 3); randInt { - case 1: - company = LastName() + ", " + LastName() + " and " + LastName() - case 2: - company = LastName() + "-" + LastName() - case 3: - company = LastName() + " " + CompanySuffix() - } - - return -} - -// CompanySuffix will generate a random company suffix string -func CompanySuffix() string { - return getRandValue([]string{"company", "suffix"}) -} - -// BuzzWord will generate a random company buzz word string -func BuzzWord() string { - return getRandValue([]string{"company", "buzzwords"}) -} - -// BS will generate a random company bs string -func BS() string { - return getRandValue([]string{"company", "bs"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/contact.go b/vendor/github.com/brianvoe/gofakeit/contact.go deleted file mode 100644 index 1eb0ae05303d..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/contact.go +++ /dev/null @@ -1,40 +0,0 @@ -package gofakeit - -import ( - "strings" -) - -// ContactInfo struct full of contact info -type ContactInfo struct { - Phone string - Email string -} - -// Contact will generate a struct with information randomly populated contact information -func Contact() *ContactInfo { - return &ContactInfo{ - Phone: Phone(), - Email: Email(), - } -} - -// Phone will generate a random phone number string -func Phone() string { - return replaceWithNumbers("##########") -} - -// PhoneFormatted will generate a random phone number string -func PhoneFormatted() string { - return replaceWithNumbers(getRandValue([]string{"contact", "phone"})) -} - -// Email will generate a random email string -func Email() string { - var email string - - email = getRandValue([]string{"person", "first"}) + getRandValue([]string{"person", "last"}) - email += "@" - email += getRandValue([]string{"person", "last"}) + "." + getRandValue([]string{"internet", "domain_suffix"}) - - return strings.ToLower(email) -} diff --git a/vendor/github.com/brianvoe/gofakeit/currency.go b/vendor/github.com/brianvoe/gofakeit/currency.go deleted file mode 100644 index c25e4d62a7aa..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/currency.go +++ /dev/null @@ -1,38 +0,0 @@ -package gofakeit - -import ( - "math" - "math/rand" - - "github.com/brianvoe/gofakeit/data" -) - -// CurrencyInfo is a struct of currency information -type CurrencyInfo struct { - Short string - Long string -} - -// Currency will generate a struct with random currency information -func Currency() *CurrencyInfo { - index := rand.Intn(len(data.Data["currency"]["short"])) - return &CurrencyInfo{ - Short: data.Data["currency"]["short"][index], - Long: data.Data["currency"]["long"][index], - } -} - -// CurrencyShort will generate a random short currency value -func CurrencyShort() string { - return getRandValue([]string{"currency", "short"}) -} - -// CurrencyLong will generate a random long currency name -func CurrencyLong() string { - return getRandValue([]string{"currency", "long"}) -} - -// Price will take in a min and max value and return a formatted price -func Price(min, max float64) float64 { - return math.Floor(randFloat64Range(min, max)*100) / 100 -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/address.go b/vendor/github.com/brianvoe/gofakeit/data/address.go deleted file mode 100644 index 671cdda91375..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/address.go +++ /dev/null @@ -1,15 +0,0 @@ -package data - -// Address consists of address information -var Address = map[string][]string{ - "number": {"#####", "####", "###"}, - "street_prefix": {"North", "East", "West", "South", "New", "Lake", "Port"}, - "street_name": {"Alley", "Avenue", "Branch", "Bridge", "Brook", "Brooks", "Burg", "Burgs", "Bypass", "Camp", "Canyon", "Cape", "Causeway", "Center", "Centers", "Circle", "Circles", "Cliff", "Cliffs", "Club", "Common", "Corner", "Corners", "Course", "Court", "Courts", "Cove", "Coves", "Creek", "Crescent", "Crest", "Crossing", "Crossroad", "Curve", "Dale", "Dam", "Divide", "Drive", "Drive", "Drives", "Estate", "Estates", "Expressway", "Extension", "Extensions", "Fall", "Falls", "Ferry", "Field", "Fields", "Flat", "Flats", "Ford", "Fords", "Forest", "Forge", "Forges", "Fork", "Forks", "Fort", "Freeway", "Garden", "Gardens", "Gateway", "Glen", "Glens", "Green", "Greens", "Grove", "Groves", "Harbor", "Harbors", "Haven", "Heights", "Highway", "Hill", "Hills", "Hollow", "Inlet", "Inlet", "Island", "Island", "Islands", "Islands", "Isle", "Isle", "Junction", "Junctions", "Key", "Keys", "Knoll", "Knolls", "Lake", "Lakes", "Land", "Landing", "Lane", "Light", "Lights", "Loaf", "Lock", "Locks", "Locks", "Lodge", "Lodge", "Loop", "Mall", "Manor", "Manors", "Meadow", "Meadows", "Mews", "Mill", "Mills", "Mission", "Mission", "Motorway", "Mount", "Mountain", "Mountain", "Mountains", "Mountains", "Neck", "Orchard", "Oval", "Overpass", "Park", "Parks", "Parkway", "Parkways", "Pass", "Passage", "Path", "Pike", "Pine", "Pines", "Place", "Plain", "Plains", "Plains", "Plaza", "Plaza", "Point", "Points", "Port", "Port", "Ports", "Ports", "Prairie", "Prairie", "Radial", "Ramp", "Ranch", "Rapid", "Rapids", "Rest", "Ridge", "Ridges", "River", "Road", "Road", "Roads", "Roads", "Route", "Row", "Rue", "Run", "Shoal", "Shoals", "Shore", "Shores", "Skyway", "Spring", "Springs", "Springs", "Spur", "Spurs", "Square", "Square", "Squares", "Squares", "Station", "Station", "Stravenue", "Stravenue", "Stream", "Stream", "Street", "Street", "Streets", "Summit", "Summit", "Terrace", "Throughway", "Trace", "Track", "Trafficway", "Trail", "Trail", "Tunnel", "Tunnel", "Turnpike", "Turnpike", "Underpass", "Union", "Unions", "Valley", "Valleys", "Via", "Viaduct", "View", "Views", "Village", "Village", "Villages", "Ville", "Vista", "Vista", "Walk", "Walks", "Wall", "Way", "Ways", "Well", "Wells"}, - "street_suffix": {"town", "ton", "land", "ville", "berg", "burgh", "borough", "bury", "view", "port", "mouth", "stad", "furt", "chester", "mouth", "fort", "haven", "side", "shire"}, - "city": {"{address.street_prefix} {name.first}{address.street_suffix}", "{address.street_prefix} {name.first}", "{name.first}{address.street_suffix}", "{name.last}{address.street_suffix}"}, - "state": {"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"}, - "state_abr": {"AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI", "VA", "WA", "WV", "WI", "WY", "AE", "AA", "AP"}, - "zip": {"#####"}, - "country": {"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", "Congo", "Cook Islands", "Costa Rica", "Cote Divoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Faroe Islands", "Falkland Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard Island and McDonald Islands", "Holy See (Vatican City State)", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea", "Korea", "Kuwait", "Kyrgyz Republic", "Lao Peoples Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands Antilles", "Netherlands", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Palestinian Territory", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia (Slovak Republic)", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard & Jan Mayen Islands", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States of America", "United States Minor Outlying Islands", "United States Virgin Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"}, - "country_abr": {"AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CK", "CR", "CI", "HR", "CU", "CY", "CZ", "DK", "DJ", "DM", "DO", "TL", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "FX", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GN", "GW", "GY", "HT", "HM", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IL", "IT", "JM", "JP", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "AN", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "NO", "OM", "PK", "PW", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "KN", "LC", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SK", "SI", "SB", "SO", "ZA", "ES", "LK", "SH", "PM", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VA", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "YU", "ZR", "ZM", "ZW"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/beer.go b/vendor/github.com/brianvoe/gofakeit/data/beer.go deleted file mode 100644 index 1192907d5f29..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/beer.go +++ /dev/null @@ -1,10 +0,0 @@ -package data - -// Beer consists of various beer information -var Beer = map[string][]string{ - "name": {"Pliny The Elder", "Founders Kentucky Breakfast", "Trappistes Rochefort 10", "HopSlam Ale", "Stone Imperial Russian Stout", "St. Bernardus Abt 12", "Founders Breakfast Stout", "Weihenstephaner Hefeweissbier", "Péché Mortel", "Celebrator Doppelbock", "Duvel", "Dreadnaught IPA", "Nugget Nectar", "La Fin Du Monde", "Bourbon County Stout", "Old Rasputin Russian Imperial Stout", "Two Hearted Ale", "Ruination IPA", "Schneider Aventinus", "Double Bastard Ale", "90 Minute IPA", "Hop Rod Rye", "Trappistes Rochefort 8", "Chimay Grande Réserve", "Stone IPA", "Arrogant Bastard Ale", "Edmund Fitzgerald Porter", "Chocolate St", "Oak Aged Yeti Imperial Stout", "Ten FIDY", "Storm King Stout", "Shakespeare Oatmeal", "Alpha King Pale Ale", "Westmalle Trappist Tripel", "Samuel Smith’s Imperial IPA", "Yeti Imperial Stout", "Hennepin", "Samuel Smith’s Oatmeal Stout", "Brooklyn Black", "Oaked Arrogant Bastard Ale", "Sublimely Self-Righteous Ale", "Trois Pistoles", "Bell’s Expedition", "Sierra Nevada Celebration Ale", "Sierra Nevada Bigfoot Barleywine Style Ale", "Racer 5 India Pale Ale, Bear Republic Bre", "Orval Trappist Ale", "Hercules Double IPA", "Maharaj", "Maudite"}, - "hop": {"Ahtanum", "Amarillo", "Bitter Gold", "Bravo", "Brewer’s Gold", "Bullion", "Cascade", "Cashmere", "Centennial", "Chelan", "Chinook", "Citra", "Cluster", "Columbia", "Columbus", "Comet", "Crystal", "Equinox", "Eroica", "Fuggle", "Galena", "Glacier", "Golding", "Hallertau", "Horizon", "Liberty", "Magnum", "Millennium", "Mosaic", "Mt. Hood", "Mt. Rainier", "Newport", "Northern Brewer", "Nugget", "Olympic", "Palisade", "Perle", "Saaz", "Santiam", "Simcoe", "Sorachi Ace", "Sterling", "Summit", "Tahoma", "Tettnang", "TriplePearl", "Ultra", "Vanguard", "Warrior", "Willamette", "Yakima Gol"}, - "yeast": {"1007 - German Ale", "1010 - American Wheat", "1028 - London Ale", "1056 - American Ale", "1084 - Irish Ale", "1098 - British Ale", "1099 - Whitbread Ale", "1187 - Ringwood Ale", "1272 - American Ale II", "1275 - Thames Valley Ale", "1318 - London Ale III", "1332 - Northwest Ale", "1335 - British Ale II", "1450 - Dennys Favorite 50", "1469 - West Yorkshire Ale", "1728 - Scottish Ale", "1968 - London ESB Ale", "2565 - Kölsch", "1214 - Belgian Abbey", "1388 - Belgian Strong Ale", "1762 - Belgian Abbey II", "3056 - Bavarian Wheat Blend", "3068 - Weihenstephan Weizen", "3278 - Belgian Lambic Blend", "3333 - German Wheat", "3463 - Forbidden Fruit", "3522 - Belgian Ardennes", "3638 - Bavarian Wheat", "3711 - French Saison", "3724 - Belgian Saison", "3763 - Roeselare Ale Blend", "3787 - Trappist High Gravity", "3942 - Belgian Wheat", "3944 - Belgian Witbier", "2000 - Budvar Lager", "2001 - Urquell Lager", "2007 - Pilsen Lager", "2035 - American Lager", "2042 - Danish Lager", "2112 - California Lager", "2124 - Bohemian Lager", "2206 - Bavarian Lager", "2278 - Czech Pils", "2308 - Munich Lager", "2633 - Octoberfest Lager Blend", "5112 - Brettanomyces bruxellensis", "5335 - Lactobacillus", "5526 - Brettanomyces lambicus", "5733 - Pediococcus"}, - "malt": {"Black malt", "Caramel", "Carapils", "Chocolate", "Munich", "Caramel", "Carapils", "Chocolate malt", "Munich", "Pale", "Roasted barley", "Rye malt", "Special roast", "Victory", "Vienna", "Wheat mal"}, - "style": {"Light Lager", "Pilsner", "European Amber Lager", "Dark Lager", "Bock", "Light Hybrid Beer", "Amber Hybrid Beer", "English Pale Ale", "Scottish And Irish Ale", "Merican Ale", "English Brown Ale", "Porter", "Stout", "India Pale Ale", "German Wheat And Rye Beer", "Belgian And French Ale", "Sour Ale", "Belgian Strong Ale", "Strong Ale", "Fruit Beer", "Vegetable Beer", "Smoke-flavored", "Wood-aged Beer"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/colors.go b/vendor/github.com/brianvoe/gofakeit/data/colors.go deleted file mode 100644 index 3aca817d69f3..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/colors.go +++ /dev/null @@ -1,7 +0,0 @@ -package data - -// Colors consists of color information -var Colors = map[string][]string{ - "safe": {"black", "maroon", "green", "navy", "olive", "purple", "teal", "lime", "blue", "silver", "gray", "yellow", "fuchsia", "aqua", "white"}, - "full": {"AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "BlanchedAlmond", "Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue", "DarkCyan", "DarkGoldenRod", "DarkGray", "DarkGreen", "DarkKhaki", "DarkMagenta", "DarkOliveGreen", "Darkorange", "DarkOrchid", "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DimGrey", "DodgerBlue", "FireBrick", "FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "GoldenRod", "Gray", "Green", "GreenYellow", "HoneyDew", "HotPink", "IndianRed ", "Indigo ", "Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", "LightCoral", "LightCyan", "LightGoldenRodYellow", "LightGray", "LightGreen", "LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon", "MediumAquaMarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", "Orchid", "PaleGoldenRod", "PaleGreen", "PaleTurquoise", "PaleVioletRed", "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown", "Salmon", "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver", "SkyBlue", "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", "Teal", "Thistle", "Tomato", "Turquoise", "Violet", "Wheat", "White", "WhiteSmoke", "Yellow", "YellowGreen"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/company.go b/vendor/github.com/brianvoe/gofakeit/data/company.go deleted file mode 100644 index b2a3790c7c68..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/company.go +++ /dev/null @@ -1,9 +0,0 @@ -package data - -// Company consists of company information -var Company = map[string][]string{ - "name": {"{person.last} {company.suffix}", "{person.last}-{person.last}", "{person.last}, {person.last} and {person.last}"}, - "suffix": {"Inc", "and Sons", "LLC", "Group"}, - "buzzwords": {"Adaptive", "Advanced", "Ameliorated", "Assimilated", "Automated", "Balanced", "Business-focused", "Centralized", "Cloned", "Compatible", "Configurable", "Cross-group", "Cross-platform", "Customer-focused", "Customizable", "De-engineered", "Decentralized", "Devolved", "Digitized", "Distributed", "Diverse", "Down-sized", "Enhanced", "Enterprise-wide", "Ergonomic", "Exclusive", "Expanded", "Extended", "Face to face", "Focused", "Front-line", "Fully-configurable", "Function-based", "Fundamental", "Future-proofed", "Grass-roots", "Horizontal", "Implemented", "Innovative", "Integrated", "Intuitive", "Inverse", "Managed", "Mandatory", "Monitored", "Multi-channelled", "Multi-lateral", "Multi-layered", "Multi-tiered", "Networked", "Object-based", "Open-architected", "Open-source", "Operative", "Optimized", "Optional", "Organic", "Organized", "Persevering", "Persistent", "Phased", "Polarised", "Pre-emptive", "Proactive", "Profit-focused", "Profound", "Programmable", "Progressive", "Public-key", "Quality-focused", "Re-contextualized", "Re-engineered", "Reactive", "Realigned", "Reduced", "Reverse-engineered", "Right-sized", "Robust", "Seamless", "Secured", "Self-enabling", "Sharable", "Stand-alone", "Streamlined", "Switchable", "Synchronised", "Synergistic", "Synergized", "Team-oriented", "Total", "Triple-buffered", "Universal", "Up-sized", "Upgradable", "User-centric", "User-friendly", "Versatile", "Virtual", "Vision-oriented", "Visionary", "24 hour", "24/7", "3rd generation", "4th generation", "5th generation", "6th generation", "actuating", "analyzing", "asymmetric", "asynchronous", "attitude-oriented", "background", "bandwidth-monitored", "bi-directional", "bifurcated", "bottom-line", "clear-thinking", "client-driven", "client-server", "coherent", "cohesive", "composite", "content-based", "context-sensitive", "contextually-based", "dedicated", "demand-driven", "didactic", "directional", "discrete", "disintermediate", "dynamic", "eco-centric", "empowering", "encompassing", "even-keeled", "executive", "explicit", "exuding", "fault-tolerant", "foreground", "fresh-thinking", "full-range", "global", "grid-enabled", "heuristic", "high-level", "holistic", "homogeneous", "human-resource", "hybrid", "impactful", "incremental", "intangible", "interactive", "intermediate", "leading edge", "local", "logistical", "maximized", "methodical", "mission-critical", "mobile", "modular", "motivating", "multi-state", "multi-tasking", "multimedia", "national", "needs-based", "neutral", "next generation", "non-volatile", "object-oriented", "optimal", "optimizing", "radical", "real-time", "reciprocal", "regional", "responsive", "scalable", "secondary", "solution-oriented", "stable", "static", "system-worthy", "systematic", "systemic", "tangible", "tertiary", "transitional", "uniform", "upward-trending", "user-facing", "value-added", "web-enabled", "well-modulated", "zero administration", "zero defect", "zero tolerance", "Graphic Interface", "Graphical User Interface", "ability", "access", "adapter", "algorithm", "alliance", "analyzer", "application", "approach", "architecture", "archive", "array", "artificial intelligence", "attitude", "benchmark", "budgetary management", "capability", "capacity", "challenge", "circuit", "collaboration", "complexity", "concept", "conglomeration", "contingency", "core", "customer loyalty", "data-warehouse", "database", "definition", "emulation", "encoding", "encryption", "extranet", "firmware", "flexibility", "focus group", "forecast", "frame", "framework", "function", "functionalities", "groupware", "hardware", "help-desk", "hierarchy", "hub", "implementation", "info-mediaries", "infrastructure", "initiative", "installation", "instruction set", "interface", "internet solution", "intranet", "knowledge base", "knowledge user", "leverage", "local area network", "matrices", "matrix", "methodology", "middleware", "migration", "model", "moderator", "monitoring", "moratorium", "neural-net", "open architecture", "open system", "orchestration", "paradigm", "parallelism", "policy", "portal", "pricing structure", "process improvement", "product", "productivity", "project", "projection", "protocol", "secured line", "service-desk", "software", "solution", "standardization", "strategy", "structure", "success", "superstructure", "support", "synergy", "system engine", "task-force", "throughput", "time-frame", "toolset", "utilisation", "website", "workforce"}, - "bs": {"aggregate", "architect", "benchmark", "brand", "cultivate", "deliver", "deploy", "disintermediate", "drive", "e-enable", "embrace", "empower", "enable", "engage", "engineer", "enhance", "envisioneer", "evolve", "expedite", "exploit", "extend", "facilitate", "generate", "grow", "harness", "implement", "incentivize", "incubate", "innovate", "integrate", "iterate", "leverage", "matrix", "maximize", "mesh", "monetize", "morph", "optimize", "orchestrate", "productize", "recontextualize", "redefine", "reintermediate", "reinvent", "repurpose", "revolutionize", "scale", "seize", "strategize", "streamline", "syndicate", "synergize", "synthesize", "target", "transform", "transition", "unleash", "utilize", "visualize", "whiteboard", "24/365", "24/7", "B2B", "B2C", "back-end", "best-of-breed", "bleeding-edge", "bricks-and-clicks", "clicks-and-mortar", "collaborative", "compelling", "cross-media", "cross-platform", "customized", "cutting-edge", "distributed", "dot-com", "dynamic", "e-business", "efficient", "end-to-end", "enterprise", "extensible", "frictionless", "front-end", "global", "granular", "holistic", "impactful", "innovative", "integrated", "interactive", "intuitive", "killer", "leading-edge", "magnetic", "mission-critical", "next-generation", "one-to-one", "open-source", "out-of-the-box", "plug-and-play", "proactive", "real-time", "revolutionary", "rich", "robust", "scalable", "seamless", "sexy", "sticky", "strategic", "synergistic", "transparent", "turn-key", "ubiquitous", "user-centric", "value-added", "vertical", "viral", "virtual", "visionary", "web-enabled", "wireless", "world-class", "ROI", "action-items", "applications", "architectures", "bandwidth", "channels", "communities", "content", "convergence", "deliverables", "e-business", "e-commerce", "e-markets", "e-services", "e-tailers", "experiences", "eyeballs", "functionalities", "infomediaries", "infrastructures", "initiatives", "interfaces", "markets", "methodologies", "metrics", "mindshare", "models", "networks", "niches", "paradigms", "partnerships", "platforms", "portals", "relationships", "schemas", "solutions", "supply-chains", "synergies", "systems", "technologies", "users", "vortals", "web services", "web-readiness"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/computer.go b/vendor/github.com/brianvoe/gofakeit/data/computer.go deleted file mode 100644 index b682c6f820cc..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/computer.go +++ /dev/null @@ -1,8 +0,0 @@ -package data - -// Computer consists of computer information -var Computer = map[string][]string{ - "linux_processor": {"i686", "x86_64"}, - "mac_processor": {"Intel", "PPC", "U; Intel", "U; PPC"}, - "windows_platform": {"Windows NT 6.2", "Windows NT 6.1", "Windows NT 6.0", "Windows NT 5.2", "Windows NT 5.1", "Windows NT 5.01", "Windows NT 5.0", "Windows NT 4.0", "Windows 98; Win 9x 4.90", "Windows 98", "Windows 95", "Windows CE"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/contact.go b/vendor/github.com/brianvoe/gofakeit/data/contact.go deleted file mode 100644 index 88b957961dbb..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/contact.go +++ /dev/null @@ -1,6 +0,0 @@ -package data - -// Contact consists of contact information -var Contact = map[string][]string{ - "phone": {"###-###-####", "(###)###-####", "1-###-###-####", "###.###.####"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/currency.go b/vendor/github.com/brianvoe/gofakeit/data/currency.go deleted file mode 100644 index 13b8019973ca..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/currency.go +++ /dev/null @@ -1,7 +0,0 @@ -package data - -// Currency consists of currency information -var Currency = map[string][]string{ - "short": {"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYR", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SPL", "SRD", "STD", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TVD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XCD", "XDR", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWD"}, - "long": {"United Arab Emirates Dirham", "Afghanistan Afghani", "Albania Lek", "Armenia Dram", "Netherlands Antilles Guilder", "Angola Kwanza", "Argentina Peso", "Australia Dollar", "Aruba Guilder", "Azerbaijan New Manat", "Bosnia and Herzegovina Convertible Marka", "Barbados Dollar", "Bangladesh Taka", "Bulgaria Lev", "Bahrain Dinar", "Burundi Franc", "Bermuda Dollar", "Brunei Darussalam Dollar", "Bolivia Boliviano", "Brazil Real", "Bahamas Dollar", "Bhutan Ngultrum", "Botswana Pula", "Belarus Ruble", "Belize Dollar", "Canada Dollar", "Congo/Kinshasa Franc", "Switzerland Franc", "Chile Peso", "China Yuan Renminbi", "Colombia Peso", "Costa Rica Colon", "Cuba Convertible Peso", "Cuba Peso", "Cape Verde Escudo", "Czech Republic Koruna", "Djibouti Franc", "Denmark Krone", "Dominican Republic Peso", "Algeria Dinar", "Egypt Pound", "Eritrea Nakfa", "Ethiopia Birr", "Euro Member Countries", "Fiji Dollar", "Falkland Islands (Malvinas) Pound", "United Kingdom Pound", "Georgia Lari", "Guernsey Pound", "Ghana Cedi", "Gibraltar Pound", "Gambia Dalasi", "Guinea Franc", "Guatemala Quetzal", "Guyana Dollar", "Hong Kong Dollar", "Honduras Lempira", "Croatia Kuna", "Haiti Gourde", "Hungary Forint", "Indonesia Rupiah", "Israel Shekel", "Isle of Man Pound", "India Rupee", "Iraq Dinar", "Iran Rial", "Iceland Krona", "Jersey Pound", "Jamaica Dollar", "Jordan Dinar", "Japan Yen", "Kenya Shilling", "Kyrgyzstan Som", "Cambodia Riel", "Comoros Franc", "Korea (North) Won", "Korea (South) Won", "Kuwait Dinar", "Cayman Islands Dollar", "Kazakhstan Tenge", "Laos Kip", "Lebanon Pound", "Sri Lanka Rupee", "Liberia Dollar", "Lesotho Loti", "Lithuania Litas", "Libya Dinar", "Morocco Dirham", "Moldova Leu", "Madagascar Ariary", "Macedonia Denar", "Myanmar (Burma) Kyat", "Mongolia Tughrik", "Macau Pataca", "Mauritania Ouguiya", "Mauritius Rupee", "Maldives (Maldive Islands) Rufiyaa", "Malawi Kwacha", "Mexico Peso", "Malaysia Ringgit", "Mozambique Metical", "Namibia Dollar", "Nigeria Naira", "Nicaragua Cordoba", "Norway Krone", "Nepal Rupee", "New Zealand Dollar", "Oman Rial", "Panama Balboa", "Peru Nuevo Sol", "Papua New Guinea Kina", "Philippines Peso", "Pakistan Rupee", "Poland Zloty", "Paraguay Guarani", "Qatar Riyal", "Romania New Leu", "Serbia Dinar", "Russia Ruble", "Rwanda Franc", "Saudi Arabia Riyal", "Solomon Islands Dollar", "Seychelles Rupee", "Sudan Pound", "Sweden Krona", "Singapore Dollar", "Saint Helena Pound", "Sierra Leone Leone", "Somalia Shilling", "Seborga Luigino", "Suriname Dollar", "São Tomé and Príncipe Dobra", "El Salvador Colon", "Syria Pound", "Swaziland Lilangeni", "Thailand Baht", "Tajikistan Somoni", "Turkmenistan Manat", "Tunisia Dinar", "Tonga Pa'anga", "Turkey Lira", "Trinidad and Tobago Dollar", "Tuvalu Dollar", "Taiwan New Dollar", "Tanzania Shilling", "Ukraine Hryvnia", "Uganda Shilling", "United States Dollar", "Uruguay Peso", "Uzbekistan Som", "Venezuela Bolivar", "Viet Nam Dong", "Vanuatu Vatu", "Samoa Tala", "Communauté Financière Africaine (BEAC) CFA Franc BEAC", "East Caribbean Dollar", "International Monetary Fund (IMF) Special Drawing Rights", "Communauté Financière Africaine (BCEAO) Franc", "Comptoirs Français du Pacifique (CFP) Franc", "Yemen Rial", "South Africa Rand", "Zambia Kwacha", "Zimbabwe Dollar"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/data.go b/vendor/github.com/brianvoe/gofakeit/data/data.go deleted file mode 100644 index d751c9994356..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/data.go +++ /dev/null @@ -1,28 +0,0 @@ -package data - -// Data consists of the main set of fake information -var Data = map[string]map[string][]string{ - "person": Person, - "contact": Contact, - "address": Address, - "company": Company, - "job": Job, - "lorem": Lorem, - "internet": Internet, - "file": Files, - "color": Colors, - "computer": Computer, - "payment": Payment, - "hipster": Hipster, - "beer": Beer, - "hacker": Hacker, - "currency": Currency, - "log_level": LogLevels, - "timezone": TimeZone, - "vehicle": Vehicle, -} - -// IntData consists of the main set of fake information (integer only) -var IntData = map[string]map[string][]int{ - "status_code": StatusCodes, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/datetime.go b/vendor/github.com/brianvoe/gofakeit/data/datetime.go deleted file mode 100644 index 3347120a67e2..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/datetime.go +++ /dev/null @@ -1,9 +0,0 @@ -package data - -// TimeZone is an array of short and long timezones -var TimeZone = map[string][]string{ - "offset": {"-12", "-11", "-10", "-8", "-7", "-7", "-8", "-7", "-6", "-6", "-6", "-5", "-5", "-6", "-5", "-4", "-4", "-4.5", "-4", "-3", "-4", "-4", "-4", "-2.5", "-3", "-3", "-3", "-3", "-3", "-3", "-2", "-1", "0", "-1", "1", "0", "0", "1", "1", "0", "2", "2", "2", "2", "1", "1", "3", "3", "2", "3", "3", "2", "3", "3", "3", "2", "3", "3", "3", "3", "3", "3", "4", "4.5", "4", "5", "4", "4", "4", "4.5", "5", "5", "5", "5.5", "5.5", "5.75", "6", "6", "6.5", "7", "7", "8", "8", "8", "8", "8", "8", "9", "9", "9", "9.5", "9.5", "10", "10", "10", "10", "10", "11", "11", "12", "12", "12", "12", "13", "13", "13"}, - "abr": {"DST", "U", "HST", "AKDT", "PDT", "PDT", "PST", "UMST", "MDT", "MDT", "CAST", "CDT", "CDT", "CCST", "SPST", "EDT", "UEDT", "VST", "PYT", "ADT", "CBST", "SWST", "PSST", "NDT", "ESAST", "AST", "SEST", "GDT", "MST", "BST", "U", "MDT", "ADT", "CVST", "MDT", "UTC", "GMT", "BST", "GDT", "GST", "WEDT", "CEDT", "RDT", "CEDT", "WCAST", "NST", "GDT", "MEDT", "EST", "SDT", "EEDT", "SAST", "FDT", "TDT", "JDT", "LST", "JST", "AST", "KST", "AST", "EAST", "MSK", "SAMT", "IDT", "AST", "ADT", "MST", "GST", "CST", "AST", "WAST", "YEKT", "PKT", "IST", "SLST", "NST", "CAST", "BST", "MST", "SAST", "NCAST", "CST", "NAST", "MPST", "WAST", "TST", "UST", "NAEST", "JST", "KST", "CAST", "ACST", "EAST", "AEST", "WPST", "TST", "YST", "CPST", "VST", "NZST", "U", "FST", "MST", "KDT", "TST", "SST"}, - "text": {"Dateline Standard Time", "UTC-11", "Hawaiian Standard Time", "Alaskan Standard Time", "Pacific Standard Time (Mexico)", "Pacific Daylight Time", "Pacific Standard Time", "US Mountain Standard Time", "Mountain Standard Time (Mexico)", "Mountain Standard Time", "Central America Standard Time", "Central Standard Time", "Central Standard Time (Mexico)", "Canada Central Standard Time", "SA Pacific Standard Time", "Eastern Standard Time", "US Eastern Standard Time", "Venezuela Standard Time", "Paraguay Standard Time", "Atlantic Standard Time", "Central Brazilian Standard Time", "SA Western Standard Time", "Pacific SA Standard Time", "Newfoundland Standard Time", "E. South America Standard Time", "Argentina Standard Time", "SA Eastern Standard Time", "Greenland Standard Time", "Montevideo Standard Time", "Bahia Standard Time", "UTC-02", "Mid-Atlantic Standard Time", "Azores Standard Time", "Cape Verde Standard Time", "Morocco Standard Time", "UTC", "Greenwich Mean Time", "British Summer Time", "GMT Standard Time", "Greenwich Standard Time", "W. Europe Standard Time", "Central Europe Standard Time", "Romance Standard Time", "Central European Standard Time", "W. Central Africa Standard Time", "Namibia Standard Time", "GTB Standard Time", "Middle East Standard Time", "Egypt Standard Time", "Syria Standard Time", "E. Europe Standard Time", "South Africa Standard Time", "FLE Standard Time", "Turkey Standard Time", "Israel Standard Time", "Libya Standard Time", "Jordan Standard Time", "Arabic Standard Time", "Kaliningrad Standard Time", "Arab Standard Time", "E. Africa Standard Time", "Moscow Standard Time", "Samara Time", "Iran Standard Time", "Arabian Standard Time", "Azerbaijan Standard Time", "Mauritius Standard Time", "Georgian Standard Time", "Caucasus Standard Time", "Afghanistan Standard Time", "West Asia Standard Time", "Yekaterinburg Time", "Pakistan Standard Time", "India Standard Time", "Sri Lanka Standard Time", "Nepal Standard Time", "Central Asia Standard Time", "Bangladesh Standard Time", "Myanmar Standard Time", "SE Asia Standard Time", "N. Central Asia Standard Time", "China Standard Time", "North Asia Standard Time", "Singapore Standard Time", "W. Australia Standard Time", "Taipei Standard Time", "Ulaanbaatar Standard Time", "North Asia East Standard Time", "Japan Standard Time", "Korea Standard Time", "Cen. Australia Standard Time", "AUS Central Standard Time", "E. Australia Standard Time", "AUS Eastern Standard Time", "West Pacific Standard Time", "Tasmania Standard Time", "Yakutsk Standard Time", "Central Pacific Standard Time", "Vladivostok Standard Time", "New Zealand Standard Time", "UTC+12", "Fiji Standard Time", "Magadan Standard Time", "Kamchatka Standard Time", "Tonga Standard Time", "Samoa Standard Time"}, - "full": {"(UTC-12:00) International Date Line West", "(UTC-11:00) Coordinated Universal Time-11", "(UTC-10:00) Hawaii", "(UTC-09:00) Alaska", "(UTC-08:00) Baja California", "(UTC-07:00) Pacific Time (US & Canada)", "(UTC-08:00) Pacific Time (US & Canada)", "(UTC-07:00) Arizona", "(UTC-07:00) Chihuahua, La Paz, Mazatlan", "(UTC-07:00) Mountain Time (US & Canada)", "(UTC-06:00) Central America", "(UTC-06:00) Central Time (US & Canada)", "(UTC-06:00) Guadalajara, Mexico City, Monterrey", "(UTC-06:00) Saskatchewan", "(UTC-05:00) Bogota, Lima, Quito", "(UTC-05:00) Eastern Time (US & Canada)", "(UTC-05:00) Indiana (East)", "(UTC-04:30) Caracas", "(UTC-04:00) Asuncion", "(UTC-04:00) Atlantic Time (Canada)", "(UTC-04:00) Cuiaba", "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan", "(UTC-04:00) Santiago", "(UTC-03:30) Newfoundland", "(UTC-03:00) Brasilia", "(UTC-03:00) Buenos Aires", "(UTC-03:00) Cayenne, Fortaleza", "(UTC-03:00) Greenland", "(UTC-03:00) Montevideo", "(UTC-03:00) Salvador", "(UTC-02:00) Coordinated Universal Time-02", "(UTC-02:00) Mid-Atlantic - Old", "(UTC-01:00) Azores", "(UTC-01:00) Cape Verde Is.", "(UTC) Casablanca", "(UTC) Coordinated Universal Time", "(UTC) Edinburgh, London", "(UTC+01:00) Edinburgh, London", "(UTC) Dublin, Lisbon", "(UTC) Monrovia, Reykjavik", "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", "(UTC+01:00) West Central Africa", "(UTC+01:00) Windhoek", "(UTC+02:00) Athens, Bucharest", "(UTC+02:00) Beirut", "(UTC+02:00) Cairo", "(UTC+02:00) Damascus", "(UTC+02:00) E. Europe", "(UTC+02:00) Harare, Pretoria", "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", "(UTC+03:00) Istanbul", "(UTC+02:00) Jerusalem", "(UTC+02:00) Tripoli", "(UTC+03:00) Amman", "(UTC+03:00) Baghdad", "(UTC+03:00) Kaliningrad, Minsk", "(UTC+03:00) Kuwait, Riyadh", "(UTC+03:00) Nairobi", "(UTC+03:00) Moscow, St. Petersburg, Volgograd", "(UTC+04:00) Samara, Ulyanovsk, Saratov", "(UTC+03:30) Tehran", "(UTC+04:00) Abu Dhabi, Muscat", "(UTC+04:00) Baku", "(UTC+04:00) Port Louis", "(UTC+04:00) Tbilisi", "(UTC+04:00) Yerevan", "(UTC+04:30) Kabul", "(UTC+05:00) Ashgabat, Tashkent", "(UTC+05:00) Yekaterinburg", "(UTC+05:00) Islamabad, Karachi", "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", "(UTC+05:30) Sri Jayawardenepura", "(UTC+05:45) Kathmandu", "(UTC+06:00) Astana", "(UTC+06:00) Dhaka", "(UTC+06:30) Yangon (Rangoon)", "(UTC+07:00) Bangkok, Hanoi, Jakarta", "(UTC+07:00) Novosibirsk", "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", "(UTC+08:00) Krasnoyarsk", "(UTC+08:00) Kuala Lumpur, Singapore", "(UTC+08:00) Perth", "(UTC+08:00) Taipei", "(UTC+08:00) Ulaanbaatar", "(UTC+09:00) Irkutsk", "(UTC+09:00) Osaka, Sapporo, Tokyo", "(UTC+09:00) Seoul", "(UTC+09:30) Adelaide", "(UTC+09:30) Darwin", "(UTC+10:00) Brisbane", "(UTC+10:00) Canberra, Melbourne, Sydney", "(UTC+10:00) Guam, Port Moresby", "(UTC+10:00) Hobart", "(UTC+10:00) Yakutsk", "(UTC+11:00) Solomon Is., New Caledonia", "(UTC+11:00) Vladivostok", "(UTC+12:00) Auckland, Wellington", "(UTC+12:00) Coordinated Universal Time+12", "(UTC+12:00) Fiji", "(UTC+12:00) Magadan", "(UTC+12:00) Petropavlovsk-Kamchatsky - Old", "(UTC+13:00) Nuku'alofa", "(UTC+13:00) Samoa"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/files.go b/vendor/github.com/brianvoe/gofakeit/data/files.go deleted file mode 100644 index 363b840017f5..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/files.go +++ /dev/null @@ -1,7 +0,0 @@ -package data - -// Files consists of file information -var Files = map[string][]string{ - "mime_type": {"x-world/x-3dmf", "application/octet-stream", "application/x-authorware-bin", "application/x-authorware-map", "application/x-authorware-seg", "text/vnd.abc", "text/html", "video/animaflex", "application/postscript", "audio/aiff", "audio/x-aiff", "audio/aiff", "audio/x-aiff", "audio/aiff", "audio/x-aiff", "application/x-aim", "text/x-audiosoft-intra", "application/x-navi-animation", "application/x-nokia-9000-communicator-add-on-software", "application/mime", "application/octet-stream", "application/arj", "application/octet-stream", "image/x-jg", "video/x-ms-asf", "text/x-asm", "text/asp", "application/x-mplayer2", "video/x-ms-asf", "video/x-ms-asf-plugin", "audio/basic", "audio/x-au", "application/x-troff-msvideo", "video/avi", "video/msvideo", "video/x-msvideo", "video/avs-video", "application/x-bcpio", "application/mac-binary", "application/macbinary", "application/octet-stream", "application/x-binary", "application/x-macbinary", "image/bmp", "image/bmp", "image/x-windows-bmp", "application/book", "application/book", "application/x-bzip2", "application/x-bsh", "application/x-bzip", "application/x-bzip2", "text/plain", "text/x-c", "text/plain", "application/vnd.ms-pki.seccat", "text/plain", "text/x-c", "application/clariscad", "application/x-cocoa", "application/cdf", "application/x-cdf", "application/x-netcdf", "application/pkix-cert", "application/x-x509-ca-cert", "application/x-chat", "application/x-chat", "application/java", "application/java-byte-code", "application/x-java-class", "application/octet-stream", "text/plain", "text/plain", "application/x-cpio", "text/x-c", "application/mac-compactpro", "application/x-compactpro", "application/x-cpt", "application/pkcs-crl", "application/pkix-crl", "application/pkix-cert", "application/x-x509-ca-cert", "application/x-x509-user-cert", "application/x-csh", "text/x-script.csh", "application/x-pointplus", "text/css", "text/plain", "application/x-director", "application/x-deepv", "text/plain", "application/x-x509-ca-cert", "video/x-dv", "application/x-director", "video/dl", "video/x-dl", "application/msword", "application/msword", "application/commonground", "application/drafting", "application/octet-stream", "video/x-dv", "application/x-dvi", "drawing/x-dwf (old)", "model/vnd.dwf", "application/acad", "image/vnd.dwg", "image/x-dwg", "application/dxf", "image/vnd.dwg", "image/x-dwg", "application/x-director", "text/x-script.elisp", "application/x-bytecode.elisp (compiled elisp)", "application/x-elc", "application/x-envoy", "application/postscript", "application/x-esrehber", "text/x-setext", "application/envoy", "application/x-envoy", "application/octet-stream", "text/plain", "text/x-fortran", "text/x-fortran", "text/plain", "text/x-fortran", "application/vnd.fdf", "application/fractals", "image/fif", "video/fli", "video/x-fli", "image/florian", "text/vnd.fmi.flexstor", "video/x-atomic3d-feature", "text/plain", "text/x-fortran", "image/vnd.fpx", "image/vnd.net-fpx", "application/freeloader", "audio/make", "text/plain", "image/g3fax", "image/gif", "video/gl", "video/x-gl", "audio/x-gsm", "audio/x-gsm", "application/x-gsp", "application/x-gss", "application/x-gtar", "application/x-compressed", "application/x-gzip", "application/x-gzip", "multipart/x-gzip", "text/plain", "text/x-h", "application/x-hdf", "application/x-helpfile", "application/vnd.hp-hpgl", "text/plain", "text/x-h", "text/x-script", "application/hlp", "application/x-helpfile", "application/x-winhelp", "application/vnd.hp-hpgl", "application/vnd.hp-hpgl", "application/binhex", "application/binhex4", "application/mac-binhex", "application/mac-binhex40", "application/x-binhex40", "application/x-mac-binhex40", "application/hta", "text/x-component", "text/html", "text/html", "text/html", "text/webviewhtml", "text/html", "x-conference/x-cooltalk", "image/x-icon", "text/plain", "image/ief", "image/ief", "application/iges", "model/iges", "application/iges", "model/iges", "application/x-ima", "application/x-httpd-imap", "application/inf", "application/x-internett-signup", "application/x-ip2", "video/x-isvideo", "audio/it", "application/x-inventor", "i-world/i-vrml", "application/x-livescreen", "audio/x-jam", "text/plain", "text/x-java-source", "text/plain", "text/x-java-source", "application/x-java-commerce", "image/jpeg", "image/pjpeg", "image/jpeg", "image/jpeg", "image/pjpeg", "image/jpeg", "image/pjpeg", "image/jpeg", "image/pjpeg", "image/x-jps", "application/x-javascript", "image/jutvision", "audio/midi", "music/x-karaoke", "application/x-ksh", "text/x-script.ksh", "audio/nspaudio", "audio/x-nspaudio", "audio/x-liveaudio", "application/x-latex", "application/lha", "application/octet-stream", "application/x-lha", "application/octet-stream", "text/plain", "audio/nspaudio", "audio/x-nspaudio", "text/plain", "application/x-lisp", "text/x-script.lisp", "text/plain", "text/x-la-asf", "application/x-latex", "application/octet-stream", "application/x-lzh", "application/lzx", "application/octet-stream", "application/x-lzx", "text/plain", "text/x-m", "video/mpeg", "audio/mpeg", "video/mpeg", "audio/x-mpequrl", "application/x-troff-man", "application/x-navimap", "text/plain", "application/mbedlet", "application/mcad", "application/x-mathcad", "image/vasa", "text/mcf", "application/netmc", "application/x-troff-me", "message/rfc822", "message/rfc822", "application/x-midi", "audio/midi", "audio/x-mid", "audio/x-midi", "music/crescendo", "x-music/x-midi", "application/x-midi", "audio/midi", "audio/x-mid", "audio/x-midi", "music/crescendo", "x-music/x-midi", "application/x-frame", "application/x-mif", "message/rfc822", "www/mime", "video/x-motion-jpeg", "application/base64", "application/x-meme", "application/base64", "audio/mod", "audio/x-mod", "video/quicktime", "video/quicktime", "video/x-sgi-movie", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "video/x-mpeq2a", "audio/mpeg3", "audio/x-mpeg-3", "video/mpeg", "video/x-mpeg", "audio/mpeg", "video/mpeg", "application/x-project", "video/mpeg", "video/mpeg", "audio/mpeg", "video/mpeg", "audio/mpeg", "application/vnd.ms-project", "application/x-project", "application/x-project", "application/x-project", "application/marc", "application/x-troff-ms", "video/x-sgi-movie", "audio/make", "application/x-vnd.audioexplosion.mzz", "image/naplps", "image/naplps", "application/x-netcdf", "application/vnd.nokia.configuration-message", "image/x-niff", "image/x-niff", "application/x-mix-transfer", "application/x-conference", "application/x-navidoc", "application/octet-stream", "application/oda", "application/x-omc", "application/x-omcdatamaker", "application/x-omcregerator", "text/x-pascal", "application/pkcs10", "application/x-pkcs10", "application/pkcs-12", "application/x-pkcs12", "application/x-pkcs7-signature", "application/pkcs7-mime", "application/x-pkcs7-mime", "application/pkcs7-mime", "application/x-pkcs7-mime", "application/x-pkcs7-certreqresp", "application/pkcs7-signature", "application/pro_eng", "text/pascal", "image/x-portable-bitmap", "application/vnd.hp-pcl", "application/x-pcl", "image/x-pict", "image/x-pcx", "chemical/x-pdb", "application/pdf", "audio/make", "audio/make.my.funk", "image/x-portable-graymap", "image/x-portable-greymap", "image/pict", "image/pict", "application/x-newton-compatible-pkg", "application/vnd.ms-pki.pko", "text/plain", "text/x-script.perl", "application/x-pixclscript", "image/x-xpixmap", "text/x-script.perl-module", "application/x-pagemaker", "application/x-pagemaker", "image/png", "application/x-portable-anymap", "image/x-portable-anymap", "application/mspowerpoint", "application/vnd.ms-powerpoint", "model/x-pov", "application/vnd.ms-powerpoint", "image/x-portable-pixmap", "application/mspowerpoint", "application/vnd.ms-powerpoint", "application/mspowerpoint", "application/powerpoint", "application/vnd.ms-powerpoint", "application/x-mspowerpoint", "application/mspowerpoint", "application/x-freelance", "application/pro_eng", "application/postscript", "application/octet-stream", "paleovu/x-pv", "application/vnd.ms-powerpoint", "text/x-script.phyton", "application/x-bytecode.python", "audio/vnd.qcelp", "x-world/x-3dmf", "x-world/x-3dmf", "image/x-quicktime", "video/quicktime", "video/x-qtc", "image/x-quicktime", "image/x-quicktime", "audio/x-pn-realaudio", "audio/x-pn-realaudio-plugin", "audio/x-realaudio", "audio/x-pn-realaudio", "application/x-cmu-raster", "image/cmu-raster", "image/x-cmu-raster", "image/cmu-raster", "text/x-script.rexx", "image/vnd.rn-realflash", "image/x-rgb", "application/vnd.rn-realmedia", "audio/x-pn-realaudio", "audio/mid", "audio/x-pn-realaudio", "audio/x-pn-realaudio", "audio/x-pn-realaudio-plugin", "application/ringing-tones", "application/vnd.nokia.ringing-tone", "application/vnd.rn-realplayer", "application/x-troff", "image/vnd.rn-realpix", "audio/x-pn-realaudio-plugin", "text/richtext", "text/vnd.rn-realtext", "application/rtf", "application/x-rtf", "text/richtext", "application/rtf", "text/richtext", "video/vnd.rn-realvideo", "text/x-asm", "audio/s3m", "application/octet-stream", "application/x-tbook", "application/x-lotusscreencam", "text/x-script.guile", "text/x-script.scheme", "video/x-scm", "text/plain", "application/sdp", "application/x-sdp", "application/sounder", "application/sea", "application/x-sea", "application/set", "text/sgml", "text/x-sgml", "text/sgml", "text/x-sgml", "application/x-bsh", "application/x-sh", "application/x-shar", "text/x-script.sh", "application/x-bsh", "application/x-shar", "text/html", "text/x-server-parsed-html", "audio/x-psid", "application/x-sit", "application/x-stuffit", "application/x-koan", "application/x-koan", "application/x-koan", "application/x-koan", "application/x-seelogo", "application/smil", "application/smil", "audio/basic", "audio/x-adpcm", "application/solids", "application/x-pkcs7-certificates", "text/x-speech", "application/futuresplash", "application/x-sprite", "application/x-sprite", "application/x-wais-source", "text/x-server-parsed-html", "application/streamingmedia", "application/vnd.ms-pki.certstore", "application/step", "application/sla", "application/vnd.ms-pki.stl", "application/x-navistyle", "application/step", "application/x-sv4cpio", "application/x-sv4crc", "image/vnd.dwg", "image/x-dwg", "application/x-world", "x-world/x-svr", "application/x-shockwave-flash", "application/x-troff", "text/x-speech", "application/x-tar", "application/toolbook", "application/x-tbook", "application/x-tcl", "text/x-script.tcl", "text/x-script.tcsh", "application/x-tex", "application/x-texinfo", "application/x-texinfo", "application/plain", "text/plain", "application/gnutar", "application/x-compressed", "image/tiff", "image/x-tiff", "image/tiff", "image/x-tiff", "application/x-troff", "audio/tsp-audio", "application/dsptype", "audio/tsplayer", "text/tab-separated-values", "image/florian", "text/plain", "text/x-uil", "text/uri-list", "text/uri-list", "application/i-deas", "text/uri-list", "text/uri-list", "application/x-ustar", "multipart/x-ustar", "application/octet-stream", "text/x-uuencode", "text/x-uuencode", "application/x-cdlink", "text/x-vcalendar", "application/vda", "video/vdo", "application/groupwise", "video/vivo", "video/vnd.vivo", "video/vivo", "video/vnd.vivo", "application/vocaltec-media-desc", "application/vocaltec-media-file", "audio/voc", "audio/x-voc", "video/vosaic", "audio/voxware", "audio/x-twinvq-plugin", "audio/x-twinvq", "audio/x-twinvq-plugin", "application/x-vrml", "model/vrml", "x-world/x-vrml", "x-world/x-vrt", "application/x-visio", "application/x-visio", "application/x-visio", "application/wordperfect6.0", "application/wordperfect6.1", "application/msword", "audio/wav", "audio/x-wav", "application/x-qpro", "image/vnd.wap.wbmp", "application/vnd.xara", "application/msword", "application/x-123", "windows/metafile", "text/vnd.wap.wml", "application/vnd.wap.wmlc", "text/vnd.wap.wmlscript", "application/vnd.wap.wmlscriptc", "application/msword", "application/wordperfect", "application/wordperfect", "application/wordperfect6.0", "application/wordperfect", "application/wordperfect", "application/x-wpwin", "application/x-lotus", "application/mswrite", "application/x-wri", "application/x-world", "model/vrml", "x-world/x-vrml", "model/vrml", "x-world/x-vrml", "text/scriplet", "application/x-wais-source", "application/x-wintalk", "image/x-xbitmap", "image/x-xbm", "image/xbm", "video/x-amt-demorun", "xgl/drawing", "image/vnd.xiff", "application/excel", "application/excel", "application/x-excel", "application/x-msexcel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/x-excel", "application/excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/x-msexcel", "application/excel", "application/x-excel", "application/excel", "application/x-excel", "application/excel", "application/vnd.ms-excel", "application/x-excel", "application/x-msexcel", "audio/xm", "application/xml", "text/xml", "xgl/movie", "application/x-vnd.ls-xpix", "image/x-xpixmap", "image/xpm", "image/png", "video/x-amt-showrun", "image/x-xwd", "image/x-xwindowdump", "chemical/x-pdb", "application/x-compress", "application/x-compressed", "application/x-compressed", "application/x-zip-compressed", "application/zip", "multipart/x-zip", "application/octet-stream", "text/x-script.zsh"}, - "extension": {"doc", "docx", "log", "msg", "odt", "pages", "rtf", "tex", "txt", "wpd", "wps", "csv", "dat", "gbr", "ged", "key", "keychain", "pps", "ppt", "pptx", "sdf", "tar", "vcf", "xml", "aif", "iff", "mid", "mpa", "ra", "wav", "wma", "asf", "asx", "avi", "flv", "mov", "mpg", "rm", "srt", "swf", "vob", "wmv", "max", "obj", "bmp", "dds", "gif", "jpg", "png", "psd", "pspimage", "tga", "thm", "tif", "tiff", "yuv", "ai", "eps", "ps", "svg", "indd", "pct", "pdf", "xlr", "xls", "xlsx", "accdb", "db", "dbf", "mdb", "pdb", "sql", "apk", "app", "bat", "cgi", "com", "exe", "gadget", "jar", "pif", "vb", "wsf", "dem", "gam", "nes", "rom", "sav", "dwg", "dxf", "gpx", "kml", "kmz", "asp", "aspx", "cer", "cfm", "csr", "css", "htm", "html", "js", "jsp", "php", "rss", "xhtml", "crx", "plugin", "fnt", "fon", "otf", "ttf", "cab", "cpl", "cur", "deskthemepack", "dll", "dmp", "drv", "icns", "ico", "lnk", "sys", "cfg", "ini", "prf", "hqx", "mim", "uue", "cbr", "deb", "gz", "pkg", "rar", "rpm", "sitx", "gz", "zip", "zipx", "bin", "cue", "dmg", "iso", "mdf", "toast", "vcd", "class", "cpp", "cs", "dtd", "fla", "java", "lua", "pl", "py", "sh", "sln", "swift", "vcxproj", "xcodeproj", "bak", "tmp", "crdownload", "ics", "msi", "part", "torrent"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/hacker.go b/vendor/github.com/brianvoe/gofakeit/data/hacker.go deleted file mode 100644 index 4735f7d560af..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/hacker.go +++ /dev/null @@ -1,20 +0,0 @@ -package data - -// Hacker consists of random hacker phrases -var Hacker = map[string][]string{ - "abbreviation": {"TCP", "HTTP", "SDD", "RAM", "GB", "CSS", "SSL", "AGP", "SQL", "FTP", "PCI", "AI", "ADP", "RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "SAS", "IB", "SCSI", "JSON", "XSS", "JBOD"}, - "adjective": {"auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform", "redundant", "online", "haptic", "multi-byte", "bluetooth", "wireless", "1080p", "neural", "optical", "solid state", "mobile"}, - "noun": {"driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card", "array", "interface", "system", "sensor", "firewall", "hard drive", "pixel", "alarm", "feed", "monitor", "application", "transmitter", "bus", "circuit", "capacitor", "matrix"}, - "verb": {"back up", "bypass", "hack", "override", "compress", "copy", "navigate", "index", "connect", "generate", "quantify", "calculate", "synthesize", "input", "transmit", "program", "reboot", "parse"}, - "ingverb": {"backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating", "indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing", "transmitting", "programming", "parsing"}, - "phrase": { - "If we {hacker.verb} the {hacker.noun}, we can get to the {hacker.abbreviation} {hacker.noun} through the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", - "We need to {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", - "Try to {hacker.verb} the {hacker.abbreviation} {hacker.noun}, maybe it will {hacker.verb} the {hacker.adjective} {hacker.noun}!", - "You can't {hacker.verb} the {hacker.noun} without {hacker.ingverb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", - "Use the {hacker.adjective} {hacker.abbreviation} {hacker.noun}, then you can {hacker.verb} the {hacker.adjective} {hacker.noun}!", - "The {hacker.abbreviation} {hacker.noun} is down, {hacker.verb} the {hacker.adjective} {hacker.noun} so we can {hacker.verb} the {hacker.abbreviation} {hacker.noun}!", - "{hacker.ingverb} the {hacker.noun} won't do anything, we need to {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!", - "I'll {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}, that should {hacker.verb} the {hacker.abbreviation} {hacker.noun}!", - }, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/hipster.go b/vendor/github.com/brianvoe/gofakeit/data/hipster.go deleted file mode 100644 index f036f4639bc8..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/hipster.go +++ /dev/null @@ -1,6 +0,0 @@ -package data - -// Hipster consists of random hipster words -var Hipster = map[string][]string{ - "word": {"Wes Anderson", "chicharrones", "narwhal", "food truck", "marfa", "aesthetic", "keytar", "art party", "sustainable", "forage", "mlkshk", "gentrify", "locavore", "swag", "hoodie", "microdosing", "VHS", "before they sold out", "pabst", "plaid", "Thundercats", "freegan", "scenester", "hella", "occupy", "truffaut", "raw denim", "beard", "post-ironic", "photo booth", "twee", "90's", "pitchfork", "cray", "cornhole", "kale chips", "pour-over", "yr", "five dollar toast", "kombucha", "you probably haven't heard of them", "mustache", "fixie", "try-hard", "franzen", "kitsch", "austin", "stumptown", "keffiyeh", "whatever", "tumblr", "DIY", "shoreditch", "biodiesel", "vegan", "pop-up", "banjo", "kogi", "cold-pressed", "letterpress", "chambray", "butcher", "synth", "trust fund", "hammock", "farm-to-table", "intelligentsia", "loko", "ugh", "offal", "poutine", "gastropub", "Godard", "jean shorts", "sriracha", "dreamcatcher", "leggings", "fashion axe", "church-key", "meggings", "tote bag", "disrupt", "readymade", "helvetica", "flannel", "meh", "roof", "hashtag", "knausgaard", "cronut", "schlitz", "green juice", "waistcoat", "normcore", "viral", "ethical", "actually", "fingerstache", "humblebrag", "deep v", "wayfarers", "tacos", "taxidermy", "selvage", "put a bird on it", "ramps", "portland", "retro", "kickstarter", "bushwick", "brunch", "distillery", "migas", "flexitarian", "XOXO", "small batch", "messenger bag", "heirloom", "tofu", "bicycle rights", "bespoke", "salvia", "wolf", "selfies", "echo", "park", "listicle", "craft beer", "chartreuse", "sartorial", "pinterest", "mumblecore", "kinfolk", "vinyl", "etsy", "umami", "8-bit", "polaroid", "banh mi", "crucifix", "bitters", "brooklyn", "PBR&B", "drinking", "vinegar", "squid", "tattooed", "skateboard", "vice", "authentic", "literally", "lomo", "celiac", "health", "goth", "artisan", "chillwave", "blue bottle", "pickled", "next level", "neutra", "organic", "Yuccie", "paleo", "blog", "single-origin coffee", "seitan", "street", "gluten-free", "mixtape", "venmo", "irony", "everyday", "carry", "slow-carb", "3 wolf moon", "direct trade", "lo-fi", "tousled", "tilde", "semiotics", "cred", "chia", "master", "cleanse", "ennui", "quinoa", "pug", "iPhone", "fanny pack", "cliche", "cardigan", "asymmetrical", "meditation", "YOLO", "typewriter", "pork belly", "shabby chic", "+1", "lumbersexual", "williamsburg"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/internet.go b/vendor/github.com/brianvoe/gofakeit/data/internet.go deleted file mode 100644 index 1f16db95c765..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/internet.go +++ /dev/null @@ -1,8 +0,0 @@ -package data - -// Internet consists of various internet information -var Internet = map[string][]string{ - "browser": {"firefox", "chrome", "internetExplorer", "opera", "safari"}, - "domain_suffix": {"com", "biz", "info", "name", "net", "org", "io"}, - "http_method": {"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/job.go b/vendor/github.com/brianvoe/gofakeit/data/job.go deleted file mode 100644 index 905dd74ee023..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/job.go +++ /dev/null @@ -1,8 +0,0 @@ -package data - -// Job consists of job data -var Job = map[string][]string{ - "title": {"Administrator", "Agent", "Analyst", "Architect", "Assistant", "Associate", "Consultant", "Coordinator", "Designer", "Developer", "Director", "Engineer", "Executive", "Facilitator", "Liaison", "Manager", "Officer", "Orchestrator", "Planner", "Producer", "Representative", "Specialist", "Strategist", "Supervisor", "Technician"}, - "descriptor": {"Central", "Chief", "Corporate", "Customer", "Direct", "District", "Dynamic", "Dynamic", "Forward", "Future", "Global", "Human", "Internal", "International", "Investor", "Lead", "Legacy", "National", "Principal", "Product", "Regional", "Senior"}, - "level": {"Accountability", "Accounts", "Applications", "Assurance", "Brand", "Branding", "Communications", "Configuration", "Creative", "Data", "Directives", "Division", "Factors", "Functionality", "Group", "Identity", "Implementation", "Infrastructure", "Integration", "Interactions", "Intranet", "Marketing", "Markets", "Metrics", "Mobility", "Operations", "Optimization", "Paradigm", "Program", "Quality", "Research", "Response", "Security", "Solutions", "Tactics", "Usability", "Web"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/log_level.go b/vendor/github.com/brianvoe/gofakeit/data/log_level.go deleted file mode 100644 index 01d98b63c6b6..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/log_level.go +++ /dev/null @@ -1,8 +0,0 @@ -package data - -// LogLevels consists of log levels for several types -var LogLevels = map[string][]string{ - "general": {"error", "warning", "info", "fatal", "trace", "debug"}, - "syslog": {"emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"}, - "apache": {"emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace1-8"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/lorem.go b/vendor/github.com/brianvoe/gofakeit/data/lorem.go deleted file mode 100644 index b0a8f8a1378f..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/lorem.go +++ /dev/null @@ -1,6 +0,0 @@ -package data - -// Lorem consists of lorem ipsum information -var Lorem = map[string][]string{ - "word": {"alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/payment.go b/vendor/github.com/brianvoe/gofakeit/data/payment.go deleted file mode 100644 index e50903a72af6..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/payment.go +++ /dev/null @@ -1,20 +0,0 @@ -package data - -// Payment contains payment information -var Payment = map[string][]string{ - "card_type": {"Visa", "MasterCard", "American Express", "Discover"}, - "number": { - // Visa - "4###############", - "4###############", - // Mastercard - "222100##########", - "272099##########", - // American Express - "34#############", - "37#############", - // Discover - "65##############", - "65##############", - }, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/person.go b/vendor/github.com/brianvoe/gofakeit/data/person.go deleted file mode 100644 index 129b59ba6e3c..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/person.go +++ /dev/null @@ -1,9 +0,0 @@ -package data - -// Person consists of a slice of people information -var Person = map[string][]string{ - "prefix": {"Mr.", "Mrs.", "Ms.", "Miss", "Dr."}, - "suffix": {"Jr.", "Sr.", "I", "II", "III", "IV", "V", "MD", "DDS", "PhD", "DVM"}, - "first": {"Aaliyah", "Aaron", "Abagail", "Abbey", "Abbie", "Abbigail", "Abby", "Abdiel", "Abdul", "Abdullah", "Abe", "Abel", "Abelardo", "Abigail", "Abigale", "Abigayle", "Abner", "Abraham", "Ada", "Adah", "Adalberto", "Adaline", "Adam", "Adan", "Addie", "Addison", "Adela", "Adelbert", "Adele", "Adelia", "Adeline", "Adell", "Adella", "Adelle", "Aditya", "Adolf", "Adolfo", "Adolph", "Adolphus", "Adonis", "Adrain", "Adrian", "Adriana", "Adrianna", "Adriel", "Adrien", "Adrienne", "Afton", "Aglae", "Agnes", "Agustin", "Agustina", "Ahmad", "Ahmed", "Aida", "Aidan", "Aiden", "Aileen", "Aimee", "Aisha", "Aiyana", "Akeem", "Al", "Alaina", "Alan", "Alana", "Alanis", "Alanna", "Alayna", "Alba", "Albert", "Alberta", "Albertha", "Alberto", "Albin", "Albina", "Alda", "Alden", "Alec", "Aleen", "Alejandra", "Alejandrin", "Alek", "Alena", "Alene", "Alessandra", "Alessandro", "Alessia", "Aletha", "Alex", "Alexa", "Alexander", "Alexandra", "Alexandre", "Alexandrea", "Alexandria", "Alexandrine", "Alexandro", "Alexane", "Alexanne", "Alexie", "Alexis", "Alexys", "Alexzander", "Alf", "Alfonso", "Alfonzo", "Alford", "Alfred", "Alfreda", "Alfredo", "Ali", "Alia", "Alice", "Alicia", "Alisa", "Alisha", "Alison", "Alivia", "Aliya", "Aliyah", "Aliza", "Alize", "Allan", "Allen", "Allene", "Allie", "Allison", "Ally", "Alphonso", "Alta", "Althea", "Alva", "Alvah", "Alvena", "Alvera", "Alverta", "Alvina", "Alvis", "Alyce", "Alycia", "Alysa", "Alysha", "Alyson", "Alysson", "Amalia", "Amanda", "Amani", "Amara", "Amari", "Amaya", "Amber", "Ambrose", "Amelia", "Amelie", "Amely", "America", "Americo", "Amie", "Amina", "Amir", "Amira", "Amiya", "Amos", "Amparo", "Amy", "Amya", "Ana", "Anabel", "Anabelle", "Anahi", "Anais", "Anastacio", "Anastasia", "Anderson", "Andre", "Andreane", "Andreanne", "Andres", "Andrew", "Andy", "Angel", "Angela", "Angelica", "Angelina", "Angeline", "Angelita", "Angelo", "Angie", "Angus", "Anibal", "Anika", "Anissa", "Anita", "Aniya", "Aniyah", "Anjali", "Anna", "Annabel", "Annabell", "Annabelle", "Annalise", "Annamae", "Annamarie", "Anne", "Annetta", "Annette", "Annie", "Ansel", "Ansley", "Anthony", "Antoinette", "Antone", "Antonetta", "Antonette", "Antonia", "Antonietta", "Antonina", "Antonio", "Antwan", "Antwon", "Anya", "April", "Ara", "Araceli", "Aracely", "Arch", "Archibald", "Ardella", "Arden", "Ardith", "Arely", "Ari", "Ariane", "Arianna", "Aric", "Ariel", "Arielle", "Arjun", "Arlene", "Arlie", "Arlo", "Armand", "Armando", "Armani", "Arnaldo", "Arne", "Arno", "Arnold", "Arnoldo", "Arnulfo", "Aron", "Art", "Arthur", "Arturo", "Arvel", "Arvid", "Arvilla", "Aryanna", "Asa", "Asha", "Ashlee", "Ashleigh", "Ashley", "Ashly", "Ashlynn", "Ashton", "Ashtyn", "Asia", "Assunta", "Astrid", "Athena", "Aubree", "Aubrey", "Audie", "Audra", "Audreanne", "Audrey", "August", "Augusta", "Augustine", "Augustus", "Aurelia", "Aurelie", "Aurelio", "Aurore", "Austen", "Austin", "Austyn", "Autumn", "Ava", "Avery", "Avis", "Axel", "Ayana", "Ayden", "Ayla", "Aylin", "Baby", "Bailee", "Bailey", "Barbara", "Barney", "Baron", "Barrett", "Barry", "Bart", "Bartholome", "Barton", "Baylee", "Beatrice", "Beau", "Beaulah", "Bell", "Bella", "Belle", "Ben", "Benedict", "Benjamin", "Bennett", "Bennie", "Benny", "Benton", "Berenice", "Bernadette", "Bernadine", "Bernard", "Bernardo", "Berneice", "Bernhard", "Bernice", "Bernie", "Berniece", "Bernita", "Berry", "Bert", "Berta", "Bertha", "Bertram", "Bertrand", "Beryl", "Bessie", "Beth", "Bethany", "Bethel", "Betsy", "Bette", "Bettie", "Betty", "Bettye", "Beulah", "Beverly", "Bianka", "Bill", "Billie", "Billy", "Birdie", "Blair", "Blaise", "Blake", "Blanca", "Blanche", "Blaze", "Bo", "Bobbie", "Bobby", "Bonita", "Bonnie", "Boris", "Boyd", "Brad", "Braden", "Bradford", "Bradley", "Bradly", "Brady", "Braeden", "Brain", "Brandi", "Brando", "Brandon", "Brandt", "Brandy", "Brandyn", "Brannon", "Branson", "Brant", "Braulio", "Braxton", "Brayan", "Breana", "Breanna", "Breanne", "Brenda", "Brendan", "Brenden", "Brendon", "Brenna", "Brennan", "Brennon", "Brent", "Bret", "Brett", "Bria", "Brian", "Briana", "Brianne", "Brice", "Bridget", "Bridgette", "Bridie", "Brielle", "Brigitte", "Brionna", "Brisa", "Britney", "Brittany", "Brock", "Broderick", "Brody", "Brook", "Brooke", "Brooklyn", "Brooks", "Brown", "Bruce", "Bryana", "Bryce", "Brycen", "Bryon", "Buck", "Bud", "Buddy", "Buford", "Bulah", "Burdette", "Burley", "Burnice", "Buster", "Cade", "Caden", "Caesar", "Caitlyn", "Cale", "Caleb", "Caleigh", "Cali", "Calista", "Callie", "Camden", "Cameron", "Camila", "Camilla", "Camille", "Camren", "Camron", "Camryn", "Camylle", "Candace", "Candelario", "Candice", "Candida", "Candido", "Cara", "Carey", "Carissa", "Carlee", "Carleton", "Carley", "Carli", "Carlie", "Carlo", "Carlos", "Carlotta", "Carmel", "Carmela", "Carmella", "Carmelo", "Carmen", "Carmine", "Carol", "Carolanne", "Carole", "Carolina", "Caroline", "Carolyn", "Carolyne", "Carrie", "Carroll", "Carson", "Carter", "Cary", "Casandra", "Casey", "Casimer", "Casimir", "Casper", "Cassandra", "Cassandre", "Cassidy", "Cassie", "Catalina", "Caterina", "Catharine", "Catherine", "Cathrine", "Cathryn", "Cathy", "Cayla", "Ceasar", "Cecelia", "Cecil", "Cecile", "Cecilia", "Cedrick", "Celestine", "Celestino", "Celia", "Celine", "Cesar", "Chad", "Chadd", "Chadrick", "Chaim", "Chance", "Chandler", "Chanel", "Chanelle", "Charity", "Charlene", "Charles", "Charley", "Charlie", "Charlotte", "Chase", "Chasity", "Chauncey", "Chaya", "Chaz", "Chelsea", "Chelsey", "Chelsie", "Chesley", "Chester", "Chet", "Cheyanne", "Cheyenne", "Chloe", "Chris", "Christ", "Christa", "Christelle", "Christian", "Christiana", "Christina", "Christine", "Christop", "Christophe", "Christopher", "Christy", "Chyna", "Ciara", "Cicero", "Cielo", "Cierra", "Cindy", "Citlalli", "Clair", "Claire", "Clara", "Clarabelle", "Clare", "Clarissa", "Clark", "Claud", "Claude", "Claudia", "Claudie", "Claudine", "Clay", "Clemens", "Clement", "Clementina", "Clementine", "Clemmie", "Cleo", "Cleora", "Cleta", "Cletus", "Cleve", "Cleveland", "Clifford", "Clifton", "Clint", "Clinton", "Clotilde", "Clovis", "Cloyd", "Clyde", "Coby", "Cody", "Colby", "Cole", "Coleman", "Colin", "Colleen", "Collin", "Colt", "Colten", "Colton", "Columbus", "Concepcion", "Conner", "Connie", "Connor", "Conor", "Conrad", "Constance", "Constantin", "Consuelo", "Cooper", "Cora", "Coralie", "Corbin", "Cordelia", "Cordell", "Cordia", "Cordie", "Corene", "Corine", "Cornelius", "Cornell", "Corrine", "Cortez", "Cortney", "Cory", "Coty", "Courtney", "Coy", "Craig", "Crawford", "Creola", "Cristal", "Cristian", "Cristina", "Cristobal", "Cristopher", "Cruz", "Crystal", "Crystel", "Cullen", "Curt", "Curtis", "Cydney", "Cynthia", "Cyril", "Cyrus", "Dagmar", "Dahlia", "Daija", "Daisha", "Daisy", "Dakota", "Dale", "Dallas", "Dallin", "Dalton", "Damaris", "Dameon", "Damian", "Damien", "Damion", "Damon", "Dan", "Dana", "Dandre", "Dane", "Dangelo", "Dangelo", "Danial", "Daniela", "Daniella", "Danielle", "Danika", "Dannie", "Danny", "Dante", "Danyka", "Daphne", "Daphnee", "Daphney", "Darby", "Daren", "Darian", "Dariana", "Darien", "Dario", "Darion", "Darius", "Darlene", "Daron", "Darrel", "Darrell", "Darren", "Darrick", "Darrin", "Darrion", "Darron", "Darryl", "Darwin", "Daryl", "Dashawn", "Dasia", "Dave", "David", "Davin", "Davion", "Davon", "Davonte", "Dawn", "Dawson", "Dax", "Dayana", "Dayna", "Dayne", "Dayton", "Dean", "Deangelo", "Deanna", "Deborah", "Declan", "Dedric", "Dedrick", "Dee", "Deion", "Deja", "Dejah", "Dejon", "Dejuan", "Delaney", "Delbert", "Delfina", "Delia", "Delilah", "Dell", "Della", "Delmer", "Delores", "Delpha", "Delphia", "Delphine", "Delta", "Demarco", "Demarcus", "Demario", "Demetris", "Demetrius", "Demond", "Dena", "Denis", "Dennis", "Deon", "Deondre", "Deontae", "Deonte", "Dereck", "Derek", "Derick", "Deron", "Derrick", "Deshaun", "Deshawn", "Desiree", "Desmond", "Dessie", "Destany", "Destin", "Destinee", "Destiney", "Destini", "Destiny", "Devan", "Devante", "Deven", "Devin", "Devon", "Devonte", "Devyn", "Dewayne", "Dewitt", "Dexter", "Diamond", "Diana", "Dianna", "Diego", "Dillan", "Dillon", "Dimitri", "Dina", "Dino", "Dion", "Dixie", "Dock", "Dolly", "Dolores", "Domenic", "Domenica", "Domenick", "Domenico", "Domingo", "Dominic", "Dominique", "Don", "Donald", "Donato", "Donavon", "Donna", "Donnell", "Donnie", "Donny", "Dora", "Dorcas", "Dorian", "Doris", "Dorothea", "Dorothy", "Dorris", "Dortha", "Dorthy", "Doug", "Douglas", "Dovie", "Doyle", "Drake", "Drew", "Duane", "Dudley", "Dulce", "Duncan", "Durward", "Dustin", "Dusty", "Dwight", "Dylan", "Earl", "Earlene", "Earline", "Earnest", "Earnestine", "Easter", "Easton", "Ebba", "Ebony", "Ed", "Eda", "Edd", "Eddie", "Eden", "Edgar", "Edgardo", "Edison", "Edmond", "Edmund", "Edna", "Eduardo", "Edward", "Edwardo", "Edwin", "Edwina", "Edyth", "Edythe", "Effie", "Efrain", "Efren", "Eileen", "Einar", "Eino", "Eladio", "Elaina", "Elbert", "Elda", "Eldon", "Eldora", "Eldred", "Eldridge", "Eleanora", "Eleanore", "Eleazar", "Electa", "Elena", "Elenor", "Elenora", "Eleonore", "Elfrieda", "Eli", "Elian", "Eliane", "Elias", "Eliezer", "Elijah", "Elinor", "Elinore", "Elisa", "Elisabeth", "Elise", "Eliseo", "Elisha", "Elissa", "Eliza", "Elizabeth", "Ella", "Ellen", "Ellie", "Elliot", "Elliott", "Ellis", "Ellsworth", "Elmer", "Elmira", "Elmo", "Elmore", "Elna", "Elnora", "Elody", "Eloisa", "Eloise", "Elouise", "Eloy", "Elroy", "Elsa", "Else", "Elsie", "Elta", "Elton", "Elva", "Elvera", "Elvie", "Elvis", "Elwin", "Elwyn", "Elyse", "Elyssa", "Elza", "Emanuel", "Emelia", "Emelie", "Emely", "Emerald", "Emerson", "Emery", "Emie", "Emil", "Emile", "Emilia", "Emiliano", "Emilie", "Emilio", "Emily", "Emma", "Emmalee", "Emmanuel", "Emmanuelle", "Emmet", "Emmett", "Emmie", "Emmitt", "Emmy", "Emory", "Ena", "Enid", "Enoch", "Enola", "Enos", "Enrico", "Enrique", "Ephraim", "Era", "Eriberto", "Eric", "Erica", "Erich", "Erick", "Ericka", "Erik", "Erika", "Erin", "Erling", "Erna", "Ernest", "Ernestina", "Ernestine", "Ernesto", "Ernie", "Ervin", "Erwin", "Eryn", "Esmeralda", "Esperanza", "Esta", "Esteban", "Estefania", "Estel", "Estell", "Estella", "Estelle", "Estevan", "Esther", "Estrella", "Etha", "Ethan", "Ethel", "Ethelyn", "Ethyl", "Ettie", "Eudora", "Eugene", "Eugenia", "Eula", "Eulah", "Eulalia", "Euna", "Eunice", "Eusebio", "Eva", "Evalyn", "Evan", "Evangeline", "Evans", "Eve", "Eveline", "Evelyn", "Everardo", "Everett", "Everette", "Evert", "Evie", "Ewald", "Ewell", "Ezekiel", "Ezequiel", "Ezra", "Fabian", "Fabiola", "Fae", "Fannie", "Fanny", "Fatima", "Faustino", "Fausto", "Favian", "Fay", "Faye", "Federico", "Felicia", "Felicita", "Felicity", "Felipa", "Felipe", "Felix", "Felton", "Fermin", "Fern", "Fernando", "Ferne", "Fidel", "Filiberto", "Filomena", "Finn", "Fiona", "Flavie", "Flavio", "Fleta", "Fletcher", "Flo", "Florence", "Florencio", "Florian", "Florida", "Florine", "Flossie", "Floy", "Floyd", "Ford", "Forest", "Forrest", "Foster", "Frances", "Francesca", "Francesco", "Francis", "Francisca", "Francisco", "Franco", "Frank", "Frankie", "Franz", "Fred", "Freda", "Freddie", "Freddy", "Frederic", "Frederick", "Frederik", "Frederique", "Fredrick", "Fredy", "Freeda", "Freeman", "Freida", "Frida", "Frieda", "Friedrich", "Fritz", "Furman", "Gabe", "Gabriel", "Gabriella", "Gabrielle", "Gaetano", "Gage", "Gail", "Gardner", "Garett", "Garfield", "Garland", "Garnet", "Garnett", "Garret", "Garrett", "Garrick", "Garrison", "Garry", "Garth", "Gaston", "Gavin", "Gay", "Gayle", "Gaylord", "Gene", "General", "Genesis", "Genevieve", "Gennaro", "Genoveva", "Geo", "Geoffrey", "George", "Georgette", "Georgiana", "Georgianna", "Geovanni", "Geovanny", "Geovany", "Gerald", "Geraldine", "Gerard", "Gerardo", "Gerda", "Gerhard", "Germaine", "German", "Gerry", "Gerson", "Gertrude", "Gia", "Gianni", "Gideon", "Gilbert", "Gilberto", "Gilda", "Giles", "Gillian", "Gina", "Gino", "Giovani", "Giovanna", "Giovanni", "Giovanny", "Gisselle", "Giuseppe", "Gladyce", "Gladys", "Glen", "Glenda", "Glenna", "Glennie", "Gloria", "Godfrey", "Golda", "Golden", "Gonzalo", "Gordon", "Grace", "Gracie", "Graciela", "Grady", "Graham", "Grant", "Granville", "Grayce", "Grayson", "Green", "Greg", "Gregg", "Gregoria", "Gregorio", "Gregory", "Greta", "Gretchen", "Greyson", "Griffin", "Grover", "Guadalupe", "Gudrun", "Guido", "Guillermo", "Guiseppe", "Gunnar", "Gunner", "Gus", "Gussie", "Gust", "Gustave", "Guy", "Gwen", "Gwendolyn", "Hadley", "Hailee", "Hailey", "Hailie", "Hal", "Haleigh", "Haley", "Halie", "Halle", "Hallie", "Hank", "Hanna", "Hannah", "Hans", "Hardy", "Harley", "Harmon", "Harmony", "Harold", "Harrison", "Harry", "Harvey", "Haskell", "Hassan", "Hassie", "Hattie", "Haven", "Hayden", "Haylee", "Hayley", "Haylie", "Hazel", "Hazle", "Heath", "Heather", "Heaven", "Heber", "Hector", "Heidi", "Helen", "Helena", "Helene", "Helga", "Hellen", "Helmer", "Heloise", "Henderson", "Henri", "Henriette", "Henry", "Herbert", "Herman", "Hermann", "Hermina", "Herminia", "Herminio", "Hershel", "Herta", "Hertha", "Hester", "Hettie", "Hilario", "Hilbert", "Hilda", "Hildegard", "Hillard", "Hillary", "Hilma", "Hilton", "Hipolito", "Hiram", "Hobart", "Holden", "Hollie", "Hollis", "Holly", "Hope", "Horace", "Horacio", "Hortense", "Hosea", "Houston", "Howard", "Howell", "Hoyt", "Hubert", "Hudson", "Hugh", "Hulda", "Humberto", "Hunter", "Hyman", "Ian", "Ibrahim", "Icie", "Ida", "Idell", "Idella", "Ignacio", "Ignatius", "Ike", "Ila", "Ilene", "Iliana", "Ima", "Imani", "Imelda", "Immanuel", "Imogene", "Ines", "Irma", "Irving", "Irwin", "Isaac", "Isabel", "Isabell", "Isabella", "Isabelle", "Isac", "Isadore", "Isai", "Isaiah", "Isaias", "Isidro", "Ismael", "Isobel", "Isom", "Israel", "Issac", "Itzel", "Iva", "Ivah", "Ivory", "Ivy", "Izabella", "Izaiah", "Jabari", "Jace", "Jacey", "Jacinthe", "Jacinto", "Jack", "Jackeline", "Jackie", "Jacklyn", "Jackson", "Jacky", "Jaclyn", "Jacquelyn", "Jacques", "Jacynthe", "Jada", "Jade", "Jaden", "Jadon", "Jadyn", "Jaeden", "Jaida", "Jaiden", "Jailyn", "Jaime", "Jairo", "Jakayla", "Jake", "Jakob", "Jaleel", "Jalen", "Jalon", "Jalyn", "Jamaal", "Jamal", "Jamar", "Jamarcus", "Jamel", "Jameson", "Jamey", "Jamie", "Jamil", "Jamir", "Jamison", "Jammie", "Jan", "Jana", "Janae", "Jane", "Janelle", "Janessa", "Janet", "Janice", "Janick", "Janie", "Janis", "Janiya", "Jannie", "Jany", "Jaquan", "Jaquelin", "Jaqueline", "Jared", "Jaren", "Jarod", "Jaron", "Jarred", "Jarrell", "Jarret", "Jarrett", "Jarrod", "Jarvis", "Jasen", "Jasmin", "Jason", "Jasper", "Jaunita", "Javier", "Javon", "Javonte", "Jay", "Jayce", "Jaycee", "Jayda", "Jayde", "Jayden", "Jaydon", "Jaylan", "Jaylen", "Jaylin", "Jaylon", "Jayme", "Jayne", "Jayson", "Jazlyn", "Jazmin", "Jazmyn", "Jazmyne", "Jean", "Jeanette", "Jeanie", "Jeanne", "Jed", "Jedediah", "Jedidiah", "Jeff", "Jefferey", "Jeffery", "Jeffrey", "Jeffry", "Jena", "Jenifer", "Jennie", "Jennifer", "Jennings", "Jennyfer", "Jensen", "Jerad", "Jerald", "Jeramie", "Jeramy", "Jerel", "Jeremie", "Jeremy", "Jermain", "Jermaine", "Jermey", "Jerod", "Jerome", "Jeromy", "Jerrell", "Jerrod", "Jerrold", "Jerry", "Jess", "Jesse", "Jessica", "Jessie", "Jessika", "Jessy", "Jessyca", "Jesus", "Jett", "Jettie", "Jevon", "Jewel", "Jewell", "Jillian", "Jimmie", "Jimmy", "Jo", "Joan", "Joana", "Joanie", "Joanne", "Joannie", "Joanny", "Joany", "Joaquin", "Jocelyn", "Jodie", "Jody", "Joe", "Joel", "Joelle", "Joesph", "Joey", "Johan", "Johann", "Johanna", "Johathan", "John", "Johnathan", "Johnathon", "Johnnie", "Johnny", "Johnpaul", "Johnson", "Jolie", "Jon", "Jonas", "Jonatan", "Jonathan", "Jonathon", "Jordan", "Jordane", "Jordi", "Jordon", "Jordy", "Jordyn", "Jorge", "Jose", "Josefa", "Josefina", "Joseph", "Josephine", "Josh", "Joshua", "Joshuah", "Josiah", "Josiane", "Josianne", "Josie", "Josue", "Jovan", "Jovani", "Jovanny", "Jovany", "Joy", "Joyce", "Juana", "Juanita", "Judah", "Judd", "Jude", "Judge", "Judson", "Judy", "Jules", "Julia", "Julian", "Juliana", "Julianne", "Julie", "Julien", "Juliet", "Julio", "Julius", "June", "Junior", "Junius", "Justen", "Justice", "Justina", "Justine", "Juston", "Justus", "Justyn", "Juvenal", "Juwan", "Kacey", "Kaci", "Kacie", "Kade", "Kaden", "Kadin", "Kaela", "Kaelyn", "Kaia", "Kailee", "Kailey", "Kailyn", "Kaitlin", "Kaitlyn", "Kale", "Kaleb", "Kaleigh", "Kaley", "Kali", "Kallie", "Kameron", "Kamille", "Kamren", "Kamron", "Kamryn", "Kane", "Kara", "Kareem", "Karelle", "Karen", "Kari", "Kariane", "Karianne", "Karina", "Karine", "Karl", "Karlee", "Karley", "Karli", "Karlie", "Karolann", "Karson", "Kasandra", "Kasey", "Kassandra", "Katarina", "Katelin", "Katelyn", "Katelynn", "Katharina", "Katherine", "Katheryn", "Kathleen", "Kathlyn", "Kathryn", "Kathryne", "Katlyn", "Katlynn", "Katrina", "Katrine", "Kattie", "Kavon", "Kay", "Kaya", "Kaycee", "Kayden", "Kayla", "Kaylah", "Kaylee", "Kayleigh", "Kayley", "Kayli", "Kaylie", "Kaylin", "Keagan", "Keanu", "Keara", "Keaton", "Keegan", "Keeley", "Keely", "Keenan", "Keira", "Keith", "Kellen", "Kelley", "Kelli", "Kellie", "Kelly", "Kelsi", "Kelsie", "Kelton", "Kelvin", "Ken", "Kendall", "Kendra", "Kendrick", "Kenna", "Kennedi", "Kennedy", "Kenneth", "Kennith", "Kenny", "Kenton", "Kenya", "Kenyatta", "Kenyon", "Keon", "Keshaun", "Keshawn", "Keven", "Kevin", "Kevon", "Keyon", "Keyshawn", "Khalid", "Khalil", "Kian", "Kiana", "Kianna", "Kiara", "Kiarra", "Kiel", "Kiera", "Kieran", "Kiley", "Kim", "Kimberly", "King", "Kip", "Kira", "Kirk", "Kirsten", "Kirstin", "Kitty", "Kobe", "Koby", "Kody", "Kolby", "Kole", "Korbin", "Korey", "Kory", "Kraig", "Kris", "Krista", "Kristian", "Kristin", "Kristina", "Kristofer", "Kristoffer", "Kristopher", "Kristy", "Krystal", "Krystel", "Krystina", "Kurt", "Kurtis", "Kyla", "Kyle", "Kylee", "Kyleigh", "Kyler", "Kylie", "Kyra", "Lacey", "Lacy", "Ladarius", "Lafayette", "Laila", "Laisha", "Lamar", "Lambert", "Lamont", "Lance", "Landen", "Lane", "Laney", "Larissa", "Laron", "Larry", "Larue", "Laura", "Laurel", "Lauren", "Laurence", "Lauretta", "Lauriane", "Laurianne", "Laurie", "Laurine", "Laury", "Lauryn", "Lavada", "Lavern", "Laverna", "Laverne", "Lavina", "Lavinia", "Lavon", "Lavonne", "Lawrence", "Lawson", "Layla", "Layne", "Lazaro", "Lea", "Leann", "Leanna", "Leanne", "Leatha", "Leda", "Lee", "Leif", "Leila", "Leilani", "Lela", "Lelah", "Leland", "Lelia", "Lempi", "Lemuel", "Lenna", "Lennie", "Lenny", "Lenora", "Lenore", "Leo", "Leola", "Leon", "Leonard", "Leonardo", "Leone", "Leonel", "Leonie", "Leonor", "Leonora", "Leopold", "Leopoldo", "Leora", "Lera", "Lesley", "Leslie", "Lesly", "Lessie", "Lester", "Leta", "Letha", "Letitia", "Levi", "Lew", "Lewis", "Lexi", "Lexie", "Lexus", "Lia", "Liam", "Liana", "Libbie", "Libby", "Lila", "Lilian", "Liliana", "Liliane", "Lilla", "Lillian", "Lilliana", "Lillie", "Lilly", "Lily", "Lilyan", "Lina", "Lincoln", "Linda", "Lindsay", "Lindsey", "Linnea", "Linnie", "Linwood", "Lionel", "Lisa", "Lisandro", "Lisette", "Litzy", "Liza", "Lizeth", "Lizzie", "Llewellyn", "Lloyd", "Logan", "Lois", "Lola", "Lolita", "Loma", "Lon", "London", "Lonie", "Lonnie", "Lonny", "Lonzo", "Lora", "Loraine", "Loren", "Lorena", "Lorenz", "Lorenza", "Lorenzo", "Lori", "Lorine", "Lorna", "Lottie", "Lou", "Louie", "Louisa", "Lourdes", "Louvenia", "Lowell", "Loy", "Loyal", "Loyce", "Lucas", "Luciano", "Lucie", "Lucienne", "Lucile", "Lucinda", "Lucio", "Lucious", "Lucius", "Lucy", "Ludie", "Ludwig", "Lue", "Luella", "Luigi", "Luis", "Luisa", "Lukas", "Lula", "Lulu", "Luna", "Lupe", "Lura", "Lurline", "Luther", "Luz", "Lyda", "Lydia", "Lyla", "Lynn", "Lyric", "Lysanne", "Mabel", "Mabelle", "Mable", "Mac", "Macey", "Maci", "Macie", "Mack", "Mackenzie", "Macy", "Madaline", "Madalyn", "Maddison", "Madeline", "Madelyn", "Madelynn", "Madge", "Madie", "Madilyn", "Madisen", "Madison", "Madisyn", "Madonna", "Madyson", "Mae", "Maegan", "Maeve", "Mafalda", "Magali", "Magdalen", "Magdalena", "Maggie", "Magnolia", "Magnus", "Maia", "Maida", "Maiya", "Major", "Makayla", "Makenna", "Makenzie", "Malachi", "Malcolm", "Malika", "Malinda", "Mallie", "Mallory", "Malvina", "Mandy", "Manley", "Manuel", "Manuela", "Mara", "Marc", "Marcel", "Marcelina", "Marcelino", "Marcella", "Marcelle", "Marcellus", "Marcelo", "Marcia", "Marco", "Marcos", "Marcus", "Margaret", "Margarete", "Margarett", "Margaretta", "Margarette", "Margarita", "Marge", "Margie", "Margot", "Margret", "Marguerite", "Maria", "Mariah", "Mariam", "Marian", "Mariana", "Mariane", "Marianna", "Marianne", "Mariano", "Maribel", "Marie", "Mariela", "Marielle", "Marietta", "Marilie", "Marilou", "Marilyne", "Marina", "Mario", "Marion", "Marisa", "Marisol", "Maritza", "Marjolaine", "Marjorie", "Marjory", "Mark", "Markus", "Marlee", "Marlen", "Marlene", "Marley", "Marlin", "Marlon", "Marques", "Marquis", "Marquise", "Marshall", "Marta", "Martin", "Martina", "Martine", "Marty", "Marvin", "Mary", "Maryam", "Maryjane", "Maryse", "Mason", "Mateo", "Mathew", "Mathias", "Mathilde", "Matilda", "Matilde", "Matt", "Matteo", "Mattie", "Maud", "Maude", "Maudie", "Maureen", "Maurice", "Mauricio", "Maurine", "Maverick", "Mavis", "Max", "Maxie", "Maxime", "Maximilian", "Maximillia", "Maximillian", "Maximo", "Maximus", "Maxine", "Maxwell", "May", "Maya", "Maybell", "Maybelle", "Maye", "Maymie", "Maynard", "Mayra", "Mazie", "Mckayla", "Mckenna", "Mckenzie", "Meagan", "Meaghan", "Meda", "Megane", "Meggie", "Meghan", "Mekhi", "Melany", "Melba", "Melisa", "Melissa", "Mellie", "Melody", "Melvin", "Melvina", "Melyna", "Melyssa", "Mercedes", "Meredith", "Merl", "Merle", "Merlin", "Merritt", "Mertie", "Mervin", "Meta", "Mia", "Micaela", "Micah", "Michael", "Michaela", "Michale", "Micheal", "Michel", "Michele", "Michelle", "Miguel", "Mikayla", "Mike", "Mikel", "Milan", "Miles", "Milford", "Miller", "Millie", "Milo", "Milton", "Mina", "Minerva", "Minnie", "Miracle", "Mireille", "Mireya", "Misael", "Missouri", "Misty", "Mitchel", "Mitchell", "Mittie", "Modesta", "Modesto", "Mohamed", "Mohammad", "Mohammed", "Moises", "Mollie", "Molly", "Mona", "Monica", "Monique", "Monroe", "Monserrat", "Monserrate", "Montana", "Monte", "Monty", "Morgan", "Moriah", "Morris", "Mortimer", "Morton", "Mose", "Moses", "Moshe", "Mossie", "Mozell", "Mozelle", "Muhammad", "Muriel", "Murl", "Murphy", "Murray", "Mustafa", "Mya", "Myah", "Mylene", "Myles", "Myra", "Myriam", "Myrl", "Myrna", "Myron", "Myrtice", "Myrtie", "Myrtis", "Myrtle", "Nadia", "Nakia", "Name", "Nannie", "Naomi", "Naomie", "Napoleon", "Narciso", "Nash", "Nasir", "Nat", "Natalia", "Natalie", "Natasha", "Nathan", "Nathanael", "Nathanial", "Nathaniel", "Nathen", "Nayeli", "Neal", "Ned", "Nedra", "Neha", "Neil", "Nelda", "Nella", "Nelle", "Nellie", "Nels", "Nelson", "Neoma", "Nestor", "Nettie", "Neva", "Newell", "Newton", "Nia", "Nicholas", "Nicholaus", "Nichole", "Nick", "Nicklaus", "Nickolas", "Nico", "Nicola", "Nicolas", "Nicole", "Nicolette", "Nigel", "Nikita", "Nikki", "Nikko", "Niko", "Nikolas", "Nils", "Nina", "Noah", "Noble", "Noe", "Noel", "Noelia", "Noemi", "Noemie", "Noemy", "Nola", "Nolan", "Nona", "Nora", "Norbert", "Norberto", "Norene", "Norma", "Norris", "Norval", "Norwood", "Nova", "Novella", "Nya", "Nyah", "Nyasia", "Obie", "Oceane", "Ocie", "Octavia", "Oda", "Odell", "Odessa", "Odie", "Ofelia", "Okey", "Ola", "Olaf", "Ole", "Olen", "Oleta", "Olga", "Olin", "Oliver", "Ollie", "Oma", "Omari", "Omer", "Ona", "Onie", "Opal", "Ophelia", "Ora", "Oral", "Oran", "Oren", "Orie", "Orin", "Orion", "Orland", "Orlando", "Orlo", "Orpha", "Orrin", "Orval", "Orville", "Osbaldo", "Osborne", "Oscar", "Osvaldo", "Oswald", "Oswaldo", "Otha", "Otho", "Otilia", "Otis", "Ottilie", "Ottis", "Otto", "Ova", "Owen", "Ozella", "Pablo", "Paige", "Palma", "Pamela", "Pansy", "Paolo", "Paris", "Parker", "Pascale", "Pasquale", "Pat", "Patience", "Patricia", "Patrick", "Patsy", "Pattie", "Paul", "Paula", "Pauline", "Paxton", "Payton", "Pearl", "Pearlie", "Pearline", "Pedro", "Peggie", "Penelope", "Percival", "Percy", "Perry", "Pete", "Peter", "Petra", "Peyton", "Philip", "Phoebe", "Phyllis", "Pierce", "Pierre", "Pietro", "Pink", "Pinkie", "Piper", "Polly", "Porter", "Precious", "Presley", "Preston", "Price", "Prince", "Princess", "Priscilla", "Providenci", "Prudence", "Queen", "Queenie", "Quentin", "Quincy", "Quinn", "Quinten", "Quinton", "Rachael", "Rachel", "Rachelle", "Rae", "Raegan", "Rafael", "Rafaela", "Raheem", "Rahsaan", "Rahul", "Raina", "Raleigh", "Ralph", "Ramiro", "Ramon", "Ramona", "Randal", "Randall", "Randi", "Randy", "Ransom", "Raoul", "Raphael", "Raphaelle", "Raquel", "Rashad", "Rashawn", "Rasheed", "Raul", "Raven", "Ray", "Raymond", "Raymundo", "Reagan", "Reanna", "Reba", "Rebeca", "Rebecca", "Rebeka", "Rebekah", "Reece", "Reed", "Reese", "Regan", "Reggie", "Reginald", "Reid", "Reilly", "Reina", "Reinhold", "Remington", "Rene", "Renee", "Ressie", "Reta", "Retha", "Retta", "Reuben", "Reva", "Rex", "Rey", "Reyes", "Reymundo", "Reyna", "Reynold", "Rhea", "Rhett", "Rhianna", "Rhiannon", "Rhoda", "Ricardo", "Richard", "Richie", "Richmond", "Rick", "Rickey", "Rickie", "Ricky", "Rico", "Rigoberto", "Riley", "Rita", "River", "Robb", "Robbie", "Robert", "Roberta", "Roberto", "Robin", "Robyn", "Rocio", "Rocky", "Rod", "Roderick", "Rodger", "Rodolfo", "Rodrick", "Rodrigo", "Roel", "Rogelio", "Roger", "Rogers", "Rolando", "Rollin", "Roma", "Romaine", "Roman", "Ron", "Ronaldo", "Ronny", "Roosevelt", "Rory", "Rosa", "Rosalee", "Rosalia", "Rosalind", "Rosalinda", "Rosalyn", "Rosamond", "Rosanna", "Rosario", "Roscoe", "Rose", "Rosella", "Roselyn", "Rosemarie", "Rosemary", "Rosendo", "Rosetta", "Rosie", "Rosina", "Roslyn", "Ross", "Rossie", "Rowan", "Rowena", "Rowland", "Roxane", "Roxanne", "Roy", "Royal", "Royce", "Rozella", "Ruben", "Rubie", "Ruby", "Rubye", "Rudolph", "Rudy", "Rupert", "Russ", "Russel", "Russell", "Rusty", "Ruth", "Ruthe", "Ruthie", "Ryan", "Ryann", "Ryder", "Rylan", "Rylee", "Ryleigh", "Ryley", "Sabina", "Sabrina", "Sabryna", "Sadie", "Sadye", "Sage", "Saige", "Sallie", "Sally", "Salma", "Salvador", "Salvatore", "Sam", "Samanta", "Samantha", "Samara", "Samir", "Sammie", "Sammy", "Samson", "Sandra", "Sandrine", "Sandy", "Sanford", "Santa", "Santiago", "Santina", "Santino", "Santos", "Sarah", "Sarai", "Sarina", "Sasha", "Saul", "Savanah", "Savanna", "Savannah", "Savion", "Scarlett", "Schuyler", "Scot", "Scottie", "Scotty", "Seamus", "Sean", "Sebastian", "Sedrick", "Selena", "Selina", "Selmer", "Serena", "Serenity", "Seth", "Shad", "Shaina", "Shakira", "Shana", "Shane", "Shanel", "Shanelle", "Shania", "Shanie", "Shaniya", "Shanna", "Shannon", "Shanny", "Shanon", "Shany", "Sharon", "Shaun", "Shawn", "Shawna", "Shaylee", "Shayna", "Shayne", "Shea", "Sheila", "Sheldon", "Shemar", "Sheridan", "Sherman", "Sherwood", "Shirley", "Shyann", "Shyanne", "Sibyl", "Sid", "Sidney", "Sienna", "Sierra", "Sigmund", "Sigrid", "Sigurd", "Silas", "Sim", "Simeon", "Simone", "Sincere", "Sister", "Skye", "Skyla", "Skylar", "Sofia", "Soledad", "Solon", "Sonia", "Sonny", "Sonya", "Sophia", "Sophie", "Spencer", "Stacey", "Stacy", "Stan", "Stanford", "Stanley", "Stanton", "Stefan", "Stefanie", "Stella", "Stephan", "Stephania", "Stephanie", "Stephany", "Stephen", "Stephon", "Sterling", "Steve", "Stevie", "Stewart", "Stone", "Stuart", "Summer", "Sunny", "Susan", "Susana", "Susanna", "Susie", "Suzanne", "Sven", "Syble", "Sydnee", "Sydney", "Sydni", "Sydnie", "Sylvan", "Sylvester", "Sylvia", "Tabitha", "Tad", "Talia", "Talon", "Tamara", "Tamia", "Tania", "Tanner", "Tanya", "Tara", "Taryn", "Tate", "Tatum", "Tatyana", "Taurean", "Tavares", "Taya", "Taylor", "Teagan", "Ted", "Telly", "Terence", "Teresa", "Terrance", "Terrell", "Terrence", "Terrill", "Terry", "Tess", "Tessie", "Tevin", "Thad", "Thaddeus", "Thalia", "Thea", "Thelma", "Theo", "Theodora", "Theodore", "Theresa", "Therese", "Theresia", "Theron", "Thomas", "Thora", "Thurman", "Tia", "Tiana", "Tianna", "Tiara", "Tierra", "Tiffany", "Tillman", "Timmothy", "Timmy", "Timothy", "Tina", "Tito", "Titus", "Tobin", "Toby", "Tod", "Tom", "Tomas", "Tomasa", "Tommie", "Toney", "Toni", "Tony", "Torey", "Torrance", "Torrey", "Toy", "Trace", "Tracey", "Tracy", "Travis", "Travon", "Tre", "Tremaine", "Tremayne", "Trent", "Trenton", "Tressa", "Tressie", "Treva", "Trever", "Trevion", "Trevor", "Trey", "Trinity", "Trisha", "Tristian", "Tristin", "Triston", "Troy", "Trudie", "Trycia", "Trystan", "Turner", "Twila", "Tyler", "Tyra", "Tyree", "Tyreek", "Tyrel", "Tyrell", "Tyrese", "Tyrique", "Tyshawn", "Tyson", "Ubaldo", "Ulices", "Ulises", "Una", "Unique", "Urban", "Uriah", "Uriel", "Ursula", "Vada", "Valentin", "Valentina", "Valentine", "Valerie", "Vallie", "Van", "Vance", "Vanessa", "Vaughn", "Veda", "Velda", "Vella", "Velma", "Velva", "Vena", "Verda", "Verdie", "Vergie", "Verla", "Verlie", "Vern", "Verna", "Verner", "Vernice", "Vernie", "Vernon", "Verona", "Veronica", "Vesta", "Vicenta", "Vicente", "Vickie", "Vicky", "Victor", "Victoria", "Vida", "Vidal", "Vilma", "Vince", "Vincent", "Vincenza", "Vincenzo", "Vinnie", "Viola", "Violet", "Violette", "Virgie", "Virgil", "Virginia", "Virginie", "Vita", "Vito", "Viva", "Vivian", "Viviane", "Vivianne", "Vivien", "Vivienne", "Vladimir", "Wade", "Waino", "Waldo", "Walker", "Wallace", "Walter", "Walton", "Wanda", "Ward", "Warren", "Watson", "Wava", "Waylon", "Wayne", "Webster", "Weldon", "Wellington", "Wendell", "Wendy", "Werner", "Westley", "Weston", "Whitney", "Wilber", "Wilbert", "Wilburn", "Wiley", "Wilford", "Wilfred", "Wilfredo", "Wilfrid", "Wilhelm", "Wilhelmine", "Will", "Willa", "Willard", "William", "Willie", "Willis", "Willow", "Willy", "Wilma", "Wilmer", "Wilson", "Wilton", "Winfield", "Winifred", "Winnifred", "Winona", "Winston", "Woodrow", "Wyatt", "Wyman", "Xander", "Xavier", "Xzavier", "Yadira", "Yasmeen", "Yasmin", "Yasmine", "Yazmin", "Yesenia", "Yessenia", "Yolanda", "Yoshiko", "Yvette", "Yvonne", "Zachariah", "Zachary", "Zachery", "Zack", "Zackary", "Zackery", "Zakary", "Zander", "Zane", "Zaria", "Zechariah", "Zelda", "Zella", "Zelma", "Zena", "Zetta", "Zion", "Zita", "Zoe", "Zoey", "Zoie", "Zoila", "Zola", "Zora", "Zula"}, - "last": {"Abbott", "Abernathy", "Abshire", "Adams", "Altenwerth", "Anderson", "Ankunding", "Armstrong", "Auer", "Aufderhar", "Bahringer", "Bailey", "Balistreri", "Barrows", "Bartell", "Bartoletti", "Barton", "Bashirian", "Batz", "Bauch", "Baumbach", "Bayer", "Beahan", "Beatty", "Bechtelar", "Becker", "Bednar", "Beer", "Beier", "Berge", "Bergnaum", "Bergstrom", "Bernhard", "Bernier", "Bins", "Blanda", "Blick", "Block", "Bode", "Boehm", "Bogan", "Bogisich", "Borer", "Bosco", "Botsford", "Boyer", "Boyle", "Bradtke", "Brakus", "Braun", "Breitenberg", "Brekke", "Brown", "Bruen", "Buckridge", "Carroll", "Carter", "Cartwright", "Casper", "Cassin", "Champlin", "Christiansen", "Cole", "Collier", "Collins", "Conn", "Connelly", "Conroy", "Considine", "Corkery", "Cormier", "Corwin", "Cremin", "Crist", "Crona", "Cronin", "Crooks", "Cruickshank", "Cummerata", "Cummings", "Dach", "Damore", "Daniel", "Dare", "Daugherty", "Davis", "Deckow", "Denesik", "Dibbert", "Dickens", "Dicki", "Dickinson", "Dietrich", "Donnelly", "Dooley", "Douglas", "Doyle", "DuBuque", "Durgan", "Ebert", "Effertz", "Eichmann", "Emard", "Emmerich", "Erdman", "Ernser", "Fadel", "Fahey", "Farrell", "Fay", "Feeney", "Feest", "Feil", "Ferry", "Fisher", "Flatley", "Frami", "Franecki", "Friesen", "Fritsch", "Funk", "Gaylord", "Gerhold", "Gerlach", "Gibson", "Gislason", "Gleason", "Gleichner", "Glover", "Goldner", "Goodwin", "Gorczany", "Gottlieb", "Goyette", "Grady", "Graham", "Grant", "Green", "Greenfelder", "Greenholt", "Grimes", "Gulgowski", "Gusikowski", "Gutkowski", "Gutmann", "Haag", "Hackett", "Hagenes", "Hahn", "Haley", "Halvorson", "Hamill", "Hammes", "Hand", "Hane", "Hansen", "Harber", "Harris", "Hartmann", "Harvey", "Hauck", "Hayes", "Heaney", "Heathcote", "Hegmann", "Heidenreich", "Heller", "Herman", "Hermann", "Hermiston", "Herzog", "Hessel", "Hettinger", "Hickle", "Hilll", "Hills", "Hilpert", "Hintz", "Hirthe", "Hodkiewicz", "Hoeger", "Homenick", "Hoppe", "Howe", "Howell", "Hudson", "Huel", "Huels", "Hyatt", "Jacobi", "Jacobs", "Jacobson", "Jakubowski", "Jaskolski", "Jast", "Jenkins", "Jerde", "Jewess", "Johns", "Johnson", "Johnston", "Jones", "Kassulke", "Kautzer", "Keebler", "Keeling", "Kemmer", "Kerluke", "Kertzmann", "Kessler", "Kiehn", "Kihn", "Kilback", "King", "Kirlin", "Klein", "Kling", "Klocko", "Koch", "Koelpin", "Koepp", "Kohler", "Konopelski", "Koss", "Kovacek", "Kozey", "Krajcik", "Kreiger", "Kris", "Kshlerin", "Kub", "Kuhic", "Kuhlman", "Kuhn", "Kulas", "Kunde", "Kunze", "Kuphal", "Kutch", "Kuvalis", "Labadie", "Lakin", "Lang", "Langosh", "Langworth", "Larkin", "Larson", "Leannon", "Lebsack", "Ledner", "Leffler", "Legros", "Lehner", "Lemke", "Lesch", "Leuschke", "Lind", "Lindgren", "Littel", "Little", "Lockman", "Lowe", "Lubowitz", "Lueilwitz", "Luettgen", "Lynch", "Macejkovic", "Maggio", "Mann", "Mante", "Marks", "Marquardt", "Marvin", "Mayer", "Mayert", "McClure", "McCullough", "McDermott", "McGlynn", "McKenzie", "McLaughlin", "Medhurst", "Mertz", "Metz", "Miller", "Mills", "Mitchell", "Moen", "Mohr", "Monahan", "Moore", "Morar", "Morissette", "Mosciski", "Mraz", "Mueller", "Muller", "Murazik", "Murphy", "Murray", "Nader", "Nicolas", "Nienow", "Nikolaus", "Nitzsche", "Nolan", "Oberbrunner", "Okuneva", "Olson", "Ondricka", "OReilly", "Orn", "Ortiz", "Osinski", "Pacocha", "Padberg", "Pagac", "Parisian", "Parker", "Paucek", "Pfannerstill", "Pfeffer", "Pollich", "Pouros", "Powlowski", "Predovic", "Price", "Prohaska", "Prosacco", "Purdy", "Quigley", "Quitzon", "Rath", "Ratke", "Rau", "Raynor", "Reichel", "Reichert", "Reilly", "Reinger", "Rempel", "Renner", "Reynolds", "Rice", "Rippin", "Ritchie", "Robel", "Roberts", "Rodriguez", "Rogahn", "Rohan", "Rolfson", "Romaguera", "Roob", "Rosenbaum", "Rowe", "Ruecker", "Runolfsdottir", "Runolfsson", "Runte", "Russel", "Rutherford", "Ryan", "Sanford", "Satterfield", "Sauer", "Sawayn", "Schaden", "Schaefer", "Schamberger", "Schiller", "Schimmel", "Schinner", "Schmeler", "Schmidt", "Schmitt", "Schneider", "Schoen", "Schowalter", "Schroeder", "Schulist", "Schultz", "Schumm", "Schuppe", "Schuster", "Senger", "Shanahan", "Shields", "Simonis", "Sipes", "Skiles", "Smith", "Smitham", "Spencer", "Spinka", "Sporer", "Stamm", "Stanton", "Stark", "Stehr", "Steuber", "Stiedemann", "Stokes", "Stoltenberg", "Stracke", "Streich", "Stroman", "Strosin", "Swaniawski", "Swift", "Terry", "Thiel", "Thompson", "Tillman", "Torp", "Torphy", "Towne", "Toy", "Trantow", "Tremblay", "Treutel", "Tromp", "Turcotte", "Turner", "Ullrich", "Upton", "Vandervort", "Veum", "Volkman", "Von", "VonRueden", "Waelchi", "Walker", "Walsh", "Walter", "Ward", "Waters", "Watsica", "Weber", "Wehner", "Weimann", "Weissnat", "Welch", "West", "White", "Wiegand", "Wilderman", "Wilkinson", "Will", "Williamson", "Willms", "Windler", "Wintheiser", "Wisoky", "Wisozk", "Witting", "Wiza", "Wolf", "Wolff", "Wuckert", "Wunsch", "Wyman", "Yost", "Yundt", "Zboncak", "Zemlak", "Ziemann", "Zieme", "Zulauf"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/status_code.go b/vendor/github.com/brianvoe/gofakeit/data/status_code.go deleted file mode 100644 index 7d78fd995026..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/status_code.go +++ /dev/null @@ -1,7 +0,0 @@ -package data - -// StatusCodes consists of commonly used HTTP status codes -var StatusCodes = map[string][]int{ - "simple": {200, 301, 302, 400, 404, 500}, - "general": {100, 200, 201, 203, 204, 205, 301, 302, 304, 400, 401, 403, 404, 405, 406, 416, 500, 501, 502, 503, 504}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/data/vehicle.go b/vendor/github.com/brianvoe/gofakeit/data/vehicle.go deleted file mode 100644 index 3b96728bccad..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/data/vehicle.go +++ /dev/null @@ -1,10 +0,0 @@ -package data - -// Vehicle Beer consists of various beer information -var Vehicle = map[string][]string{ - "vehicle_type": {"Passenger car mini", "Passenger car light", "Passenger car compact", "Passenger car medium", "Passenger car heavy", "Sport utility vehicle", "Pickup truck", "Van"}, - "fuel_type": {"Gasoline", "Methanol", "Ethanol", "Diesel", "LPG", "CNG", "Electric"}, - "transmission_type": {"Manual", "Automatic"}, - "maker": {"Alfa Romeo", "Aston Martin", "Audi", "Bentley", "Benz", "BMW", "Bugatti", "Cadillac", "Chevrolet", "Chrysler", "Citroen", "Corvette", "DAF", "Dacia", "Daewoo", "Daihatsu", "Datsun", "De Lorean", "Dino", "Dodge", "Farboud", "Ferrari", "Fiat", "Ford", "Honda", "Hummer", "Hyundai", "Jaguar", "Jeep", "KIA", "Koenigsegg", "Lada", "Lamborghini", "Lancia", "Land Rover", "Lexus", "Ligier", "Lincoln", "Lotus", "Martini", "Maserati", "Maybach", "Mazda", "McLaren", "Mercedes", "Mercedes-Benz", "Mini", "Mitsubishi", "Nissan", "Noble", "Opel", "Peugeot", "Pontiac", "Porsche", "Renault", "Rolls-Royce", "Rover", "Saab", "Seat", "Skoda", "Smart", "Spyker", "Subaru", "Suzuki", "Toyota", "Tesla", "Vauxhall", "Volkswagen", "Volvo"}, - "model": {"Db9 Coupe", "Db9 Coupe Manual", "Db9 Volante", "V12 Vanquish S", "V8 Vantage", "A3", "A4", "A4 Avant Quattro", "A4 Cabriolet", "A4 Cabriolet Quattro", "A4 Quattro", "A6", "A6 Avant Quattro", "A6 Quattro", "A8 L", "Gti", "Passat", "S4", "S4 Avant", "S4 Cabriolet", "Tt Coupe", "Tt Roadster", "Bentley Arnage", "Continental Flying Spur", "Continental Gt", " 325ci Convertible", " 325i", " 325xi", " 325xi Sport Wagon", " 330ci Convertible", " 330i", " 330xi", " 525i", " 525xi", " 530i", " 530xi", " 530xi Sport Wagon", " 550i", " 650ci", " 650ci Convertible", " 750li", " 760li", " M3", " M3 Convertible", " M5", " M6", " Mini Cooper", " Mini Cooper Convertible", " Mini Cooper S", " Mini Cooper S Convertible", " X3", " X5", " X5 4.8is", " Z4 3.0 Si Coupe", " Z4 3.0i", " Z4 3.0si", " Z4 M Roadster", "Veyron", "300c/srt-8", "Caravan 2wd", "Charger", "Commander 4wd", "Crossfire Roadster", "Dakota Pickup 2wd", "Dakota Pickup 4wd", "Durango 2wd", "Durango 4wd", "Grand Cherokee 2wd", "Grand Cherokee 4wd", "Liberty/cherokee 2wd", "Liberty/cherokee 4wd", "Pacifica 2wd", "Pacifica Awd", "Pt Cruiser", "Ram 1500 Pickup 2wd", "Ram 1500 Pickup 4wd", "Sebring 4-dr", "Stratus 4-dr", "Town & Country 2wd", "Viper Convertible", "Wrangler/tj 4wd", "F430", "Ferrari 612 Scaglietti", "Ferrari F141", "B4000 4wd", "Crown Victoria Police", "E150 Club Wagon", "E150 Econoline 2wd", "Escape 4wd", "Escape Fwd", "Escape Hybrid 4wd", "Escape Hybrid Fwd", "Expedition 2wd", "Explorer 2wd", "Explorer 4wd", "F150 Ffv 2wd", "F150 Ffv 4wd", "F150 Pickup 2wd", "F150 Pickup 4wd", "Five Hundred Awd", "Focus Fwd", "Focus Station Wag", "Freestar Wagon Fwd", "Freestyle Awd", "Freestyle Fwd", "Grand Marquis", "Gt 2wd", "Ls", "Mark Lt", "Milan", "Monterey Wagon Fwd", "Mountaineer 4wd", "Mustang", "Navigator 2wd", "Ranger Pickup 2wd", "Ranger Pickup 4wd", "Taurus", "Taurus Ethanol Ffv", "Thunderbird", "Town Car", "Zephyr", "B9 Tribeca Awd", "Baja Awd", "Forester Awd", "Impreza Awd", "Impreza Wgn/outback Spt Awd", "Legacy Awd", "Legacy Wagon Awd", "Outback Awd", "Outback Wagon Awd", "9-3 Convertible", "9-3 Sport Sedan", "9-5 Sedan", "C15 Silverado Hybrid 2wd", "C1500 Silverado 2wd", "C1500 Suburban 2wd", "C1500 Tahoe 2wd", "C1500 Yukon 2wd", "Cobalt", "Colorado 2wd", "Colorado 4wd", "Colorado Cab Chassis Inc 2wd", "Colorado Crew Cab 2wd", "Colorado Crew Cab 4wd", "Corvette", "Cts", "Dts", "Envoy 2wd", "Envoy Xl 4wd", "Equinox Awd", "Equinox Fwd", "Escalade 2wd", "Escalade Esv Awd", "G15/25chev Van 2wd Conv", "G1500/2500 Chevy Express 2wd", "G1500/2500 Chevy Van 2wd", "G6", "G6 Gt/gtp Convertible", "Grand Prix", "Gto", "H3 4wd", "Hhr Fwd", "I-280 2wd Ext Cab", "Impala", "K15 Silverado Hybrid 4wd", "K1500 Avalanche 4wd", "K1500 Silverado 4wd", "K1500 Tahoe 4wd", "Lacrosse/allure", "Limousine", "Malibu", "Montana Sv6 Awd", "Monte Carlo", "Rendezvous Awd", "Rendezvous Fwd", "Solstice", "Srx 2wd", "Srx Awd", "Ssr Pickup 2wd", "Sts", "Sts Awd", "Terraza Fwd", "Trailblazer 2wd", "Trailblazer 4wd", "Trailblazer Awd", "Trailblazer Ext 4wd", "Uplander Fwd", "Vue Awd", "Vue Fwd", "Xlr", "Aveo", "Forenza", "Forenza Wagon", "Verona", "Accord", "Accord Hybrid", "Civic", "Civic Hybrid", "Cr-v 4wd", "Element 2wd", "Element 4wd", "Insight", "Mdx 4wd", "Odyssey 2wd", "Pilot 2wd", "Pilot 4wd", "Ridgeline 4wd", "Rl", "Rsx", "S2000", "Tl", "Tsx", "Accent", "Azera", "Elantra", "Santafe 2wd", "Santafe 4wd", "Sonata", "Tiburon", "Tucson 2wd", "Tucson 4wd", "S-type 3.0 Litre", "S-type 4.2 Litre", "S-type R", "Vdp Lwb", "Xj8", "Xk8 Convertible", "Xkr Convertible", "X-type", "X-type Sport Brake", "Amanti", "Optima", "Optima(ms)", "Rio", "Sedona", "Sorento 2wd", "Sorento 4wd", "Spectra(ld)", "Sportage 2wd", "Sportage 4wd", "L-140/715 Gallardo", "L-147/148 Murcielago", "Lr3", "Range Rover", "Range Rover Sport", "Elise/exige", "Coupe Cambiocorsa/gt/g-sport", "Quattroporte", "Mazda 3", "Mazda 5", "Mazda 6", "Mazda 6 Sport Wagon", "Mazda Rx-8", "Mpv", "Mx-5", "C230", "C280", "C280 4matic", "C350", "C350 4matic", "C55 Amg", "Cl65 Amg", "Clk350", "Clk350 (cabriolet)", "Clk55 Amg (cabriolet)", "Cls500", "Cls55 Amg", "E320 Cdi", "E350", "E350 (wagon)", "E350 4matic", "E350 4matic (wagon)", "E500", "E55 Amg", "E55 Amg (wagon)", "Maybach 57s", "Maybach 62", "Ml350", "Ml500", "R350", "R500", "S350", "S430", "Sl500", "Sl600", "Sl65 Amg", "Slk280", "Slk350", "Slr", "Eclipse", "Endeavor 2wd", "Endeavor 4wd", "Galant", "Lancer", "Lancer Evolution", "Lancer Sportback", "Montero", "Outlander 2wd", "Outlander 4wd", "Vibe", "350z", "350z Roadster", "Altima", "Armada 2wd", "Armada 4wd", "Frontier 2wd", "Frontier V6-2wd", "Frontier V6-4wd", "Fx35 Awd", "Fx35 Rwd", "Fx45 Awd", "G35", "M35", "M35x", "M45", "Maxima", "Murano Awd", "Murano Fwd", "Pathfinder 2wd", "Pathfinder 4wd", "Q45", "Q45 Sport", "Quest", "Qx56 4wd", "Sentra", "Titan 2wd", "Titan 4wd", "Xterra 2wd", "Xterra 4wd", "Boxster", "Boxster S", "Carrera 2 Coupe", "Cayenne", "Cayenne S", "Cayenne Turbo", "Cayman S", "Phantom", "F150 Supercrew 4wd", "C8 Spyder", "Aerio", "Aerio Sx", "Aerio Sx Awd", "Grand Vitara Xl-7", "Grand Vitara Xl-7 4wd", "Grand Vitara Xv6", "Grand Vitara Xv6 Awd", "4runner 2wd", "4runner 4wd", "Avalon", "Camry", "Camry Solara", "Camry Solara Convertible", "Corolla", "Corolla Matrix", "Es 330", "Gs 300 4wd", "Gs 300/gs 430", "Gx 470", "Highlander 2wd", "Highlander 4wd", "Highlander Hybrid 2wd", "Highlander Hybrid 4wd", "Is 250", "Is 250 Awd", "Is 350", "Ls 430", "Lx 470", "Prius", "Rav4 2wd", "Rav4 4wd", "Rx 330 2wd", "Rx 330 4wd", "Rx 400h 4wd", "Sc 430", "Scion Tc", "Scion Xa", "Scion Xb", "Sequoia 2wd", "Sequoia 4wd", "Sienna 2wd", "Sienna 4wd", "Toyota Tacoma 2wd", "Toyota Tacoma 4wd", "Toyota Tundra 2wd", "Toyota Tundra 4wd", "Yaris", "A3 Quattro", "Golf", "Jetta", "New Beetle", "New Beetle Convertible", "Passat Wagon 4motion", "Phaeton", "Rabbit", "Touareg", "Tt Coupe Quattro", "Tt Roadster Quattro", "C70 Convertible", "S40 Awd", "S40 Fwd", "S60 Awd", "S60 Fwd", "S60 R Awd", "S80 Fwd", "V50 Awd", "V70 Fwd", "V70 R Awd", "Xc 70 Awd", "Xc 90 Awd", "Xc 90 Fwd"}, -} diff --git a/vendor/github.com/brianvoe/gofakeit/datetime.go b/vendor/github.com/brianvoe/gofakeit/datetime.go deleted file mode 100644 index 8c064473d3d8..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/datetime.go +++ /dev/null @@ -1,77 +0,0 @@ -package gofakeit - -import ( - "strconv" - "time" -) - -// Date will generate a random time.Time struct -func Date() time.Time { - return time.Date(Year(), time.Month(Number(0, 12)), Day(), Hour(), Minute(), Second(), NanoSecond(), time.UTC) -} - -// DateRange will generate a random time.Time struct between a start and end date -func DateRange(start, end time.Time) time.Time { - return time.Unix(0, int64(Number(int(start.UnixNano()), int(end.UnixNano())))).UTC() -} - -// Month will generate a random month string -func Month() string { - return time.Month(Number(1, 12)).String() -} - -// Day will generate a random day between 1 - 31 -func Day() int { - return Number(1, 31) -} - -// WeekDay will generate a random weekday string (Monday-Sunday) -func WeekDay() string { - return time.Weekday(Number(0, 6)).String() -} - -// Year will generate a random year between 1900 - current year -func Year() int { - return Number(1900, time.Now().Year()) -} - -// Hour will generate a random hour - in military time -func Hour() int { - return Number(0, 23) -} - -// Minute will generate a random minute -func Minute() int { - return Number(0, 59) -} - -// Second will generate a random second -func Second() int { - return Number(0, 59) -} - -// NanoSecond will generate a random nano second -func NanoSecond() int { - return Number(0, 999999999) -} - -// TimeZone will select a random timezone string -func TimeZone() string { - return getRandValue([]string{"timezone", "text"}) -} - -// TimeZoneFull will select a random full timezone string -func TimeZoneFull() string { - return getRandValue([]string{"timezone", "full"}) -} - -// TimeZoneAbv will select a random timezone abbreviation string -func TimeZoneAbv() string { - return getRandValue([]string{"timezone", "abr"}) -} - -// TimeZoneOffset will select a random timezone offset -func TimeZoneOffset() float32 { - value, _ := strconv.ParseFloat(getRandValue([]string{"timezone", "offset"}), 32) - return float32(value) -} diff --git a/vendor/github.com/brianvoe/gofakeit/doc.go b/vendor/github.com/brianvoe/gofakeit/doc.go deleted file mode 100644 index c53335e634f1..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -/* -Package gofakeit is a random data generator written in go - -Every function has an example and a benchmark - -See the full list here https://godoc.org/github.com/brianvoe/gofakeit - -80+ Functions!!! -*/ -package gofakeit diff --git a/vendor/github.com/brianvoe/gofakeit/faker.go b/vendor/github.com/brianvoe/gofakeit/faker.go deleted file mode 100644 index 38062d5cdf91..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/faker.go +++ /dev/null @@ -1,15 +0,0 @@ -package gofakeit - -import ( - "math/rand" - "time" -) - -// Seed random. Setting seed to 0 will use time.Now().UnixNano() -func Seed(seed int64) { - if seed == 0 { - rand.Seed(time.Now().UTC().UnixNano()) - } else { - rand.Seed(seed) - } -} diff --git a/vendor/github.com/brianvoe/gofakeit/file.go b/vendor/github.com/brianvoe/gofakeit/file.go deleted file mode 100644 index 6c1e8d56cba1..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/file.go +++ /dev/null @@ -1,11 +0,0 @@ -package gofakeit - -// MimeType will generate a random mime file type -func MimeType() string { - return getRandValue([]string{"file", "mime_type"}) -} - -// Extension will generate a random file extension -func Extension() string { - return getRandValue([]string{"file", "extension"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/generate.go b/vendor/github.com/brianvoe/gofakeit/generate.go deleted file mode 100644 index 284eef8bb108..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/generate.go +++ /dev/null @@ -1,41 +0,0 @@ -package gofakeit - -import ( - "strings" -) - -// Generate fake information from given string. String should contain {category.subcategory} -// -// Ex: {person.first} - random firstname -// -// Ex: {person.first}###{person.last}@{person.last}.{internet.domain_suffix} - billy834smith@smith.com -// -// Ex: ### - 481 - random numbers -// -// Ex: ??? - fda - random letters -// -// For a complete list possible categories use the Categories() function. -func Generate(dataVal string) string { - // Identify items between brackets: {person.first} - for strings.Count(dataVal, "{") > 0 && strings.Count(dataVal, "}") > 0 { - catValue := "" - startIndex := strings.Index(dataVal, "{") - endIndex := strings.Index(dataVal, "}") - replace := dataVal[(startIndex + 1):endIndex] - categories := strings.Split(replace, ".") - - if len(categories) >= 2 && dataCheck([]string{categories[0], categories[1]}) { - catValue = getRandValue([]string{categories[0], categories[1]}) - } - - dataVal = strings.Replace(dataVal, "{"+replace+"}", catValue, 1) - } - - // Replace # with numbers - dataVal = replaceWithNumbers(dataVal) - - // Replace ? with letters - dataVal = replaceWithLetters(dataVal) - - return dataVal -} diff --git a/vendor/github.com/brianvoe/gofakeit/hacker.go b/vendor/github.com/brianvoe/gofakeit/hacker.go deleted file mode 100644 index 0ac73b7109f3..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/hacker.go +++ /dev/null @@ -1,35 +0,0 @@ -package gofakeit - -import "strings" - -// HackerPhrase will return a random hacker sentence -func HackerPhrase() string { - words := strings.Split(Generate(getRandValue([]string{"hacker", "phrase"})), " ") - words[0] = strings.Title(words[0]) - return strings.Join(words, " ") -} - -// HackerAbbreviation will return a random hacker abbreviation -func HackerAbbreviation() string { - return getRandValue([]string{"hacker", "abbreviation"}) -} - -// HackerAdjective will return a random hacker adjective -func HackerAdjective() string { - return getRandValue([]string{"hacker", "adjective"}) -} - -// HackerNoun will return a random hacker noun -func HackerNoun() string { - return getRandValue([]string{"hacker", "noun"}) -} - -// HackerVerb will return a random hacker verb -func HackerVerb() string { - return getRandValue([]string{"hacker", "verb"}) -} - -// HackerIngverb will return a random hacker ingverb -func HackerIngverb() string { - return getRandValue([]string{"hacker", "ingverb"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/hipster.go b/vendor/github.com/brianvoe/gofakeit/hipster.go deleted file mode 100644 index 3166a9966a13..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/hipster.go +++ /dev/null @@ -1,20 +0,0 @@ -package gofakeit - -// HipsterWord will return a single hipster word -func HipsterWord() string { - return getRandValue([]string{"hipster", "word"}) -} - -// HipsterSentence will generate a random sentence -func HipsterSentence(wordCount int) string { - return sentence(wordCount, HipsterWord) -} - -// HipsterParagraph will generate a random paragraphGenerator -// Set Paragraph Count -// Set Sentence Count -// Set Word Count -// Set Paragraph Separator -func HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string { - return paragraphGenerator(paragrapOptions{paragraphCount, sentenceCount, wordCount, separator}, HipsterSentence) -} diff --git a/vendor/github.com/brianvoe/gofakeit/image.go b/vendor/github.com/brianvoe/gofakeit/image.go deleted file mode 100644 index de5a2e6d916c..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/image.go +++ /dev/null @@ -1,8 +0,0 @@ -package gofakeit - -import "strconv" - -// ImageURL will generate a random Image Based Upon Height And Width. https://picsum.photos/ -func ImageURL(width int, height int) string { - return "https://picsum.photos/" + strconv.Itoa(width) + "/" + strconv.Itoa(height) -} diff --git a/vendor/github.com/brianvoe/gofakeit/internet.go b/vendor/github.com/brianvoe/gofakeit/internet.go deleted file mode 100644 index 69dd700e5231..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/internet.go +++ /dev/null @@ -1,55 +0,0 @@ -package gofakeit - -import ( - "fmt" - "math/rand" - "strings" -) - -// DomainName will generate a random url domain name -func DomainName() string { - return strings.ToLower(JobDescriptor()+BS()) + "." + DomainSuffix() -} - -// DomainSuffix will generate a random domain suffix -func DomainSuffix() string { - return getRandValue([]string{"internet", "domain_suffix"}) -} - -// URL will generate a random url string -func URL() string { - url := "http" + RandString([]string{"s", ""}) + "://www." - url += DomainName() - - // Slugs - num := Number(1, 4) - slug := make([]string, num) - for i := 0; i < num; i++ { - slug[i] = BS() - } - url += "/" + strings.ToLower(strings.Join(slug, "/")) - - return url -} - -// HTTPMethod will generate a random http method -func HTTPMethod() string { - return getRandValue([]string{"internet", "http_method"}) -} - -// IPv4Address will generate a random version 4 ip address -func IPv4Address() string { - num := func() int { return 2 + rand.Intn(254) } - return fmt.Sprintf("%d.%d.%d.%d", num(), num(), num(), num()) -} - -// IPv6Address will generate a random version 6 ip address -func IPv6Address() string { - num := 65536 - return fmt.Sprintf("2001:cafe:%x:%x:%x:%x:%x:%x", rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num)) -} - -// Username will genrate a random username based upon picking a random lastname and random numbers at the end -func Username() string { - return getRandValue([]string{"person", "last"}) + replaceWithNumbers("####") -} diff --git a/vendor/github.com/brianvoe/gofakeit/job.go b/vendor/github.com/brianvoe/gofakeit/job.go deleted file mode 100644 index c156bde77243..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/job.go +++ /dev/null @@ -1,34 +0,0 @@ -package gofakeit - -// JobInfo is a struct of job information -type JobInfo struct { - Company string - Title string - Descriptor string - Level string -} - -// Job will generate a struct with random job information -func Job() *JobInfo { - return &JobInfo{ - Company: Company(), - Title: JobTitle(), - Descriptor: JobDescriptor(), - Level: JobLevel(), - } -} - -// JobTitle will generate a random job title string -func JobTitle() string { - return getRandValue([]string{"job", "title"}) -} - -// JobDescriptor will generate a random job descriptor string -func JobDescriptor() string { - return getRandValue([]string{"job", "descriptor"}) -} - -// JobLevel will generate a random job level string -func JobLevel() string { - return getRandValue([]string{"job", "level"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/log_level.go b/vendor/github.com/brianvoe/gofakeit/log_level.go deleted file mode 100644 index bde9bf310588..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/log_level.go +++ /dev/null @@ -1,15 +0,0 @@ -package gofakeit - -import ( - "github.com/brianvoe/gofakeit/data" -) - -// LogLevel will generate a random log level -// See data/LogLevels for list of available levels -func LogLevel(logType string) string { - if _, ok := data.LogLevels[logType]; ok { - return getRandValue([]string{"log_level", logType}) - } - - return getRandValue([]string{"log_level", "general"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/logo.png b/vendor/github.com/brianvoe/gofakeit/logo.png deleted file mode 100644 index a97962030afda2145a035db6a01b2f3c256384fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36022 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 '9' { - return false - } - if i&1 == odd { - sum += t[c-'0'] - } else { - sum += int(c - '0') - } - } - return sum%10 == 0 -} diff --git a/vendor/github.com/brianvoe/gofakeit/person.go b/vendor/github.com/brianvoe/gofakeit/person.go deleted file mode 100644 index 5fd6cbe22a11..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/person.go +++ /dev/null @@ -1,45 +0,0 @@ -package gofakeit - -import "strconv" - -// SSN will generate a random Social Security Number -func SSN() string { - return strconv.Itoa(randIntRange(100000000, 999999999)) -} - -// Gender will generate a random gender string -func Gender() string { - if Bool() == true { - return "male" - } - - return "female" -} - -// PersonInfo is a struct of person information -type PersonInfo struct { - FirstName string - LastName string - Gender string - SSN string - Image string - Job *JobInfo - Address *AddressInfo - Contact *ContactInfo - CreditCard *CreditCardInfo -} - -// Person will generate a struct with person information -func Person() *PersonInfo { - return &PersonInfo{ - FirstName: FirstName(), - LastName: LastName(), - Gender: Gender(), - SSN: SSN(), - Image: ImageURL(300, 300) + "/people", - Job: Job(), - Address: Address(), - Contact: Contact(), - CreditCard: CreditCard(), - } -} diff --git a/vendor/github.com/brianvoe/gofakeit/status_code.go b/vendor/github.com/brianvoe/gofakeit/status_code.go deleted file mode 100644 index 1751c0fbe401..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/status_code.go +++ /dev/null @@ -1,11 +0,0 @@ -package gofakeit - -// SimpleStatusCode will generate a random simple status code -func SimpleStatusCode() int { - return getRandIntValue([]string{"status_code", "simple"}) -} - -// StatusCode will generate a random status code -func StatusCode() int { - return getRandIntValue([]string{"status_code", "general"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/string.go b/vendor/github.com/brianvoe/gofakeit/string.go deleted file mode 100644 index fc646cf38ac1..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/string.go +++ /dev/null @@ -1,48 +0,0 @@ -package gofakeit - -import ( - "math/rand" -) - -// Letter will generate a single random lower case ASCII letter -func Letter() string { - return string(randLetter()) -} - -// Digit will generate a single ASCII digit -func Digit() string { - return string(randDigit()) -} - -// Lexify will replace ? will random generated letters -func Lexify(str string) string { - return replaceWithLetters(str) -} - -// ShuffleStrings will randomize a slice of strings -func ShuffleStrings(a []string) { - swap := func(i, j int) { - a[i], a[j] = a[j], a[i] - } - //to avoid upgrading to 1.10 I copied the algorithm - n := len(a) - if n <= 1 { - return - } - - //if size is > int32 probably it will never finish, or ran out of entropy - i := n - 1 - for ; i > 0; i-- { - j := int(rand.Int31n(int32(i + 1))) - swap(i, j) - } -} - -// RandString will take in a slice of string and return a randomly selected value -func RandString(a []string) string { - size := len(a) - if size == 0 { - return "" - } - return a[rand.Intn(size)] -} diff --git a/vendor/github.com/brianvoe/gofakeit/struct.go b/vendor/github.com/brianvoe/gofakeit/struct.go deleted file mode 100644 index 2c68a9a3cb21..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/struct.go +++ /dev/null @@ -1,87 +0,0 @@ -package gofakeit - -import ( - "reflect" -) - -// Struct fills in exported elements of a struct with random data -// based on the value of `fake` tag of exported elements. -// Use `fake:"skip"` to explicitly skip an element. -// All built-in types are supported, with templating support -// for string types. -func Struct(v interface{}) { - r(reflect.TypeOf(v), reflect.ValueOf(v), "") -} - -func r(t reflect.Type, v reflect.Value, template string) { - switch t.Kind() { - case reflect.Ptr: - rPointer(t, v, template) - case reflect.Struct: - rStruct(t, v) - case reflect.String: - rString(template, v) - case reflect.Uint8: - v.SetUint(uint64(Uint8())) - case reflect.Uint16: - v.SetUint(uint64(Uint16())) - case reflect.Uint32: - v.SetUint(uint64(Uint32())) - case reflect.Uint64: - //capped at [0, math.MaxInt64) - v.SetUint(uint64(Uint64())) - case reflect.Int: - v.SetInt(int64(Int64())) - case reflect.Int8: - v.SetInt(int64(Int8())) - case reflect.Int16: - v.SetInt(int64(Int16())) - case reflect.Int32: - v.SetInt(int64(Int32())) - case reflect.Int64: - v.SetInt(int64(Int64())) - case reflect.Float64: - v.SetFloat(Float64()) - case reflect.Float32: - v.SetFloat(float64(Float32())) - case reflect.Bool: - v.SetBool(Bool()) - } -} - -func rString(template string, v reflect.Value) { - if template != "" { - r := Generate(template) - v.SetString(r) - } else { - v.SetString(Generate("???????????????????")) - // we don't have a String(len int) string function!! - } -} - -func rStruct(t reflect.Type, v reflect.Value) { - n := t.NumField() - for i := 0; i < n; i++ { - elementT := t.Field(i) - elementV := v.Field(i) - fake := true - t, ok := elementT.Tag.Lookup("fake") - if ok && t == "skip" { - fake = false - } - if fake && elementV.CanSet() { - r(elementT.Type, elementV, t) - } - } -} - -func rPointer(t reflect.Type, v reflect.Value, template string) { - elemT := t.Elem() - if v.IsNil() { - nv := reflect.New(elemT) - r(elemT, nv.Elem(), template) - v.Set(nv) - } else { - r(elemT, v.Elem(), template) - } -} diff --git a/vendor/github.com/brianvoe/gofakeit/unique.go b/vendor/github.com/brianvoe/gofakeit/unique.go deleted file mode 100644 index 4b969a7e9b8f..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/unique.go +++ /dev/null @@ -1,34 +0,0 @@ -package gofakeit - -import ( - "encoding/hex" - "math/rand" -) - -// UUID (version 4) will generate a random unique identifier based upon random nunbers -// Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -func UUID() string { - version := byte(4) - uuid := make([]byte, 16) - rand.Read(uuid) - - // Set version - uuid[6] = (uuid[6] & 0x0f) | (version << 4) - - // Set variant - uuid[8] = (uuid[8] & 0xbf) | 0x80 - - buf := make([]byte, 36) - var dash byte = '-' - hex.Encode(buf[0:8], uuid[0:4]) - buf[8] = dash - hex.Encode(buf[9:13], uuid[4:6]) - buf[13] = dash - hex.Encode(buf[14:18], uuid[6:8]) - buf[18] = dash - hex.Encode(buf[19:23], uuid[8:10]) - buf[23] = dash - hex.Encode(buf[24:], uuid[10:]) - - return string(buf) -} diff --git a/vendor/github.com/brianvoe/gofakeit/user_agent.go b/vendor/github.com/brianvoe/gofakeit/user_agent.go deleted file mode 100644 index 2ba334121452..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/user_agent.go +++ /dev/null @@ -1,92 +0,0 @@ -package gofakeit - -import "strconv" - -// UserAgent will generate a random broswer user agent -func UserAgent() string { - randNum := randIntRange(0, 4) - switch randNum { - case 0: - return ChromeUserAgent() - case 1: - return FirefoxUserAgent() - case 2: - return SafariUserAgent() - case 3: - return OperaUserAgent() - default: - return ChromeUserAgent() - } -} - -// ChromeUserAgent will generate a random chrome browser user agent string -func ChromeUserAgent() string { - randNum1 := strconv.Itoa(randIntRange(531, 536)) + strconv.Itoa(randIntRange(0, 2)) - randNum2 := strconv.Itoa(randIntRange(36, 40)) - randNum3 := strconv.Itoa(randIntRange(800, 899)) - return "Mozilla/5.0 " + "(" + randomPlatform() + ") AppleWebKit/" + randNum1 + " (KHTML, like Gecko) Chrome/" + randNum2 + ".0." + randNum3 + ".0 Mobile Safari/" + randNum1 -} - -// FirefoxUserAgent will generate a random firefox broswer user agent string -func FirefoxUserAgent() string { - ver := "Gecko/" + Date().Format("2006-02-01") + " Firefox/" + strconv.Itoa(randIntRange(35, 37)) + ".0" - platforms := []string{ - "(" + windowsPlatformToken() + "; " + "en-US" + "; rv:1.9." + strconv.Itoa(randIntRange(0, 3)) + ".20) " + ver, - "(" + linuxPlatformToken() + "; rv:" + strconv.Itoa(randIntRange(5, 8)) + ".0) " + ver, - "(" + macPlatformToken() + " rv:" + strconv.Itoa(randIntRange(2, 7)) + ".0) " + ver, - } - - return "Mozilla/5.0 " + RandString(platforms) -} - -// SafariUserAgent will generate a random safari browser user agent string -func SafariUserAgent() string { - randNum := strconv.Itoa(randIntRange(531, 536)) + "." + strconv.Itoa(randIntRange(1, 51)) + "." + strconv.Itoa(randIntRange(1, 8)) - ver := strconv.Itoa(randIntRange(4, 6)) + "." + strconv.Itoa(randIntRange(0, 2)) - - mobileDevices := []string{ - "iPhone; CPU iPhone OS", - "iPad; CPU OS", - } - - platforms := []string{ - "(Windows; U; " + windowsPlatformToken() + ") AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + ver + " Safari/" + randNum, - "(" + macPlatformToken() + " rv:" + strconv.Itoa(randIntRange(4, 7)) + ".0; en-US) AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + ver + " Safari/" + randNum, - "(" + RandString(mobileDevices) + " " + strconv.Itoa(randIntRange(7, 9)) + "_" + strconv.Itoa(randIntRange(0, 3)) + "_" + strconv.Itoa(randIntRange(1, 3)) + " like Mac OS X; " + "en-US" + ") AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + strconv.Itoa(randIntRange(3, 5)) + ".0.5 Mobile/8B" + strconv.Itoa(randIntRange(111, 120)) + " Safari/6" + randNum, - } - - return "Mozilla/5.0 " + RandString(platforms) -} - -// OperaUserAgent will generate a random opera browser user agent string -func OperaUserAgent() string { - platform := "(" + randomPlatform() + "; en-US) Presto/2." + strconv.Itoa(randIntRange(8, 13)) + "." + strconv.Itoa(randIntRange(160, 355)) + " Version/" + strconv.Itoa(randIntRange(10, 13)) + ".00" - - return "Opera/" + strconv.Itoa(randIntRange(8, 10)) + "." + strconv.Itoa(randIntRange(10, 99)) + " " + platform -} - -// linuxPlatformToken will generate a random linux platform -func linuxPlatformToken() string { - return "X11; Linux " + getRandValue([]string{"computer", "linux_processor"}) -} - -// macPlatformToken will generate a random mac platform -func macPlatformToken() string { - return "Macintosh; " + getRandValue([]string{"computer", "mac_processor"}) + " Mac OS X 10_" + strconv.Itoa(randIntRange(5, 9)) + "_" + strconv.Itoa(randIntRange(0, 10)) -} - -// windowsPlatformToken will generate a random windows platform -func windowsPlatformToken() string { - return getRandValue([]string{"computer", "windows_platform"}) -} - -// randomPlatform will generate a random platform -func randomPlatform() string { - platforms := []string{ - linuxPlatformToken(), - macPlatformToken(), - windowsPlatformToken(), - } - - return RandString(platforms) -} diff --git a/vendor/github.com/brianvoe/gofakeit/vehicle.go b/vendor/github.com/brianvoe/gofakeit/vehicle.go deleted file mode 100644 index 093fe3a1d84c..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/vehicle.go +++ /dev/null @@ -1,55 +0,0 @@ -package gofakeit - -// VehicleInfo is a struct dataset of all vehicle information -type VehicleInfo struct { - // Vehicle type - VehicleType string - // Fuel type - Fuel string - // Transmission type - TransmissionGear string - // Brand name - Brand string - // Vehicle model - Model string - // Vehicle model year - Year int -} - -// Vehicle will generate a struct with vehicle information -func Vehicle() *VehicleInfo { - return &VehicleInfo{ - VehicleType: VehicleType(), - Fuel: FuelType(), - TransmissionGear: TransmissionGearType(), - Brand: CarMaker(), - Model: CarModel(), - Year: Year(), - } - -} - -// VehicleType will generate a random vehicle type string -func VehicleType() string { - return getRandValue([]string{"vehicle", "vehicle_type"}) -} - -// FuelType will return a random fuel type -func FuelType() string { - return getRandValue([]string{"vehicle", "fuel_type"}) -} - -// TransmissionGearType will return a random transmission gear type -func TransmissionGearType() string { - return getRandValue([]string{"vehicle", "transmission_type"}) -} - -// CarMaker will return a random car maker -func CarMaker() string { - return getRandValue([]string{"vehicle", "maker"}) -} - -// CarModel will return a random car model -func CarModel() string { - return getRandValue([]string{"vehicle", "model"}) -} diff --git a/vendor/github.com/brianvoe/gofakeit/words.go b/vendor/github.com/brianvoe/gofakeit/words.go deleted file mode 100644 index 631e45c7ddd7..000000000000 --- a/vendor/github.com/brianvoe/gofakeit/words.go +++ /dev/null @@ -1,100 +0,0 @@ -package gofakeit - -import ( - "bytes" - "strings" - "unicode" -) - -type paragrapOptions struct { - paragraphCount int - sentenceCount int - wordCount int - separator string -} - -const bytesPerWordEstimation = 6 - -type sentenceGenerator func(wordCount int) string -type wordGenerator func() string - -// Word will generate a random word -func Word() string { - return getRandValue([]string{"lorem", "word"}) -} - -// Sentence will generate a random sentence -func Sentence(wordCount int) string { - return sentence(wordCount, Word) -} - -// Paragraph will generate a random paragraphGenerator -// Set Paragraph Count -// Set Sentence Count -// Set Word Count -// Set Paragraph Separator -func Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string { - return paragraphGenerator(paragrapOptions{paragraphCount, sentenceCount, wordCount, separator}, Sentence) -} - -func sentence(wordCount int, word wordGenerator) string { - if wordCount <= 0 { - return "" - } - - wordSeparator := ' ' - sentence := bytes.Buffer{} - sentence.Grow(wordCount * bytesPerWordEstimation) - - for i := 0; i < wordCount; i++ { - word := word() - if i == 0 { - runes := []rune(word) - runes[0] = unicode.ToTitle(runes[0]) - word = string(runes) - } - sentence.WriteString(word) - if i < wordCount-1 { - sentence.WriteRune(wordSeparator) - } - } - sentence.WriteRune('.') - return sentence.String() -} - -func paragraphGenerator(opts paragrapOptions, sentecer sentenceGenerator) string { - if opts.paragraphCount <= 0 || opts.sentenceCount <= 0 || opts.wordCount <= 0 { - return "" - } - - //to avoid making Go 1.10 dependency, we cannot use strings.Builder - paragraphs := bytes.Buffer{} - //we presume the length - paragraphs.Grow(opts.paragraphCount * opts.sentenceCount * opts.wordCount * bytesPerWordEstimation) - wordSeparator := ' ' - - for i := 0; i < opts.paragraphCount; i++ { - for e := 0; e < opts.sentenceCount; e++ { - paragraphs.WriteString(sentecer(opts.wordCount)) - if e < opts.sentenceCount-1 { - paragraphs.WriteRune(wordSeparator) - } - } - - if i < opts.paragraphCount-1 { - paragraphs.WriteString(opts.separator) - } - } - - return paragraphs.String() -} - -// Question will return a random question -func Question() string { - return strings.Replace(HipsterSentence(Number(3, 10)), ".", "?", 1) -} - -// Quote will return a random quote from a random person -func Quote() string { - return `"` + HipsterSentence(Number(3, 10)) + `" - ` + FirstName() + " " + LastName() -} diff --git a/vendor/github.com/robfig/cron/README.md b/vendor/github.com/robfig/cron/README.md index 4e0ae1c25f39..ec40c95fcb9d 100644 --- a/vendor/github.com/robfig/cron/README.md +++ b/vendor/github.com/robfig/cron/README.md @@ -1,4 +1,4 @@ -[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) +[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) [![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron) # cron diff --git a/vendor/github.com/robfig/cron/doc.go b/vendor/github.com/robfig/cron/doc.go index 1ce84f7bf462..d02ec2f3b563 100644 --- a/vendor/github.com/robfig/cron/doc.go +++ b/vendor/github.com/robfig/cron/doc.go @@ -84,7 +84,7 @@ You may use one of several pre-defined schedules in place of a cron expression. Intervals -You may also schedule a job to execute at fixed intervals, starting at the time it's added +You may also schedule a job to execute at fixed intervals, starting at the time it's added or cron is run. This is supported by formatting the cron spec like this: @every diff --git a/vendor/modules.txt b/vendor/modules.txt index 1cf623aa57d4..19e66874848b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -56,9 +56,6 @@ github.com/benbjohnson/clock github.com/beorn7/perks/quantile # github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 github.com/bradfitz/gomemcache/memcache -# github.com/brianvoe/gofakeit v3.17.0+incompatible -github.com/brianvoe/gofakeit -github.com/brianvoe/gofakeit/data # github.com/codegangsta/cli v1.20.0 github.com/codegangsta/cli # github.com/davecgh/go-spew v1.1.1 From a3092dc57b7a91d9d38ece34c142a51912e33a65 Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Wed, 29 May 2019 16:12:42 +0300 Subject: [PATCH 92/92] LDAP: remove unused function (#17351) --- pkg/services/ldap/ldap.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index c64533d34922..418673be4463 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -30,7 +30,6 @@ type IConnection interface { type IServer interface { Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error) Users([]string) ([]*models.ExternalUserInfo, error) - ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error) InitialBind(string, string) error Dial() error Close() @@ -148,6 +147,11 @@ func (server *Server) Login(query *models.LoginUserQuery) ( // Check if a second user bind is needed user := users[0] + + if err := server.validateGrafanaUser(user); err != nil { + return nil, err + } + if server.requireSecondBind { err = server.secondBind(user, query.Password) if err != nil { @@ -188,16 +192,6 @@ func (server *Server) Users(logins []string) ( return serializedUsers, nil } -// ExtractGrafanaUser extracts external user info from LDAP user -func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserInfo, error) { - result := server.buildGrafanaUser(user) - if err := server.validateGrafanaUser(result); err != nil { - return nil, err - } - - return result, nil -} - // validateGrafanaUser validates user access. // If there are no ldap group mappings access is true // otherwise a single group must match