From 8fb9518cf13718a43cf40205711a92a66b3082a7 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 31 May 2019 14:19:05 -0400 Subject: [PATCH 01/27] Project init --- .../plugins/fleet/common/constants/index.ts | 8 ++++ .../fleet/common/constants/index_names.ts | 15 ++++++++ .../plugins/fleet/common/constants/plugin.ts | 10 +++++ x-pack/plugins/fleet/index.ts | 38 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 x-pack/plugins/fleet/common/constants/index.ts create mode 100644 x-pack/plugins/fleet/common/constants/index_names.ts create mode 100644 x-pack/plugins/fleet/common/constants/plugin.ts create mode 100644 x-pack/plugins/fleet/index.ts diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts new file mode 100644 index 00000000000000..da3cf73059c179 --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { INDEX_NAMES } from './index_names'; +export { PLUGIN } from './plugin'; diff --git a/x-pack/plugins/fleet/common/constants/index_names.ts b/x-pack/plugins/fleet/common/constants/index_names.ts new file mode 100644 index 00000000000000..8a038735c0116e --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/index_names.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const INDEX_NAMES = { + FLEET: '.fleet', + EVENTS: '.fleet-agent-events-*', + EVENTS_ALIAS: '.fleet-agent-events', +}; + +export const POLICY_NAMES = { + EVENTS: '.fleet-agent-events-retention', +}; diff --git a/x-pack/plugins/fleet/common/constants/plugin.ts b/x-pack/plugins/fleet/common/constants/plugin.ts new file mode 100644 index 00000000000000..5a482bc92f9875 --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/plugin.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const PLUGIN = { + ID: 'fleet', +}; +export const CONFIG_PREFIX = 'xpack.fleet'; diff --git a/x-pack/plugins/fleet/index.ts b/x-pack/plugins/fleet/index.ts new file mode 100644 index 00000000000000..4073bc0e58414f --- /dev/null +++ b/x-pack/plugins/fleet/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import Joi from 'joi'; +import { resolve } from 'path'; +import { PLUGIN } from './common/constants'; +import { CONFIG_PREFIX } from './common/constants/plugin'; +import { i18n } from '../../../packages/kbn-i18n/src/index'; +// + +export const config = Joi.object({ + enabled: Joi.boolean().default(true), +}).default(); + +export function beats(kibana: any) { + return new kibana.Plugin({ + id: PLUGIN.ID, + require: ['kibana', 'elasticsearch', 'xpack_main'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + app: { + title: 'Elastic Fleet', + description: i18n.translate('xpack.fleet.elasticFleetDescription', { + defaultMessage: 'Manage your elastic data ingestion stack', + }), + main: 'plugins/fleet/index', + icon: 'plugins/fleet/icon.svg', + euiIconType: 'apmApp', + order: 8000, + }, + }, + config: () => config, + configPrefix: CONFIG_PREFIX, + init(server: any) {}, + }); +} From e1c50200d2d675588eff847a956ff8ef96bc3248 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 31 May 2019 22:03:53 -0400 Subject: [PATCH 02/27] initial client libs --- src/dev/typescript/projects.ts | 1 - .../fleet/common/constants/security.ts | 9 + .../plugins/fleet/common/types/domain_data.ts | 80 ++++++ x-pack/plugins/fleet/common/types/helpers.ts | 7 + x-pack/plugins/fleet/common/types/io_ts.ts | 33 +++ x-pack/plugins/fleet/common/types/security.ts | 7 + .../fleet/common/types/std_return_format.ts | 116 ++++++++ x-pack/plugins/fleet/index.ts | 4 +- .../lib/adapters/agent/adapter_types.ts | 5 + .../adapters/agent/memory_agent_adapter.ts | 42 +++ .../lib/adapters/agent/rest_agent_adapter.ts | 60 ++++ .../adapters/elasticsearch/adapter_types.ts | 12 + .../lib/adapters/elasticsearch/memory.ts | 29 ++ .../public/lib/adapters/elasticsearch/rest.ts | 76 +++++ .../lib/adapters/framework/adapter_types.ts | 86 ++++++ .../framework/kibana_framework_adapter.ts | 263 ++++++++++++++++++ .../framework/testing_framework_adapter.ts | 69 +++++ .../lib/adapters/rest_api/adapter_types.ts | 13 + .../rest_api/axios_rest_api_adapter.ts | 78 ++++++ .../rest_api/node_axios_api_adapter.ts | 92 ++++++ x-pack/plugins/fleet/public/lib/agent.ts | 50 ++++ .../fleet/public/lib/compose/kibana.ts | 53 ++++ .../fleet/public/lib/compose/memory.ts | 59 ++++ .../fleet/public/lib/compose/scripts.ts | 55 ++++ .../plugins/fleet/public/lib/elasticsearch.ts | 69 +++++ x-pack/plugins/fleet/public/lib/framework.ts | 63 +++++ x-pack/plugins/fleet/public/lib/types.ts | 52 ++++ x-pack/plugins/fleet/tsconfig.json | 7 + 28 files changed, 1487 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/fleet/common/constants/security.ts create mode 100644 x-pack/plugins/fleet/common/types/domain_data.ts create mode 100644 x-pack/plugins/fleet/common/types/helpers.ts create mode 100644 x-pack/plugins/fleet/common/types/io_ts.ts create mode 100644 x-pack/plugins/fleet/common/types/security.ts create mode 100644 x-pack/plugins/fleet/common/types/std_return_format.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/agent.ts create mode 100644 x-pack/plugins/fleet/public/lib/compose/kibana.ts create mode 100644 x-pack/plugins/fleet/public/lib/compose/memory.ts create mode 100644 x-pack/plugins/fleet/public/lib/compose/scripts.ts create mode 100644 x-pack/plugins/fleet/public/lib/elasticsearch.ts create mode 100644 x-pack/plugins/fleet/public/lib/framework.ts create mode 100644 x-pack/plugins/fleet/public/lib/types.ts create mode 100644 x-pack/plugins/fleet/tsconfig.json diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index ffd0ac51f332e9..7ac65c2dea2e90 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -19,7 +19,6 @@ import glob from 'glob'; import { resolve } from 'path'; - import { REPO_ROOT } from '../constants'; import { Project } from './project'; diff --git a/x-pack/plugins/fleet/common/constants/security.ts b/x-pack/plugins/fleet/common/constants/security.ts new file mode 100644 index 00000000000000..f27c45fc903b4e --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/security.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const REQUIRED_ROLES = ['fleet_admin']; +export const REQUIRED_LICENSES = ['standard', 'gold', 'trial', 'platinum']; +export const LICENSES = ['oss', 'basic', 'standard', 'gold', 'trial', 'platinum']; diff --git a/x-pack/plugins/fleet/common/types/domain_data.ts b/x-pack/plugins/fleet/common/types/domain_data.ts new file mode 100644 index 00000000000000..41ec4eae4c5d84 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/domain_data.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { DateFromString } from './io_ts'; + +// Here we create the runtime check for a generic, unknown beat config type. +// We can also pass in optional params to create spacific runtime checks that +// can be used to validate blocs on the API and UI +export const createConfigurationInterface = (beatConfigInterface: t.Mixed = t.Dictionary) => + t.interface( + { + id: t.union([t.undefined, t.string]), + name: t.string, + description: t.union([t.undefined, t.string]), + config: beatConfigInterface, + last_updated_by: t.union([t.undefined, t.string]), + last_updated: t.union([t.undefined, t.number]), + }, + 'Config' + ); +const BaseConfiguration = createConfigurationInterface(); +export interface ConfigurationBlock + extends Pick< + t.TypeOf, + Exclude, 'id'> + > { + id: string; +} + +export interface Agent { + id: string; + status?: AgentEvent; + enrollment_token: string; + active: boolean; + access_token: string; + verified_on?: string; + type: string; + version?: string; + host_ip: string; + host_name: string; + ephemeral_id?: string; + last_checkin?: Date; + event_rate?: string; + tags: string[]; + metadata?: {}; + name?: string; + last_updated: number; +} + +export const RuntimeAgentEvent = t.interface( + { + type: t.union([t.literal('STATE'), t.literal('ERROR')]), + beat: t.union([t.undefined, t.string]), + timestamp: DateFromString, + event: t.type({ + type: t.union([ + t.literal('RUNNING'), + t.literal('STARTING'), + t.literal('IN_PROGRESS'), + t.literal('CONFIG'), + t.literal('FAILED'), + t.literal('STOPPED'), + ]), + message: t.string, + uuid: t.union([t.undefined, t.string]), + }), + }, + 'AgentEvent' +); +export interface AgentEvent + extends Pick< + t.TypeOf, + Exclude, 'timestamp'> + > { + agent: string; + timestamp: Date; +} diff --git a/x-pack/plugins/fleet/common/types/helpers.ts b/x-pack/plugins/fleet/common/types/helpers.ts new file mode 100644 index 00000000000000..4258461310e164 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/helpers.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type FlatObject = { [Key in keyof T]: string }; diff --git a/x-pack/plugins/fleet/common/types/io_ts.ts b/x-pack/plugins/fleet/common/types/io_ts.ts new file mode 100644 index 00000000000000..51ab838ddd6c2b --- /dev/null +++ b/x-pack/plugins/fleet/common/types/io_ts.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +export class DateFromStringType extends t.Type { + // eslint-disable-next-line + public readonly _tag: 'DateFromISOStringType' = 'DateFromISOStringType'; + constructor() { + super( + 'DateFromString', + (u): u is Date => u instanceof Date, + (u, c) => { + const validation = t.string.validate(u, c); + if (validation.isLeft()) { + return validation as any; + } else { + const s = validation.value; + const d = new Date(s); + return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d); + } + }, + a => a.toISOString() + ); + } +} +// eslint-disable-next-line +export interface DateFromString extends DateFromStringType {} + +export const DateFromString: DateFromString = new DateFromStringType(); diff --git a/x-pack/plugins/fleet/common/types/security.ts b/x-pack/plugins/fleet/common/types/security.ts new file mode 100644 index 00000000000000..691ea82b294d30 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/security.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type LicenseType = 'oss' | 'basic' | 'trial' | 'standard' | 'basic' | 'gold' | 'platinum'; diff --git a/x-pack/plugins/fleet/common/types/std_return_format.ts b/x-pack/plugins/fleet/common/types/std_return_format.ts new file mode 100644 index 00000000000000..ded94bbff7f19e --- /dev/null +++ b/x-pack/plugins/fleet/common/types/std_return_format.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface BaseReturnType { + error?: { + message: string; + code?: number; + }; + success: boolean; +} + +export interface ReturnTypeCreate extends BaseReturnType { + item: T; + action: 'created'; +} + +export interface ReturnTypeUpdate extends BaseReturnType { + item: T; + action: 'updated'; +} + +export interface ReturnTypeBulkCreate extends BaseReturnType { + results: Array<{ + item: T; + success: boolean; + action: 'created'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// delete +export interface ReturnTypeDelete extends BaseReturnType { + action: 'deleted'; +} + +export interface ReturnTypeBulkDelete extends BaseReturnType { + results: Array<{ + success: boolean; + action: 'deleted'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// upsert +export interface ReturnTypeUpsert extends BaseReturnType { + item: T; + action: 'created' | 'updated'; +} + +// upsert bulk +export interface ReturnTypeBulkUpsert extends BaseReturnType { + results: Array<{ + success: boolean; + action: 'created' | 'updated'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// list +export interface ReturnTypeList extends BaseReturnType { + list: T[]; + page: number; + total: number; +} + +// get +export interface ReturnTypeGet extends BaseReturnType { + item: T; +} + +export interface ReturnTypeBulkGet extends BaseReturnType { + items: T[]; +} + +// action -- e.g. validate config block. Like ES simulate endpoint +export interface ReturnTypeAction extends BaseReturnType { + result: { + [key: string]: any; + }; +} +// e.g. +// { +// result: { +// username: { valid: true }, +// password: { valid: false, error: 'something' }, +// hosts: [ +// { valid: false }, { valid: true }, +// ] +// } +// } + +// bulk action +export interface ReturnTypeBulkAction extends BaseReturnType { + results?: Array<{ + success: boolean; + result?: { + [key: string]: any; + }; + error?: { + message: string; + code?: number; + }; + }>; +} diff --git a/x-pack/plugins/fleet/index.ts b/x-pack/plugins/fleet/index.ts index 4073bc0e58414f..bbeec8fc99c804 100644 --- a/x-pack/plugins/fleet/index.ts +++ b/x-pack/plugins/fleet/index.ts @@ -5,16 +5,16 @@ */ import Joi from 'joi'; import { resolve } from 'path'; +import { i18n } from '../../../packages/kbn-i18n/src/index'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; -import { i18n } from '../../../packages/kbn-i18n/src/index'; // export const config = Joi.object({ enabled: Joi.boolean().default(true), }).default(); -export function beats(kibana: any) { +export function fleet(kibana: any) { return new kibana.Plugin({ id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts new file mode 100644 index 00000000000000..41bc2aa2588073 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts new file mode 100644 index 00000000000000..3f6c4a3143750a --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { omit } from 'lodash'; +import { Agent } from '../../../../common/types/domain_data'; + +export class AgentAdapter { + private memoryDB: Agent[]; + + constructor(db: Agent[]) { + this.memoryDB = db; + } + + public async get(id: string) { + return this.memoryDB.find(beat => beat.id === id) || null; + } + + public async update(id: string, beatData: Partial): Promise { + const index = this.memoryDB.findIndex(beat => beat.id === id); + + if (index === -1) { + return false; + } + + this.memoryDB[index] = { ...this.memoryDB[index], ...beatData }; + return true; + } + + public async getAll(ESQuery?: string) { + return this.memoryDB.map((beat: any) => omit(beat, ['access_token'])); + } + public async getOnConfig(tagId: string): Promise { + return this.memoryDB.map((beat: any) => omit(beat, ['access_token'])); + } + + public async getWithToken(enrollmentToken: string): Promise { + return this.memoryDB.map((beat: any) => omit(beat, ['access_token']))[0]; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts new file mode 100644 index 00000000000000..da04f615554a25 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Agent } from '../../../../common/types/domain_data'; +import { + ReturnTypeGet, + ReturnTypeList, + ReturnTypeUpdate, +} from '../../../../common/types/std_return_format'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { AgentAdapter } from './memory_agent_adapter'; + +export class RestAgentAdapter extends AgentAdapter { + constructor(private readonly REST: RestAPIAdapter) { + super([]); + } + + public async get(id: string): Promise { + try { + return (await this.REST.get>(`/api/fleet/agent/${id}`)).item; + } catch (e) { + return null; + } + } + + public async getWithToken(enrollmentToken: string): Promise { + try { + return (await this.REST.get>( + `/api/fleet/agent/unknown/${enrollmentToken}` + )).item; + } catch (e) { + return null; + } + } + + public async getAll(ESQuery?: string): Promise { + try { + return (await this.REST.get>('/api/fleet/agents/all', { ESQuery })) + .list; + } catch (e) { + return []; + } + } + + public async getOnConfig(tagId: string): Promise { + try { + return (await this.REST.get>(`/api/fleet/agents/tag/${tagId}`)).list; + } catch (e) { + return []; + } + } + + public async update(id: string, beatData: Partial): Promise { + await this.REST.put>(`/api/fleet/agent/${id}`, beatData); + return true; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts new file mode 100644 index 00000000000000..49408574932757 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; + +export interface ElasticsearchAdapter { + convertKueryToEsQuery: (kuery: string) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; + isKueryValid(kuery: string): boolean; +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts new file mode 100644 index 00000000000000..1b918fb72c8091 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { ElasticsearchAdapter } from './adapter_types'; + +export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { + constructor( + private readonly mockIsKueryValid: (kuery: string) => boolean, + private readonly mockKueryToEsQuery: (kuery: string) => string, + private readonly suggestions: AutocompleteSuggestion[] + ) {} + + public isKueryValid(kuery: string): boolean { + return this.mockIsKueryValid(kuery); + } + public async convertKueryToEsQuery(kuery: string): Promise { + return this.mockKueryToEsQuery(kuery); + } + public async getSuggestions( + kuery: string, + selectionStart: any + ): Promise { + return this.suggestions; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts new file mode 100644 index 00000000000000..8899ddd7976d55 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { isEmpty } from 'lodash'; +import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { ElasticsearchAdapter } from './adapter_types'; + +export class RestElasticsearchAdapter implements ElasticsearchAdapter { + private cachedIndexPattern: any = null; + constructor(private readonly api: RestAPIAdapter, private readonly indexPatternName: string) {} + + public isKueryValid(kuery: string): boolean { + try { + fromKueryExpression(kuery); + } catch (err) { + return false; + } + + return true; + } + public async convertKueryToEsQuery(kuery: string): Promise { + if (!this.isKueryValid(kuery)) { + return ''; + } + const ast = fromKueryExpression(kuery); + const indexPattern = await this.getIndexPattern(); + return JSON.stringify(toElasticsearchQuery(ast, indexPattern)); + } + public async getSuggestions( + kuery: string, + selectionStart: any + ): Promise { + const autocompleteProvider = getAutocompleteProvider('kuery'); + if (!autocompleteProvider) { + return []; + } + const config = { + get: () => true, + }; + const indexPattern = await this.getIndexPattern(); + + const getAutocompleteSuggestions = autocompleteProvider({ + config, + indexPatterns: [indexPattern], + boolFilter: null, + }); + const results = getAutocompleteSuggestions({ + query: kuery || '', + selectionStart, + selectionEnd: selectionStart, + }); + return results; + } + + private async getIndexPattern() { + if (this.cachedIndexPattern) { + return this.cachedIndexPattern; + } + const res = await this.api.get( + `/api/index_patterns/_fields_for_wildcard?pattern=${this.indexPatternName}` + ); + if (isEmpty(res.fields)) { + return; + } + this.cachedIndexPattern = { + fields: res.fields, + title: `${this.indexPatternName}`, + }; + return this.cachedIndexPattern; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts new file mode 100644 index 00000000000000..b8a75e284c2480 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import * as t from 'io-ts'; +import { LICENSES } from './../../../../common/constants/security'; + +export interface FrameworkAdapter { + // Instance vars + info: FrameworkInfo; + version: string; + currentUser: FrameworkUser; + // Methods + waitUntilFrameworkReady(): Promise; + renderUIAtPath( + path: string, + component: React.ReactElement, + toController: 'management' | 'self' + ): void; + registerManagementSection(settings: { + id?: string; + name: string; + iconName: string; + order?: number; + }): void; + registerManagementUI(settings: { + sectionId?: string; + name: string; + basePath: string; + visable?: boolean; + order?: number; + }): void; +} + +export const RuntimeFrameworkInfo = t.type({ + basePath: t.string, + license: t.type({ + type: t.union(LICENSES.map(s => t.literal(s))), + expired: t.boolean, + expiry_date_in_millis: t.number, + }), + security: t.type({ + enabled: t.boolean, + available: t.boolean, + }), + settings: t.type({ + // encryptionKey: t.string, + // enrollmentTokensTtlInSeconds: t.number, + // defaultUserRoles: t.array(t.string), + }), +}); + +export interface FrameworkInfo extends t.TypeOf {} + +interface ManagementSection { + register( + sectionId: string, + options: { + visible: boolean; + display: string; + order: number; + url: string; + } + ): void; +} +export interface ManagementAPI { + getSection(sectionId: string): ManagementSection; + hasItem(sectionId: string): boolean; + register(sectionId: string, options: { display: string; icon: string; order: number }): void; +} + +export const RuntimeFrameworkUser = t.interface( + { + username: t.string, + roles: t.array(t.string), + full_name: t.union([t.null, t.string]), + email: t.union([t.null, t.string]), + enabled: t.boolean, + }, + 'FrameworkUser' +); +export interface FrameworkUser extends t.TypeOf {} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 00000000000000..0a8acd954c0a12 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,263 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable max-classes-per-file */ +import { IScope } from 'angular'; +import { PathReporter } from 'io-ts/lib/PathReporter'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { UIRoutes } from 'ui/routes'; +import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; +import { + FrameworkAdapter, + FrameworkInfo, + FrameworkUser, + ManagementAPI, + RuntimeFrameworkInfo, + RuntimeFrameworkUser, +} from './adapter_types'; +interface IInjector { + get(injectable: string): any; +} + +export class KibanaFrameworkAdapter implements FrameworkAdapter { + public get info() { + if (this.xpackInfo) { + return this.xpackInfo; + } else { + throw new Error('framework adapter must have init called before anything else'); + } + } + + public get currentUser() { + return this.shieldUser!; + } + private xpackInfo: FrameworkInfo | null = null; + private adapterService: KibanaAdapterServiceProvider; + private shieldUser: FrameworkUser | null = null; + constructor( + private readonly PLUGIN_ID: string, + private readonly management: ManagementAPI, + private readonly routes: UIRoutes, + private readonly getBasePath: () => string, + private readonly onKibanaReady: () => Promise, + private readonly XPackInfoProvider: unknown, + public readonly version: string + ) { + this.adapterService = new KibanaAdapterServiceProvider(); + } + + public setUISettings = (key: string, value: any) => { + this.adapterService.callOrBuffer(({ config }) => { + config.set(key, value); + }); + }; + + public async waitUntilFrameworkReady(): Promise { + const $injector = await this.onKibanaReady(); + const Private: any = $injector.get('Private'); + + let xpackInfo: any; + try { + xpackInfo = Private(this.XPackInfoProvider); + } catch (e) { + xpackInfo = false; + } + + let xpackInfoUnpacked: FrameworkInfo; + try { + xpackInfoUnpacked = { + basePath: this.getBasePath(), + license: { + type: xpackInfo ? xpackInfo.getLicense().type : 'oss', + expired: xpackInfo ? !xpackInfo.getLicense().isActive : false, + expiry_date_in_millis: + xpackInfo.getLicense().expiryDateInMillis !== undefined + ? xpackInfo.getLicense().expiryDateInMillis + : -1, + }, + security: { + enabled: xpackInfo + ? xpackInfo.get(`features.${this.PLUGIN_ID}.security.enabled`, false) + : false, + available: xpackInfo + ? xpackInfo.get(`features.${this.PLUGIN_ID}.security.available`, false) + : false, + }, + settings: xpackInfo ? xpackInfo.get(`features.${this.PLUGIN_ID}.settings`) : {}, + }; + } catch (e) { + throw new Error(`Unexpected data structure from XPackInfoProvider, ${JSON.stringify(e)}`); + } + + const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked); + if (assertData.isLeft()) { + throw new Error( + `Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}` + ); + } + this.xpackInfo = xpackInfoUnpacked; + + try { + this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise; + const assertUser = RuntimeFrameworkUser.decode(this.shieldUser); + + if (assertUser.isLeft()) { + throw new Error( + `Error parsing user info in ${this.PLUGIN_ID}, ${PathReporter.report(assertUser)[0]}` + ); + } + } catch (e) { + this.shieldUser = null; + } + } + + public renderUIAtPath( + path: string, + component: React.ReactElement, + toController: 'management' | 'self' = 'self' + ) { + const adapter = this; + this.routes.when( + `${path}${[...Array(6)].map((e, n) => `/:arg${n}?`).join('')}`, // Hack because angular 1 does not support wildcards + { + template: + toController === 'self' + ? `<${this.PLUGIN_ID}>
` + : ` +
+
+ `, + // eslint-disable-next-line max-classes-per-file + controller: ($scope: any, $route: any) => { + try { + $scope.$$postDigest(() => { + const elem = document.getElementById(`${this.PLUGIN_ID}ReactRoot`); + ReactDOM.render(component, elem); + adapter.manageAngularLifecycle($scope, $route, elem); + }); + $scope.$onInit = () => { + $scope.topNavMenu = []; + }; + } catch (e) { + throw new Error(`Error rendering Elastic Fleet to the dom, ${e.message}`); + } + }, + } + ); + } + + public registerManagementSection(settings: { + id?: string; + name: string; + iconName: string; + order?: number; + }) { + const sectionId = settings.id || this.PLUGIN_ID; + + if (!this.management.hasItem(sectionId)) { + this.management.register(sectionId, { + display: settings.name, + icon: settings.iconName, + order: settings.order || 30, + }); + } + } + + public registerManagementUI(settings: { + sectionId?: string; + name: string; + basePath: string; + visable?: boolean; + order?: number; + }) { + const sectionId = settings.sectionId || this.PLUGIN_ID; + + if (!this.management.hasItem(sectionId)) { + throw new Error( + `registerManagementUI was called with a sectionId of ${sectionId}, and that is is not yet regestered as a section` + ); + } + + const section = this.management.getSection(sectionId); + + section.register(sectionId, { + visible: settings.visable || true, + display: settings.name, + order: settings.order || 30, + url: `#${settings.basePath}`, + }); + } + + private manageAngularLifecycle($scope: any, $route: any, elem: any) { + const lastRoute = $route.current; + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + // if templates are the same we are on the same route + if (lastRoute.$$route.template === currentRoute.$$route.template) { + // this prevents angular from destroying scope + $route.current = lastRoute; + } else { + if (elem) { + ReactDOM.unmountComponentAtNode(elem); + elem.remove(); + } + } + }); + $scope.$on('$destroy', () => { + if (deregister) { + deregister(); + } + + // manually unmount component when scope is destroyed + if (elem) { + ReactDOM.unmountComponentAtNode(elem); + elem.remove(); + } + }); + } +} + +class KibanaAdapterServiceProvider { + public serviceRefs: KibanaAdapterServiceRefs | null = null; + public bufferedCalls: Array> = []; + + public $get($rootScope: IScope, config: KibanaUIConfig) { + this.serviceRefs = { + config, + rootScope: $rootScope, + }; + + this.applyBufferedCalls(this.bufferedCalls); + + return this; + } + + public callOrBuffer(serviceCall: (serviceRefs: KibanaAdapterServiceRefs) => void) { + if (this.serviceRefs !== null) { + this.applyBufferedCalls([serviceCall]); + } else { + this.bufferedCalls.push(serviceCall); + } + } + + public applyBufferedCalls( + bufferedCalls: Array> + ) { + if (!this.serviceRefs) { + return; + } + + this.serviceRefs.rootScope.$apply(() => { + bufferedCalls.forEach(serviceCall => { + if (!this.serviceRefs) { + return; + } + return serviceCall(this.serviceRefs); + }); + }); + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts new file mode 100644 index 00000000000000..9045c7ded2adaa --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { FrameworkAdapter, FrameworkInfo, FrameworkUser } from './adapter_types'; + +export class TestingFrameworkAdapter implements FrameworkAdapter { + public get info() { + if (this.xpackInfo) { + return this.xpackInfo; + } else { + throw new Error('framework adapter must have init called before anything else'); + } + } + + public get currentUser() { + return this.shieldUser!; + } + private settings: any; + constructor( + private readonly xpackInfo: FrameworkInfo | null, + private readonly shieldUser: FrameworkUser | null, + public readonly version: string + ) {} + + // We dont really want to have this, but it's needed to conditionaly render for k7 due to + // when that data is needed. + public getUISetting(key: 'k7design'): boolean { + return this.settings[key]; + } + + public setUISettings = (key: string, value: any) => { + this.settings[key] = value; + }; + + public async waitUntilFrameworkReady(): Promise { + return; + } + + public renderUIAtPath( + path: string, + component: React.ReactElement, + toController: 'management' | 'self' = 'self' + ) { + throw new Error('not yet implamented'); + } + + public registerManagementSection(settings: { + id?: string; + name: string; + iconName: string; + order?: number; + }) { + throw new Error('not yet implamented'); + } + + public registerManagementUI(settings: { + sectionId?: string; + name: string; + basePath: string; + visable?: boolean; + order?: number; + }) { + throw new Error('not yet implamented'); + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts new file mode 100644 index 00000000000000..c40575eb6567fe --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FlatObject } from '../../../../common/types/helpers'; + +export interface RestAPIAdapter { + get(url: string, query?: FlatObject): Promise; + post(url: string, body?: { [key: string]: any }): Promise; + delete(url: string): Promise; + put(url: string, body?: any): Promise; +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts new file mode 100644 index 00000000000000..4d0a1728c28f89 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosInstance } from 'axios'; +import { FlatObject } from '../../../../common/types/helpers'; +import { RestAPIAdapter } from './adapter_types'; +let globalAPI: AxiosInstance; + +export class AxiosRestAPIAdapter implements RestAPIAdapter { + constructor(private readonly xsrfToken: string, private readonly basePath: string) {} + + public async get(url: string, query?: FlatObject): Promise { + return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); + } + + public async post( + url: string, + body?: { [key: string]: any } + ): Promise { + return await this.REST.post(url, body).then(resp => resp.data); + } + + public async delete(url: string): Promise { + return await this.REST.delete(url).then(resp => resp.data); + } + + public async put(url: string, body?: any): Promise { + return await this.REST.put(url, body).then(resp => resp.data); + } + + private get REST() { + if (globalAPI) { + return globalAPI; + } + + globalAPI = axios.create({ + baseURL: this.basePath, + withCredentials: true, + responseType: 'json', + timeout: 30000, + headers: { + Accept: 'application/json', + credentials: 'same-origin', + 'Content-Type': 'application/json', + 'kbn-version': this.xsrfToken, + 'kbn-xsrf': this.xsrfToken, + }, + }); + // Add a request interceptor + globalAPI.interceptors.request.use( + config => { + // Do something before request is sent + return config; + }, + error => { + // Do something with request error + return Promise.reject(error); + } + ); + + // Add a response interceptor + globalAPI.interceptors.response.use( + response => { + // Do something with response data + return response; + }, + error => { + // Do something with response error + return Promise.reject(error); + } + ); + + return globalAPI; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts new file mode 100644 index 00000000000000..112d8b2210065b --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosInstance } from 'axios'; +import fs from 'fs'; +import { join, resolve } from 'path'; +import { FlatObject } from '../../../../common/types/helpers'; +import { RestAPIAdapter } from './adapter_types'; +const pkg = JSON.parse( + fs.readFileSync(resolve(join(__dirname, '../../../../../../../package.json'))).toString() +); + +let globalAPI: AxiosInstance; + +export class NodeAxiosAPIAdapter implements RestAPIAdapter { + constructor( + private readonly username: string, + private readonly password: string, + private readonly basePath: string + ) {} + + public async get(url: string, query?: FlatObject): Promise { + return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); + } + + public async post( + url: string, + body?: { [key: string]: any } + ): Promise { + return await this.REST.post(url, body).then(resp => resp.data); + } + + public async delete(url: string): Promise { + return await this.REST.delete(url).then(resp => resp.data); + } + + public async put(url: string, body?: any): Promise { + return await this.REST.put(url, body).then(resp => resp.data); + } + + private get REST() { + if (globalAPI) { + return globalAPI; + } + + globalAPI = axios.create({ + baseURL: this.basePath, + withCredentials: true, + responseType: 'json', + timeout: 60 * 10 * 1000, // 10min + auth: { + username: this.username, + password: this.password, + }, + headers: { + 'Access-Control-Allow-Origin': '*', + Accept: 'application/json', + 'Content-Type': 'application/json', + 'kbn-version': (pkg as any).version, + 'kbn-xsrf': 'xxx', + }, + }); + // Add a request interceptor + globalAPI.interceptors.request.use( + config => { + // Do something before request is sent + return config; + }, + error => { + // Do something with request error + return Promise.reject(error); + } + ); + + // Add a response interceptor + globalAPI.interceptors.response.use( + response => { + // Do something with response data + return response; + }, + error => { + // Do something with response error + return Promise.reject(JSON.stringify(error.response.data)); + } + ); + + return globalAPI; + } +} diff --git a/x-pack/plugins/fleet/public/lib/agent.ts b/x-pack/plugins/fleet/public/lib/agent.ts new file mode 100644 index 00000000000000..49710a2d5d36cf --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/agent.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Agent } from '../../common/types/domain_data'; +import { AgentAdapter } from './adapters/agent/memory_agent_adapter'; +import { ElasticsearchLib } from './elasticsearch'; + +export class AgentsLib { + constructor( + private readonly adapter: AgentAdapter, + private readonly elasticsearch: ElasticsearchLib + ) {} + + /** Get a single beat using it's ID for lookup */ + public async get(id: string): Promise { + const agent = await this.adapter.get(id); + return agent; + } + + /** Get a single agent using the token it was enrolled in for lookup */ + public getWithToken = async (enrollmentToken: string): Promise => { + const agent = await this.adapter.getWithToken(enrollmentToken); + return agent; + }; + + /** Get an array of agents that have a given tag id assigned to it */ + public getOnConfig = async (configId: string): Promise => { + const agents = await this.adapter.getOnConfig(configId); + return agents; + }; + + // FIXME: This needs to be paginated https://github.com/elastic/kibana/issues/26022 + /** Get an array of all enrolled agents. */ + public getAll = async (kuery?: string): Promise => { + let ESQuery; + if (kuery) { + ESQuery = await this.elasticsearch.convertKueryToEsQuery(kuery); + } + const agents = await this.adapter.getAll(ESQuery); + return agents; + }; + + /** Update a given agent via it's ID */ + public update = async (id: string, agentData: Partial): Promise => { + return await this.adapter.update(id, agentData); + }; +} diff --git a/x-pack/plugins/fleet/public/lib/compose/kibana.ts b/x-pack/plugins/fleet/public/lib/compose/kibana.ts new file mode 100644 index 00000000000000..17991b764cd716 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/compose/kibana.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { camelCase } from 'lodash'; +// @ts-ignore not typed yet +import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info'; +import 'ui/autoload/all'; +import chrome from 'ui/chrome'; +// @ts-ignore not typed yet +import { management } from 'ui/management'; +import routes from 'ui/routes'; +import { INDEX_NAMES } from '../../../common/constants/index_names'; +import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; +import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest'; +import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; +import { AgentsLib } from '../agent'; +import { ElasticsearchLib } from '../elasticsearch'; +import { FrontendLibs } from '../types'; +import { PLUGIN } from './../../../common/constants/plugin'; +import { FrameworkLib } from './../framework'; + +// A super early spot in kibana loading that we can use to hook before most other things +const onKibanaReady = chrome.dangerouslyGetActiveInjector; + +export function compose(): FrontendLibs { + const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); + const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.FLEET); + const elasticsearchLib = new ElasticsearchLib(esAdapter); + const agents = new AgentsLib(new RestAgentAdapter(api), elasticsearchLib); + + const framework = new FrameworkLib( + new KibanaFrameworkAdapter( + camelCase(PLUGIN.ID), + management, + routes, + chrome.getBasePath, + onKibanaReady, + XPackInfoProvider, + chrome.getKibanaVersion() + ) + ); + + const libs: FrontendLibs = { + framework, + elasticsearch: elasticsearchLib, + agents, + }; + return libs; +} diff --git a/x-pack/plugins/fleet/public/lib/compose/memory.ts b/x-pack/plugins/fleet/public/lib/compose/memory.ts new file mode 100644 index 00000000000000..5501c4066d89e0 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/compose/memory.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import { management } from 'ui/management'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +// @ts-ignore: path dynamic for kibana +import routes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { MemoryAgentAdapter } from '../adapters/agent/memory_agents_adapter'; +import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AgentsLib } from '../agent'; +import { FrameworkLib } from '../framework'; +import { FrontendLibs } from '../types'; +import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; +import { ElasticsearchLib } from './../elasticsearch'; + +const onKibanaReady = uiModules.get('kibana').run; + +export function compose( + mockIsKueryValid: (kuery: string) => boolean, + mockKueryToEsQuery: (kuery: string) => string, + suggestions: AutocompleteSuggestion[] +): FrontendLibs { + const esAdapter = new MemoryElasticsearchAdapter( + mockIsKueryValid, + mockKueryToEsQuery, + suggestions + ); + const elasticsearchLib = new ElasticsearchLib(esAdapter); + + const agents = new AgentsLib(new MemoryAgentAdapter([]), elasticsearchLib); + + const pluginUIModule = uiModules.get('app/fleet'); + + const framework = new FrameworkLib( + new KibanaFrameworkAdapter( + pluginUIModule, + management, + routes, + () => '', + onKibanaReady, + null, + '7.0.0' + ) + ); + const libs: FrontendLibs = { + framework, + elasticsearch: elasticsearchLib, + agents, + }; + return libs; +} diff --git a/x-pack/plugins/fleet/public/lib/compose/scripts.ts b/x-pack/plugins/fleet/public/lib/compose/scripts.ts new file mode 100644 index 00000000000000..174782150d6e02 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/compose/scripts.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; +import { MemoryElasticsearchAdapter } from '../adapters/elasticsearch/memory'; +import { TestingFrameworkAdapter } from '../adapters/framework/testing_framework_adapter'; +import { NodeAxiosAPIAdapter } from '../adapters/rest_api/node_axios_api_adapter'; +import { AgentsLib } from '../agent'; +import { ElasticsearchLib } from '../elasticsearch'; +import { FrameworkLib } from '../framework'; +import { FrontendLibs } from '../types'; + +export function compose(basePath: string): FrontendLibs { + const api = new NodeAxiosAPIAdapter('elastic', 'changeme', basePath); + const esAdapter = new MemoryElasticsearchAdapter(() => true, () => '', []); + const elasticsearchLib = new ElasticsearchLib(esAdapter); + + const agents = new AgentsLib(new RestAgentAdapter(api), elasticsearchLib); + + const framework = new FrameworkLib( + new TestingFrameworkAdapter( + { + basePath, + license: { + type: 'gold', + expired: false, + expiry_date_in_millis: 34353453452345, + }, + security: { + enabled: true, + available: true, + }, + settings: {}, + }, + { + username: 'joeuser', + roles: ['fleet_admin'], + enabled: true, + full_name: null, + email: null, + }, + '6.7.0' + ) + ); + + const libs: FrontendLibs = { + framework, + elasticsearch: elasticsearchLib, + agents, + }; + return libs; +} diff --git a/x-pack/plugins/fleet/public/lib/elasticsearch.ts b/x-pack/plugins/fleet/public/lib/elasticsearch.ts new file mode 100644 index 00000000000000..7ea32a2eb9467e --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/elasticsearch.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; + +interface HiddenFields { + op: 'is' | 'startsWith' | 'withoutPrefix'; + value: string; +} + +export class ElasticsearchLib { + private readonly hiddenFields: HiddenFields[] = [ + { op: 'startsWith', value: 'enrollment_token' }, + { op: 'is', value: 'beat.active' }, + { op: 'is', value: 'beat.enrollment_token' }, + { op: 'is', value: 'beat.access_token' }, + { op: 'is', value: 'beat.ephemeral_id' }, + { op: 'is', value: 'beat.verified_on' }, + ]; + + constructor(private readonly adapter: ElasticsearchAdapter) {} + + public isKueryValid(kuery: string): boolean { + return this.adapter.isKueryValid(kuery); + } + public async convertKueryToEsQuery(kuery: string): Promise { + return await this.adapter.convertKueryToEsQuery(kuery); + } + + public async getSuggestions( + kuery: string, + selectionStart: any, + fieldPrefix?: string + ): Promise { + const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); + + const filteredSuggestions = suggestions.filter(suggestion => { + const hiddenFieldsCheck = this.hiddenFields; + + if (fieldPrefix) { + hiddenFieldsCheck.push({ + op: 'withoutPrefix', + value: `${fieldPrefix}.`, + }); + } + + return hiddenFieldsCheck.reduce((isvalid, field) => { + if (!isvalid) { + return false; + } + + switch (field.op) { + case 'startsWith': + return !suggestion.text.startsWith(field.value); + case 'is': + return suggestion.text.trim() !== field.value; + case 'withoutPrefix': + return suggestion.text.startsWith(field.value); + } + }, true); + }); + + return filteredSuggestions; + } +} diff --git a/x-pack/plugins/fleet/public/lib/framework.ts b/x-pack/plugins/fleet/public/lib/framework.ts new file mode 100644 index 00000000000000..e6ae33168384e9 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/framework.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { difference, get } from 'lodash'; +import { LICENSES } from '../../common/constants/security'; +import { LicenseType } from '../../common/types/security'; +import { FrameworkAdapter } from './adapters/framework/adapter_types'; + +export class FrameworkLib { + public waitUntilFrameworkReady = this.adapter.waitUntilFrameworkReady.bind(this.adapter); + public renderUIAtPath = this.adapter.renderUIAtPath.bind(this.adapter); + public registerManagementSection = this.adapter.registerManagementSection.bind(this.adapter); + public registerManagementUI = this.adapter.registerManagementUI.bind(this.adapter); + + constructor(private readonly adapter: FrameworkAdapter) {} + + public get currentUser() { + return this.adapter.currentUser; + } + + public get info() { + return this.adapter.info; + } + + public licenseIsAtLeast(type: LicenseType) { + return ( + LICENSES.indexOf(get(this.adapter.info, 'license.type', 'oss')) >= LICENSES.indexOf(type) + ); + } + + public versionGreaterThen(version: string) { + const pa = this.adapter.version.split('.'); + const pb = version.split('.'); + for (let i = 0; i < 3; i++) { + const na = Number(pa[i]); + const nb = Number(pb[i]); + // version is greater + if (na > nb) { + return true; + } + // version is less then + if (nb > na) { + return false; + } + if (!isNaN(na) && isNaN(nb)) { + return true; + } + if (isNaN(na) && !isNaN(nb)) { + return false; + } + } + return true; + } + + public currentUserHasOneOfRoles(roles: string[]) { + // If the user has at least one of the roles requested, the returnd difference will be less + // then the orig array size. difference only compares based on the left side arg + return difference(roles, get(this.currentUser, 'roles', [])).length < roles.length; + } +} diff --git a/x-pack/plugins/fleet/public/lib/types.ts b/x-pack/plugins/fleet/public/lib/types.ts new file mode 100644 index 00000000000000..c5a068a881be4d --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/types.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IModule, IScope } from 'angular'; +import { AxiosRequestConfig } from 'axios'; +import { FrameworkAdapter } from './adapters/framework/adapter_types'; +import { AgentsLib } from './agent'; +import { ElasticsearchLib } from './elasticsearch'; +import { FrameworkLib } from './framework'; + +export interface FrontendLibs { + elasticsearch: ElasticsearchLib; + framework: FrameworkLib; + agents: AgentsLib; +} + +export type FramworkAdapterConstructable = new (uiModule: IModule) => FrameworkAdapter; + +// FIXME: replace AxiosRequestConfig with something more defined +export type RequestConfig = AxiosRequestConfig; + +export interface ApiAdapter { + kbnVersion: string; + + get(url: string, config?: RequestConfig | undefined): Promise; + post(url: string, data?: any, config?: AxiosRequestConfig | undefined): Promise; + delete(url: string, config?: RequestConfig | undefined): Promise; + put(url: string, data?: any, config?: RequestConfig | undefined): Promise; +} + +export interface UiKibanaAdapterScope extends IScope { + breadcrumbs: any[]; + topNavMenu: any[]; +} + +export interface KibanaUIConfig { + get(key: string): any; + set(key: string, value: any): Promise; +} + +export interface KibanaAdapterServiceRefs { + config: KibanaUIConfig; + rootScope: IScope; +} + +export type BufferedKibanaServiceCall = (serviceRefs: ServiceRefs) => void; + +export interface Chrome { + setRootTemplate(template: string): void; +} diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json new file mode 100644 index 00000000000000..67fefc7286ca4f --- /dev/null +++ b/x-pack/plugins/fleet/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["**/node_modules/**"], + "paths": { + "react": ["../../../node_modules/@types/react"] + } +} From f141fa76fad9a496e9f69d127f9c5de025ffd1fe Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 4 Jun 2019 13:41:12 -0400 Subject: [PATCH 03/27] add initial UI framework --- .../plugins/fleet/common/constants/index.ts | 1 + .../public/components/layouts/background.tsx | 11 ++ .../public/components/layouts/no_data.tsx | 34 ++++++ .../public/components/layouts/primary.tsx | 83 ++++++++++++++ .../public/components/layouts/walkthrough.tsx | 54 ++++++++++ .../fleet/public/components/loading.tsx | 16 +++ .../components/navigation/child_routes.tsx | 38 +++++++ .../components/navigation/connected_link.tsx | 41 +++++++ .../hooks/with_kuery_autocompletion.tsx | 88 +++++++++++++++ .../fleet/public/hooks/with_url_state.tsx | 101 ++++++++++++++++++ x-pack/plugins/fleet/public/index.tsx | 26 +++++ .../public/pages/error/enforce_security.tsx | 26 +++++ .../public/pages/error/invalid_license.tsx | 27 +++++ .../fleet/public/pages/error/no_access.tsx | 28 +++++ x-pack/plugins/fleet/public/pages/index.tsx | 11 ++ x-pack/plugins/fleet/public/routes.tsx | 94 ++++++++++++++++ 16 files changed, 679 insertions(+) create mode 100644 x-pack/plugins/fleet/public/components/layouts/background.tsx create mode 100644 x-pack/plugins/fleet/public/components/layouts/no_data.tsx create mode 100644 x-pack/plugins/fleet/public/components/layouts/primary.tsx create mode 100644 x-pack/plugins/fleet/public/components/layouts/walkthrough.tsx create mode 100644 x-pack/plugins/fleet/public/components/loading.tsx create mode 100644 x-pack/plugins/fleet/public/components/navigation/child_routes.tsx create mode 100644 x-pack/plugins/fleet/public/components/navigation/connected_link.tsx create mode 100644 x-pack/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx create mode 100644 x-pack/plugins/fleet/public/hooks/with_url_state.tsx create mode 100644 x-pack/plugins/fleet/public/index.tsx create mode 100644 x-pack/plugins/fleet/public/pages/error/enforce_security.tsx create mode 100644 x-pack/plugins/fleet/public/pages/error/invalid_license.tsx create mode 100644 x-pack/plugins/fleet/public/pages/error/no_access.tsx create mode 100644 x-pack/plugins/fleet/public/pages/index.tsx create mode 100644 x-pack/plugins/fleet/public/routes.tsx diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index da3cf73059c179..a03a573de2c81b 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -6,3 +6,4 @@ export { INDEX_NAMES } from './index_names'; export { PLUGIN } from './plugin'; +export const BASE_PATH = '/fleet'; diff --git a/x-pack/plugins/fleet/public/components/layouts/background.tsx b/x-pack/plugins/fleet/public/components/layouts/background.tsx new file mode 100644 index 00000000000000..a8eeda1f6571b6 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/layouts/background.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import styled from 'styled-components'; + +export const Background = styled.div` + flex-grow: 1; +`; diff --git a/x-pack/plugins/fleet/public/components/layouts/no_data.tsx b/x-pack/plugins/fleet/public/components/layouts/no_data.tsx new file mode 100644 index 00000000000000..8d4c7703dfc4d7 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/layouts/no_data.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui'; +import React from 'react'; +import { withRouter } from 'react-router-dom'; + +interface LayoutProps { + title: string | React.ReactNode; + actionSection?: React.ReactNode; + modalClosePath?: string; +} + +export const NoDataLayout: React.SFC = withRouter( + ({ actionSection, title, modalClosePath, children, history }) => { + return ( + + + + {title}} + body={children} + actions={actionSection} + /> + + + + ); + } +) as any; diff --git a/x-pack/plugins/fleet/public/components/layouts/primary.tsx b/x-pack/plugins/fleet/public/components/layouts/primary.tsx new file mode 100644 index 00000000000000..f7106668f7b128 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/layouts/primary.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiHeader, + EuiHeaderBreadcrumbs, + EuiHeaderSection, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; +import React, { Component, ReactNode } from 'react'; +import styled from 'styled-components'; +import { BreadcrumbConsumer } from '../navigation/breadcrumb'; + +type RenderCallback = ((component: () => JSX.Element) => void); +interface PrimaryLayoutProps { + title: string | React.ReactNode; + actionSection?: React.ReactNode; + hideBreadcrumbs?: boolean; +} +export class PrimaryLayout extends Component { + private actionSection: (() => JSX.Element) | null = null; + constructor(props: PrimaryLayoutProps) { + super(props); + } + + public render() { + const children: (callback: RenderCallback) => void | ReactNode = this.props.children as any; + return ( + + {!this.props.hideBreadcrumbs && ( + + {({ breadcrumbs }) => ( + + + + + + )} + + )} + + + + + +

{this.props.title}

+
+
+ + {(this.actionSection && this.actionSection()) || this.props.actionSection} + +
+ + + {(children && typeof children === 'function' + ? children(this.renderAction) + : children) || } + + +
+
+
+ ); + } + + private renderAction = (component: () => JSX.Element) => { + this.actionSection = component; + this.forceUpdate(); + }; +} + +const HeaderWrapper = styled(EuiHeader)` + height: 29px; +`; diff --git a/x-pack/plugins/fleet/public/components/layouts/walkthrough.tsx b/x-pack/plugins/fleet/public/components/layouts/walkthrough.tsx new file mode 100644 index 00000000000000..0a63ccdc87239a --- /dev/null +++ b/x-pack/plugins/fleet/public/components/layouts/walkthrough.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { + EuiPageContent, + EuiPageContentBody, + // @ts-ignore + EuiStepsHorizontal, + EuiTitle, +} from '@elastic/eui'; + +interface LayoutProps { + title: string; + goTo: (path: string) => any; + walkthroughSteps: Array<{ + id: string; + name: string; + }>; + activePath: string; +} + +export const WalkthroughLayout: React.SFC = ({ + walkthroughSteps, + title, + activePath, + goTo, + children, +}) => { + const indexOfCurrent = walkthroughSteps.findIndex(step => activePath === step.id); + return ( + + +

{title}

+
+
+
+ ({ + title: step.name, + isComplete: i <= indexOfCurrent, + onClick: () => goTo(step.id), + }))} + /> +
+
+ {children} +
+ ); +}; diff --git a/x-pack/plugins/fleet/public/components/loading.tsx b/x-pack/plugins/fleet/public/components/loading.tsx new file mode 100644 index 00000000000000..f1c2455ec85b97 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/loading.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import * as React from 'react'; + +export const Loading: React.SFC<{}> = () => ( + + + + + +); diff --git a/x-pack/plugins/fleet/public/components/navigation/child_routes.tsx b/x-pack/plugins/fleet/public/components/navigation/child_routes.tsx new file mode 100644 index 00000000000000..189d7b1d2a3bd2 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/navigation/child_routes.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { SFC } from 'react'; +import { Route, Switch } from 'react-router-dom'; + +interface RouteConfig { + path: string; + component: React.ComponentType; + routes?: RouteConfig[]; +} + +export const ChildRoutes: SFC<{ + routes?: RouteConfig[]; + useSwitch?: boolean; + [other: string]: any; +}> = ({ routes, useSwitch = true, ...rest }) => { + if (!routes) { + return null; + } + const Parent = useSwitch ? Switch : React.Fragment; + return ( + + {routes.map(route => ( + { + const Component = route.component; + return ; + }} + /> + ))} + + ); +}; diff --git a/x-pack/plugins/fleet/public/components/navigation/connected_link.tsx b/x-pack/plugins/fleet/public/components/navigation/connected_link.tsx new file mode 100644 index 00000000000000..30d12c9ce10dee --- /dev/null +++ b/x-pack/plugins/fleet/public/components/navigation/connected_link.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; + +import { EuiLink } from '@elastic/eui'; +import { Link, withRouter } from 'react-router-dom'; + +export function ConnectedLinkComponent({ + location, + path, + query, + disabled, + children, + ...props +}: { + location: any; + path: string; + disabled: boolean; + query: any; + [key: string]: any; +}) { + if (disabled) { + return ; + } + + // Shorthand for pathname + const pathname = path || _.get(props.to, 'pathname') || location.pathname; + + return ( + + ); +} + +export const ConnectedLink = withRouter(ConnectedLinkComponent); diff --git a/x-pack/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx b/x-pack/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx new file mode 100644 index 00000000000000..4fbf65653a4042 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; + +import { FrontendLibs } from '../lib/types'; +import { RendererFunction } from '../utils/typed_react'; + +interface WithKueryAutocompletionLifecycleProps { + libs: FrontendLibs; + fieldPrefix?: string; + children: RendererFunction<{ + isLoadingSuggestions: boolean; + loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; + suggestions: AutocompleteSuggestion[]; + }>; +} + +interface WithKueryAutocompletionLifecycleState { + // lacking cancellation support in the autocompletion api, + // this is used to keep older, slower requests from clobbering newer ones + currentRequest: { + expression: string; + cursorPosition: number; + } | null; + suggestions: AutocompleteSuggestion[]; +} + +export class WithKueryAutocompletion extends React.Component< + WithKueryAutocompletionLifecycleProps, + WithKueryAutocompletionLifecycleState +> { + public readonly state: WithKueryAutocompletionLifecycleState = { + currentRequest: null, + suggestions: [], + }; + + public render() { + const { currentRequest, suggestions } = this.state; + + return this.props.children({ + isLoadingSuggestions: currentRequest !== null, + loadSuggestions: this.loadSuggestions, + suggestions, + }); + } + + private loadSuggestions = async ( + expression: string, + cursorPosition: number, + maxSuggestions?: number + ) => { + this.setState({ + currentRequest: { + expression, + cursorPosition, + }, + suggestions: [], + }); + let suggestions: any[] = []; + try { + suggestions = await this.props.libs.elasticsearch.getSuggestions( + expression, + cursorPosition, + this.props.fieldPrefix + ); + } catch (e) { + suggestions = []; + } + + this.setState(state => + state.currentRequest && + state.currentRequest.expression !== expression && + state.currentRequest.cursorPosition !== cursorPosition + ? state // ignore this result, since a newer request is in flight + : { + ...state, + currentRequest: null, + suggestions: maxSuggestions ? suggestions.slice(0, maxSuggestions) : suggestions, + } + ); + }; +} diff --git a/x-pack/plugins/fleet/public/hooks/with_url_state.tsx b/x-pack/plugins/fleet/public/hooks/with_url_state.tsx new file mode 100644 index 00000000000000..0802b4d8ea7e80 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/with_url_state.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { parse, stringify } from 'querystring'; +import React from 'react'; +import { withRouter } from 'react-router-dom'; +import { FlatObject } from '../frontend_types'; +import { RendererFunction } from '../utils/typed_react'; + +type StateCallback = (previousState: T) => T; + +export interface URLStateProps { + goTo: (path: string) => void; + setUrlState: ( + newState: + | Partial> + | StateCallback + | Promise> + ) => void; + urlState: URLState; +} +interface ComponentProps { + history: any; + match: any; + children: RendererFunction>; +} + +export class WithURLStateComponent extends React.Component< + ComponentProps +> { + private get URLState(): URLState { + // slice because parse does not account for the initial ? in the search string + return parse(decodeURIComponent(this.props.history.location.search).substring(1)) as URLState; + } + + private historyListener: (() => void) | null = null; + + public componentWillUnmount() { + if (this.historyListener) { + this.historyListener(); + } + } + public render() { + return this.props.children({ + goTo: this.goTo, + setUrlState: this.setURLState, + urlState: this.URLState || {}, + }); + } + + private setURLState = async ( + state: + | Partial> + | StateCallback + | Promise> + ) => { + let newState; + const pastState = this.URLState; + if (typeof state === 'function') { + newState = await state(pastState); + } else { + newState = state; + } + + const search: string = stringify({ + ...(pastState as any), + ...(newState as any), + }); + + const newLocation = { + ...this.props.history.location, + search, + }; + + this.props.history.replace(newLocation); + this.forceUpdate(); + }; + + private goTo = (path: string) => { + this.props.history.push({ + pathname: path, + search: this.props.history.location.search, + }); + }; +} +export const WithURLState = withRouter(WithURLStateComponent); + +export function withUrlState( + UnwrappedComponent: React.ComponentType +): React.SFC { + return (origProps: OP) => { + return ( + + {(URLProps: URLStateProps) => } + + ); + }; +} diff --git a/x-pack/plugins/fleet/public/index.tsx b/x-pack/plugins/fleet/public/index.tsx new file mode 100644 index 00000000000000..22f55a7f026ef8 --- /dev/null +++ b/x-pack/plugins/fleet/public/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { HashRouter } from 'react-router-dom'; +import { BASE_PATH } from '../common/constants'; +import { compose } from './lib/compose/kibana'; +import { FrontendLibs } from './lib/types'; +import { AppRoutes } from './routes'; + +async function startApp(libs: FrontendLibs) { + libs.framework.renderUIAtPath( + BASE_PATH, + + + , + 'self' + ); + + await libs.framework.waitUntilFrameworkReady(); +} + +startApp(compose()); diff --git a/x-pack/plugins/fleet/public/pages/error/enforce_security.tsx b/x-pack/plugins/fleet/public/pages/error/enforce_security.tsx new file mode 100644 index 00000000000000..07f7efbb05e33b --- /dev/null +++ b/x-pack/plugins/fleet/public/pages/error/enforce_security.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import * as React from 'react'; +import { NoDataLayout } from '../../components/layouts/no_data'; + +export const EnforceSecurityPage = injectI18n(({ intl }) => ( + +

+ +

+
+)); diff --git a/x-pack/plugins/fleet/public/pages/error/invalid_license.tsx b/x-pack/plugins/fleet/public/pages/error/invalid_license.tsx new file mode 100644 index 00000000000000..5565be059569a8 --- /dev/null +++ b/x-pack/plugins/fleet/public/pages/error/invalid_license.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import * as React from 'react'; +import { NoDataLayout } from '../../components/layouts/no_data'; + +export const InvalidLicensePage = injectI18n(({ intl }) => ( + +

+ +

+
+)); diff --git a/x-pack/plugins/fleet/public/pages/error/no_access.tsx b/x-pack/plugins/fleet/public/pages/error/no_access.tsx new file mode 100644 index 00000000000000..a468616052b093 --- /dev/null +++ b/x-pack/plugins/fleet/public/pages/error/no_access.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import * as React from 'react'; +import { NoDataLayout } from '../../components/layouts/no_data'; + +export const NoAccessPage = injectI18n(({ intl }) => ( + +

+ +

+
+)); diff --git a/x-pack/plugins/fleet/public/pages/index.tsx b/x-pack/plugins/fleet/public/pages/index.tsx new file mode 100644 index 00000000000000..1c5d28130dabe3 --- /dev/null +++ b/x-pack/plugins/fleet/public/pages/index.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export const IndexPage = () => { + return Elastic Fleet; +}; diff --git a/x-pack/plugins/fleet/public/routes.tsx b/x-pack/plugins/fleet/public/routes.tsx new file mode 100644 index 00000000000000..8e65f048bfa84b --- /dev/null +++ b/x-pack/plugins/fleet/public/routes.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import React, { Component } from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import { REQUIRED_ROLES } from '../common/constants/security'; +import { Loading } from './components/loading'; +import { ChildRoutes } from './components/navigation/child_routes'; +import { URLStateProps, WithURLState } from './hooks/with_url_state'; +import { FrontendLibs } from './lib/types'; + +interface RouterProps { + libs: FrontendLibs; +} +interface RouterState { + loading: boolean; +} + +export class AppRoutes extends Component { + constructor(props: RouterProps) { + super(props); + this.state = { + loading: true, + }; + } + + public render() { + if (this.state.loading === true) { + return ; + } + + return ( + + {/* Redirects mapping */} + + {/* License check (UI displays when license exists but is expired) */} + {get(this.props.libs.framework.info, 'license.expired', true) && ( + + !props.location.pathname.includes('/error') ? ( + + ) : null + } + /> + )} + + {/* Ensure security is eanabled for elastic and kibana */} + {!get(this.props.libs.framework.info, 'security.enabled', true) && ( + + !props.location.pathname.includes('/error') ? ( + + ) : null + } + /> + )} + + {/* Make sure the user has correct permissions */} + {!this.props.libs.framework.currentUserHasOneOfRoles( + REQUIRED_ROLES.concat(this.props.libs.framework.info.settings.defaultUserRoles) + ) && ( + + !props.location.pathname.includes('/error') ? ( + + ) : null + } + /> + )} + + {/* This app does not make use of a homepage. The mainpage is overview/enrolled_agents */} + } /> + + + {/* Render routes from the FS */} + + {(URLProps: URLStateProps) => ( + + )} + + + ); + } +} From 288e5f62abebf4f9e3a865aa6fe50dac1bcfac30 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 25 Jun 2019 09:46:02 -0400 Subject: [PATCH 04/27] move fleet to legacy --- .../plugins/fleet/common/constants/index.ts | 0 .../plugins/fleet/common/constants/index_names.ts | 0 .../plugins/fleet/common/constants/plugin.ts | 0 .../plugins/fleet/common/constants/security.ts | 0 .../plugins/fleet/common/types/domain_data.ts | 0 .../plugins/fleet/common/types/helpers.ts | 0 .../plugins/fleet/common/types/io_ts.ts | 0 .../plugins/fleet/common/types/security.ts | 0 .../fleet/common/types/std_return_format.ts | 0 .../plugins/fleet/dev/load_testing/artillery.yml | 14 ++++++++++++++ x-pack/{ => legacy}/plugins/fleet/index.ts | 9 ++++++--- .../public/components/layouts/background.tsx | 0 .../fleet/public/components/layouts/no_data.tsx | 0 .../fleet/public/components/layouts/primary.tsx | 0 .../public/components/layouts/walkthrough.tsx | 0 .../plugins/fleet/public/components/loading.tsx | 0 .../public/components/navigation/child_routes.tsx | 0 .../components/navigation/connected_link.tsx | 0 .../public/hooks/with_kuery_autocompletion.tsx | 0 .../plugins/fleet/public/hooks/with_url_state.tsx | 0 .../{ => legacy}/plugins/fleet/public/index.tsx | 0 .../public/lib/adapters/agent/adapter_types.ts | 0 .../lib/adapters/agent/memory_agent_adapter.ts | 0 .../lib/adapters/agent/rest_agent_adapter.ts | 0 .../lib/adapters/elasticsearch/adapter_types.ts | 0 .../public/lib/adapters/elasticsearch/memory.ts | 0 .../public/lib/adapters/elasticsearch/rest.ts | 0 .../lib/adapters/framework/adapter_types.ts | 2 +- .../framework/kibana_framework_adapter.ts | 0 .../framework/testing_framework_adapter.ts | 0 .../public/lib/adapters/rest_api/adapter_types.ts | 0 .../adapters/rest_api/axios_rest_api_adapter.ts | 0 .../adapters/rest_api/node_axios_api_adapter.ts | 0 .../plugins/fleet/public/lib/agent.ts | 0 .../plugins/fleet/public/lib/compose/kibana.ts | 4 ++-- .../plugins/fleet/public/lib/compose/memory.ts | 4 ++-- .../plugins/fleet/public/lib/compose/scripts.ts | 0 .../plugins/fleet/public/lib/elasticsearch.ts | 0 .../plugins/fleet/public/lib/framework.ts | 0 .../plugins/fleet/public/lib/types.ts | 0 .../fleet/public/pages/error/enforce_security.tsx | 0 .../fleet/public/pages/error/invalid_license.tsx | 0 .../fleet/public/pages/error/no_access.tsx | 0 .../plugins/fleet/public/pages/index.tsx | 0 .../{ => legacy}/plugins/fleet/public/routes.tsx | 0 .../legacy/plugins/fleet/server/kibana.index.ts | 15 +++++++++++++++ x-pack/{ => legacy}/plugins/fleet/tsconfig.json | 0 47 files changed, 40 insertions(+), 8 deletions(-) rename x-pack/{ => legacy}/plugins/fleet/common/constants/index.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/constants/index_names.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/constants/plugin.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/constants/security.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/types/domain_data.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/types/helpers.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/types/io_ts.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/types/security.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/common/types/std_return_format.ts (100%) create mode 100644 x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml rename x-pack/{ => legacy}/plugins/fleet/index.ts (84%) rename x-pack/{ => legacy}/plugins/fleet/public/components/layouts/background.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/components/layouts/no_data.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/components/layouts/primary.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/components/layouts/walkthrough.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/components/loading.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/components/navigation/child_routes.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/components/navigation/connected_link.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/hooks/with_url_state.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/index.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/agent/adapter_types.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/framework/adapter_types.ts (96%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/agent.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/compose/kibana.ts (94%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/compose/memory.ts (92%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/compose/scripts.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/elasticsearch.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/framework.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/lib/types.ts (100%) rename x-pack/{ => legacy}/plugins/fleet/public/pages/error/enforce_security.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/pages/error/invalid_license.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/pages/error/no_access.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/pages/index.tsx (100%) rename x-pack/{ => legacy}/plugins/fleet/public/routes.tsx (100%) create mode 100644 x-pack/legacy/plugins/fleet/server/kibana.index.ts rename x-pack/{ => legacy}/plugins/fleet/tsconfig.json (100%) diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/legacy/plugins/fleet/common/constants/index.ts similarity index 100% rename from x-pack/plugins/fleet/common/constants/index.ts rename to x-pack/legacy/plugins/fleet/common/constants/index.ts diff --git a/x-pack/plugins/fleet/common/constants/index_names.ts b/x-pack/legacy/plugins/fleet/common/constants/index_names.ts similarity index 100% rename from x-pack/plugins/fleet/common/constants/index_names.ts rename to x-pack/legacy/plugins/fleet/common/constants/index_names.ts diff --git a/x-pack/plugins/fleet/common/constants/plugin.ts b/x-pack/legacy/plugins/fleet/common/constants/plugin.ts similarity index 100% rename from x-pack/plugins/fleet/common/constants/plugin.ts rename to x-pack/legacy/plugins/fleet/common/constants/plugin.ts diff --git a/x-pack/plugins/fleet/common/constants/security.ts b/x-pack/legacy/plugins/fleet/common/constants/security.ts similarity index 100% rename from x-pack/plugins/fleet/common/constants/security.ts rename to x-pack/legacy/plugins/fleet/common/constants/security.ts diff --git a/x-pack/plugins/fleet/common/types/domain_data.ts b/x-pack/legacy/plugins/fleet/common/types/domain_data.ts similarity index 100% rename from x-pack/plugins/fleet/common/types/domain_data.ts rename to x-pack/legacy/plugins/fleet/common/types/domain_data.ts diff --git a/x-pack/plugins/fleet/common/types/helpers.ts b/x-pack/legacy/plugins/fleet/common/types/helpers.ts similarity index 100% rename from x-pack/plugins/fleet/common/types/helpers.ts rename to x-pack/legacy/plugins/fleet/common/types/helpers.ts diff --git a/x-pack/plugins/fleet/common/types/io_ts.ts b/x-pack/legacy/plugins/fleet/common/types/io_ts.ts similarity index 100% rename from x-pack/plugins/fleet/common/types/io_ts.ts rename to x-pack/legacy/plugins/fleet/common/types/io_ts.ts diff --git a/x-pack/plugins/fleet/common/types/security.ts b/x-pack/legacy/plugins/fleet/common/types/security.ts similarity index 100% rename from x-pack/plugins/fleet/common/types/security.ts rename to x-pack/legacy/plugins/fleet/common/types/security.ts diff --git a/x-pack/plugins/fleet/common/types/std_return_format.ts b/x-pack/legacy/plugins/fleet/common/types/std_return_format.ts similarity index 100% rename from x-pack/plugins/fleet/common/types/std_return_format.ts rename to x-pack/legacy/plugins/fleet/common/types/std_return_format.ts diff --git a/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml b/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml new file mode 100644 index 00000000000000..549cbfe6235136 --- /dev/null +++ b/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml @@ -0,0 +1,14 @@ +config: + target: 'http://0.0.0.0:5603/qgv' + phases: + - duration: 20 + arrivalRate: 10 + rampTo: 50 + name: 'Warm up the stack' + - duration: 400 + arrivalRate: 100000 + name: 'Sustained max load' +scenarios: + - flow: + - get: + url: '/api/fleet/load' diff --git a/x-pack/plugins/fleet/index.ts b/x-pack/legacy/plugins/fleet/index.ts similarity index 84% rename from x-pack/plugins/fleet/index.ts rename to x-pack/legacy/plugins/fleet/index.ts index bbeec8fc99c804..f129e9a45457c8 100644 --- a/x-pack/plugins/fleet/index.ts +++ b/x-pack/legacy/plugins/fleet/index.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; +import * as Joi from 'joi'; import { resolve } from 'path'; -import { i18n } from '../../../packages/kbn-i18n/src/index'; +import { i18n } from '@kbn/i18n/src'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; +import { initServerWithKibana } from './server/kibana.index'; // export const config = Joi.object({ @@ -33,6 +34,8 @@ export function fleet(kibana: any) { }, config: () => config, configPrefix: CONFIG_PREFIX, - init(server: any) {}, + init(server: any) { + initServerWithKibana(server); + }, }); } diff --git a/x-pack/plugins/fleet/public/components/layouts/background.tsx b/x-pack/legacy/plugins/fleet/public/components/layouts/background.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/layouts/background.tsx rename to x-pack/legacy/plugins/fleet/public/components/layouts/background.tsx diff --git a/x-pack/plugins/fleet/public/components/layouts/no_data.tsx b/x-pack/legacy/plugins/fleet/public/components/layouts/no_data.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/layouts/no_data.tsx rename to x-pack/legacy/plugins/fleet/public/components/layouts/no_data.tsx diff --git a/x-pack/plugins/fleet/public/components/layouts/primary.tsx b/x-pack/legacy/plugins/fleet/public/components/layouts/primary.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/layouts/primary.tsx rename to x-pack/legacy/plugins/fleet/public/components/layouts/primary.tsx diff --git a/x-pack/plugins/fleet/public/components/layouts/walkthrough.tsx b/x-pack/legacy/plugins/fleet/public/components/layouts/walkthrough.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/layouts/walkthrough.tsx rename to x-pack/legacy/plugins/fleet/public/components/layouts/walkthrough.tsx diff --git a/x-pack/plugins/fleet/public/components/loading.tsx b/x-pack/legacy/plugins/fleet/public/components/loading.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/loading.tsx rename to x-pack/legacy/plugins/fleet/public/components/loading.tsx diff --git a/x-pack/plugins/fleet/public/components/navigation/child_routes.tsx b/x-pack/legacy/plugins/fleet/public/components/navigation/child_routes.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/navigation/child_routes.tsx rename to x-pack/legacy/plugins/fleet/public/components/navigation/child_routes.tsx diff --git a/x-pack/plugins/fleet/public/components/navigation/connected_link.tsx b/x-pack/legacy/plugins/fleet/public/components/navigation/connected_link.tsx similarity index 100% rename from x-pack/plugins/fleet/public/components/navigation/connected_link.tsx rename to x-pack/legacy/plugins/fleet/public/components/navigation/connected_link.tsx diff --git a/x-pack/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx similarity index 100% rename from x-pack/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx rename to x-pack/legacy/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx diff --git a/x-pack/plugins/fleet/public/hooks/with_url_state.tsx b/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx similarity index 100% rename from x-pack/plugins/fleet/public/hooks/with_url_state.tsx rename to x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx diff --git a/x-pack/plugins/fleet/public/index.tsx b/x-pack/legacy/plugins/fleet/public/index.tsx similarity index 100% rename from x-pack/plugins/fleet/public/index.tsx rename to x-pack/legacy/plugins/fleet/public/index.tsx diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/agent/adapter_types.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/agent/adapter_types.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/framework/adapter_types.ts similarity index 96% rename from x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/framework/adapter_types.ts index b8a75e284c2480..d1f1d27a6faea6 100644 --- a/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/fleet/public/lib/adapters/framework/adapter_types.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/no-empty-interface */ import * as t from 'io-ts'; -import { LICENSES } from './../../../../common/constants/security'; +import { LICENSES } from '../../../../common/constants/security'; export interface FrameworkAdapter { // Instance vars diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts b/x-pack/legacy/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts rename to x-pack/legacy/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts diff --git a/x-pack/plugins/fleet/public/lib/agent.ts b/x-pack/legacy/plugins/fleet/public/lib/agent.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/agent.ts rename to x-pack/legacy/plugins/fleet/public/lib/agent.ts diff --git a/x-pack/plugins/fleet/public/lib/compose/kibana.ts b/x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts similarity index 94% rename from x-pack/plugins/fleet/public/lib/compose/kibana.ts rename to x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts index 17991b764cd716..787d9469c1dd2d 100644 --- a/x-pack/plugins/fleet/public/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts @@ -20,8 +20,8 @@ import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter import { AgentsLib } from '../agent'; import { ElasticsearchLib } from '../elasticsearch'; import { FrontendLibs } from '../types'; -import { PLUGIN } from './../../../common/constants/plugin'; -import { FrameworkLib } from './../framework'; +import { PLUGIN } from '../../../common/constants/plugin'; +import { FrameworkLib } from '../framework'; // A super early spot in kibana loading that we can use to hook before most other things const onKibanaReady = chrome.dangerouslyGetActiveInjector; diff --git a/x-pack/plugins/fleet/public/lib/compose/memory.ts b/x-pack/legacy/plugins/fleet/public/lib/compose/memory.ts similarity index 92% rename from x-pack/plugins/fleet/public/lib/compose/memory.ts rename to x-pack/legacy/plugins/fleet/public/lib/compose/memory.ts index 5501c4066d89e0..fea87f6fdd9112 100644 --- a/x-pack/plugins/fleet/public/lib/compose/memory.ts +++ b/x-pack/legacy/plugins/fleet/public/lib/compose/memory.ts @@ -18,8 +18,8 @@ import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_a import { AgentsLib } from '../agent'; import { FrameworkLib } from '../framework'; import { FrontendLibs } from '../types'; -import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; -import { ElasticsearchLib } from './../elasticsearch'; +import { MemoryElasticsearchAdapter } from '../adapters/elasticsearch/memory'; +import { ElasticsearchLib } from '../elasticsearch'; const onKibanaReady = uiModules.get('kibana').run; diff --git a/x-pack/plugins/fleet/public/lib/compose/scripts.ts b/x-pack/legacy/plugins/fleet/public/lib/compose/scripts.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/compose/scripts.ts rename to x-pack/legacy/plugins/fleet/public/lib/compose/scripts.ts diff --git a/x-pack/plugins/fleet/public/lib/elasticsearch.ts b/x-pack/legacy/plugins/fleet/public/lib/elasticsearch.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/elasticsearch.ts rename to x-pack/legacy/plugins/fleet/public/lib/elasticsearch.ts diff --git a/x-pack/plugins/fleet/public/lib/framework.ts b/x-pack/legacy/plugins/fleet/public/lib/framework.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/framework.ts rename to x-pack/legacy/plugins/fleet/public/lib/framework.ts diff --git a/x-pack/plugins/fleet/public/lib/types.ts b/x-pack/legacy/plugins/fleet/public/lib/types.ts similarity index 100% rename from x-pack/plugins/fleet/public/lib/types.ts rename to x-pack/legacy/plugins/fleet/public/lib/types.ts diff --git a/x-pack/plugins/fleet/public/pages/error/enforce_security.tsx b/x-pack/legacy/plugins/fleet/public/pages/error/enforce_security.tsx similarity index 100% rename from x-pack/plugins/fleet/public/pages/error/enforce_security.tsx rename to x-pack/legacy/plugins/fleet/public/pages/error/enforce_security.tsx diff --git a/x-pack/plugins/fleet/public/pages/error/invalid_license.tsx b/x-pack/legacy/plugins/fleet/public/pages/error/invalid_license.tsx similarity index 100% rename from x-pack/plugins/fleet/public/pages/error/invalid_license.tsx rename to x-pack/legacy/plugins/fleet/public/pages/error/invalid_license.tsx diff --git a/x-pack/plugins/fleet/public/pages/error/no_access.tsx b/x-pack/legacy/plugins/fleet/public/pages/error/no_access.tsx similarity index 100% rename from x-pack/plugins/fleet/public/pages/error/no_access.tsx rename to x-pack/legacy/plugins/fleet/public/pages/error/no_access.tsx diff --git a/x-pack/plugins/fleet/public/pages/index.tsx b/x-pack/legacy/plugins/fleet/public/pages/index.tsx similarity index 100% rename from x-pack/plugins/fleet/public/pages/index.tsx rename to x-pack/legacy/plugins/fleet/public/pages/index.tsx diff --git a/x-pack/plugins/fleet/public/routes.tsx b/x-pack/legacy/plugins/fleet/public/routes.tsx similarity index 100% rename from x-pack/plugins/fleet/public/routes.tsx rename to x-pack/legacy/plugins/fleet/public/routes.tsx diff --git a/x-pack/legacy/plugins/fleet/server/kibana.index.ts b/x-pack/legacy/plugins/fleet/server/kibana.index.ts new file mode 100644 index 00000000000000..2d09002af0229b --- /dev/null +++ b/x-pack/legacy/plugins/fleet/server/kibana.index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const initServerWithKibana = (hapiServer: any) => { + hapiServer.route({ + method: 'GET', + path: '/api/fleet/load', + handler: (request: any, h: any) => { + return 'Hello World!'; + }, + }); +}; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/legacy/plugins/fleet/tsconfig.json similarity index 100% rename from x-pack/plugins/fleet/tsconfig.json rename to x-pack/legacy/plugins/fleet/tsconfig.json From 339c6e1732b0ff2233f33add695ce798aaf2726d Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 25 Jun 2019 10:05:24 -0400 Subject: [PATCH 05/27] add readme --- x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml | 4 ++-- x-pack/legacy/plugins/fleet/dev/readme.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/fleet/dev/readme.md diff --git a/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml b/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml index 549cbfe6235136..07fd6d11f331bd 100644 --- a/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml +++ b/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml @@ -1,5 +1,5 @@ config: - target: 'http://0.0.0.0:5603/qgv' + target: 'http://0.0.0.0:5601' phases: - duration: 20 arrivalRate: 10 @@ -11,4 +11,4 @@ config: scenarios: - flow: - get: - url: '/api/fleet/load' + url: '/wuc/api/fleet/load' diff --git a/x-pack/legacy/plugins/fleet/dev/readme.md b/x-pack/legacy/plugins/fleet/dev/readme.md new file mode 100644 index 00000000000000..76e6d8bd00ce37 --- /dev/null +++ b/x-pack/legacy/plugins/fleet/dev/readme.md @@ -0,0 +1,4 @@ +#### Testing load + +artillery run x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml +but edit for kibana path first... From b33174339bda2632b81c70ad488fca21b8ec6a1f Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 22 Jul 2019 17:04:57 -0400 Subject: [PATCH 06/27] Add shell ingest plugin --- .../load_testing/artillery.yml | 0 .../plugins/fleet/{dev => scripts}/readme.md | 0 x-pack/legacy/plugins/fleet/tsconfig.json | 2 +- .../plugins/ingest/common/constants/index.ts | 9 + .../ingest/common/constants/index_names.ts | 9 + .../plugins/ingest/common/constants/plugin.ts | 10 + .../ingest/common/constants/security.ts | 8 + .../ingest/common/types/domain_data.ts | 80 +++++ .../plugins/ingest/common/types/helpers.ts | 7 + .../plugins/ingest/common/types/io_ts.ts | 20 ++ .../plugins/ingest/common/types/security.ts | 7 + .../ingest/common/types/std_return_format.ts | 116 +++++++ x-pack/legacy/plugins/ingest/index.ts | 29 ++ .../plugins/ingest/server/kibana.index.ts | 15 + .../adapters/configurations/adapter_types.ts | 75 +++++ .../libs/adapters/configurations/default.ts | 88 +++++ .../libs/adapters/database/adapter_types.ts | 309 ++++++++++++++++++ .../database/kibana_database_adapter.ts | 130 ++++++++ .../libs/adapters/framework/adapter_types.ts | 175 ++++++++++ .../framework/kibana_framework_adapter.ts | 140 ++++++++ .../ingest/server/libs/compose/kibana.ts | 31 ++ .../ingest/server/libs/compose/testing.ts | 51 +++ .../ingest/server/libs/configuration.ts | 17 + .../plugins/ingest/server/libs/framework.ts | 33 ++ .../plugins/ingest/server/libs/types.ts | 13 + x-pack/legacy/plugins/ingest/tsconfig.json | 7 + 26 files changed, 1380 insertions(+), 1 deletion(-) rename x-pack/legacy/plugins/fleet/{dev => scripts}/load_testing/artillery.yml (100%) rename x-pack/legacy/plugins/fleet/{dev => scripts}/readme.md (100%) create mode 100644 x-pack/legacy/plugins/ingest/common/constants/index.ts create mode 100644 x-pack/legacy/plugins/ingest/common/constants/index_names.ts create mode 100644 x-pack/legacy/plugins/ingest/common/constants/plugin.ts create mode 100644 x-pack/legacy/plugins/ingest/common/constants/security.ts create mode 100644 x-pack/legacy/plugins/ingest/common/types/domain_data.ts create mode 100644 x-pack/legacy/plugins/ingest/common/types/helpers.ts create mode 100644 x-pack/legacy/plugins/ingest/common/types/io_ts.ts create mode 100644 x-pack/legacy/plugins/ingest/common/types/security.ts create mode 100644 x-pack/legacy/plugins/ingest/common/types/std_return_format.ts create mode 100644 x-pack/legacy/plugins/ingest/index.ts create mode 100644 x-pack/legacy/plugins/ingest/server/kibana.index.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/configuration.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/framework.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/types.ts create mode 100644 x-pack/legacy/plugins/ingest/tsconfig.json diff --git a/x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml b/x-pack/legacy/plugins/fleet/scripts/load_testing/artillery.yml similarity index 100% rename from x-pack/legacy/plugins/fleet/dev/load_testing/artillery.yml rename to x-pack/legacy/plugins/fleet/scripts/load_testing/artillery.yml diff --git a/x-pack/legacy/plugins/fleet/dev/readme.md b/x-pack/legacy/plugins/fleet/scripts/readme.md similarity index 100% rename from x-pack/legacy/plugins/fleet/dev/readme.md rename to x-pack/legacy/plugins/fleet/scripts/readme.md diff --git a/x-pack/legacy/plugins/fleet/tsconfig.json b/x-pack/legacy/plugins/fleet/tsconfig.json index 67fefc7286ca4f..3989853fda3149 100644 --- a/x-pack/legacy/plugins/fleet/tsconfig.json +++ b/x-pack/legacy/plugins/fleet/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../../Ã¥tsconfig.json", "exclude": ["**/node_modules/**"], "paths": { "react": ["../../../node_modules/@types/react"] diff --git a/x-pack/legacy/plugins/ingest/common/constants/index.ts b/x-pack/legacy/plugins/ingest/common/constants/index.ts new file mode 100644 index 00000000000000..947620f2c42362 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/constants/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { INDEX_NAMES } from './index_names'; +export { PLUGIN } from './plugin'; +export const BASE_PATH = '/ingest'; diff --git a/x-pack/legacy/plugins/ingest/common/constants/index_names.ts b/x-pack/legacy/plugins/ingest/common/constants/index_names.ts new file mode 100644 index 00000000000000..16a9c9971877cc --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/constants/index_names.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const INDEX_NAMES = { + FLEET: '.ingest', +}; diff --git a/x-pack/legacy/plugins/ingest/common/constants/plugin.ts b/x-pack/legacy/plugins/ingest/common/constants/plugin.ts new file mode 100644 index 00000000000000..48a93fed18229b --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/constants/plugin.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const PLUGIN = { + ID: 'ingest-data', +}; +export const CONFIG_PREFIX = 'xpack.ingest-do-not-disable'; diff --git a/x-pack/legacy/plugins/ingest/common/constants/security.ts b/x-pack/legacy/plugins/ingest/common/constants/security.ts new file mode 100644 index 00000000000000..89cac2b043f5f6 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/constants/security.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const REQUIRED_LICENSES = ['standard', 'gold', 'trial', 'platinum']; +export const LICENSES = ['oss', 'basic', 'standard', 'gold', 'trial', 'platinum']; diff --git a/x-pack/legacy/plugins/ingest/common/types/domain_data.ts b/x-pack/legacy/plugins/ingest/common/types/domain_data.ts new file mode 100644 index 00000000000000..41ec4eae4c5d84 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/types/domain_data.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { DateFromString } from './io_ts'; + +// Here we create the runtime check for a generic, unknown beat config type. +// We can also pass in optional params to create spacific runtime checks that +// can be used to validate blocs on the API and UI +export const createConfigurationInterface = (beatConfigInterface: t.Mixed = t.Dictionary) => + t.interface( + { + id: t.union([t.undefined, t.string]), + name: t.string, + description: t.union([t.undefined, t.string]), + config: beatConfigInterface, + last_updated_by: t.union([t.undefined, t.string]), + last_updated: t.union([t.undefined, t.number]), + }, + 'Config' + ); +const BaseConfiguration = createConfigurationInterface(); +export interface ConfigurationBlock + extends Pick< + t.TypeOf, + Exclude, 'id'> + > { + id: string; +} + +export interface Agent { + id: string; + status?: AgentEvent; + enrollment_token: string; + active: boolean; + access_token: string; + verified_on?: string; + type: string; + version?: string; + host_ip: string; + host_name: string; + ephemeral_id?: string; + last_checkin?: Date; + event_rate?: string; + tags: string[]; + metadata?: {}; + name?: string; + last_updated: number; +} + +export const RuntimeAgentEvent = t.interface( + { + type: t.union([t.literal('STATE'), t.literal('ERROR')]), + beat: t.union([t.undefined, t.string]), + timestamp: DateFromString, + event: t.type({ + type: t.union([ + t.literal('RUNNING'), + t.literal('STARTING'), + t.literal('IN_PROGRESS'), + t.literal('CONFIG'), + t.literal('FAILED'), + t.literal('STOPPED'), + ]), + message: t.string, + uuid: t.union([t.undefined, t.string]), + }), + }, + 'AgentEvent' +); +export interface AgentEvent + extends Pick< + t.TypeOf, + Exclude, 'timestamp'> + > { + agent: string; + timestamp: Date; +} diff --git a/x-pack/legacy/plugins/ingest/common/types/helpers.ts b/x-pack/legacy/plugins/ingest/common/types/helpers.ts new file mode 100644 index 00000000000000..4258461310e164 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/types/helpers.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type FlatObject = { [Key in keyof T]: string }; diff --git a/x-pack/legacy/plugins/ingest/common/types/io_ts.ts b/x-pack/legacy/plugins/ingest/common/types/io_ts.ts new file mode 100644 index 00000000000000..d5533afa790248 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/types/io_ts.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { either } from 'fp-ts/lib/Either'; +export type DateType = t.Type; + +export const DateFromString: DateType = new t.Type( + 'DateFromString', + (u): u is Date => u instanceof Date, + (u, c) => + either.chain(t.string.validate(u, c), s => { + const d = new Date(s); + return isNaN(d.getTime()) ? t.failure(u, c) : t.success(d); + }), + a => a.toISOString() +); diff --git a/x-pack/legacy/plugins/ingest/common/types/security.ts b/x-pack/legacy/plugins/ingest/common/types/security.ts new file mode 100644 index 00000000000000..691ea82b294d30 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/types/security.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type LicenseType = 'oss' | 'basic' | 'trial' | 'standard' | 'basic' | 'gold' | 'platinum'; diff --git a/x-pack/legacy/plugins/ingest/common/types/std_return_format.ts b/x-pack/legacy/plugins/ingest/common/types/std_return_format.ts new file mode 100644 index 00000000000000..ded94bbff7f19e --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/types/std_return_format.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface BaseReturnType { + error?: { + message: string; + code?: number; + }; + success: boolean; +} + +export interface ReturnTypeCreate extends BaseReturnType { + item: T; + action: 'created'; +} + +export interface ReturnTypeUpdate extends BaseReturnType { + item: T; + action: 'updated'; +} + +export interface ReturnTypeBulkCreate extends BaseReturnType { + results: Array<{ + item: T; + success: boolean; + action: 'created'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// delete +export interface ReturnTypeDelete extends BaseReturnType { + action: 'deleted'; +} + +export interface ReturnTypeBulkDelete extends BaseReturnType { + results: Array<{ + success: boolean; + action: 'deleted'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// upsert +export interface ReturnTypeUpsert extends BaseReturnType { + item: T; + action: 'created' | 'updated'; +} + +// upsert bulk +export interface ReturnTypeBulkUpsert extends BaseReturnType { + results: Array<{ + success: boolean; + action: 'created' | 'updated'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// list +export interface ReturnTypeList extends BaseReturnType { + list: T[]; + page: number; + total: number; +} + +// get +export interface ReturnTypeGet extends BaseReturnType { + item: T; +} + +export interface ReturnTypeBulkGet extends BaseReturnType { + items: T[]; +} + +// action -- e.g. validate config block. Like ES simulate endpoint +export interface ReturnTypeAction extends BaseReturnType { + result: { + [key: string]: any; + }; +} +// e.g. +// { +// result: { +// username: { valid: true }, +// password: { valid: false, error: 'something' }, +// hosts: [ +// { valid: false }, { valid: true }, +// ] +// } +// } + +// bulk action +export interface ReturnTypeBulkAction extends BaseReturnType { + results?: Array<{ + success: boolean; + result?: { + [key: string]: any; + }; + error?: { + message: string; + code?: number; + }; + }>; +} diff --git a/x-pack/legacy/plugins/ingest/index.ts b/x-pack/legacy/plugins/ingest/index.ts new file mode 100644 index 00000000000000..cc6f7049d25399 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as Joi from 'joi'; +import { resolve } from 'path'; +import { i18n } from '@kbn/i18n/src'; +import { PLUGIN } from './common/constants'; +import { CONFIG_PREFIX } from './common/constants/plugin'; +import { initServerWithKibana } from './server/kibana.index'; +// + +export const config = Joi.object({ + enabled: Joi.boolean().default(true), +}).default(); + +export function ingest(kibana: any) { + return new kibana.Plugin({ + id: PLUGIN.ID, + require: ['kibana', 'elasticsearch', 'xpack_main'], + publicDir: resolve(__dirname, 'public'), + config: () => config, + configPrefix: CONFIG_PREFIX, + init(server: any) { + initServerWithKibana(server); + }, + }); +} diff --git a/x-pack/legacy/plugins/ingest/server/kibana.index.ts b/x-pack/legacy/plugins/ingest/server/kibana.index.ts new file mode 100644 index 00000000000000..2d09002af0229b --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/kibana.index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const initServerWithKibana = (hapiServer: any) => { + hapiServer.route({ + method: 'GET', + path: '/api/fleet/load', + handler: (request: any, h: any) => { + return 'Hello World!'; + }, + }); +}; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts new file mode 100644 index 00000000000000..b4fb6ccbfb851f --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { DateFromString } from '../../../../common/types/io_ts'; + +export const RuntimeDatasourceInput = t.interface( + { + id: t.string, + meta: t.union([t.undefined, t.string]), + config: t.string, + }, + 'DatasourceInput' +); + +const DataSource = t.interface({ + uuid: t.string, + ref_source: t.union([t.undefined, t.string]), + ref: t.union([t.undefined, t.string]), + config: t.union([t.undefined, t.string]), + inputs: t.array(t.string), +}); + +export const NewRuntimeConfigurationFile = t.interface( + { + name: t.string, + description: t.string, + output: t.string, + monitoring_enabled: t.boolean, + agent_version: t.string, + data_sources: t.array(DataSource), + }, + 'ConfigurationFile' +); + +export const NewRuntimeBackupConfigurationFile = t.interface( + { + name: t.string, + description: t.string, + output: t.string, + monitoring_enabled: t.boolean, + agent_version: t.string, + flat_data_sources: t.string, + }, + 'BackupConfigurationFile' +); + +const ExistingDocument = t.partial({ + id: t.string, + shared_id: t.string, + version: t.number, + updated_at: DateFromString, + created_by: t.union([t.undefined, t.string]), + updated_on: DateFromString, + updated_by: t.union([t.undefined, t.string]), +}); + +export const RuntimeBackupConfigurationFile = t.intersection([ + NewRuntimeBackupConfigurationFile, + ExistingDocument, +]); + +export const RuntimeConfigurationFile = t.intersection([ + NewRuntimeConfigurationFile, + ExistingDocument, +]); + +export type NewBackupConfigurationFile = t.TypeOf; +export type BackupConfigurationFile = t.TypeOf; +export type ConfigurationFile = t.TypeOf; +export type NewConfigurationFile = t.TypeOf; +export type DatasourceInput = t.TypeOf; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts new file mode 100644 index 00000000000000..dd0a546d1818ac --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ConfigurationFile, + NewConfigurationFile, + DatasourceInput, + BackupConfigurationFile, +} from './adapter_types'; + +export class DefaultConfigAdapter { + public async create( + configuration: NewConfigurationFile + ): Promise<{ id: string; shared_id: string; version: number }> { + return { + id: 'fsdfsdf', + shared_id: 'wjkhefkjhfkjs', + version: 0, + }; + } + + public async get(sharedID: string, version?: number): Promise { + return {} as ConfigurationFile; + } + + public async list(sharedID: string, version?: number): Promise { + return [{} as ConfigurationFile]; + } + + public async update( + sharedID: string, + fromVersion: number, + configuration: ConfigurationFile + ): Promise<{ id: string; version: number }> { + return { + id: 'fsdfsdf', + version: 0, + }; + } + + public async delete( + sharedID: string, + version?: number + ): Promise<{ success: boolean; error?: string }> { + return { + success: true, + }; + } + + public async createBackup( + sharedID: string, + version?: number + ): Promise<{ success: boolean; id?: string; error?: string }> { + return { + success: true, + id: 'k3jh5lk3j4h5kljh43', + }; + } + + public async getBackup(sharedID: string, version?: number): Promise { + return {} as BackupConfigurationFile; + } + + /** + * Inputs sub-domain type + */ + public async getInputsById(ids: string[]): Promise { + return [{} as DatasourceInput]; + } + + public async addInputs( + sharedID: string, + version: number, + dsUUID: string, + input: DatasourceInput + ): Promise { + return 'htkjerhtkwerhtkjehr'; + } + + public async deleteInputs(inputID: string[]): Promise<{ success: boolean; error?: string }> { + return { + success: true, + }; + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts new file mode 100644 index 00000000000000..0a06c3dcc6412d --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts @@ -0,0 +1,309 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; + +export interface DatabaseAdapter { + get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise>; + create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise; + index( + user: FrameworkUser, + params: DatabaseIndexDocumentParams + ): Promise; + delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise; + deleteByQuery( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise; + mget(user: FrameworkUser, params: DatabaseMGetParams): Promise>; + bulk( + user: FrameworkUser, + params: DatabaseBulkIndexDocumentsParams + ): Promise; + search(user: FrameworkUser, params: DatabaseSearchParams): Promise>; + searchAll( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise>; + putTemplate(name: string, template: any): Promise; +} + +export interface DatabaseKbnESCluster { + callWithInternalUser(esMethod: string, options: {}): Promise; + callWithRequest(req: FrameworkRequest, esMethod: string, options: {}): Promise; +} + +export interface DatabaseKbnESPlugin { + getCluster(clusterName: string): DatabaseKbnESCluster; +} + +export interface DatabaseSearchParams extends DatabaseGenericParams { + analyzer?: string; + analyzeWildcard?: boolean; + defaultOperator?: DefaultOperator; + df?: string; + explain?: boolean; + storedFields?: DatabaseNameList; + docvalueFields?: DatabaseNameList; + fielddataFields?: DatabaseNameList; + from?: number; + ignoreUnavailable?: boolean; + allowNoIndices?: boolean; + expandWildcards?: ExpandWildcards; + lenient?: boolean; + lowercaseExpandedTerms?: boolean; + preference?: string; + q?: string; + routing?: DatabaseNameList; + scroll?: string; + searchType?: 'query_then_fetch' | 'dfs_query_then_fetch'; + size?: number; + sort?: DatabaseNameList; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + terminateAfter?: number; + stats?: DatabaseNameList; + suggestField?: string; + suggestMode?: 'missing' | 'popular' | 'always'; + suggestSize?: number; + suggestText?: string; + timeout?: string; + trackScores?: boolean; + version?: boolean; + requestCache?: boolean; + index?: DatabaseNameList; + type?: DatabaseNameList; +} + +export interface DatabaseSearchResponse { + took: number; + timed_out: boolean; + _scroll_id?: string; + _shards: DatabaseShardsResponse; + hits: { + total: number; + max_score: number; + hits: Array<{ + _index: string; + _id: string; + _score: number; + _source: T; + _seq_no?: number; + _primary_term?: number; + _explanation?: DatabaseExplanation; + fields?: any; + highlight?: any; + inner_hits?: any; + sort?: string[]; + }>; + }; + aggregations?: any; +} + +export interface DatabaseExplanation { + value: number; + description: string; + details: DatabaseExplanation[]; +} + +export interface DatabaseShardsResponse { + total: number; + successful: number; + failed: number; + skipped: number; +} + +export interface DatabaseGetDocumentResponse { + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + found: boolean; + _source: Source; +} + +export interface DatabaseBulkResponse { + took: number; + errors: boolean; + items: Array< + DatabaseDeleteDocumentResponse | DatabaseIndexDocumentResponse | DatabaseUpdateDocumentResponse + >; +} + +export interface DatabaseBulkIndexDocumentsParams extends DatabaseGenericParams { + waitForActiveShards?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + fields?: DatabaseNameList; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + pipeline?: string; + index?: string; +} + +export interface DatabaseMGetParams extends DatabaseGenericParams { + storedFields?: DatabaseNameList; + preference?: string; + realtime?: boolean; + refresh?: boolean; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + index: string; +} + +export interface DatabaseMGetResponse { + docs?: Array>; +} + +export interface DatabasePutTemplateParams extends DatabaseGenericParams { + name: string; + body: any; +} + +export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + index: string; + id: string; +} + +export interface DatabaseIndexDocumentResponse { + found: boolean; + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} + +export interface DatabaseUpdateDocumentResponse { + found: boolean; + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} + +export interface DatabaseDeleteDocumentResponse { + found: boolean; + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} + +export interface DatabaseIndexDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + opType?: 'index' | 'create'; + parent?: string; + refresh?: string; + routing?: string; + timeout?: string; + timestamp?: Date | number; + ttl?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + pipeline?: string; + id?: string; + index: string; + body: T; +} + +export interface DatabaseGetResponse { + found: boolean; + _source: T; +} +export interface DatabaseCreateDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + timestamp?: Date | number; + ttl?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + pipeline?: string; + id?: string; + index: string; +} + +export interface DatabaseCreateDocumentResponse { + created: boolean; + result: string; +} + +export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + index: string; + id: string; +} + +export interface DatabaseGetParams extends DatabaseGenericParams { + storedFields?: DatabaseNameList; + parent?: string; + preference?: string; + realtime?: boolean; + refresh?: boolean; + routing?: string; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + ifSeqNo?: number; + ifPrimaryTerm?: number; + id: string; + index: string; +} + +export type DatabaseNameList = string | string[] | boolean; +export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | ''; +export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all'; +export type DefaultOperator = 'AND' | 'OR'; +export type DatabaseConflicts = 'abort' | 'proceed'; + +export interface DatabaseGenericParams { + requestTimeout?: number; + maxRetries?: number; + method?: string; + body?: any; + ignore?: number | number[]; + filterPath?: string | string[]; +} + +export interface DatabaseDeleteDocumentResponse { + found: boolean; + _index: string; + _type: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts new file mode 100644 index 00000000000000..0538dee64be4be --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkUser } from '../framework/adapter_types'; +import { internalAuthData } from '../framework/adapter_types'; +import { + DatabaseAdapter, + DatabaseBulkIndexDocumentsParams, + DatabaseCreateDocumentParams, + DatabaseCreateDocumentResponse, + DatabaseDeleteDocumentParams, + DatabaseDeleteDocumentResponse, + DatabaseGetDocumentResponse, + DatabaseGetParams, + DatabaseIndexDocumentParams, + DatabaseKbnESCluster, + DatabaseKbnESPlugin, + DatabaseMGetParams, + DatabaseMGetResponse, + DatabaseSearchParams, + DatabaseSearchResponse, +} from './adapter_types'; + +export class KibanaDatabaseAdapter implements DatabaseAdapter { + private es: DatabaseKbnESCluster; + + constructor(kbnElasticSearch: DatabaseKbnESPlugin) { + this.es = kbnElasticSearch.getCluster('admin'); + } + + public async get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise> { + const result = await this.callWithUser(user, 'get', params); + return result; + // todo + } + + public async mget( + user: FrameworkUser, + params: DatabaseMGetParams + ): Promise> { + const result = await this.callWithUser(user, 'mget', params); + return result; + // todo + } + + public async bulk(user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams): Promise { + const result = await this.callWithUser(user, 'bulk', params); + return result; + } + + public async create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise { + const result = await this.callWithUser(user, 'create', params); + return result; + } + public async index(user: FrameworkUser, params: DatabaseIndexDocumentParams): Promise { + const result = await this.callWithUser(user, 'index', params); + return result; + } + public async delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise { + const result = await this.callWithUser(user, 'delete', params); + return result; + } + + public async deleteByQuery( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise { + const result = await this.callWithUser(user, 'deleteByQuery', params); + return result; + } + + public async search( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise> { + const result = await this.callWithUser(user, 'search', params); + return result; + } + + public async searchAll( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise> { + const result = await this.callWithUser(user, 'search', { + scroll: '1m', + ...params, + body: { + size: 1000, + ...params.body, + }, + }); + return result; + } + + public async putTemplate(name: string, template: any): Promise { + const result = await this.callWithUser({ kind: 'internal' }, 'indices.putTemplate', { + name, + body: template, + }); + + return result; + } + + private callWithUser(user: FrameworkUser, esMethod: string, options: any = {}): any { + if (user.kind === 'authenticated') { + return this.es.callWithRequest( + { + headers: user[internalAuthData], + } as any, + esMethod, + options + ); + } else if (user.kind === 'internal') { + return this.es.callWithInternalUser(esMethod, options); + } else { + throw new Error('Invalid user type'); + } + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts new file mode 100644 index 00000000000000..4325e857cce00f --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { Lifecycle, ResponseToolkit } from 'hapi'; +import * as t from 'io-ts'; +import { LICENSES } from '../../../../common/constants/security'; + +export const internalAuthData = Symbol('internalAuthData'); +export const internalUser: FrameworkInternalUser = { + kind: 'internal', +}; + +export interface XpackInfo { + license: { + getType: () => typeof LICENSES[number]; + /** Is the license expired */ + isActive: () => boolean; + getExpiryDateInMillis: () => number; + }; + feature: (pluginId: string) => any; + isAvailable: () => boolean; +} + +export interface BackendFrameworkAdapter { + internalUser: FrameworkInternalUser; + info: null | FrameworkInfo; + log(text: string): void; + on(event: 'xpack.status.green' | 'elasticsearch.status.green', cb: () => void): void; + getSetting(settingPath: string): any; + getUser(request: KibanaServerRequest): Promise; + exposeMethod(name: string, method: () => any): void; +} + +export interface KibanaLegacyServer { + plugins: { + xpack_main: { + status: { + on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; + }; + info: XpackInfo; + }; + kibana: { + status: { + plugin: { + version: string; + }; + }; + }; + security: { + getUser: (request: KibanaServerRequest) => any; + }; + elasticsearch: { + status: { + on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; + }; + getCluster: () => any; + }; + ingest: {}; + }; + expose: (name: string, value: any) => void; + config: () => any; + route: (routeConfig: any) => void; + log: (message: string) => void; +} + +export const RuntimeFrameworkInfo = t.interface( + { + kibana: t.type({ + version: t.string, + }), + license: t.type({ + type: t.union( + ['oss', 'trial', 'standard', 'basic', 'gold', 'platinum'].map(s => t.literal(s)) + ), + expired: t.boolean, + expiry_date_in_millis: t.number, + }), + security: t.type({ + enabled: t.boolean, + available: t.boolean, + }), + watcher: t.type({ + enabled: t.boolean, + available: t.boolean, + }), + }, + 'FrameworkInfo' +); +export interface FrameworkInfo extends t.TypeOf {} + +export const RuntimeKibanaServerRequest = t.interface( + { + params: t.object, + payload: t.object, + query: t.object, + headers: t.type({ + authorization: t.union([t.string, t.null]), + }), + info: t.type({ + remoteAddress: t.string, + }), + }, + 'KibanaServerRequest' +); +export interface KibanaServerRequest extends t.TypeOf {} + +export const RuntimeKibanaUser = t.interface( + { + username: t.string, + roles: t.array(t.string), + full_name: t.union([t.null, t.string]), + email: t.union([t.null, t.string]), + enabled: t.boolean, + }, + 'KibanaUser' +); +export interface KibanaUser extends t.TypeOf {} + +export interface FrameworkAuthenticatedUser { + kind: 'authenticated'; + [internalAuthData]: AuthDataType; + username: string; + roles: string[]; + full_name: string | null; + email: string | null; + enabled: boolean; +} + +export interface FrameworkUnAuthenticatedUser { + kind: 'unauthenticated'; +} + +export interface FrameworkInternalUser { + kind: 'internal'; +} + +export type FrameworkUser = + | FrameworkAuthenticatedUser + | FrameworkUnAuthenticatedUser + | FrameworkInternalUser; +export interface FrameworkRequest< + KibanaServerRequestGenaric extends Partial = any +> { + user: FrameworkUser; + headers: KibanaServerRequestGenaric['headers']; + info: KibanaServerRequest['info']; + payload: KibanaServerRequestGenaric['payload']; + params: KibanaServerRequestGenaric['params']; + query: KibanaServerRequestGenaric['query']; +} + +export interface FrameworkRouteOptions< + RouteRequest extends FrameworkRequest = FrameworkRequest, + RouteResponse extends FrameworkResponse = any +> { + path: string; + method: string | string[]; + vhost?: string; + licenseRequired?: string[]; + requiredRoles?: string[]; + handler: FrameworkRouteHandler; + config?: {}; +} + +export type FrameworkRouteHandler< + RouteRequest extends KibanaServerRequest, + RouteResponse extends FrameworkResponse +> = (request: FrameworkRequest, h: ResponseToolkit) => Promise; + +export type FrameworkResponse = Lifecycle.ReturnValue; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 00000000000000..e2c2ba68e9b55e --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PathReporter } from 'io-ts/lib/PathReporter'; +import { get } from 'lodash'; +// @ts-ignore +import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status'; +import { + BackendFrameworkAdapter, + FrameworkInfo, + internalUser, + KibanaLegacyServer, + KibanaServerRequest, + KibanaUser, + RuntimeFrameworkInfo, + RuntimeKibanaUser, + XpackInfo, +} from './adapter_types'; + +export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { + public readonly internalUser = internalUser; + public info: null | FrameworkInfo = null; + + constructor( + private readonly PLUGIN_ID: string, + private readonly server: KibanaLegacyServer, + private readonly CONFIG_PREFIX?: string + ) { + const xpackMainPlugin = this.server.plugins.xpack_main; + const thisPlugin = this.server.plugins.ingest; + + mirrorPluginStatus(xpackMainPlugin, thisPlugin); + + xpackMainPlugin.status.on('green', () => { + this.xpackInfoWasUpdatedHandler(xpackMainPlugin.info); + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info + .feature(this.PLUGIN_ID) + .registerLicenseCheckResultsGenerator(this.xpackInfoWasUpdatedHandler); + }); + } + + public on(event: 'xpack.status.green' | 'elasticsearch.status.green', cb: () => void) { + switch (event) { + case 'xpack.status.green': + this.server.plugins.xpack_main.status.on('green', cb); + case 'elasticsearch.status.green': + this.server.plugins.elasticsearch.status.on('green', cb); + } + } + + public getSetting(settingPath: string) { + return this.server.config().get(settingPath); + } + + public log(text: string) { + this.server.log(text); + } + + public exposeMethod(name: string, method: () => any) { + this.server.expose(name, method); + } + + public async getUser(request: KibanaServerRequest): Promise { + let user; + try { + user = await this.server.plugins.security.getUser(request); + } catch (e) { + return null; + } + if (user === null) { + return null; + } + const assertKibanaUser = RuntimeKibanaUser.decode(user); + if (assertKibanaUser.isLeft()) { + throw new Error( + `Error parsing user info in ${this.PLUGIN_ID}, ${ + PathReporter.report(assertKibanaUser)[0] + }` + ); + } + + return user; + } + + private xpackInfoWasUpdatedHandler = (xpackInfo: XpackInfo) => { + let xpackInfoUnpacked: FrameworkInfo; + + // If, for some reason, we cannot get the license information + // from Elasticsearch, assume worst case and disable + if (!xpackInfo || !xpackInfo.isAvailable()) { + this.info = null; + return; + } + + try { + xpackInfoUnpacked = { + kibana: { + version: get(this.server, 'plugins.kibana.status.plugin.version', 'unknown'), + }, + license: { + type: xpackInfo.license.getType(), + expired: !xpackInfo.license.isActive(), + expiry_date_in_millis: + xpackInfo.license.getExpiryDateInMillis() !== undefined + ? xpackInfo.license.getExpiryDateInMillis() + : -1, + }, + security: { + enabled: !!xpackInfo.feature('security') && xpackInfo.feature('security').isEnabled(), + available: !!xpackInfo.feature('security'), + }, + watcher: { + enabled: !!xpackInfo.feature('watcher') && xpackInfo.feature('watcher').isEnabled(), + available: !!xpackInfo.feature('watcher'), + }, + }; + } catch (e) { + this.server.log(`Error accessing required xPackInfo in ${this.PLUGIN_ID} Kibana adapter`); + throw e; + } + + const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked); + if (assertData.isLeft()) { + throw new Error( + `Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}` + ); + } + this.info = xpackInfoUnpacked; + + return { + security: xpackInfoUnpacked.security, + settings: this.getSetting(this.CONFIG_PREFIX || this.PLUGIN_ID), + }; + }; +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts new file mode 100644 index 00000000000000..aeab928b8f3b37 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { camelCase } from 'lodash'; +import { PLUGIN } from '../../../common/constants'; +import { CONFIG_PREFIX } from '../../../common/constants/plugin'; + +import { DatabaseKbnESPlugin } from '../adapters/database/adapter_types'; +import { KibanaDatabaseAdapter } from '../adapters/database/kibana_database_adapter'; +import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; +import { KibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; + +import { ServerLibs } from '../types'; +import { BackendFrameworkLib } from './../framework'; + +export function compose(server: KibanaLegacyServer): ServerLibs { + const framework = new BackendFrameworkLib( + new KibanaBackendFrameworkAdapter(camelCase(PLUGIN.ID), server, CONFIG_PREFIX) + ); + const database = new KibanaDatabaseAdapter(server.plugins.elasticsearch as DatabaseKbnESPlugin); + + const libs: ServerLibs = { + framework, + database, + }; + + return libs; +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts new file mode 100644 index 00000000000000..b5fe6195fc7c70 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; +import { MemoryConfigurationBlockAdapter } from '../adapters/configuration_blocks/memory_tags_adapter'; +import { HapiBackendFrameworkAdapter } from '../adapters/framework/hapi_framework_adapter'; +import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; +import { BeatEventsLib } from '../beat_events'; +import { CMBeatsDomain } from '../beats'; +import { ConfigurationBlocksLib } from '../configuration_blocks'; +import { BackendFrameworkLib } from '../framework'; +import { CMTagsDomain } from '../tags'; +import { CMTokensDomain } from '../tokens'; +import { CMServerLibs } from '../types'; + +export function compose(server: any): CMServerLibs { + const framework = new BackendFrameworkLib(new HapiBackendFrameworkAdapter(undefined, server)); + + const beatsAdapter = new MemoryBeatsAdapter(server.beatsDB || []); + const configAdapter = new MemoryConfigurationBlockAdapter(server.configsDB || []); + const tags = new CMTagsDomain( + new MemoryTagsAdapter(server.tagsDB || []), + configAdapter, + beatsAdapter + ); + const configurationBlocks = new ConfigurationBlocksLib(configAdapter, tags); + const tokens = new CMTokensDomain(new MemoryTokensAdapter(server.tokensDB || []), { + framework, + }); + const beats = new CMBeatsDomain(beatsAdapter, { + tags, + tokens, + framework, + }); + const beatEvents = new BeatEventsLib({} as any, beats); + + const libs: CMServerLibs = { + beatEvents, + framework, + beats, + tags, + tokens, + configurationBlocks, + }; + + return libs; +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts new file mode 100644 index 00000000000000..4d0e42b2f327b7 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export class ConfigurationLib { + public async rollForward( + sharedID: string, + version?: number + ): Promise<{ id: string; version: number }> { + return { + id: 'fsdfsdf', + version: 0, + }; + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts new file mode 100644 index 00000000000000..719902eb6f411c --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BackendFrameworkAdapter, KibanaServerRequest } from './adapters/framework/adapter_types'; + +export class BackendFrameworkLib { + /** + * Expired `null` happens when we have no xpack info + */ + public license = { + type: this.adapter.info ? this.adapter.info.license.type : 'unknown', + expired: this.adapter.info ? this.adapter.info.license.expired : null, + }; + public securityIsEnabled = this.adapter.info ? this.adapter.info.security.enabled : false; + public log = this.adapter.log; + public on = this.adapter.on.bind(this.adapter); + public internalUser = this.adapter.internalUser; + constructor(private readonly adapter: BackendFrameworkAdapter) {} + + public getCurrentUser(request: KibanaServerRequest) { + return this.adapter.getUser(request); + } + public getSetting(setting: 'defaultUserRoles'): string[]; + public getSetting(setting: 'defaultUserRoles') { + return this.adapter.getSetting(`xpack.ingest-do-not-disable.${setting}`); + } + public exposeMethod(name: string, method: () => any) { + return this.adapter.exposeMethod(name, method); + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/types.ts b/x-pack/legacy/plugins/ingest/server/libs/types.ts new file mode 100644 index 00000000000000..a1d5f0b9b3dbd4 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DatabaseAdapter } from './adapters/database/adapter_types'; +import { FrameworkUser } from './adapters/framework/adapter_types'; +import { BackendFrameworkLib } from './framework'; +export interface ServerLibs { + framework: BackendFrameworkLib; + database?: DatabaseAdapter; +} diff --git a/x-pack/legacy/plugins/ingest/tsconfig.json b/x-pack/legacy/plugins/ingest/tsconfig.json new file mode 100644 index 00000000000000..67fefc7286ca4f --- /dev/null +++ b/x-pack/legacy/plugins/ingest/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["**/node_modules/**"], + "paths": { + "react": ["../../../node_modules/@types/react"] + } +} From 684936c48f30ac26695734d4fed14d698a5855f9 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 22 Jul 2019 17:16:56 -0400 Subject: [PATCH 07/27] fix typo --- x-pack/legacy/plugins/fleet/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/fleet/tsconfig.json b/x-pack/legacy/plugins/fleet/tsconfig.json index 3989853fda3149..7ade047bad32e5 100644 --- a/x-pack/legacy/plugins/fleet/tsconfig.json +++ b/x-pack/legacy/plugins/fleet/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../Ã¥tsconfig.json", + "extends": "../../../tsconfig.json", "exclude": ["**/node_modules/**"], "paths": { "react": ["../../../node_modules/@types/react"] From dd83486574215c82e8686d8f32675e781e450f75 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 22 Jul 2019 21:29:11 -0400 Subject: [PATCH 08/27] update paths for legacy API. cleanup linting in vscode --- .../public/components/layouts/primary.tsx | 23 ++----------------- x-pack/legacy/plugins/fleet/tsconfig.json | 2 +- x-pack/legacy/plugins/ingest/tsconfig.json | 4 ++-- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/x-pack/legacy/plugins/fleet/public/components/layouts/primary.tsx b/x-pack/legacy/plugins/fleet/public/components/layouts/primary.tsx index f7106668f7b128..635ee687ba63d5 100644 --- a/x-pack/legacy/plugins/fleet/public/components/layouts/primary.tsx +++ b/x-pack/legacy/plugins/fleet/public/components/layouts/primary.tsx @@ -5,9 +5,6 @@ */ import { - EuiHeader, - EuiHeaderBreadcrumbs, - EuiHeaderSection, EuiPage, EuiPageBody, EuiPageContent, @@ -17,10 +14,9 @@ import { EuiTitle, } from '@elastic/eui'; import React, { Component, ReactNode } from 'react'; -import styled from 'styled-components'; -import { BreadcrumbConsumer } from '../navigation/breadcrumb'; -type RenderCallback = ((component: () => JSX.Element) => void); +type RenderCallback = (component: () => JSX.Element) => void; + interface PrimaryLayoutProps { title: string | React.ReactNode; actionSection?: React.ReactNode; @@ -36,17 +32,6 @@ export class PrimaryLayout extends Component { const children: (callback: RenderCallback) => void | ReactNode = this.props.children as any; return ( - {!this.props.hideBreadcrumbs && ( - - {({ breadcrumbs }) => ( - - - - - - )} - - )} @@ -77,7 +62,3 @@ export class PrimaryLayout extends Component { this.forceUpdate(); }; } - -const HeaderWrapper = styled(EuiHeader)` - height: 29px; -`; diff --git a/x-pack/legacy/plugins/fleet/tsconfig.json b/x-pack/legacy/plugins/fleet/tsconfig.json index 7ade047bad32e5..d7e27bbef41227 100644 --- a/x-pack/legacy/plugins/fleet/tsconfig.json +++ b/x-pack/legacy/plugins/fleet/tsconfig.json @@ -2,6 +2,6 @@ "extends": "../../../tsconfig.json", "exclude": ["**/node_modules/**"], "paths": { - "react": ["../../../node_modules/@types/react"] + "react": ["../../../../node_modules/@types/react"] } } diff --git a/x-pack/legacy/plugins/ingest/tsconfig.json b/x-pack/legacy/plugins/ingest/tsconfig.json index 67fefc7286ca4f..d7e27bbef41227 100644 --- a/x-pack/legacy/plugins/ingest/tsconfig.json +++ b/x-pack/legacy/plugins/ingest/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.json", + "extends": "../../../tsconfig.json", "exclude": ["**/node_modules/**"], "paths": { - "react": ["../../../node_modules/@types/react"] + "react": ["../../../../node_modules/@types/react"] } } From af602e280243795c6558ce9c48c24315f589c643 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 22 Jul 2019 21:35:35 -0400 Subject: [PATCH 09/27] remove CRUFT --- x-pack/legacy/plugins/ingest/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/legacy/plugins/ingest/index.ts b/x-pack/legacy/plugins/ingest/index.ts index cc6f7049d25399..e5c8c856d86a09 100644 --- a/x-pack/legacy/plugins/ingest/index.ts +++ b/x-pack/legacy/plugins/ingest/index.ts @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n/src'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; import { initServerWithKibana } from './server/kibana.index'; -// export const config = Joi.object({ enabled: Joi.boolean().default(true), From 701ffdc5132c74a9be6bd98094d66bce7b4eda64 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 22 Jul 2019 21:35:53 -0400 Subject: [PATCH 10/27] remove more CRUFT --- x-pack/legacy/plugins/ingest/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/legacy/plugins/ingest/index.ts b/x-pack/legacy/plugins/ingest/index.ts index e5c8c856d86a09..5f6196b8c1da42 100644 --- a/x-pack/legacy/plugins/ingest/index.ts +++ b/x-pack/legacy/plugins/ingest/index.ts @@ -5,7 +5,6 @@ */ import * as Joi from 'joi'; import { resolve } from 'path'; -import { i18n } from '@kbn/i18n/src'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; import { initServerWithKibana } from './server/kibana.index'; From 84757fb4732c7628aa9d8b447dae43845c0c501e Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 24 Jul 2019 21:01:56 -0400 Subject: [PATCH 11/27] [Ingest] cleanup names and files/paths to confirm to a standard format (#41773) * [Maps] Rename modules for clarity (#41608) * [Docs] Add simple phrase highlighting to Logs UI (#41610) * [Docs] Add simple phrase highlighting to Logs UI * Fix heading level * [DOCS] Timelion cleanup (#41381) * [Canvas] Removes doc links from backticks. (#41601) * Upgrade EUI to 12.4.0 (#41577) * eui 12.4.0 * styled-components ts conflict * update combobox test service to always open on open call * Revert "update combobox test service to always open on open call" This reverts commit 43074e60061afcaf5c87e56ae5782aed2a4b68dc. * scroll combobox into view * scroll before filter * Move CSP config default values to csp module (#41676) This gives us a little more control over the default values of these configurations to help ensure (though not guarantee) that any changes here can be audited by the security team. * Remove notifications plugin (#41674) The notifications functionality has been replaced by the features of the actions plugin. This notifications plugin was never actually used by end-user facing features of Kibana. * [Logs UI] Make column configurations reorderable (#41035) * [Logs UI] Make column configurations reorderable * Improve typing aand memoize callback * Guard against index bounds and rename reorderLogColumns * Fix useCallback memoization * Add functional test for reordering log columns * Use browser.keys instead of Key in functional test * [Maps] populate _id in tooltip (#41684) * [ML] Data Frames - search bar on list page (#41415) * add search and filter to df list table * add mode filter to list table * adds id + description search * type fix * ensure search syntax is valid * ensure types are correct * retain filter on refresh * fix progress bar jump * [DOCS] Changed Visual Builder to TSVB (#39539) * [DOCS] Changed Visual Builder to TSVB * Reorg of interface changes * Content reorg * Updated image * Added task content * Content conslidation * Final clean up * Comments from Gail * [DOCS] Adds missing Timelion link (#41709) * [Infra UI] Fix section mapping bug in node detail page (#41641) * [Infra UI] Fix section mapping bug in node detail page * Fixing filter to match TSVB * Adding an enum for the InfraMetricsQueryType * removing unnecessary change * Change id to InfraMetric to make less error prone * Fixing type in Metrics Explorer * [Infra UI] Add UI to customize Metrics Explorer chart style (#41022) * Add UI to customize Metrics Explorer chart style * Re-order chart options form * Adding chart options to TSVB link * Rename line series to series chart * Fixing chart context menu tests * Adding test for calculate domain * Ensure caclulateDomain returns numbers * fixing typo * Bump backport to 4.6.1 (#41720) * hide top nav menu in full screen mode of maps and dashboard (#41672) * hide top nav menu in full screen mode of maps and dashboard * Fixed dashboard full screen mode and added full screen mode test to maps * improve typing (#41563) * [Code] test colorize before load a file (#41645) * [Code] handle status when repo is not exists (#41643) fix can't switch to head on root path * [Code] implement filtree api by using isogit (#41558) * remove obsolete http secutiry settings (#41569) * core doesn't use Record for public API (#41448) * core contracts don't use unknown to support type assignment limitations of https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#-k-string-unknown--is-no-longer-a-wildcard-assignment-target * regenereate docs * remove type over-write * Narrow type of PluginDeps to an object (#40846) * Narrow type of PluginDeps to an object * re-generate docs * [APM] Fix "Show trace logs" link (#41570) * [APM] Fix "Show trace logs" link * Add type for infra link items; escape url.domain param for uptime link * Comment out flakey test steps (#41743) This comments out the problematic portions of the functional test, which caused #41717 to occur. * [ML] Fixes model memory limit for metricbeat system module jobs (#41747) * [i18n] fix i18nrc parsing (#41741) * check for translations in file * update template * Add ownership of new platform security plugin to security team. (#41748) * Clean up top nav \ search bar \ query bar directives (#41636) * Move timepicker (to be deprecated) into old kbn_top_nav * Deleted search-bar and query-bar directives! * moved search bar to kibana_react (it's a generic react component, not a service) * translations * Moved superDatePicker directive to kbn_top_nav (to be deprecated) Deleted unused react_component directives call-out and tool-bar-search-box * TS test fix * Delete relative options * [ML] Use NavigationMenu without angularjs wrapper. (#41343) Follow up to #40830 and #41054 in preparation for single metric viewer migration. The previous PR introduced the navigation menu as a React component. This PR moves dependencies down from the angularjs wrapper directive directly to the React component so the component can also be used stand-alone without the angularjs wrapper. For simple angularjs based HTML templates this stand-alone usage is also part of this PR. Unfortunately the PR turned out to be quite big due to: Most page react components had to be wrapped in another to allow the addition of thus leading to large diffs for the components. All component code inside the was not touched though. * [telemetry] Analytics Package (#41113) * kbn-analytics * kbn-analytics * expose provider * add logger * performance and stats reporters * finalize ui stats metric * functional tests * remove readme file for now * update readme * add types file into to tsconfigs * Update packages/kbn-analytics/src/report.ts Co-Authored-By: Josh Dover * fix typechecks * use enum instead of strings for metric types * getUiStatsReporter -> createUiStatsReporter * fix special typo in README * remove unused stop method * fix tests * default debug to false * use chrome.getInjected * add METRIC_TYPE to jest module mocks * mock create fn * handle enabled:false * init ui_metric in test setup env * regenerator runtime * transform-regenerator * update lock file * update babel configs * runtime dep * add regenerator * babel configs * use env-preset * merge conflicts * fix workpad telemetry tests * regeneratorRuntime attempt to fix number 30000 * env targets * remove module config * try again * try without regenerator * use kbn/babel-preset/webpack_preset only * runtime * just use typescript * update tsconfig * Caches trackers by app value for infra useTrackMetric hook * replace all occurences of placeholder in drilldown url template (#41673) * cleanup names and files/paths to confirm to a standard format * tack down WIP code * remove things not or not yet needed * Added Flexmonster Pivot Table to known plugins list (#41655) * Add Flexmonster Pivot Table to known plugins list * Update docs/plugins/known-plugins.asciidoc Co-Authored-By: Larry Gregory * Fix typo (#41705) * turn on filtering tests (#41202) * turn on filtering tests * run x-pack-firefoxSmoke ciGroup 40 times, run dashboard tests 20 times per job * Revert "run x-pack-firefoxSmoke ciGroup 40 times, run dashboard tests 20 times per job" This reverts commit 5ef02cc53ba5085c3f74431cb6ef20be2d876cde. * GoodBye Notifier (#41663) * Begin notifier removal * Remove remaining notifier traces * Remove dead translations * Remove Angular from config listener * Import angular-sanitize explicitly in map * Revert "lock es snapshot to avoid failing CI" (#41539) This reverts commit 4eca0f3383f5f7746f375c4f1559838a3bedb9df. * fix more types, define SO * [SIEM] - Fix Jest test errors and warnings (#41712) * Fixes #41787 (#41791) * [DOCS] Puts Spaces content on single page (#41536) * [DOCS] Puts Spaces content on single page * [DOCS] Incorporates review comments * [DOCS] Incorporated review comments * [SIEM] - Hosts and Network Tables from LoadMore to Paginated (#41532) * [DOCS] Updates Console doc (#41371) * [DOCS] Updates Console doc * [DOCS] Incorporates comments on Console docs * [DOCS] Updated Console images * Fixed unused variables. Added a few methods to the SO adapter Co-authored-by: Nicolas Chaulet * fix type * Revert "Merge branch 'master' of github.com:elastic/kibana into feature-fleet" This reverts commit 997490feadce9e246e5e3c2123570523e30455f8, reversing changes made to db5fc8fa3feac6f67a36cc33a3aef09b291ae789. * removed blank test file * Fix file path * add i18n --- x-pack/.i18nrc.json | 2 + .../plugins/fleet/common/types/helpers.ts | 2 + x-pack/legacy/plugins/fleet/index.ts | 2 +- .../hooks/with_kuery_autocompletion.tsx | 2 +- .../fleet/public/hooks/with_url_state.tsx | 3 +- .../plugins/fleet/public/lib/elasticsearch.ts | 2 +- x-pack/legacy/plugins/fleet/public/routes.tsx | 16 +-- .../plugins/ingest/server/kibana.index.ts | 11 +- .../adapters/configurations/adapter_types.ts | 8 +- .../libs/adapters/configurations/default.ts | 98 +++++++++++-- .../adapter_types.ts | 36 +---- .../default.ts} | 3 +- .../libs/adapters/framework/adapter_types.ts | 69 ++++----- ...kibana_framework_adapter.ts => default.ts} | 20 ++- .../adapters/so_database/adapter_types.ts | 5 + .../libs/adapters/so_database/default.ts | 134 ++++++++++++++++++ .../ingest/server/libs/compose/kibana.ts | 22 +-- .../ingest/server/libs/compose/testing.ts | 51 ------- .../ingest/server/libs/configuration.ts | 9 +- .../plugins/ingest/server/libs/framework.ts | 5 +- .../plugins/ingest/server/libs/types.ts | 7 +- 21 files changed, 304 insertions(+), 203 deletions(-) rename x-pack/legacy/plugins/ingest/server/libs/adapters/{database => es_database}/adapter_types.ts (84%) rename x-pack/legacy/plugins/ingest/server/libs/adapters/{database/kibana_database_adapter.ts => es_database/default.ts} (97%) rename x-pack/legacy/plugins/ingest/server/libs/adapters/framework/{kibana_framework_adapter.ts => default.ts} (87%) create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/adapter_types.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts delete mode 100644 x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index f8c37c792d51cc..945df1747c5b45 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -10,11 +10,13 @@ "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.fileUpload": "legacy/plugins/file_upload", + "xpack.fleet": "legacy/plugins/fleet", "xpack.graph": "legacy/plugins/graph", "xpack.grokDebugger": "legacy/plugins/grokdebugger", "xpack.idxMgmt": "legacy/plugins/index_management", "xpack.indexLifecycleMgmt": "legacy/plugins/index_lifecycle_management", "xpack.infra": "legacy/plugins/infra", + "xpack.ingest": "legacy/plugins/ingest", "xpack.kueryAutocomplete": "legacy/plugins/kuery_autocomplete", "xpack.licenseMgmt": "legacy/plugins/license_management", "xpack.maps": "legacy/plugins/maps", diff --git a/x-pack/legacy/plugins/fleet/common/types/helpers.ts b/x-pack/legacy/plugins/fleet/common/types/helpers.ts index 4258461310e164..f9d648fd61bb0f 100644 --- a/x-pack/legacy/plugins/fleet/common/types/helpers.ts +++ b/x-pack/legacy/plugins/fleet/common/types/helpers.ts @@ -5,3 +5,5 @@ */ export type FlatObject = { [Key in keyof T]: string }; +export type RendererResult = React.ReactElement | null; +export type RendererFunction = (args: RenderArgs) => Result; diff --git a/x-pack/legacy/plugins/fleet/index.ts b/x-pack/legacy/plugins/fleet/index.ts index f129e9a45457c8..d4b48a9e23c58a 100644 --- a/x-pack/legacy/plugins/fleet/index.ts +++ b/x-pack/legacy/plugins/fleet/index.ts @@ -5,7 +5,7 @@ */ import * as Joi from 'joi'; import { resolve } from 'path'; -import { i18n } from '@kbn/i18n/src'; +import { i18n } from '@kbn/i18n'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; import { initServerWithKibana } from './server/kibana.index'; diff --git a/x-pack/legacy/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx index 4fbf65653a4042..2bfc1a1f4db192 100644 --- a/x-pack/legacy/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/fleet/public/hooks/with_kuery_autocompletion.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; import { FrontendLibs } from '../lib/types'; -import { RendererFunction } from '../utils/typed_react'; +import { RendererFunction } from '../../common/types/helpers'; interface WithKueryAutocompletionLifecycleProps { libs: FrontendLibs; diff --git a/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx b/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx index 0802b4d8ea7e80..2db45f50b2c6cb 100644 --- a/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx +++ b/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx @@ -7,8 +7,7 @@ import { parse, stringify } from 'querystring'; import React from 'react'; import { withRouter } from 'react-router-dom'; -import { FlatObject } from '../frontend_types'; -import { RendererFunction } from '../utils/typed_react'; +import { FlatObject, RendererFunction } from '../../common/types/helpers'; type StateCallback = (previousState: T) => T; diff --git a/x-pack/legacy/plugins/fleet/public/lib/elasticsearch.ts b/x-pack/legacy/plugins/fleet/public/lib/elasticsearch.ts index 7ea32a2eb9467e..dee7f579ed59bb 100644 --- a/x-pack/legacy/plugins/fleet/public/lib/elasticsearch.ts +++ b/x-pack/legacy/plugins/fleet/public/lib/elasticsearch.ts @@ -48,7 +48,7 @@ export class ElasticsearchLib { }); } - return hiddenFieldsCheck.reduce((isvalid, field) => { + return hiddenFieldsCheck.reduce((isvalid: boolean, field) => { if (!isvalid) { return false; } diff --git a/x-pack/legacy/plugins/fleet/public/routes.tsx b/x-pack/legacy/plugins/fleet/public/routes.tsx index 8e65f048bfa84b..82fb275784235c 100644 --- a/x-pack/legacy/plugins/fleet/public/routes.tsx +++ b/x-pack/legacy/plugins/fleet/public/routes.tsx @@ -7,7 +7,6 @@ import { get } from 'lodash'; import React, { Component } from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import { REQUIRED_ROLES } from '../common/constants/security'; import { Loading } from './components/loading'; import { ChildRoutes } from './components/navigation/child_routes'; import { URLStateProps, WithURLState } from './hooks/with_url_state'; @@ -59,19 +58,6 @@ export class AppRoutes extends Component { /> )} - {/* Make sure the user has correct permissions */} - {!this.props.libs.framework.currentUserHasOneOfRoles( - REQUIRED_ROLES.concat(this.props.libs.framework.info.settings.defaultUserRoles) - ) && ( - - !props.location.pathname.includes('/error') ? ( - - ) : null - } - /> - )} - {/* This app does not make use of a homepage. The mainpage is overview/enrolled_agents */} } /> @@ -80,7 +66,7 @@ export class AppRoutes extends Component { {(URLProps: URLStateProps) => ( { - hapiServer.route({ - method: 'GET', - path: '/api/fleet/load', - handler: (request: any, h: any) => { - return 'Hello World!'; - }, - }); + const libs = compose(hapiServer); + libs.framework.log('Ingest is composed -- debug message'); }; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts index b4fb6ccbfb851f..c29b4c142c8180 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts @@ -5,7 +5,6 @@ */ import * as t from 'io-ts'; -import { DateFromString } from '../../../../common/types/io_ts'; export const RuntimeDatasourceInput = t.interface( { @@ -48,13 +47,14 @@ export const NewRuntimeBackupConfigurationFile = t.interface( 'BackupConfigurationFile' ); -const ExistingDocument = t.partial({ +const ExistingDocument = t.interface({ id: t.string, shared_id: t.string, version: t.number, - updated_at: DateFromString, + active: t.boolean, + updated_at: t.string, created_by: t.union([t.undefined, t.string]), - updated_on: DateFromString, + updated_on: t.string, updated_by: t.union([t.undefined, t.string]), }); diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index dd0a546d1818ac..dbbdaa9471f346 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -4,30 +4,98 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ConfigurationFile, - NewConfigurationFile, - DatasourceInput, - BackupConfigurationFile, -} from './adapter_types'; - -export class DefaultConfigAdapter { +import { SODatabaseAdapter } from '../so_database/default'; +import { RuntimeConfigurationFile, NewConfigurationFile } from './adapter_types'; + +import { ConfigurationFile, DatasourceInput, BackupConfigurationFile } from './adapter_types'; + +export class ConfigAdapter { + constructor(private readonly so: SODatabaseAdapter) {} + public async create( configuration: NewConfigurationFile ): Promise<{ id: string; shared_id: string; version: number }> { + const newSo = await this.so.create( + 'configurations', + (configuration as any) as ConfigurationFile + ); + return { - id: 'fsdfsdf', - shared_id: 'wjkhefkjhfkjs', - version: 0, + id: newSo.id, + shared_id: newSo.attributes.shared_id, + version: newSo.attributes.version, }; } - public async get(sharedID: string, version?: number): Promise { - return {} as ConfigurationFile; + public async get(id: string): Promise { + const config = await this.so.get('configurations', id); + + if (config.error) { + throw new Error(config.error.message); + } + + if (!config.attributes) { + throw new Error(`No configuration found with ID of ${id}`); + } + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes as ConfigurationFile; + } else { + throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); + } + } + + public async list(): Promise { + const configs = await this.so.find({ + type: 'configurations', + search: '*', + searchFields: ['shared_id'], + }); + const uniqConfigurationFile = configs.saved_objects + .map(config => { + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes; + } else { + throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); + } + }) + .reduce((acc, config: ConfigurationFile) => { + if (!acc.has(config.shared_id)) { + acc.set(config.shared_id, config); + } + const prevConfig = acc.get(config.shared_id); + if (prevConfig && prevConfig.version < config.version) { + acc.set(config.shared_id, config); + } + + return acc; + }, new Map()); + + return [...uniqConfigurationFile.values()]; } - public async list(sharedID: string, version?: number): Promise { - return [{} as ConfigurationFile]; + public async listVersions(sharedID: string, activeOnly = true): Promise { + const configs = (await this.so.find({ + type: 'configurations', + search: sharedID, + searchFields: ['shared_id'], + })).saved_objects; + + if (!activeOnly) { + const backupConfigs = await this.so.find({ + type: 'backup_configurations', + search: sharedID, + searchFields: ['shared_id'], + }); + configs.concat(backupConfigs.saved_objects); + } + + return configs.map(config => { + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes; + } else { + throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); + } + }); } public async update( diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/es_database/adapter_types.ts similarity index 84% rename from x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts rename to x-pack/legacy/plugins/ingest/server/libs/adapters/es_database/adapter_types.ts index 0a06c3dcc6412d..835c75bdb04cee 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/database/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/es_database/adapter_types.ts @@ -3,41 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; - -export interface DatabaseAdapter { - get( - user: FrameworkUser, - params: DatabaseGetParams - ): Promise>; - create( - user: FrameworkUser, - params: DatabaseCreateDocumentParams - ): Promise; - index( - user: FrameworkUser, - params: DatabaseIndexDocumentParams - ): Promise; - delete( - user: FrameworkUser, - params: DatabaseDeleteDocumentParams - ): Promise; - deleteByQuery( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise; - mget(user: FrameworkUser, params: DatabaseMGetParams): Promise>; - bulk( - user: FrameworkUser, - params: DatabaseBulkIndexDocumentsParams - ): Promise; - search(user: FrameworkUser, params: DatabaseSearchParams): Promise>; - searchAll( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise>; - putTemplate(name: string, template: any): Promise; -} +import { FrameworkRequest } from '../framework/adapter_types'; export interface DatabaseKbnESCluster { callWithInternalUser(esMethod: string, options: {}): Promise; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/es_database/default.ts similarity index 97% rename from x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts rename to x-pack/legacy/plugins/ingest/server/libs/adapters/es_database/default.ts index 0538dee64be4be..8779d43bc73d09 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/database/kibana_database_adapter.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/es_database/default.ts @@ -6,7 +6,6 @@ import { FrameworkUser } from '../framework/adapter_types'; import { internalAuthData } from '../framework/adapter_types'; import { - DatabaseAdapter, DatabaseBulkIndexDocumentsParams, DatabaseCreateDocumentParams, DatabaseCreateDocumentResponse, @@ -23,7 +22,7 @@ import { DatabaseSearchResponse, } from './adapter_types'; -export class KibanaDatabaseAdapter implements DatabaseAdapter { +export class ESDatabaseAdapter { private es: DatabaseKbnESCluster; constructor(kbnElasticSearch: DatabaseKbnESPlugin) { diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts index 4325e857cce00f..e412d61ed9186a 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts @@ -8,41 +8,32 @@ import { Lifecycle, ResponseToolkit } from 'hapi'; import * as t from 'io-ts'; -import { LICENSES } from '../../../../common/constants/security'; +import { Legacy } from 'kibana'; +import { Cluster, ClusterConfig } from 'src/legacy/core_plugins/elasticsearch'; +import { ApmOssPlugin } from 'src/legacy/core_plugins/apm_oss'; +import { Request } from 'src/legacy/server/kbn_server'; +import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; +import { + Feature, + FeatureWithAllOrReadPrivileges, +} from '../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { SecurityPlugin } from '../../../../../security'; export const internalAuthData = Symbol('internalAuthData'); export const internalUser: FrameworkInternalUser = { kind: 'internal', }; -export interface XpackInfo { - license: { - getType: () => typeof LICENSES[number]; - /** Is the license expired */ - isActive: () => boolean; - getExpiryDateInMillis: () => number; - }; - feature: (pluginId: string) => any; - isAvailable: () => boolean; -} - -export interface BackendFrameworkAdapter { - internalUser: FrameworkInternalUser; - info: null | FrameworkInfo; - log(text: string): void; - on(event: 'xpack.status.green' | 'elasticsearch.status.green', cb: () => void): void; - getSetting(settingPath: string): any; - getUser(request: KibanaServerRequest): Promise; - exposeMethod(name: string, method: () => any): void; -} - -export interface KibanaLegacyServer { +export interface KibanaLegacyServer extends Legacy.Server { plugins: { xpack_main: { status: { on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; }; - info: XpackInfo; + info: XPackInfo; + createXPackInfo(options: any): any; + getFeatures(): Feature[]; + registerFeature(feature: FeatureWithAllOrReadPrivileges): void; }; kibana: { status: { @@ -51,18 +42,20 @@ export interface KibanaLegacyServer { }; }; }; - security: { - getUser: (request: KibanaServerRequest) => any; - }; + security: SecurityPlugin; elasticsearch: { status: { on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; }; - getCluster: () => any; + getCluster(name: string): Cluster; + createCluster(name: string, config: ClusterConfig): Cluster; + waitUntilReady(): Promise; }; - ingest: {}; + spaces: any; + apm_oss: ApmOssPlugin; + ingest: any; }; - expose: (name: string, value: any) => void; + expose: { (key: string, value: any): void; (obj: object): void }; config: () => any; route: (routeConfig: any) => void; log: (message: string) => void; @@ -107,7 +100,6 @@ export const RuntimeKibanaServerRequest = t.interface( }, 'KibanaServerRequest' ); -export interface KibanaServerRequest extends t.TypeOf {} export const RuntimeKibanaUser = t.interface( { @@ -143,32 +135,27 @@ export type FrameworkUser = | FrameworkAuthenticatedUser | FrameworkUnAuthenticatedUser | FrameworkInternalUser; -export interface FrameworkRequest< - KibanaServerRequestGenaric extends Partial = any -> { +export interface FrameworkRequest = Request> { user: FrameworkUser; headers: KibanaServerRequestGenaric['headers']; - info: KibanaServerRequest['info']; + info: Request['info']; payload: KibanaServerRequestGenaric['payload']; params: KibanaServerRequestGenaric['params']; query: KibanaServerRequestGenaric['query']; } -export interface FrameworkRouteOptions< - RouteRequest extends FrameworkRequest = FrameworkRequest, - RouteResponse extends FrameworkResponse = any -> { +export interface FrameworkRouteOptions { path: string; method: string | string[]; vhost?: string; licenseRequired?: string[]; requiredRoles?: string[]; - handler: FrameworkRouteHandler; + handler: FrameworkRouteHandler; config?: {}; } export type FrameworkRouteHandler< - RouteRequest extends KibanaServerRequest, + RouteRequest extends Request, RouteResponse extends FrameworkResponse > = (request: FrameworkRequest, h: ResponseToolkit) => Promise; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts similarity index 87% rename from x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts rename to x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts index e2c2ba68e9b55e..30d61e9fa27da2 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts @@ -6,21 +6,20 @@ import { PathReporter } from 'io-ts/lib/PathReporter'; import { get } from 'lodash'; +import { Request } from 'src/legacy/server/kbn_server'; +import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; // @ts-ignore import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status'; import { - BackendFrameworkAdapter, FrameworkInfo, internalUser, KibanaLegacyServer, - KibanaServerRequest, KibanaUser, RuntimeFrameworkInfo, RuntimeKibanaUser, - XpackInfo, } from './adapter_types'; -export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { +export class BackendFrameworkAdapter { public readonly internalUser = internalUser; public info: null | FrameworkInfo = null; @@ -65,7 +64,7 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { this.server.expose(name, method); } - public async getUser(request: KibanaServerRequest): Promise { + public async getUser(request: Request): Promise { let user; try { user = await this.server.plugins.security.getUser(request); @@ -87,7 +86,7 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { return user; } - private xpackInfoWasUpdatedHandler = (xpackInfo: XpackInfo) => { + private xpackInfoWasUpdatedHandler = (xpackInfo: XPackInfo) => { let xpackInfoUnpacked: FrameworkInfo; // If, for some reason, we cannot get the license information @@ -103,12 +102,11 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { version: get(this.server, 'plugins.kibana.status.plugin.version', 'unknown'), }, license: { - type: xpackInfo.license.getType(), + type: xpackInfo.license.getType() || 'oss', expired: !xpackInfo.license.isActive(), - expiry_date_in_millis: - xpackInfo.license.getExpiryDateInMillis() !== undefined - ? xpackInfo.license.getExpiryDateInMillis() - : -1, + expiry_date_in_millis: (xpackInfo.license.getExpiryDateInMillis() !== undefined + ? xpackInfo.license.getExpiryDateInMillis() + : -1) as number, }, security: { enabled: !!xpackInfo.feature('security') && xpackInfo.feature('security').isEnabled(), diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/adapter_types.ts new file mode 100644 index 00000000000000..41bc2aa2588073 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/adapter_types.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts new file mode 100644 index 00000000000000..b70d2d2c5e823e --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + SavedObjectsService, + SavedObjectsClient as SavedObjectsClientType, + SavedObjectAttributes, + SavedObjectsBulkCreateObject, + SavedObjectsBaseOptions, + SavedObjectsFindOptions, + SavedObjectsFindResponse, + SavedObjectsBulkResponse, + SavedObject, + SavedObjectsUpdateOptions, +} from 'src/core/server'; +import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; +import { + SavedObjectsCreateOptions, + SavedObjectsBulkGetObject, + SavedObjectsUpdateResponse, +} from 'target/types/server'; + +export class SODatabaseAdapter { + private client: SavedObjectsClientType; + constructor(savedObjects: SavedObjectsService, elasticsearch: ElasticsearchPlugin) { + const { SavedObjectsClient, getSavedObjectsRepository } = savedObjects; + const { callWithInternalUser } = elasticsearch.getCluster('admin'); + const internalRepository = getSavedObjectsRepository(callWithInternalUser); + + this.client = new SavedObjectsClient(internalRepository); + } + + /** + * Persists a SavedObject + * + * @param type + * @param attributes + * @param options + */ + async create( + type: string, + data: T, + options?: SavedObjectsCreateOptions + ) { + return await this.client.create(type, data, options); + } + + /** + * Persists multiple documents batched together as a single request + * + * @param objects + * @param options + */ + async bulkCreate( + objects: Array>, + options?: SavedObjectsCreateOptions + ) { + return await this.client.bulkCreate(objects, options); + } + + /** + * Deletes a SavedObject + * + * @param type + * @param id + * @param options + */ + async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + return await this.client.delete(type, id, options); + } + + /** + * Find all SavedObjects matching the search query + * + * @param options + */ + async find( + options: SavedObjectsFindOptions + ): Promise> { + return await this.client.find(options); + } + + /** + * Returns an array of objects by id + * + * @param objects - an array of ids, or an array of objects containing id, type and optionally fields + * @example + * + * bulkGet([ + * { id: 'one', type: 'config' }, + * { id: 'foo', type: 'index-pattern' } + * ]) + */ + async bulkGet( + objects: SavedObjectsBulkGetObject[] = [], + options: SavedObjectsBaseOptions = {} + ): Promise> { + return await this.client.bulkGet(objects, options); + } + + /** + * Retrieves a single object + * + * @param type - The type of SavedObject to retrieve + * @param id - The ID of the SavedObject to retrieve + * @param options + */ + async get( + type: string, + id: string, + options: SavedObjectsBaseOptions = {} + ): Promise> { + return await this.client.get(type, id, options); + } + + /** + * Updates an SavedObject + * + * @param type + * @param id + * @param options + */ + async update( + type: string, + id: string, + attributes: Partial, + options: SavedObjectsUpdateOptions = {} + ): Promise> { + return await this.client.update(type, id, attributes, options); + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts index aeab928b8f3b37..3b3bf058092bc7 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts @@ -7,22 +7,28 @@ import { camelCase } from 'lodash'; import { PLUGIN } from '../../../common/constants'; import { CONFIG_PREFIX } from '../../../common/constants/plugin'; - -import { DatabaseKbnESPlugin } from '../adapters/database/adapter_types'; -import { KibanaDatabaseAdapter } from '../adapters/database/kibana_database_adapter'; -import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; -import { KibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; - +import { DatabaseKbnESPlugin } from '../adapters/es_database/adapter_types'; +import { ESDatabaseAdapter } from '../adapters/es_database/default'; +import { BackendFrameworkAdapter } from '../adapters/framework/default'; import { ServerLibs } from '../types'; import { BackendFrameworkLib } from './../framework'; +import { ConfigurationLib } from '../configuration'; +import { ConfigAdapter } from '../adapters/configurations/default'; +import { SODatabaseAdapter } from '../adapters/so_database/default'; +import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; export function compose(server: KibanaLegacyServer): ServerLibs { const framework = new BackendFrameworkLib( - new KibanaBackendFrameworkAdapter(camelCase(PLUGIN.ID), server, CONFIG_PREFIX) + new BackendFrameworkAdapter(camelCase(PLUGIN.ID), server, CONFIG_PREFIX) ); - const database = new KibanaDatabaseAdapter(server.plugins.elasticsearch as DatabaseKbnESPlugin); + const database = new ESDatabaseAdapter(server.plugins.elasticsearch as DatabaseKbnESPlugin); + const soDatabase = new SODatabaseAdapter(server.savedObjects, server.plugins.elasticsearch); + + const configAdapter = new ConfigAdapter(soDatabase); + const configuration = new ConfigurationLib(configAdapter); const libs: ServerLibs = { + configuration, framework, database, }; diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts deleted file mode 100644 index b5fe6195fc7c70..00000000000000 --- a/x-pack/legacy/plugins/ingest/server/libs/compose/testing.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; -import { MemoryConfigurationBlockAdapter } from '../adapters/configuration_blocks/memory_tags_adapter'; -import { HapiBackendFrameworkAdapter } from '../adapters/framework/hapi_framework_adapter'; -import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; -import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; -import { BeatEventsLib } from '../beat_events'; -import { CMBeatsDomain } from '../beats'; -import { ConfigurationBlocksLib } from '../configuration_blocks'; -import { BackendFrameworkLib } from '../framework'; -import { CMTagsDomain } from '../tags'; -import { CMTokensDomain } from '../tokens'; -import { CMServerLibs } from '../types'; - -export function compose(server: any): CMServerLibs { - const framework = new BackendFrameworkLib(new HapiBackendFrameworkAdapter(undefined, server)); - - const beatsAdapter = new MemoryBeatsAdapter(server.beatsDB || []); - const configAdapter = new MemoryConfigurationBlockAdapter(server.configsDB || []); - const tags = new CMTagsDomain( - new MemoryTagsAdapter(server.tagsDB || []), - configAdapter, - beatsAdapter - ); - const configurationBlocks = new ConfigurationBlocksLib(configAdapter, tags); - const tokens = new CMTokensDomain(new MemoryTokensAdapter(server.tokensDB || []), { - framework, - }); - const beats = new CMBeatsDomain(beatsAdapter, { - tags, - tokens, - framework, - }); - const beatEvents = new BeatEventsLib({} as any, beats); - - const libs: CMServerLibs = { - beatEvents, - framework, - beats, - tags, - tokens, - configurationBlocks, - }; - - return libs; -} diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index 4d0e42b2f327b7..6dad8c69c5b399 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -3,12 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { ConfigAdapter } from './adapters/configurations/default'; export class ConfigurationLib { - public async rollForward( - sharedID: string, - version?: number - ): Promise<{ id: string; version: number }> { + constructor(private readonly adapter: ConfigAdapter) {} + + public async rollForward(id: string): Promise<{ id: string; version: number }> { + this.adapter.get(id); return { id: 'fsdfsdf', version: 0, diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts index 719902eb6f411c..7dadd94c9a2462 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/framework.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BackendFrameworkAdapter, KibanaServerRequest } from './adapters/framework/adapter_types'; +import { Request } from 'src/legacy/server/kbn_server'; +import { BackendFrameworkAdapter } from './adapters/framework/default'; export class BackendFrameworkLib { /** @@ -20,7 +21,7 @@ export class BackendFrameworkLib { public internalUser = this.adapter.internalUser; constructor(private readonly adapter: BackendFrameworkAdapter) {} - public getCurrentUser(request: KibanaServerRequest) { + public getCurrentUser(request: Request) { return this.adapter.getUser(request); } public getSetting(setting: 'defaultUserRoles'): string[]; diff --git a/x-pack/legacy/plugins/ingest/server/libs/types.ts b/x-pack/legacy/plugins/ingest/server/libs/types.ts index a1d5f0b9b3dbd4..9c4771945e12e2 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/types.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DatabaseAdapter } from './adapters/database/adapter_types'; -import { FrameworkUser } from './adapters/framework/adapter_types'; import { BackendFrameworkLib } from './framework'; +import { ConfigurationLib } from './configuration'; +import { ESDatabaseAdapter } from './adapters/es_database/default'; export interface ServerLibs { + configuration: ConfigurationLib; framework: BackendFrameworkLib; - database?: DatabaseAdapter; + database?: ESDatabaseAdapter; } From 15804ed8c3a542ab63b6dc7fd574ffe8d1089b6c Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 31 May 2019 22:03:53 -0400 Subject: [PATCH 12/27] initial client libs --- x-pack/legacy/plugins/fleet/index.ts | 1 - .../fleet/common/constants/security.ts | 9 + .../plugins/fleet/common/types/domain_data.ts | 80 ++++++ x-pack/plugins/fleet/common/types/helpers.ts | 7 + x-pack/plugins/fleet/common/types/io_ts.ts | 33 +++ x-pack/plugins/fleet/common/types/security.ts | 7 + .../fleet/common/types/std_return_format.ts | 116 ++++++++ .../lib/adapters/agent/adapter_types.ts | 5 + .../adapters/agent/memory_agent_adapter.ts | 42 +++ .../lib/adapters/agent/rest_agent_adapter.ts | 60 ++++ .../adapters/elasticsearch/adapter_types.ts | 12 + .../lib/adapters/elasticsearch/memory.ts | 29 ++ .../public/lib/adapters/elasticsearch/rest.ts | 76 +++++ .../lib/adapters/framework/adapter_types.ts | 86 ++++++ .../framework/kibana_framework_adapter.ts | 263 ++++++++++++++++++ .../framework/testing_framework_adapter.ts | 69 +++++ .../lib/adapters/rest_api/adapter_types.ts | 13 + .../rest_api/axios_rest_api_adapter.ts | 78 ++++++ .../rest_api/node_axios_api_adapter.ts | 92 ++++++ x-pack/plugins/fleet/public/lib/agent.ts | 50 ++++ .../fleet/public/lib/compose/kibana.ts | 53 ++++ .../fleet/public/lib/compose/memory.ts | 59 ++++ .../fleet/public/lib/compose/scripts.ts | 55 ++++ .../plugins/fleet/public/lib/elasticsearch.ts | 69 +++++ x-pack/plugins/fleet/public/lib/framework.ts | 63 +++++ x-pack/plugins/fleet/public/lib/types.ts | 52 ++++ x-pack/plugins/fleet/tsconfig.json | 7 + 27 files changed, 1485 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/common/constants/security.ts create mode 100644 x-pack/plugins/fleet/common/types/domain_data.ts create mode 100644 x-pack/plugins/fleet/common/types/helpers.ts create mode 100644 x-pack/plugins/fleet/common/types/io_ts.ts create mode 100644 x-pack/plugins/fleet/common/types/security.ts create mode 100644 x-pack/plugins/fleet/common/types/std_return_format.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts create mode 100644 x-pack/plugins/fleet/public/lib/agent.ts create mode 100644 x-pack/plugins/fleet/public/lib/compose/kibana.ts create mode 100644 x-pack/plugins/fleet/public/lib/compose/memory.ts create mode 100644 x-pack/plugins/fleet/public/lib/compose/scripts.ts create mode 100644 x-pack/plugins/fleet/public/lib/elasticsearch.ts create mode 100644 x-pack/plugins/fleet/public/lib/framework.ts create mode 100644 x-pack/plugins/fleet/public/lib/types.ts create mode 100644 x-pack/plugins/fleet/tsconfig.json diff --git a/x-pack/legacy/plugins/fleet/index.ts b/x-pack/legacy/plugins/fleet/index.ts index d4b48a9e23c58a..7bd4792c926929 100644 --- a/x-pack/legacy/plugins/fleet/index.ts +++ b/x-pack/legacy/plugins/fleet/index.ts @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; import { initServerWithKibana } from './server/kibana.index'; -// export const config = Joi.object({ enabled: Joi.boolean().default(true), diff --git a/x-pack/plugins/fleet/common/constants/security.ts b/x-pack/plugins/fleet/common/constants/security.ts new file mode 100644 index 00000000000000..f27c45fc903b4e --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/security.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const REQUIRED_ROLES = ['fleet_admin']; +export const REQUIRED_LICENSES = ['standard', 'gold', 'trial', 'platinum']; +export const LICENSES = ['oss', 'basic', 'standard', 'gold', 'trial', 'platinum']; diff --git a/x-pack/plugins/fleet/common/types/domain_data.ts b/x-pack/plugins/fleet/common/types/domain_data.ts new file mode 100644 index 00000000000000..41ec4eae4c5d84 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/domain_data.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { DateFromString } from './io_ts'; + +// Here we create the runtime check for a generic, unknown beat config type. +// We can also pass in optional params to create spacific runtime checks that +// can be used to validate blocs on the API and UI +export const createConfigurationInterface = (beatConfigInterface: t.Mixed = t.Dictionary) => + t.interface( + { + id: t.union([t.undefined, t.string]), + name: t.string, + description: t.union([t.undefined, t.string]), + config: beatConfigInterface, + last_updated_by: t.union([t.undefined, t.string]), + last_updated: t.union([t.undefined, t.number]), + }, + 'Config' + ); +const BaseConfiguration = createConfigurationInterface(); +export interface ConfigurationBlock + extends Pick< + t.TypeOf, + Exclude, 'id'> + > { + id: string; +} + +export interface Agent { + id: string; + status?: AgentEvent; + enrollment_token: string; + active: boolean; + access_token: string; + verified_on?: string; + type: string; + version?: string; + host_ip: string; + host_name: string; + ephemeral_id?: string; + last_checkin?: Date; + event_rate?: string; + tags: string[]; + metadata?: {}; + name?: string; + last_updated: number; +} + +export const RuntimeAgentEvent = t.interface( + { + type: t.union([t.literal('STATE'), t.literal('ERROR')]), + beat: t.union([t.undefined, t.string]), + timestamp: DateFromString, + event: t.type({ + type: t.union([ + t.literal('RUNNING'), + t.literal('STARTING'), + t.literal('IN_PROGRESS'), + t.literal('CONFIG'), + t.literal('FAILED'), + t.literal('STOPPED'), + ]), + message: t.string, + uuid: t.union([t.undefined, t.string]), + }), + }, + 'AgentEvent' +); +export interface AgentEvent + extends Pick< + t.TypeOf, + Exclude, 'timestamp'> + > { + agent: string; + timestamp: Date; +} diff --git a/x-pack/plugins/fleet/common/types/helpers.ts b/x-pack/plugins/fleet/common/types/helpers.ts new file mode 100644 index 00000000000000..4258461310e164 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/helpers.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type FlatObject = { [Key in keyof T]: string }; diff --git a/x-pack/plugins/fleet/common/types/io_ts.ts b/x-pack/plugins/fleet/common/types/io_ts.ts new file mode 100644 index 00000000000000..51ab838ddd6c2b --- /dev/null +++ b/x-pack/plugins/fleet/common/types/io_ts.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +export class DateFromStringType extends t.Type { + // eslint-disable-next-line + public readonly _tag: 'DateFromISOStringType' = 'DateFromISOStringType'; + constructor() { + super( + 'DateFromString', + (u): u is Date => u instanceof Date, + (u, c) => { + const validation = t.string.validate(u, c); + if (validation.isLeft()) { + return validation as any; + } else { + const s = validation.value; + const d = new Date(s); + return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d); + } + }, + a => a.toISOString() + ); + } +} +// eslint-disable-next-line +export interface DateFromString extends DateFromStringType {} + +export const DateFromString: DateFromString = new DateFromStringType(); diff --git a/x-pack/plugins/fleet/common/types/security.ts b/x-pack/plugins/fleet/common/types/security.ts new file mode 100644 index 00000000000000..691ea82b294d30 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/security.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type LicenseType = 'oss' | 'basic' | 'trial' | 'standard' | 'basic' | 'gold' | 'platinum'; diff --git a/x-pack/plugins/fleet/common/types/std_return_format.ts b/x-pack/plugins/fleet/common/types/std_return_format.ts new file mode 100644 index 00000000000000..ded94bbff7f19e --- /dev/null +++ b/x-pack/plugins/fleet/common/types/std_return_format.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface BaseReturnType { + error?: { + message: string; + code?: number; + }; + success: boolean; +} + +export interface ReturnTypeCreate extends BaseReturnType { + item: T; + action: 'created'; +} + +export interface ReturnTypeUpdate extends BaseReturnType { + item: T; + action: 'updated'; +} + +export interface ReturnTypeBulkCreate extends BaseReturnType { + results: Array<{ + item: T; + success: boolean; + action: 'created'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// delete +export interface ReturnTypeDelete extends BaseReturnType { + action: 'deleted'; +} + +export interface ReturnTypeBulkDelete extends BaseReturnType { + results: Array<{ + success: boolean; + action: 'deleted'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// upsert +export interface ReturnTypeUpsert extends BaseReturnType { + item: T; + action: 'created' | 'updated'; +} + +// upsert bulk +export interface ReturnTypeBulkUpsert extends BaseReturnType { + results: Array<{ + success: boolean; + action: 'created' | 'updated'; + error?: { + message: string; + code?: number; + }; + }>; +} + +// list +export interface ReturnTypeList extends BaseReturnType { + list: T[]; + page: number; + total: number; +} + +// get +export interface ReturnTypeGet extends BaseReturnType { + item: T; +} + +export interface ReturnTypeBulkGet extends BaseReturnType { + items: T[]; +} + +// action -- e.g. validate config block. Like ES simulate endpoint +export interface ReturnTypeAction extends BaseReturnType { + result: { + [key: string]: any; + }; +} +// e.g. +// { +// result: { +// username: { valid: true }, +// password: { valid: false, error: 'something' }, +// hosts: [ +// { valid: false }, { valid: true }, +// ] +// } +// } + +// bulk action +export interface ReturnTypeBulkAction extends BaseReturnType { + results?: Array<{ + success: boolean; + result?: { + [key: string]: any; + }; + error?: { + message: string; + code?: number; + }; + }>; +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts new file mode 100644 index 00000000000000..41bc2aa2588073 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts new file mode 100644 index 00000000000000..3f6c4a3143750a --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { omit } from 'lodash'; +import { Agent } from '../../../../common/types/domain_data'; + +export class AgentAdapter { + private memoryDB: Agent[]; + + constructor(db: Agent[]) { + this.memoryDB = db; + } + + public async get(id: string) { + return this.memoryDB.find(beat => beat.id === id) || null; + } + + public async update(id: string, beatData: Partial): Promise { + const index = this.memoryDB.findIndex(beat => beat.id === id); + + if (index === -1) { + return false; + } + + this.memoryDB[index] = { ...this.memoryDB[index], ...beatData }; + return true; + } + + public async getAll(ESQuery?: string) { + return this.memoryDB.map((beat: any) => omit(beat, ['access_token'])); + } + public async getOnConfig(tagId: string): Promise { + return this.memoryDB.map((beat: any) => omit(beat, ['access_token'])); + } + + public async getWithToken(enrollmentToken: string): Promise { + return this.memoryDB.map((beat: any) => omit(beat, ['access_token']))[0]; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts new file mode 100644 index 00000000000000..da04f615554a25 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Agent } from '../../../../common/types/domain_data'; +import { + ReturnTypeGet, + ReturnTypeList, + ReturnTypeUpdate, +} from '../../../../common/types/std_return_format'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { AgentAdapter } from './memory_agent_adapter'; + +export class RestAgentAdapter extends AgentAdapter { + constructor(private readonly REST: RestAPIAdapter) { + super([]); + } + + public async get(id: string): Promise { + try { + return (await this.REST.get>(`/api/fleet/agent/${id}`)).item; + } catch (e) { + return null; + } + } + + public async getWithToken(enrollmentToken: string): Promise { + try { + return (await this.REST.get>( + `/api/fleet/agent/unknown/${enrollmentToken}` + )).item; + } catch (e) { + return null; + } + } + + public async getAll(ESQuery?: string): Promise { + try { + return (await this.REST.get>('/api/fleet/agents/all', { ESQuery })) + .list; + } catch (e) { + return []; + } + } + + public async getOnConfig(tagId: string): Promise { + try { + return (await this.REST.get>(`/api/fleet/agents/tag/${tagId}`)).list; + } catch (e) { + return []; + } + } + + public async update(id: string, beatData: Partial): Promise { + await this.REST.put>(`/api/fleet/agent/${id}`, beatData); + return true; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts new file mode 100644 index 00000000000000..49408574932757 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; + +export interface ElasticsearchAdapter { + convertKueryToEsQuery: (kuery: string) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; + isKueryValid(kuery: string): boolean; +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts new file mode 100644 index 00000000000000..1b918fb72c8091 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { ElasticsearchAdapter } from './adapter_types'; + +export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { + constructor( + private readonly mockIsKueryValid: (kuery: string) => boolean, + private readonly mockKueryToEsQuery: (kuery: string) => string, + private readonly suggestions: AutocompleteSuggestion[] + ) {} + + public isKueryValid(kuery: string): boolean { + return this.mockIsKueryValid(kuery); + } + public async convertKueryToEsQuery(kuery: string): Promise { + return this.mockKueryToEsQuery(kuery); + } + public async getSuggestions( + kuery: string, + selectionStart: any + ): Promise { + return this.suggestions; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts new file mode 100644 index 00000000000000..8899ddd7976d55 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { isEmpty } from 'lodash'; +import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { ElasticsearchAdapter } from './adapter_types'; + +export class RestElasticsearchAdapter implements ElasticsearchAdapter { + private cachedIndexPattern: any = null; + constructor(private readonly api: RestAPIAdapter, private readonly indexPatternName: string) {} + + public isKueryValid(kuery: string): boolean { + try { + fromKueryExpression(kuery); + } catch (err) { + return false; + } + + return true; + } + public async convertKueryToEsQuery(kuery: string): Promise { + if (!this.isKueryValid(kuery)) { + return ''; + } + const ast = fromKueryExpression(kuery); + const indexPattern = await this.getIndexPattern(); + return JSON.stringify(toElasticsearchQuery(ast, indexPattern)); + } + public async getSuggestions( + kuery: string, + selectionStart: any + ): Promise { + const autocompleteProvider = getAutocompleteProvider('kuery'); + if (!autocompleteProvider) { + return []; + } + const config = { + get: () => true, + }; + const indexPattern = await this.getIndexPattern(); + + const getAutocompleteSuggestions = autocompleteProvider({ + config, + indexPatterns: [indexPattern], + boolFilter: null, + }); + const results = getAutocompleteSuggestions({ + query: kuery || '', + selectionStart, + selectionEnd: selectionStart, + }); + return results; + } + + private async getIndexPattern() { + if (this.cachedIndexPattern) { + return this.cachedIndexPattern; + } + const res = await this.api.get( + `/api/index_patterns/_fields_for_wildcard?pattern=${this.indexPatternName}` + ); + if (isEmpty(res.fields)) { + return; + } + this.cachedIndexPattern = { + fields: res.fields, + title: `${this.indexPatternName}`, + }; + return this.cachedIndexPattern; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts new file mode 100644 index 00000000000000..b8a75e284c2480 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import * as t from 'io-ts'; +import { LICENSES } from './../../../../common/constants/security'; + +export interface FrameworkAdapter { + // Instance vars + info: FrameworkInfo; + version: string; + currentUser: FrameworkUser; + // Methods + waitUntilFrameworkReady(): Promise; + renderUIAtPath( + path: string, + component: React.ReactElement, + toController: 'management' | 'self' + ): void; + registerManagementSection(settings: { + id?: string; + name: string; + iconName: string; + order?: number; + }): void; + registerManagementUI(settings: { + sectionId?: string; + name: string; + basePath: string; + visable?: boolean; + order?: number; + }): void; +} + +export const RuntimeFrameworkInfo = t.type({ + basePath: t.string, + license: t.type({ + type: t.union(LICENSES.map(s => t.literal(s))), + expired: t.boolean, + expiry_date_in_millis: t.number, + }), + security: t.type({ + enabled: t.boolean, + available: t.boolean, + }), + settings: t.type({ + // encryptionKey: t.string, + // enrollmentTokensTtlInSeconds: t.number, + // defaultUserRoles: t.array(t.string), + }), +}); + +export interface FrameworkInfo extends t.TypeOf {} + +interface ManagementSection { + register( + sectionId: string, + options: { + visible: boolean; + display: string; + order: number; + url: string; + } + ): void; +} +export interface ManagementAPI { + getSection(sectionId: string): ManagementSection; + hasItem(sectionId: string): boolean; + register(sectionId: string, options: { display: string; icon: string; order: number }): void; +} + +export const RuntimeFrameworkUser = t.interface( + { + username: t.string, + roles: t.array(t.string), + full_name: t.union([t.null, t.string]), + email: t.union([t.null, t.string]), + enabled: t.boolean, + }, + 'FrameworkUser' +); +export interface FrameworkUser extends t.TypeOf {} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 00000000000000..0a8acd954c0a12 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,263 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable max-classes-per-file */ +import { IScope } from 'angular'; +import { PathReporter } from 'io-ts/lib/PathReporter'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { UIRoutes } from 'ui/routes'; +import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; +import { + FrameworkAdapter, + FrameworkInfo, + FrameworkUser, + ManagementAPI, + RuntimeFrameworkInfo, + RuntimeFrameworkUser, +} from './adapter_types'; +interface IInjector { + get(injectable: string): any; +} + +export class KibanaFrameworkAdapter implements FrameworkAdapter { + public get info() { + if (this.xpackInfo) { + return this.xpackInfo; + } else { + throw new Error('framework adapter must have init called before anything else'); + } + } + + public get currentUser() { + return this.shieldUser!; + } + private xpackInfo: FrameworkInfo | null = null; + private adapterService: KibanaAdapterServiceProvider; + private shieldUser: FrameworkUser | null = null; + constructor( + private readonly PLUGIN_ID: string, + private readonly management: ManagementAPI, + private readonly routes: UIRoutes, + private readonly getBasePath: () => string, + private readonly onKibanaReady: () => Promise, + private readonly XPackInfoProvider: unknown, + public readonly version: string + ) { + this.adapterService = new KibanaAdapterServiceProvider(); + } + + public setUISettings = (key: string, value: any) => { + this.adapterService.callOrBuffer(({ config }) => { + config.set(key, value); + }); + }; + + public async waitUntilFrameworkReady(): Promise { + const $injector = await this.onKibanaReady(); + const Private: any = $injector.get('Private'); + + let xpackInfo: any; + try { + xpackInfo = Private(this.XPackInfoProvider); + } catch (e) { + xpackInfo = false; + } + + let xpackInfoUnpacked: FrameworkInfo; + try { + xpackInfoUnpacked = { + basePath: this.getBasePath(), + license: { + type: xpackInfo ? xpackInfo.getLicense().type : 'oss', + expired: xpackInfo ? !xpackInfo.getLicense().isActive : false, + expiry_date_in_millis: + xpackInfo.getLicense().expiryDateInMillis !== undefined + ? xpackInfo.getLicense().expiryDateInMillis + : -1, + }, + security: { + enabled: xpackInfo + ? xpackInfo.get(`features.${this.PLUGIN_ID}.security.enabled`, false) + : false, + available: xpackInfo + ? xpackInfo.get(`features.${this.PLUGIN_ID}.security.available`, false) + : false, + }, + settings: xpackInfo ? xpackInfo.get(`features.${this.PLUGIN_ID}.settings`) : {}, + }; + } catch (e) { + throw new Error(`Unexpected data structure from XPackInfoProvider, ${JSON.stringify(e)}`); + } + + const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked); + if (assertData.isLeft()) { + throw new Error( + `Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}` + ); + } + this.xpackInfo = xpackInfoUnpacked; + + try { + this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise; + const assertUser = RuntimeFrameworkUser.decode(this.shieldUser); + + if (assertUser.isLeft()) { + throw new Error( + `Error parsing user info in ${this.PLUGIN_ID}, ${PathReporter.report(assertUser)[0]}` + ); + } + } catch (e) { + this.shieldUser = null; + } + } + + public renderUIAtPath( + path: string, + component: React.ReactElement, + toController: 'management' | 'self' = 'self' + ) { + const adapter = this; + this.routes.when( + `${path}${[...Array(6)].map((e, n) => `/:arg${n}?`).join('')}`, // Hack because angular 1 does not support wildcards + { + template: + toController === 'self' + ? `<${this.PLUGIN_ID}>
` + : ` +
+
+ `, + // eslint-disable-next-line max-classes-per-file + controller: ($scope: any, $route: any) => { + try { + $scope.$$postDigest(() => { + const elem = document.getElementById(`${this.PLUGIN_ID}ReactRoot`); + ReactDOM.render(component, elem); + adapter.manageAngularLifecycle($scope, $route, elem); + }); + $scope.$onInit = () => { + $scope.topNavMenu = []; + }; + } catch (e) { + throw new Error(`Error rendering Elastic Fleet to the dom, ${e.message}`); + } + }, + } + ); + } + + public registerManagementSection(settings: { + id?: string; + name: string; + iconName: string; + order?: number; + }) { + const sectionId = settings.id || this.PLUGIN_ID; + + if (!this.management.hasItem(sectionId)) { + this.management.register(sectionId, { + display: settings.name, + icon: settings.iconName, + order: settings.order || 30, + }); + } + } + + public registerManagementUI(settings: { + sectionId?: string; + name: string; + basePath: string; + visable?: boolean; + order?: number; + }) { + const sectionId = settings.sectionId || this.PLUGIN_ID; + + if (!this.management.hasItem(sectionId)) { + throw new Error( + `registerManagementUI was called with a sectionId of ${sectionId}, and that is is not yet regestered as a section` + ); + } + + const section = this.management.getSection(sectionId); + + section.register(sectionId, { + visible: settings.visable || true, + display: settings.name, + order: settings.order || 30, + url: `#${settings.basePath}`, + }); + } + + private manageAngularLifecycle($scope: any, $route: any, elem: any) { + const lastRoute = $route.current; + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + // if templates are the same we are on the same route + if (lastRoute.$$route.template === currentRoute.$$route.template) { + // this prevents angular from destroying scope + $route.current = lastRoute; + } else { + if (elem) { + ReactDOM.unmountComponentAtNode(elem); + elem.remove(); + } + } + }); + $scope.$on('$destroy', () => { + if (deregister) { + deregister(); + } + + // manually unmount component when scope is destroyed + if (elem) { + ReactDOM.unmountComponentAtNode(elem); + elem.remove(); + } + }); + } +} + +class KibanaAdapterServiceProvider { + public serviceRefs: KibanaAdapterServiceRefs | null = null; + public bufferedCalls: Array> = []; + + public $get($rootScope: IScope, config: KibanaUIConfig) { + this.serviceRefs = { + config, + rootScope: $rootScope, + }; + + this.applyBufferedCalls(this.bufferedCalls); + + return this; + } + + public callOrBuffer(serviceCall: (serviceRefs: KibanaAdapterServiceRefs) => void) { + if (this.serviceRefs !== null) { + this.applyBufferedCalls([serviceCall]); + } else { + this.bufferedCalls.push(serviceCall); + } + } + + public applyBufferedCalls( + bufferedCalls: Array> + ) { + if (!this.serviceRefs) { + return; + } + + this.serviceRefs.rootScope.$apply(() => { + bufferedCalls.forEach(serviceCall => { + if (!this.serviceRefs) { + return; + } + return serviceCall(this.serviceRefs); + }); + }); + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts new file mode 100644 index 00000000000000..9045c7ded2adaa --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { FrameworkAdapter, FrameworkInfo, FrameworkUser } from './adapter_types'; + +export class TestingFrameworkAdapter implements FrameworkAdapter { + public get info() { + if (this.xpackInfo) { + return this.xpackInfo; + } else { + throw new Error('framework adapter must have init called before anything else'); + } + } + + public get currentUser() { + return this.shieldUser!; + } + private settings: any; + constructor( + private readonly xpackInfo: FrameworkInfo | null, + private readonly shieldUser: FrameworkUser | null, + public readonly version: string + ) {} + + // We dont really want to have this, but it's needed to conditionaly render for k7 due to + // when that data is needed. + public getUISetting(key: 'k7design'): boolean { + return this.settings[key]; + } + + public setUISettings = (key: string, value: any) => { + this.settings[key] = value; + }; + + public async waitUntilFrameworkReady(): Promise { + return; + } + + public renderUIAtPath( + path: string, + component: React.ReactElement, + toController: 'management' | 'self' = 'self' + ) { + throw new Error('not yet implamented'); + } + + public registerManagementSection(settings: { + id?: string; + name: string; + iconName: string; + order?: number; + }) { + throw new Error('not yet implamented'); + } + + public registerManagementUI(settings: { + sectionId?: string; + name: string; + basePath: string; + visable?: boolean; + order?: number; + }) { + throw new Error('not yet implamented'); + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts new file mode 100644 index 00000000000000..c40575eb6567fe --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FlatObject } from '../../../../common/types/helpers'; + +export interface RestAPIAdapter { + get(url: string, query?: FlatObject): Promise; + post(url: string, body?: { [key: string]: any }): Promise; + delete(url: string): Promise; + put(url: string, body?: any): Promise; +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts new file mode 100644 index 00000000000000..4d0a1728c28f89 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosInstance } from 'axios'; +import { FlatObject } from '../../../../common/types/helpers'; +import { RestAPIAdapter } from './adapter_types'; +let globalAPI: AxiosInstance; + +export class AxiosRestAPIAdapter implements RestAPIAdapter { + constructor(private readonly xsrfToken: string, private readonly basePath: string) {} + + public async get(url: string, query?: FlatObject): Promise { + return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); + } + + public async post( + url: string, + body?: { [key: string]: any } + ): Promise { + return await this.REST.post(url, body).then(resp => resp.data); + } + + public async delete(url: string): Promise { + return await this.REST.delete(url).then(resp => resp.data); + } + + public async put(url: string, body?: any): Promise { + return await this.REST.put(url, body).then(resp => resp.data); + } + + private get REST() { + if (globalAPI) { + return globalAPI; + } + + globalAPI = axios.create({ + baseURL: this.basePath, + withCredentials: true, + responseType: 'json', + timeout: 30000, + headers: { + Accept: 'application/json', + credentials: 'same-origin', + 'Content-Type': 'application/json', + 'kbn-version': this.xsrfToken, + 'kbn-xsrf': this.xsrfToken, + }, + }); + // Add a request interceptor + globalAPI.interceptors.request.use( + config => { + // Do something before request is sent + return config; + }, + error => { + // Do something with request error + return Promise.reject(error); + } + ); + + // Add a response interceptor + globalAPI.interceptors.response.use( + response => { + // Do something with response data + return response; + }, + error => { + // Do something with response error + return Promise.reject(error); + } + ); + + return globalAPI; + } +} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts new file mode 100644 index 00000000000000..112d8b2210065b --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosInstance } from 'axios'; +import fs from 'fs'; +import { join, resolve } from 'path'; +import { FlatObject } from '../../../../common/types/helpers'; +import { RestAPIAdapter } from './adapter_types'; +const pkg = JSON.parse( + fs.readFileSync(resolve(join(__dirname, '../../../../../../../package.json'))).toString() +); + +let globalAPI: AxiosInstance; + +export class NodeAxiosAPIAdapter implements RestAPIAdapter { + constructor( + private readonly username: string, + private readonly password: string, + private readonly basePath: string + ) {} + + public async get(url: string, query?: FlatObject): Promise { + return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); + } + + public async post( + url: string, + body?: { [key: string]: any } + ): Promise { + return await this.REST.post(url, body).then(resp => resp.data); + } + + public async delete(url: string): Promise { + return await this.REST.delete(url).then(resp => resp.data); + } + + public async put(url: string, body?: any): Promise { + return await this.REST.put(url, body).then(resp => resp.data); + } + + private get REST() { + if (globalAPI) { + return globalAPI; + } + + globalAPI = axios.create({ + baseURL: this.basePath, + withCredentials: true, + responseType: 'json', + timeout: 60 * 10 * 1000, // 10min + auth: { + username: this.username, + password: this.password, + }, + headers: { + 'Access-Control-Allow-Origin': '*', + Accept: 'application/json', + 'Content-Type': 'application/json', + 'kbn-version': (pkg as any).version, + 'kbn-xsrf': 'xxx', + }, + }); + // Add a request interceptor + globalAPI.interceptors.request.use( + config => { + // Do something before request is sent + return config; + }, + error => { + // Do something with request error + return Promise.reject(error); + } + ); + + // Add a response interceptor + globalAPI.interceptors.response.use( + response => { + // Do something with response data + return response; + }, + error => { + // Do something with response error + return Promise.reject(JSON.stringify(error.response.data)); + } + ); + + return globalAPI; + } +} diff --git a/x-pack/plugins/fleet/public/lib/agent.ts b/x-pack/plugins/fleet/public/lib/agent.ts new file mode 100644 index 00000000000000..49710a2d5d36cf --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/agent.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Agent } from '../../common/types/domain_data'; +import { AgentAdapter } from './adapters/agent/memory_agent_adapter'; +import { ElasticsearchLib } from './elasticsearch'; + +export class AgentsLib { + constructor( + private readonly adapter: AgentAdapter, + private readonly elasticsearch: ElasticsearchLib + ) {} + + /** Get a single beat using it's ID for lookup */ + public async get(id: string): Promise { + const agent = await this.adapter.get(id); + return agent; + } + + /** Get a single agent using the token it was enrolled in for lookup */ + public getWithToken = async (enrollmentToken: string): Promise => { + const agent = await this.adapter.getWithToken(enrollmentToken); + return agent; + }; + + /** Get an array of agents that have a given tag id assigned to it */ + public getOnConfig = async (configId: string): Promise => { + const agents = await this.adapter.getOnConfig(configId); + return agents; + }; + + // FIXME: This needs to be paginated https://github.com/elastic/kibana/issues/26022 + /** Get an array of all enrolled agents. */ + public getAll = async (kuery?: string): Promise => { + let ESQuery; + if (kuery) { + ESQuery = await this.elasticsearch.convertKueryToEsQuery(kuery); + } + const agents = await this.adapter.getAll(ESQuery); + return agents; + }; + + /** Update a given agent via it's ID */ + public update = async (id: string, agentData: Partial): Promise => { + return await this.adapter.update(id, agentData); + }; +} diff --git a/x-pack/plugins/fleet/public/lib/compose/kibana.ts b/x-pack/plugins/fleet/public/lib/compose/kibana.ts new file mode 100644 index 00000000000000..17991b764cd716 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/compose/kibana.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { camelCase } from 'lodash'; +// @ts-ignore not typed yet +import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info'; +import 'ui/autoload/all'; +import chrome from 'ui/chrome'; +// @ts-ignore not typed yet +import { management } from 'ui/management'; +import routes from 'ui/routes'; +import { INDEX_NAMES } from '../../../common/constants/index_names'; +import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; +import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest'; +import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; +import { AgentsLib } from '../agent'; +import { ElasticsearchLib } from '../elasticsearch'; +import { FrontendLibs } from '../types'; +import { PLUGIN } from './../../../common/constants/plugin'; +import { FrameworkLib } from './../framework'; + +// A super early spot in kibana loading that we can use to hook before most other things +const onKibanaReady = chrome.dangerouslyGetActiveInjector; + +export function compose(): FrontendLibs { + const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); + const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.FLEET); + const elasticsearchLib = new ElasticsearchLib(esAdapter); + const agents = new AgentsLib(new RestAgentAdapter(api), elasticsearchLib); + + const framework = new FrameworkLib( + new KibanaFrameworkAdapter( + camelCase(PLUGIN.ID), + management, + routes, + chrome.getBasePath, + onKibanaReady, + XPackInfoProvider, + chrome.getKibanaVersion() + ) + ); + + const libs: FrontendLibs = { + framework, + elasticsearch: elasticsearchLib, + agents, + }; + return libs; +} diff --git a/x-pack/plugins/fleet/public/lib/compose/memory.ts b/x-pack/plugins/fleet/public/lib/compose/memory.ts new file mode 100644 index 00000000000000..5501c4066d89e0 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/compose/memory.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import { management } from 'ui/management'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +// @ts-ignore: path dynamic for kibana +import routes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { MemoryAgentAdapter } from '../adapters/agent/memory_agents_adapter'; +import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AgentsLib } from '../agent'; +import { FrameworkLib } from '../framework'; +import { FrontendLibs } from '../types'; +import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; +import { ElasticsearchLib } from './../elasticsearch'; + +const onKibanaReady = uiModules.get('kibana').run; + +export function compose( + mockIsKueryValid: (kuery: string) => boolean, + mockKueryToEsQuery: (kuery: string) => string, + suggestions: AutocompleteSuggestion[] +): FrontendLibs { + const esAdapter = new MemoryElasticsearchAdapter( + mockIsKueryValid, + mockKueryToEsQuery, + suggestions + ); + const elasticsearchLib = new ElasticsearchLib(esAdapter); + + const agents = new AgentsLib(new MemoryAgentAdapter([]), elasticsearchLib); + + const pluginUIModule = uiModules.get('app/fleet'); + + const framework = new FrameworkLib( + new KibanaFrameworkAdapter( + pluginUIModule, + management, + routes, + () => '', + onKibanaReady, + null, + '7.0.0' + ) + ); + const libs: FrontendLibs = { + framework, + elasticsearch: elasticsearchLib, + agents, + }; + return libs; +} diff --git a/x-pack/plugins/fleet/public/lib/compose/scripts.ts b/x-pack/plugins/fleet/public/lib/compose/scripts.ts new file mode 100644 index 00000000000000..174782150d6e02 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/compose/scripts.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; +import { MemoryElasticsearchAdapter } from '../adapters/elasticsearch/memory'; +import { TestingFrameworkAdapter } from '../adapters/framework/testing_framework_adapter'; +import { NodeAxiosAPIAdapter } from '../adapters/rest_api/node_axios_api_adapter'; +import { AgentsLib } from '../agent'; +import { ElasticsearchLib } from '../elasticsearch'; +import { FrameworkLib } from '../framework'; +import { FrontendLibs } from '../types'; + +export function compose(basePath: string): FrontendLibs { + const api = new NodeAxiosAPIAdapter('elastic', 'changeme', basePath); + const esAdapter = new MemoryElasticsearchAdapter(() => true, () => '', []); + const elasticsearchLib = new ElasticsearchLib(esAdapter); + + const agents = new AgentsLib(new RestAgentAdapter(api), elasticsearchLib); + + const framework = new FrameworkLib( + new TestingFrameworkAdapter( + { + basePath, + license: { + type: 'gold', + expired: false, + expiry_date_in_millis: 34353453452345, + }, + security: { + enabled: true, + available: true, + }, + settings: {}, + }, + { + username: 'joeuser', + roles: ['fleet_admin'], + enabled: true, + full_name: null, + email: null, + }, + '6.7.0' + ) + ); + + const libs: FrontendLibs = { + framework, + elasticsearch: elasticsearchLib, + agents, + }; + return libs; +} diff --git a/x-pack/plugins/fleet/public/lib/elasticsearch.ts b/x-pack/plugins/fleet/public/lib/elasticsearch.ts new file mode 100644 index 00000000000000..7ea32a2eb9467e --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/elasticsearch.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; + +interface HiddenFields { + op: 'is' | 'startsWith' | 'withoutPrefix'; + value: string; +} + +export class ElasticsearchLib { + private readonly hiddenFields: HiddenFields[] = [ + { op: 'startsWith', value: 'enrollment_token' }, + { op: 'is', value: 'beat.active' }, + { op: 'is', value: 'beat.enrollment_token' }, + { op: 'is', value: 'beat.access_token' }, + { op: 'is', value: 'beat.ephemeral_id' }, + { op: 'is', value: 'beat.verified_on' }, + ]; + + constructor(private readonly adapter: ElasticsearchAdapter) {} + + public isKueryValid(kuery: string): boolean { + return this.adapter.isKueryValid(kuery); + } + public async convertKueryToEsQuery(kuery: string): Promise { + return await this.adapter.convertKueryToEsQuery(kuery); + } + + public async getSuggestions( + kuery: string, + selectionStart: any, + fieldPrefix?: string + ): Promise { + const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); + + const filteredSuggestions = suggestions.filter(suggestion => { + const hiddenFieldsCheck = this.hiddenFields; + + if (fieldPrefix) { + hiddenFieldsCheck.push({ + op: 'withoutPrefix', + value: `${fieldPrefix}.`, + }); + } + + return hiddenFieldsCheck.reduce((isvalid, field) => { + if (!isvalid) { + return false; + } + + switch (field.op) { + case 'startsWith': + return !suggestion.text.startsWith(field.value); + case 'is': + return suggestion.text.trim() !== field.value; + case 'withoutPrefix': + return suggestion.text.startsWith(field.value); + } + }, true); + }); + + return filteredSuggestions; + } +} diff --git a/x-pack/plugins/fleet/public/lib/framework.ts b/x-pack/plugins/fleet/public/lib/framework.ts new file mode 100644 index 00000000000000..e6ae33168384e9 --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/framework.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { difference, get } from 'lodash'; +import { LICENSES } from '../../common/constants/security'; +import { LicenseType } from '../../common/types/security'; +import { FrameworkAdapter } from './adapters/framework/adapter_types'; + +export class FrameworkLib { + public waitUntilFrameworkReady = this.adapter.waitUntilFrameworkReady.bind(this.adapter); + public renderUIAtPath = this.adapter.renderUIAtPath.bind(this.adapter); + public registerManagementSection = this.adapter.registerManagementSection.bind(this.adapter); + public registerManagementUI = this.adapter.registerManagementUI.bind(this.adapter); + + constructor(private readonly adapter: FrameworkAdapter) {} + + public get currentUser() { + return this.adapter.currentUser; + } + + public get info() { + return this.adapter.info; + } + + public licenseIsAtLeast(type: LicenseType) { + return ( + LICENSES.indexOf(get(this.adapter.info, 'license.type', 'oss')) >= LICENSES.indexOf(type) + ); + } + + public versionGreaterThen(version: string) { + const pa = this.adapter.version.split('.'); + const pb = version.split('.'); + for (let i = 0; i < 3; i++) { + const na = Number(pa[i]); + const nb = Number(pb[i]); + // version is greater + if (na > nb) { + return true; + } + // version is less then + if (nb > na) { + return false; + } + if (!isNaN(na) && isNaN(nb)) { + return true; + } + if (isNaN(na) && !isNaN(nb)) { + return false; + } + } + return true; + } + + public currentUserHasOneOfRoles(roles: string[]) { + // If the user has at least one of the roles requested, the returnd difference will be less + // then the orig array size. difference only compares based on the left side arg + return difference(roles, get(this.currentUser, 'roles', [])).length < roles.length; + } +} diff --git a/x-pack/plugins/fleet/public/lib/types.ts b/x-pack/plugins/fleet/public/lib/types.ts new file mode 100644 index 00000000000000..c5a068a881be4d --- /dev/null +++ b/x-pack/plugins/fleet/public/lib/types.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IModule, IScope } from 'angular'; +import { AxiosRequestConfig } from 'axios'; +import { FrameworkAdapter } from './adapters/framework/adapter_types'; +import { AgentsLib } from './agent'; +import { ElasticsearchLib } from './elasticsearch'; +import { FrameworkLib } from './framework'; + +export interface FrontendLibs { + elasticsearch: ElasticsearchLib; + framework: FrameworkLib; + agents: AgentsLib; +} + +export type FramworkAdapterConstructable = new (uiModule: IModule) => FrameworkAdapter; + +// FIXME: replace AxiosRequestConfig with something more defined +export type RequestConfig = AxiosRequestConfig; + +export interface ApiAdapter { + kbnVersion: string; + + get(url: string, config?: RequestConfig | undefined): Promise; + post(url: string, data?: any, config?: AxiosRequestConfig | undefined): Promise; + delete(url: string, config?: RequestConfig | undefined): Promise; + put(url: string, data?: any, config?: RequestConfig | undefined): Promise; +} + +export interface UiKibanaAdapterScope extends IScope { + breadcrumbs: any[]; + topNavMenu: any[]; +} + +export interface KibanaUIConfig { + get(key: string): any; + set(key: string, value: any): Promise; +} + +export interface KibanaAdapterServiceRefs { + config: KibanaUIConfig; + rootScope: IScope; +} + +export type BufferedKibanaServiceCall = (serviceRefs: ServiceRefs) => void; + +export interface Chrome { + setRootTemplate(template: string): void; +} diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json new file mode 100644 index 00000000000000..67fefc7286ca4f --- /dev/null +++ b/x-pack/plugins/fleet/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["**/node_modules/**"], + "paths": { + "react": ["../../../node_modules/@types/react"] + } +} From 5988633d0d734730618e27f296fca226627b520a Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 23 Jul 2019 10:06:23 -0400 Subject: [PATCH 13/27] cleanup names and files/paths to confirm to a standard format --- .../lib/adapters/database/adapter_types.ts | 309 ------------------ .../database/kibana_database_adapter.ts | 130 -------- .../plugins/ingest/server/kibana.index.ts | 3 +- .../libs/adapters/configurations/default.ts | 94 +----- .../libs/adapters/framework/adapter_types.ts | 59 ++-- .../ingest/server/libs/compose/kibana.ts | 6 +- .../ingest/server/libs/configuration.ts | 6 +- .../plugins/ingest/server/libs/framework.ts | 4 +- 8 files changed, 54 insertions(+), 557 deletions(-) delete mode 100644 x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts delete mode 100644 x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts deleted file mode 100644 index 0a06c3dcc6412d..00000000000000 --- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; - -export interface DatabaseAdapter { - get( - user: FrameworkUser, - params: DatabaseGetParams - ): Promise>; - create( - user: FrameworkUser, - params: DatabaseCreateDocumentParams - ): Promise; - index( - user: FrameworkUser, - params: DatabaseIndexDocumentParams - ): Promise; - delete( - user: FrameworkUser, - params: DatabaseDeleteDocumentParams - ): Promise; - deleteByQuery( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise; - mget(user: FrameworkUser, params: DatabaseMGetParams): Promise>; - bulk( - user: FrameworkUser, - params: DatabaseBulkIndexDocumentsParams - ): Promise; - search(user: FrameworkUser, params: DatabaseSearchParams): Promise>; - searchAll( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise>; - putTemplate(name: string, template: any): Promise; -} - -export interface DatabaseKbnESCluster { - callWithInternalUser(esMethod: string, options: {}): Promise; - callWithRequest(req: FrameworkRequest, esMethod: string, options: {}): Promise; -} - -export interface DatabaseKbnESPlugin { - getCluster(clusterName: string): DatabaseKbnESCluster; -} - -export interface DatabaseSearchParams extends DatabaseGenericParams { - analyzer?: string; - analyzeWildcard?: boolean; - defaultOperator?: DefaultOperator; - df?: string; - explain?: boolean; - storedFields?: DatabaseNameList; - docvalueFields?: DatabaseNameList; - fielddataFields?: DatabaseNameList; - from?: number; - ignoreUnavailable?: boolean; - allowNoIndices?: boolean; - expandWildcards?: ExpandWildcards; - lenient?: boolean; - lowercaseExpandedTerms?: boolean; - preference?: string; - q?: string; - routing?: DatabaseNameList; - scroll?: string; - searchType?: 'query_then_fetch' | 'dfs_query_then_fetch'; - size?: number; - sort?: DatabaseNameList; - _source?: DatabaseNameList; - _sourceExclude?: DatabaseNameList; - _source_includes?: DatabaseNameList; - terminateAfter?: number; - stats?: DatabaseNameList; - suggestField?: string; - suggestMode?: 'missing' | 'popular' | 'always'; - suggestSize?: number; - suggestText?: string; - timeout?: string; - trackScores?: boolean; - version?: boolean; - requestCache?: boolean; - index?: DatabaseNameList; - type?: DatabaseNameList; -} - -export interface DatabaseSearchResponse { - took: number; - timed_out: boolean; - _scroll_id?: string; - _shards: DatabaseShardsResponse; - hits: { - total: number; - max_score: number; - hits: Array<{ - _index: string; - _id: string; - _score: number; - _source: T; - _seq_no?: number; - _primary_term?: number; - _explanation?: DatabaseExplanation; - fields?: any; - highlight?: any; - inner_hits?: any; - sort?: string[]; - }>; - }; - aggregations?: any; -} - -export interface DatabaseExplanation { - value: number; - description: string; - details: DatabaseExplanation[]; -} - -export interface DatabaseShardsResponse { - total: number; - successful: number; - failed: number; - skipped: number; -} - -export interface DatabaseGetDocumentResponse { - _index: string; - _id: string; - _seq_no: number; - _primary_term: number; - found: boolean; - _source: Source; -} - -export interface DatabaseBulkResponse { - took: number; - errors: boolean; - items: Array< - DatabaseDeleteDocumentResponse | DatabaseIndexDocumentResponse | DatabaseUpdateDocumentResponse - >; -} - -export interface DatabaseBulkIndexDocumentsParams extends DatabaseGenericParams { - waitForActiveShards?: string; - refresh?: DatabaseRefresh; - routing?: string; - timeout?: string; - fields?: DatabaseNameList; - _source?: DatabaseNameList; - _sourceExclude?: DatabaseNameList; - _source_includes?: DatabaseNameList; - pipeline?: string; - index?: string; -} - -export interface DatabaseMGetParams extends DatabaseGenericParams { - storedFields?: DatabaseNameList; - preference?: string; - realtime?: boolean; - refresh?: boolean; - _source?: DatabaseNameList; - _sourceExclude?: DatabaseNameList; - _source_includes?: DatabaseNameList; - index: string; -} - -export interface DatabaseMGetResponse { - docs?: Array>; -} - -export interface DatabasePutTemplateParams extends DatabaseGenericParams { - name: string; - body: any; -} - -export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { - waitForActiveShards?: string; - parent?: string; - refresh?: DatabaseRefresh; - routing?: string; - timeout?: string; - ifSeqNo?: number; - ifPrimaryTerm?: number; - index: string; - id: string; -} - -export interface DatabaseIndexDocumentResponse { - found: boolean; - _index: string; - _id: string; - _seq_no: number; - _primary_term: number; - result: string; -} - -export interface DatabaseUpdateDocumentResponse { - found: boolean; - _index: string; - _id: string; - _seq_no: number; - _primary_term: number; - result: string; -} - -export interface DatabaseDeleteDocumentResponse { - found: boolean; - _index: string; - _id: string; - _seq_no: number; - _primary_term: number; - result: string; -} - -export interface DatabaseIndexDocumentParams extends DatabaseGenericParams { - waitForActiveShards?: string; - opType?: 'index' | 'create'; - parent?: string; - refresh?: string; - routing?: string; - timeout?: string; - timestamp?: Date | number; - ttl?: string; - ifSeqNo?: number; - ifPrimaryTerm?: number; - pipeline?: string; - id?: string; - index: string; - body: T; -} - -export interface DatabaseGetResponse { - found: boolean; - _source: T; -} -export interface DatabaseCreateDocumentParams extends DatabaseGenericParams { - waitForActiveShards?: string; - parent?: string; - refresh?: DatabaseRefresh; - routing?: string; - timeout?: string; - timestamp?: Date | number; - ttl?: string; - ifSeqNo?: number; - ifPrimaryTerm?: number; - pipeline?: string; - id?: string; - index: string; -} - -export interface DatabaseCreateDocumentResponse { - created: boolean; - result: string; -} - -export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { - waitForActiveShards?: string; - parent?: string; - refresh?: DatabaseRefresh; - routing?: string; - timeout?: string; - ifSeqNo?: number; - ifPrimaryTerm?: number; - index: string; - id: string; -} - -export interface DatabaseGetParams extends DatabaseGenericParams { - storedFields?: DatabaseNameList; - parent?: string; - preference?: string; - realtime?: boolean; - refresh?: boolean; - routing?: string; - _source?: DatabaseNameList; - _sourceExclude?: DatabaseNameList; - _source_includes?: DatabaseNameList; - ifSeqNo?: number; - ifPrimaryTerm?: number; - id: string; - index: string; -} - -export type DatabaseNameList = string | string[] | boolean; -export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | ''; -export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all'; -export type DefaultOperator = 'AND' | 'OR'; -export type DatabaseConflicts = 'abort' | 'proceed'; - -export interface DatabaseGenericParams { - requestTimeout?: number; - maxRetries?: number; - method?: string; - body?: any; - ignore?: number | number[]; - filterPath?: string | string[]; -} - -export interface DatabaseDeleteDocumentResponse { - found: boolean; - _index: string; - _type: string; - _id: string; - _seq_no: number; - _primary_term: number; - result: string; -} diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts deleted file mode 100644 index 1ca3bcae8bfca6..00000000000000 --- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { FrameworkUser } from '../framework/adapter_types'; -import { internalAuthData } from './../framework/adapter_types'; -import { - DatabaseAdapter, - DatabaseBulkIndexDocumentsParams, - DatabaseCreateDocumentParams, - DatabaseCreateDocumentResponse, - DatabaseDeleteDocumentParams, - DatabaseDeleteDocumentResponse, - DatabaseGetDocumentResponse, - DatabaseGetParams, - DatabaseIndexDocumentParams, - DatabaseKbnESCluster, - DatabaseKbnESPlugin, - DatabaseMGetParams, - DatabaseMGetResponse, - DatabaseSearchParams, - DatabaseSearchResponse, -} from './adapter_types'; - -export class KibanaDatabaseAdapter implements DatabaseAdapter { - private es: DatabaseKbnESCluster; - - constructor(kbnElasticSearch: DatabaseKbnESPlugin) { - this.es = kbnElasticSearch.getCluster('admin'); - } - - public async get( - user: FrameworkUser, - params: DatabaseGetParams - ): Promise> { - const result = await this.callWithUser(user, 'get', params); - return result; - // todo - } - - public async mget( - user: FrameworkUser, - params: DatabaseMGetParams - ): Promise> { - const result = await this.callWithUser(user, 'mget', params); - return result; - // todo - } - - public async bulk(user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams): Promise { - const result = await this.callWithUser(user, 'bulk', params); - return result; - } - - public async create( - user: FrameworkUser, - params: DatabaseCreateDocumentParams - ): Promise { - const result = await this.callWithUser(user, 'create', params); - return result; - } - public async index(user: FrameworkUser, params: DatabaseIndexDocumentParams): Promise { - const result = await this.callWithUser(user, 'index', params); - return result; - } - public async delete( - user: FrameworkUser, - params: DatabaseDeleteDocumentParams - ): Promise { - const result = await this.callWithUser(user, 'delete', params); - return result; - } - - public async deleteByQuery( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise { - const result = await this.callWithUser(user, 'deleteByQuery', params); - return result; - } - - public async search( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise> { - const result = await this.callWithUser(user, 'search', params); - return result; - } - - public async searchAll( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise> { - const result = await this.callWithUser(user, 'search', { - scroll: '1m', - ...params, - body: { - size: 1000, - ...params.body, - }, - }); - return result; - } - - public async putTemplate(name: string, template: any): Promise { - const result = await this.callWithUser({ kind: 'internal' }, 'indices.putTemplate', { - name, - body: template, - }); - - return result; - } - - private callWithUser(user: FrameworkUser, esMethod: string, options: any = {}): any { - if (user.kind === 'authenticated') { - return this.es.callWithRequest( - { - headers: user[internalAuthData], - } as any, - esMethod, - options - ); - } else if (user.kind === 'internal') { - return this.es.callWithInternalUser(esMethod, options); - } else { - throw new Error('Invalid user type'); - } - } -} diff --git a/x-pack/legacy/plugins/ingest/server/kibana.index.ts b/x-pack/legacy/plugins/ingest/server/kibana.index.ts index 8dbdbdd04a4b42..0169bbff398eb4 100644 --- a/x-pack/legacy/plugins/ingest/server/kibana.index.ts +++ b/x-pack/legacy/plugins/ingest/server/kibana.index.ts @@ -8,5 +8,6 @@ import { compose } from './libs/compose/kibana'; export const initServerWithKibana = (hapiServer: any) => { const libs = compose(hapiServer); - libs.framework.log('Ingest is composed -- debug message'); + + libs.framework.exposeMethod('create'); }; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index dbbdaa9471f346..5fbb32375cadc7 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -4,98 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SODatabaseAdapter } from '../so_database/default'; -import { RuntimeConfigurationFile, NewConfigurationFile } from './adapter_types'; - -import { ConfigurationFile, DatasourceInput, BackupConfigurationFile } from './adapter_types'; +import { + ConfigurationFile, + NewConfigurationFile, + DatasourceInput, + BackupConfigurationFile, +} from './adapter_types'; export class ConfigAdapter { - constructor(private readonly so: SODatabaseAdapter) {} - public async create( configuration: NewConfigurationFile ): Promise<{ id: string; shared_id: string; version: number }> { - const newSo = await this.so.create( - 'configurations', - (configuration as any) as ConfigurationFile - ); - return { - id: newSo.id, - shared_id: newSo.attributes.shared_id, - version: newSo.attributes.version, + id: 'fsdfsdf', + shared_id: 'wjkhefkjhfkjs', + version: 0, }; } - public async get(id: string): Promise { - const config = await this.so.get('configurations', id); - - if (config.error) { - throw new Error(config.error.message); - } - - if (!config.attributes) { - throw new Error(`No configuration found with ID of ${id}`); - } - if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { - return config.attributes as ConfigurationFile; - } else { - throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); - } + public async get(sharedID: string, version?: number): Promise { + return {} as ConfigurationFile; } - public async list(): Promise { - const configs = await this.so.find({ - type: 'configurations', - search: '*', - searchFields: ['shared_id'], - }); - const uniqConfigurationFile = configs.saved_objects - .map(config => { - if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { - return config.attributes; - } else { - throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); - } - }) - .reduce((acc, config: ConfigurationFile) => { - if (!acc.has(config.shared_id)) { - acc.set(config.shared_id, config); - } - const prevConfig = acc.get(config.shared_id); - if (prevConfig && prevConfig.version < config.version) { - acc.set(config.shared_id, config); - } - - return acc; - }, new Map()); - - return [...uniqConfigurationFile.values()]; - } - - public async listVersions(sharedID: string, activeOnly = true): Promise { - const configs = (await this.so.find({ - type: 'configurations', - search: sharedID, - searchFields: ['shared_id'], - })).saved_objects; - - if (!activeOnly) { - const backupConfigs = await this.so.find({ - type: 'backup_configurations', - search: sharedID, - searchFields: ['shared_id'], - }); - configs.concat(backupConfigs.saved_objects); - } - - return configs.map(config => { - if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { - return config.attributes; - } else { - throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); - } - }); + public async list(sharedID: string, version?: number): Promise { + return [{} as ConfigurationFile]; } public async update( diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts index e412d61ed9186a..065b99dbb77b64 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts @@ -8,32 +8,31 @@ import { Lifecycle, ResponseToolkit } from 'hapi'; import * as t from 'io-ts'; -import { Legacy } from 'kibana'; -import { Cluster, ClusterConfig } from 'src/legacy/core_plugins/elasticsearch'; -import { ApmOssPlugin } from 'src/legacy/core_plugins/apm_oss'; -import { Request } from 'src/legacy/server/kbn_server'; -import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; -import { - Feature, - FeatureWithAllOrReadPrivileges, -} from '../../../../../xpack_main/server/lib/feature_registry/feature_registry'; -import { SecurityPlugin } from '../../../../../security'; +import { LICENSES } from '../../../../common/constants/security'; export const internalAuthData = Symbol('internalAuthData'); export const internalUser: FrameworkInternalUser = { kind: 'internal', }; -export interface KibanaLegacyServer extends Legacy.Server { +export interface XpackInfo { + license: { + getType: () => typeof LICENSES[number]; + /** Is the license expired */ + isActive: () => boolean; + getExpiryDateInMillis: () => number; + }; + feature: (pluginId: string) => any; + isAvailable: () => boolean; +} + +export interface KibanaLegacyServer { plugins: { xpack_main: { status: { on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; }; - info: XPackInfo; - createXPackInfo(options: any): any; - getFeatures(): Feature[]; - registerFeature(feature: FeatureWithAllOrReadPrivileges): void; + info: XpackInfo; }; kibana: { status: { @@ -42,20 +41,18 @@ export interface KibanaLegacyServer extends Legacy.Server { }; }; }; - security: SecurityPlugin; + security: { + getUser: (request: KibanaServerRequest) => any; + }; elasticsearch: { status: { on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; }; - getCluster(name: string): Cluster; - createCluster(name: string, config: ClusterConfig): Cluster; - waitUntilReady(): Promise; + getCluster: () => any; }; - spaces: any; - apm_oss: ApmOssPlugin; - ingest: any; + ingest: {}; }; - expose: { (key: string, value: any): void; (obj: object): void }; + expose: (name: string, value: any) => void; config: () => any; route: (routeConfig: any) => void; log: (message: string) => void; @@ -100,6 +97,7 @@ export const RuntimeKibanaServerRequest = t.interface( }, 'KibanaServerRequest' ); +export interface KibanaServerRequest extends t.TypeOf {} export const RuntimeKibanaUser = t.interface( { @@ -135,27 +133,32 @@ export type FrameworkUser = | FrameworkAuthenticatedUser | FrameworkUnAuthenticatedUser | FrameworkInternalUser; -export interface FrameworkRequest = Request> { +export interface FrameworkRequest< + KibanaServerRequestGenaric extends Partial = any +> { user: FrameworkUser; headers: KibanaServerRequestGenaric['headers']; - info: Request['info']; + info: KibanaServerRequest['info']; payload: KibanaServerRequestGenaric['payload']; params: KibanaServerRequestGenaric['params']; query: KibanaServerRequestGenaric['query']; } -export interface FrameworkRouteOptions { +export interface FrameworkRouteOptions< + RouteRequest extends FrameworkRequest = FrameworkRequest, + RouteResponse extends FrameworkResponse = any +> { path: string; method: string | string[]; vhost?: string; licenseRequired?: string[]; requiredRoles?: string[]; - handler: FrameworkRouteHandler; + handler: FrameworkRouteHandler; config?: {}; } export type FrameworkRouteHandler< - RouteRequest extends Request, + RouteRequest extends KibanaServerRequest, RouteResponse extends FrameworkResponse > = (request: FrameworkRequest, h: ResponseToolkit) => Promise; diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts index 3b3bf058092bc7..a0e348e5e3370f 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts @@ -9,22 +9,20 @@ import { PLUGIN } from '../../../common/constants'; import { CONFIG_PREFIX } from '../../../common/constants/plugin'; import { DatabaseKbnESPlugin } from '../adapters/es_database/adapter_types'; import { ESDatabaseAdapter } from '../adapters/es_database/default'; +import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; import { BackendFrameworkAdapter } from '../adapters/framework/default'; import { ServerLibs } from '../types'; import { BackendFrameworkLib } from './../framework'; import { ConfigurationLib } from '../configuration'; import { ConfigAdapter } from '../adapters/configurations/default'; -import { SODatabaseAdapter } from '../adapters/so_database/default'; -import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; export function compose(server: KibanaLegacyServer): ServerLibs { const framework = new BackendFrameworkLib( new BackendFrameworkAdapter(camelCase(PLUGIN.ID), server, CONFIG_PREFIX) ); const database = new ESDatabaseAdapter(server.plugins.elasticsearch as DatabaseKbnESPlugin); - const soDatabase = new SODatabaseAdapter(server.savedObjects, server.plugins.elasticsearch); - const configAdapter = new ConfigAdapter(soDatabase); + const configAdapter = new ConfigAdapter(); const configuration = new ConfigurationLib(configAdapter); const libs: ServerLibs = { diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index 6dad8c69c5b399..4f2878d203fdb1 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -8,8 +8,10 @@ import { ConfigAdapter } from './adapters/configurations/default'; export class ConfigurationLib { constructor(private readonly adapter: ConfigAdapter) {} - public async rollForward(id: string): Promise<{ id: string; version: number }> { - this.adapter.get(id); + public async rollForward( + sharedID: string, + version?: number + ): Promise<{ id: string; version: number }> { return { id: 'fsdfsdf', version: 0, diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts index 7dadd94c9a2462..2fd6d9defd4d30 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/framework.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'src/legacy/server/kbn_server'; +import { KibanaServerRequest } from './adapters/framework/adapter_types'; import { BackendFrameworkAdapter } from './adapters/framework/default'; export class BackendFrameworkLib { @@ -21,7 +21,7 @@ export class BackendFrameworkLib { public internalUser = this.adapter.internalUser; constructor(private readonly adapter: BackendFrameworkAdapter) {} - public getCurrentUser(request: Request) { + public getCurrentUser(request: KibanaServerRequest) { return this.adapter.getUser(request); } public getSetting(setting: 'defaultUserRoles'): string[]; From e92e872faa77cd6ef1a9e2216c946f9592d2b1a7 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 23 Jul 2019 10:11:55 -0400 Subject: [PATCH 14/27] tack down WIP code --- x-pack/legacy/plugins/ingest/server/kibana.index.ts | 3 +-- x-pack/legacy/plugins/ingest/server/libs/configuration.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/ingest/server/kibana.index.ts b/x-pack/legacy/plugins/ingest/server/kibana.index.ts index 0169bbff398eb4..8dbdbdd04a4b42 100644 --- a/x-pack/legacy/plugins/ingest/server/kibana.index.ts +++ b/x-pack/legacy/plugins/ingest/server/kibana.index.ts @@ -8,6 +8,5 @@ import { compose } from './libs/compose/kibana'; export const initServerWithKibana = (hapiServer: any) => { const libs = compose(hapiServer); - - libs.framework.exposeMethod('create'); + libs.framework.log('Ingest is composed -- debug message'); }; diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index 4f2878d203fdb1..fdf2c0504f4d65 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -12,6 +12,7 @@ export class ConfigurationLib { sharedID: string, version?: number ): Promise<{ id: string; version: number }> { + this.adapter.get(sharedID, version); return { id: 'fsdfsdf', version: 0, From 7ed7ec2c4db233697cd9d19e1fca701ac9d9ef59 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 23 Jul 2019 11:30:58 -0400 Subject: [PATCH 15/27] remove things not or not yet needed --- .../server/lib/compose/testing.ts | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts diff --git a/x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts b/x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts deleted file mode 100644 index b5fe6195fc7c70..00000000000000 --- a/x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; -import { MemoryConfigurationBlockAdapter } from '../adapters/configuration_blocks/memory_tags_adapter'; -import { HapiBackendFrameworkAdapter } from '../adapters/framework/hapi_framework_adapter'; -import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; -import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; -import { BeatEventsLib } from '../beat_events'; -import { CMBeatsDomain } from '../beats'; -import { ConfigurationBlocksLib } from '../configuration_blocks'; -import { BackendFrameworkLib } from '../framework'; -import { CMTagsDomain } from '../tags'; -import { CMTokensDomain } from '../tokens'; -import { CMServerLibs } from '../types'; - -export function compose(server: any): CMServerLibs { - const framework = new BackendFrameworkLib(new HapiBackendFrameworkAdapter(undefined, server)); - - const beatsAdapter = new MemoryBeatsAdapter(server.beatsDB || []); - const configAdapter = new MemoryConfigurationBlockAdapter(server.configsDB || []); - const tags = new CMTagsDomain( - new MemoryTagsAdapter(server.tagsDB || []), - configAdapter, - beatsAdapter - ); - const configurationBlocks = new ConfigurationBlocksLib(configAdapter, tags); - const tokens = new CMTokensDomain(new MemoryTokensAdapter(server.tokensDB || []), { - framework, - }); - const beats = new CMBeatsDomain(beatsAdapter, { - tags, - tokens, - framework, - }); - const beatEvents = new BeatEventsLib({} as any, beats); - - const libs: CMServerLibs = { - beatEvents, - framework, - beats, - tags, - tokens, - configurationBlocks, - }; - - return libs; -} From 9f030a9bb2c8550bc86f00ce1aca36be046118b2 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 23 Jul 2019 13:09:52 -0400 Subject: [PATCH 16/27] fix more types, define SO --- .../libs/adapters/configurations/default.ts | 4 + .../libs/adapters/framework/adapter_types.ts | 59 +++++---- .../libs/adapters/so_database/default.ts | 117 +----------------- .../ingest/server/libs/compose/kibana.ts | 6 +- .../plugins/ingest/server/libs/framework.ts | 4 +- 5 files changed, 39 insertions(+), 151 deletions(-) diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index 5fbb32375cadc7..84a352177d2f66 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SODatabaseAdapter } from '../so_database/default'; + import { ConfigurationFile, NewConfigurationFile, @@ -12,6 +14,8 @@ import { } from './adapter_types'; export class ConfigAdapter { + constructor(private readonly so: SODatabaseAdapter) {} + public async create( configuration: NewConfigurationFile ): Promise<{ id: string; shared_id: string; version: number }> { diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts index 065b99dbb77b64..e412d61ed9186a 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/adapter_types.ts @@ -8,31 +8,32 @@ import { Lifecycle, ResponseToolkit } from 'hapi'; import * as t from 'io-ts'; -import { LICENSES } from '../../../../common/constants/security'; +import { Legacy } from 'kibana'; +import { Cluster, ClusterConfig } from 'src/legacy/core_plugins/elasticsearch'; +import { ApmOssPlugin } from 'src/legacy/core_plugins/apm_oss'; +import { Request } from 'src/legacy/server/kbn_server'; +import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; +import { + Feature, + FeatureWithAllOrReadPrivileges, +} from '../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { SecurityPlugin } from '../../../../../security'; export const internalAuthData = Symbol('internalAuthData'); export const internalUser: FrameworkInternalUser = { kind: 'internal', }; -export interface XpackInfo { - license: { - getType: () => typeof LICENSES[number]; - /** Is the license expired */ - isActive: () => boolean; - getExpiryDateInMillis: () => number; - }; - feature: (pluginId: string) => any; - isAvailable: () => boolean; -} - -export interface KibanaLegacyServer { +export interface KibanaLegacyServer extends Legacy.Server { plugins: { xpack_main: { status: { on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; }; - info: XpackInfo; + info: XPackInfo; + createXPackInfo(options: any): any; + getFeatures(): Feature[]; + registerFeature(feature: FeatureWithAllOrReadPrivileges): void; }; kibana: { status: { @@ -41,18 +42,20 @@ export interface KibanaLegacyServer { }; }; }; - security: { - getUser: (request: KibanaServerRequest) => any; - }; + security: SecurityPlugin; elasticsearch: { status: { on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void; }; - getCluster: () => any; + getCluster(name: string): Cluster; + createCluster(name: string, config: ClusterConfig): Cluster; + waitUntilReady(): Promise; }; - ingest: {}; + spaces: any; + apm_oss: ApmOssPlugin; + ingest: any; }; - expose: (name: string, value: any) => void; + expose: { (key: string, value: any): void; (obj: object): void }; config: () => any; route: (routeConfig: any) => void; log: (message: string) => void; @@ -97,7 +100,6 @@ export const RuntimeKibanaServerRequest = t.interface( }, 'KibanaServerRequest' ); -export interface KibanaServerRequest extends t.TypeOf {} export const RuntimeKibanaUser = t.interface( { @@ -133,32 +135,27 @@ export type FrameworkUser = | FrameworkAuthenticatedUser | FrameworkUnAuthenticatedUser | FrameworkInternalUser; -export interface FrameworkRequest< - KibanaServerRequestGenaric extends Partial = any -> { +export interface FrameworkRequest = Request> { user: FrameworkUser; headers: KibanaServerRequestGenaric['headers']; - info: KibanaServerRequest['info']; + info: Request['info']; payload: KibanaServerRequestGenaric['payload']; params: KibanaServerRequestGenaric['params']; query: KibanaServerRequestGenaric['query']; } -export interface FrameworkRouteOptions< - RouteRequest extends FrameworkRequest = FrameworkRequest, - RouteResponse extends FrameworkResponse = any -> { +export interface FrameworkRouteOptions { path: string; method: string | string[]; vhost?: string; licenseRequired?: string[]; requiredRoles?: string[]; - handler: FrameworkRouteHandler; + handler: FrameworkRouteHandler; config?: {}; } export type FrameworkRouteHandler< - RouteRequest extends KibanaServerRequest, + RouteRequest extends Request, RouteResponse extends FrameworkResponse > = (request: FrameworkRequest, h: ResponseToolkit) => Promise; diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts index b70d2d2c5e823e..e11c8b76967483 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts @@ -4,24 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SavedObjectsService, - SavedObjectsClient as SavedObjectsClientType, - SavedObjectAttributes, - SavedObjectsBulkCreateObject, - SavedObjectsBaseOptions, - SavedObjectsFindOptions, - SavedObjectsFindResponse, - SavedObjectsBulkResponse, - SavedObject, - SavedObjectsUpdateOptions, -} from 'src/core/server'; +import { SavedObjectsService, SavedObjectsClient as SavedObjectsClientType } from 'src/core/server'; import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; -import { - SavedObjectsCreateOptions, - SavedObjectsBulkGetObject, - SavedObjectsUpdateResponse, -} from 'target/types/server'; export class SODatabaseAdapter { private client: SavedObjectsClientType; @@ -32,103 +16,4 @@ export class SODatabaseAdapter { this.client = new SavedObjectsClient(internalRepository); } - - /** - * Persists a SavedObject - * - * @param type - * @param attributes - * @param options - */ - async create( - type: string, - data: T, - options?: SavedObjectsCreateOptions - ) { - return await this.client.create(type, data, options); - } - - /** - * Persists multiple documents batched together as a single request - * - * @param objects - * @param options - */ - async bulkCreate( - objects: Array>, - options?: SavedObjectsCreateOptions - ) { - return await this.client.bulkCreate(objects, options); - } - - /** - * Deletes a SavedObject - * - * @param type - * @param id - * @param options - */ - async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - return await this.client.delete(type, id, options); - } - - /** - * Find all SavedObjects matching the search query - * - * @param options - */ - async find( - options: SavedObjectsFindOptions - ): Promise> { - return await this.client.find(options); - } - - /** - * Returns an array of objects by id - * - * @param objects - an array of ids, or an array of objects containing id, type and optionally fields - * @example - * - * bulkGet([ - * { id: 'one', type: 'config' }, - * { id: 'foo', type: 'index-pattern' } - * ]) - */ - async bulkGet( - objects: SavedObjectsBulkGetObject[] = [], - options: SavedObjectsBaseOptions = {} - ): Promise> { - return await this.client.bulkGet(objects, options); - } - - /** - * Retrieves a single object - * - * @param type - The type of SavedObject to retrieve - * @param id - The ID of the SavedObject to retrieve - * @param options - */ - async get( - type: string, - id: string, - options: SavedObjectsBaseOptions = {} - ): Promise> { - return await this.client.get(type, id, options); - } - - /** - * Updates an SavedObject - * - * @param type - * @param id - * @param options - */ - async update( - type: string, - id: string, - attributes: Partial, - options: SavedObjectsUpdateOptions = {} - ): Promise> { - return await this.client.update(type, id, attributes, options); - } } diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts index a0e348e5e3370f..3b3bf058092bc7 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts @@ -9,20 +9,22 @@ import { PLUGIN } from '../../../common/constants'; import { CONFIG_PREFIX } from '../../../common/constants/plugin'; import { DatabaseKbnESPlugin } from '../adapters/es_database/adapter_types'; import { ESDatabaseAdapter } from '../adapters/es_database/default'; -import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; import { BackendFrameworkAdapter } from '../adapters/framework/default'; import { ServerLibs } from '../types'; import { BackendFrameworkLib } from './../framework'; import { ConfigurationLib } from '../configuration'; import { ConfigAdapter } from '../adapters/configurations/default'; +import { SODatabaseAdapter } from '../adapters/so_database/default'; +import { KibanaLegacyServer } from '../adapters/framework/adapter_types'; export function compose(server: KibanaLegacyServer): ServerLibs { const framework = new BackendFrameworkLib( new BackendFrameworkAdapter(camelCase(PLUGIN.ID), server, CONFIG_PREFIX) ); const database = new ESDatabaseAdapter(server.plugins.elasticsearch as DatabaseKbnESPlugin); + const soDatabase = new SODatabaseAdapter(server.savedObjects, server.plugins.elasticsearch); - const configAdapter = new ConfigAdapter(); + const configAdapter = new ConfigAdapter(soDatabase); const configuration = new ConfigurationLib(configAdapter); const libs: ServerLibs = { diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts index 2fd6d9defd4d30..7dadd94c9a2462 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/framework.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaServerRequest } from './adapters/framework/adapter_types'; +import { Request } from 'src/legacy/server/kbn_server'; import { BackendFrameworkAdapter } from './adapters/framework/default'; export class BackendFrameworkLib { @@ -21,7 +21,7 @@ export class BackendFrameworkLib { public internalUser = this.adapter.internalUser; constructor(private readonly adapter: BackendFrameworkAdapter) {} - public getCurrentUser(request: KibanaServerRequest) { + public getCurrentUser(request: Request) { return this.adapter.getUser(request); } public getSetting(setting: 'defaultUserRoles'): string[]; From 8cc1f612fe504be6180f6cc8aeaef77791b53ff9 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 23 Jul 2019 14:45:32 -0400 Subject: [PATCH 17/27] Fixed unused variables. Added a few methods to the SO adapter Co-authored-by: Nicolas Chaulet --- .../__integrations_tests__/adapter.test.ts | 5 + .../libs/adapters/configurations/default.ts | 90 ++++++++++++-- .../libs/adapters/so_database/default.ts | 117 +++++++++++++++++- 3 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts new file mode 100644 index 00000000000000..41bc2aa2588073 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index 84a352177d2f66..dbbdaa9471f346 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -5,13 +5,9 @@ */ import { SODatabaseAdapter } from '../so_database/default'; +import { RuntimeConfigurationFile, NewConfigurationFile } from './adapter_types'; -import { - ConfigurationFile, - NewConfigurationFile, - DatasourceInput, - BackupConfigurationFile, -} from './adapter_types'; +import { ConfigurationFile, DatasourceInput, BackupConfigurationFile } from './adapter_types'; export class ConfigAdapter { constructor(private readonly so: SODatabaseAdapter) {} @@ -19,19 +15,87 @@ export class ConfigAdapter { public async create( configuration: NewConfigurationFile ): Promise<{ id: string; shared_id: string; version: number }> { + const newSo = await this.so.create( + 'configurations', + (configuration as any) as ConfigurationFile + ); + return { - id: 'fsdfsdf', - shared_id: 'wjkhefkjhfkjs', - version: 0, + id: newSo.id, + shared_id: newSo.attributes.shared_id, + version: newSo.attributes.version, }; } - public async get(sharedID: string, version?: number): Promise { - return {} as ConfigurationFile; + public async get(id: string): Promise { + const config = await this.so.get('configurations', id); + + if (config.error) { + throw new Error(config.error.message); + } + + if (!config.attributes) { + throw new Error(`No configuration found with ID of ${id}`); + } + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes as ConfigurationFile; + } else { + throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); + } } - public async list(sharedID: string, version?: number): Promise { - return [{} as ConfigurationFile]; + public async list(): Promise { + const configs = await this.so.find({ + type: 'configurations', + search: '*', + searchFields: ['shared_id'], + }); + const uniqConfigurationFile = configs.saved_objects + .map(config => { + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes; + } else { + throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); + } + }) + .reduce((acc, config: ConfigurationFile) => { + if (!acc.has(config.shared_id)) { + acc.set(config.shared_id, config); + } + const prevConfig = acc.get(config.shared_id); + if (prevConfig && prevConfig.version < config.version) { + acc.set(config.shared_id, config); + } + + return acc; + }, new Map()); + + return [...uniqConfigurationFile.values()]; + } + + public async listVersions(sharedID: string, activeOnly = true): Promise { + const configs = (await this.so.find({ + type: 'configurations', + search: sharedID, + searchFields: ['shared_id'], + })).saved_objects; + + if (!activeOnly) { + const backupConfigs = await this.so.find({ + type: 'backup_configurations', + search: sharedID, + searchFields: ['shared_id'], + }); + configs.concat(backupConfigs.saved_objects); + } + + return configs.map(config => { + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes; + } else { + throw new Error(`Invalid ConfigurationFile data. == ${config.attributes}`); + } + }); } public async update( diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts index e11c8b76967483..b70d2d2c5e823e 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/so_database/default.ts @@ -4,8 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsService, SavedObjectsClient as SavedObjectsClientType } from 'src/core/server'; +import { + SavedObjectsService, + SavedObjectsClient as SavedObjectsClientType, + SavedObjectAttributes, + SavedObjectsBulkCreateObject, + SavedObjectsBaseOptions, + SavedObjectsFindOptions, + SavedObjectsFindResponse, + SavedObjectsBulkResponse, + SavedObject, + SavedObjectsUpdateOptions, +} from 'src/core/server'; import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; +import { + SavedObjectsCreateOptions, + SavedObjectsBulkGetObject, + SavedObjectsUpdateResponse, +} from 'target/types/server'; export class SODatabaseAdapter { private client: SavedObjectsClientType; @@ -16,4 +32,103 @@ export class SODatabaseAdapter { this.client = new SavedObjectsClient(internalRepository); } + + /** + * Persists a SavedObject + * + * @param type + * @param attributes + * @param options + */ + async create( + type: string, + data: T, + options?: SavedObjectsCreateOptions + ) { + return await this.client.create(type, data, options); + } + + /** + * Persists multiple documents batched together as a single request + * + * @param objects + * @param options + */ + async bulkCreate( + objects: Array>, + options?: SavedObjectsCreateOptions + ) { + return await this.client.bulkCreate(objects, options); + } + + /** + * Deletes a SavedObject + * + * @param type + * @param id + * @param options + */ + async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + return await this.client.delete(type, id, options); + } + + /** + * Find all SavedObjects matching the search query + * + * @param options + */ + async find( + options: SavedObjectsFindOptions + ): Promise> { + return await this.client.find(options); + } + + /** + * Returns an array of objects by id + * + * @param objects - an array of ids, or an array of objects containing id, type and optionally fields + * @example + * + * bulkGet([ + * { id: 'one', type: 'config' }, + * { id: 'foo', type: 'index-pattern' } + * ]) + */ + async bulkGet( + objects: SavedObjectsBulkGetObject[] = [], + options: SavedObjectsBaseOptions = {} + ): Promise> { + return await this.client.bulkGet(objects, options); + } + + /** + * Retrieves a single object + * + * @param type - The type of SavedObject to retrieve + * @param id - The ID of the SavedObject to retrieve + * @param options + */ + async get( + type: string, + id: string, + options: SavedObjectsBaseOptions = {} + ): Promise> { + return await this.client.get(type, id, options); + } + + /** + * Updates an SavedObject + * + * @param type + * @param id + * @param options + */ + async update( + type: string, + id: string, + attributes: Partial, + options: SavedObjectsUpdateOptions = {} + ): Promise> { + return await this.client.update(type, id, attributes, options); + } } From 1a96780566a49910320cb0526334fa3788df4504 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 24 Jul 2019 08:32:54 -0400 Subject: [PATCH 18/27] fix type --- x-pack/legacy/plugins/ingest/server/libs/configuration.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index fdf2c0504f4d65..6dad8c69c5b399 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -8,11 +8,8 @@ import { ConfigAdapter } from './adapters/configurations/default'; export class ConfigurationLib { constructor(private readonly adapter: ConfigAdapter) {} - public async rollForward( - sharedID: string, - version?: number - ): Promise<{ id: string; version: number }> { - this.adapter.get(sharedID, version); + public async rollForward(id: string): Promise<{ id: string; version: number }> { + this.adapter.get(id); return { id: 'fsdfsdf', version: 0, From 0c72c0433776aa0cb6ae133402a7d1dc4e276304 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 24 Jul 2019 10:15:58 -0400 Subject: [PATCH 19/27] removed blank test file --- .../configurations/__integrations_tests__/adapter.test.ts | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts deleted file mode 100644 index 41bc2aa2588073..00000000000000 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/__integrations_tests__/adapter.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ From 8a4acc98ac35ccf880845d67aa2e789656e9a202 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 29 Jul 2019 15:39:30 -0400 Subject: [PATCH 20/27] add config adapter (no tests yet) --- .../adapters/configurations/adapter_types.ts | 1 + .../libs/adapters/configurations/default.ts | 103 +++++++++++++----- .../ingest/server/libs/configuration.ts | 5 +- 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts index c29b4c142c8180..31dc63451d3f71 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts @@ -10,6 +10,7 @@ export const RuntimeDatasourceInput = t.interface( { id: t.string, meta: t.union([t.undefined, t.string]), + config_id: t.string, config: t.string, }, 'DatasourceInput' diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index dbbdaa9471f346..aed8800e52148c 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -28,7 +28,7 @@ export class ConfigAdapter { } public async get(id: string): Promise { - const config = await this.so.get('configurations', id); + const config = await this.so.get('configurations', id); if (config.error) { throw new Error(config.error.message); @@ -99,56 +99,109 @@ export class ConfigAdapter { } public async update( - sharedID: string, - fromVersion: number, + id: string, configuration: ConfigurationFile ): Promise<{ id: string; version: number }> { + const config = await this.so.update('configurations', id, configuration); + return { - id: 'fsdfsdf', - version: 0, + id: config.id, + version: config.attributes.version || 1, }; } - public async delete( - sharedID: string, - version?: number - ): Promise<{ success: boolean; error?: string }> { + public async delete(id: string): Promise<{ success: boolean }> { + await this.so.delete('configurations', id); return { success: true, }; } public async createBackup( - sharedID: string, - version?: number + configuration: BackupConfigurationFile ): Promise<{ success: boolean; id?: string; error?: string }> { + const newSo = await this.so.create( + 'configurations', + (configuration as any) as ConfigurationFile + ); + return { - success: true, - id: 'k3jh5lk3j4h5kljh43', + success: newSo.error ? false : true, + id: newSo.id, + error: newSo.error ? newSo.error.message : undefined, }; } - public async getBackup(sharedID: string, version?: number): Promise { - return {} as BackupConfigurationFile; + public async getBackup(id: string): Promise { + const config = await this.so.get('backup_configurations', id); + + if (config.error) { + throw new Error(config.error.message); + } + + if (!config.attributes) { + throw new Error(`No backup configuration found with ID of ${id}`); + } + if (RuntimeConfigurationFile.decode(config.attributes).isRight()) { + return config.attributes as BackupConfigurationFile; + } else { + throw new Error(`Invalid BackupConfigurationFile data. == ${config.attributes}`); + } } /** * Inputs sub-domain type */ - public async getInputsById(ids: string[]): Promise { - return [{} as DatasourceInput]; + public async getInputsById( + ids: string[], + page: number = 1, + perPage: number = 25 + ): Promise { + const inputs = await this.so.find({ + type: 'configurations', + search: ids.reduce((query, id, i) => { + if (i === ids.length - 1) { + return `${query} ${id}`; + } + return `${query} ${id} |`; + }, ''), + searchFields: ['id'], + perPage, + page, + }); + + return inputs.saved_objects.map(input => input.attributes); } - public async addInputs( - sharedID: string, - version: number, - dsUUID: string, - input: DatasourceInput - ): Promise { - return 'htkjerhtkwerhtkjehr'; + public async listInputsforConfiguration( + configurationId: string, + page: number = 1, + perPage: number = 25 + ): Promise { + const inputs = await this.so.find({ + type: 'configurations', + search: configurationId, + searchFields: ['config_id'], + perPage, + page, + }); + + return inputs.saved_objects.map(input => input.attributes); } - public async deleteInputs(inputID: string[]): Promise<{ success: boolean; error?: string }> { + public async addInputs(inputs: DatasourceInput[]): Promise { + const newInputs = []; + for (const input of inputs) { + newInputs.push(await this.so.create('inputs', input)); + } + + return newInputs.map(input => input.attributes.id); + } + + public async deleteInputs(inputIDs: string[]): Promise<{ success: boolean }> { + for (const id of inputIDs) { + await this.so.delete('inputs', id); + } return { success: true, }; diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index 6dad8c69c5b399..027cea09868384 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -8,7 +8,10 @@ import { ConfigAdapter } from './adapters/configurations/default'; export class ConfigurationLib { constructor(private readonly adapter: ConfigAdapter) {} - public async rollForward(id: string): Promise<{ id: string; version: number }> { + public async rollForward( + id: string, + toVersion: number + ): Promise<{ id: string; version: number }> { this.adapter.get(id); return { id: 'fsdfsdf', From c16d4f646ff10121a52ed4de5c179c1904c9fd63 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 5 Aug 2019 11:02:11 -0400 Subject: [PATCH 21/27] progress with config lib --- .../ingest/common/utils/is_version_greater.ts | 41 +++++ .../adapters/configurations/adapter_types.ts | 2 +- .../libs/adapters/configurations/default.ts | 15 +- .../ingest/server/libs/configuration.ts | 164 +++++++++++++++++- .../plugins/ingest/server/libs/framework.ts | 3 + 5 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 x-pack/legacy/plugins/ingest/common/utils/is_version_greater.ts diff --git a/x-pack/legacy/plugins/ingest/common/utils/is_version_greater.ts b/x-pack/legacy/plugins/ingest/common/utils/is_version_greater.ts new file mode 100644 index 00000000000000..f1b18b87f16da4 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/common/utils/is_version_greater.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export function isVersionGreater(v1: string, v2: string): 1 | 0 | -1 { + const v1parts = v1.split('.'); + const v2parts = v2.split('.'); + + function isValidPart(x: string) { + return /^\d+$/.test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + throw new Error('versions are not valid'); + } + + while (v1parts.length < v2parts.length) v1parts.push('0'); + while (v2parts.length < v1parts.length) v2parts.push('0'); + + for (let i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + + if (v1parts[i] === v2parts[i]) { + continue; + } else if (v1parts[i] > v2parts[i]) { + return 1; + } else { + return -1; + } + } + + if (v1parts.length !== v2parts.length) { + return -1; + } + + return 0; +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts index 31dc63451d3f71..85d7f4ae697c80 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts @@ -52,7 +52,7 @@ const ExistingDocument = t.interface({ id: t.string, shared_id: t.string, version: t.number, - active: t.boolean, + status: t.union(['active', 'locked', 'inactive'].map(s => t.literal(s))), updated_at: t.string, created_by: t.union([t.undefined, t.string]), updated_on: t.string, diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index aed8800e52148c..a9c5563e7e1eb6 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -44,11 +44,13 @@ export class ConfigAdapter { } } - public async list(): Promise { + public async list(page: number = 1, perPage: number = 25): Promise { const configs = await this.so.find({ type: 'configurations', search: '*', searchFields: ['shared_id'], + page, + perPage, }); const uniqConfigurationFile = configs.saved_objects .map(config => { @@ -73,15 +75,22 @@ export class ConfigAdapter { return [...uniqConfigurationFile.values()]; } - public async listVersions(sharedID: string, activeOnly = true): Promise { + public async listVersions( + sharedID: string, + activeOnly = true, + page: number = 1, + perPage: number = 25 + ): Promise { const configs = (await this.so.find({ type: 'configurations', search: sharedID, searchFields: ['shared_id'], + page, + perPage, })).saved_objects; if (!activeOnly) { - const backupConfigs = await this.so.find({ + const backupConfigs = await this.so.find({ type: 'backup_configurations', search: sharedID, searchFields: ['shared_id'], diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index 027cea09868384..04ee15cf0a0384 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -3,19 +3,175 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { merge, omit } from 'lodash'; +import uuidv4 from 'uuid/v4'; import { ConfigAdapter } from './adapters/configurations/default'; +import { BackendFrameworkLib } from './framework'; +import { ConfigurationFile } from './adapters/configurations/adapter_types'; export class ConfigurationLib { - constructor(private readonly adapter: ConfigAdapter) {} + constructor( + private readonly adapter: ConfigAdapter, + private readonly libs: { + framework: BackendFrameworkLib; + } + ) {} + public async create(name: string, description?: string) { + if (!this.libs.framework.version) { + throw new Error('Could not get version information about Kibana from xpack'); + } - public async rollForward( + await this.adapter.create({ + name, + description: description || '', + output: 'defaut', + monitoring_enabled: true, + agent_version: this.libs.framework.version, + data_sources: [], + }); + } + + public async get(id: string): Promise { + const config = await this.adapter.get(id); + return config; + } + + public async list(page: number = 1, perPage: number = 25): Promise { + const configs = await this.adapter.list(page, perPage); + return configs; + } + + public async listVersions( + sharedID: string, + activeOnly = true, + page: number = 1, + perPage: number = 25 + ): Promise { + const configs = await this.adapter.listVersions(sharedID, activeOnly, page, perPage); + return configs; + } + + public async update( id: string, - toVersion: number + configuration: Partial<{ + name: string; + description: string; + output: string; + monitoring_enabled: boolean; + }> ): Promise<{ id: string; version: number }> { - this.adapter.get(id); + const invalidKeys = Object.keys(configuration).filter( + key => !['name', 'description', 'output', 'monitoring_enabled'].includes(key) + ); + + if (invalidKeys.length !== -1) { + throw new Error( + `Update was called with configuration paramaters that are not allowed: ${invalidKeys}` + ); + } + const oldConfig = await this.adapter.get(id); + + if (oldConfig.status === 'active') { + throw new Error( + `Config ${oldConfig.id} can not be updated becuase it is ${oldConfig.status}` + ); + } + + const newConfig = await this._update(oldConfig, configuration); + return newConfig; + } + + public async delete(id: string): Promise<{ success: boolean }> { + return await this.adapter.delete(id); + } + + public async createNewConfigFrom(configId: string) { + const { id, data_sources: dataSources, ...oldConfig } = await this.adapter.get(configId); + const newConfig = await this.adapter.create({ ...oldConfig, data_sources: [] }); + + const newDSs: ConfigurationFile['data_sources'] = []; + for (const ds of dataSources) { + // TODO page through vs one large query as this will break if there are more then 10k inputs + // a likely case for uptime + const oldInputs = await this.adapter.getInputsById(ds.inputs, 1, 10000); + const newInputs = await this.adapter.addInputs( + oldInputs.map(input => ({ + ...input, + id: uuidv4(), + config_id: newConfig.id, + })) + ); + + newDSs.push({ ...ds, uuid: uuidv4(), inputs: newInputs }); + } + + await this.adapter.update(newConfig.id, { + id: newConfig.id, + ...oldConfig, + data_sources: newDSs, + }); + // TODO fire events for fleet that update was made + } + + public async upgrade(configId: string, version: string) { + const { id, agent_version: agentVersion, ...oldConfig } = await this.adapter.get(configId); + const newConfig = await this.adapter.create({ ...oldConfig, agent_version: agentVersion }); + + // TODO: ensure new version is greater then old + // TODO: Ensure new version is a valid version number for agent + // TODO: ensure new version works with current ES version + + await this.adapter.update(newConfig.id, { + id: newConfig.id, + ...oldConfig, + agent_version: version, + }); + // TODO fire events for fleet that update was made + } + + public async finishUpdateFrom(id: string) {} + + public async rollForward(id: string): Promise<{ id: string; version: number }> { return { id: 'fsdfsdf', version: 0, }; } + + /** + * request* because in the future with an approval flow it will not directly make the change + */ + public async requestAddDataSource(id: string) { + const oldConfig = await this.adapter.get(id); + + if (oldConfig.status === 'active') { + throw new Error( + `Config ${oldConfig.id} can not be updated becuase it is ${oldConfig.status}` + ); + } + + // const newConfig = await this._update(oldConfig, configuration); + } + + /** + * request* because in the future with an approval flow it will not directly make the change + */ + public async requestDeleteDataSource() { + throw new Error('Not yet implamented'); + } + + public async listDataSources() { + throw new Error('Not yet implamented'); + } + + private async _update(oldConfig: ConfigurationFile, config: Partial) { + const newConfig = await this.adapter.create( + merge({}, omit(oldConfig, ['id']), config) + ); + + // TODO update oldConfig to set status to locked + // TODO fire events for fleet that update was made + + return newConfig; + } } diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts index 7dadd94c9a2462..65552a04c6437e 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/framework.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -15,6 +15,9 @@ export class BackendFrameworkLib { type: this.adapter.info ? this.adapter.info.license.type : 'unknown', expired: this.adapter.info ? this.adapter.info.license.expired : null, }; + public get version() { + return this.adapter.info ? this.adapter.info.kibana.version : null; + } public securityIsEnabled = this.adapter.info ? this.adapter.info.security.enabled : false; public log = this.adapter.log; public on = this.adapter.on.bind(this.adapter); From d9779e6aa243a7586c17ac0db360e4a01283626e Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 12 Aug 2019 18:53:57 -0400 Subject: [PATCH 22/27] working! --- src/test_utils/kbn_server.ts | 7 +- x-pack/index.js | 2 + .../lib/adapters/database/adapter_types.ts | 309 ++++++++++++++++++ .../database/kibana_database_adapter.ts | 130 ++++++++ .../plugins/ingest/common/constants/plugin.ts | 2 +- x-pack/legacy/plugins/ingest/index.ts | 4 + .../configurations.contract.test.ts.snap | 77 +++++ .../adapters/configurations/adapter_types.ts | 2 + .../libs/adapters/configurations/default.ts | 1 - .../libs/adapters/configurations/memorized.ts | 217 ++++++++++++ .../server/libs/adapters/framework/default.ts | 23 +- .../libs/adapters/framework/memorized.ts | 61 ++++ .../ingest/server/libs/compose/kibana.ts | 2 +- .../ingest/server/libs/configuration.ts | 18 +- .../libs/configurations.contract.test.ts | 68 ++++ .../plugins/ingest/server/libs/framework.ts | 24 +- .../legacy/plugins/ingest/server/mappings.ts | 67 ++++ .../test_utils/jest/contract_tests/servers.ts | 14 +- 18 files changed, 1005 insertions(+), 23 deletions(-) create mode 100644 x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts create mode 100644 x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/adapters/framework/memorized.ts create mode 100644 x-pack/legacy/plugins/ingest/server/libs/configurations.contract.test.ts create mode 100644 x-pack/legacy/plugins/ingest/server/mappings.ts diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 706a8050a0adb5..0a36915f693c31 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -120,8 +120,11 @@ export function createRoot(settings = {}, cliArgs: Partial = {}) { * @param {Object} [settings={}] Any config overrides for this instance. * @returns {Root} */ -export function createRootWithCorePlugins(settings = {}) { - return createRootWithSettings(defaultsDeep({}, settings, DEFAULT_SETTINGS_WITH_CORE_PLUGINS)); +export function createRootWithCorePlugins(settings = {}, cliArgs: Partial = {}) { + return createRootWithSettings( + defaultsDeep({}, settings, DEFAULT_SETTINGS_WITH_CORE_PLUGINS), + cliArgs + ); } /** diff --git a/x-pack/index.js b/x-pack/index.js index 9b62fc18616f78..05b3e75e2db5ba 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -43,6 +43,7 @@ import { encryptedSavedObjects } from './legacy/plugins/encrypted_saved_objects' import { snapshotRestore } from './legacy/plugins/snapshot_restore'; import { actions } from './legacy/plugins/actions'; import { alerting } from './legacy/plugins/alerting'; +import { ingest } from './legacy/plugins/ingest'; module.exports = function (kibana) { return [ @@ -85,5 +86,6 @@ module.exports = function (kibana) { snapshotRestore(kibana), actions(kibana), alerting(kibana), + ingest(kibana), ]; }; diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts new file mode 100644 index 00000000000000..0a06c3dcc6412d --- /dev/null +++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/adapter_types.ts @@ -0,0 +1,309 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; + +export interface DatabaseAdapter { + get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise>; + create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise; + index( + user: FrameworkUser, + params: DatabaseIndexDocumentParams + ): Promise; + delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise; + deleteByQuery( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise; + mget(user: FrameworkUser, params: DatabaseMGetParams): Promise>; + bulk( + user: FrameworkUser, + params: DatabaseBulkIndexDocumentsParams + ): Promise; + search(user: FrameworkUser, params: DatabaseSearchParams): Promise>; + searchAll( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise>; + putTemplate(name: string, template: any): Promise; +} + +export interface DatabaseKbnESCluster { + callWithInternalUser(esMethod: string, options: {}): Promise; + callWithRequest(req: FrameworkRequest, esMethod: string, options: {}): Promise; +} + +export interface DatabaseKbnESPlugin { + getCluster(clusterName: string): DatabaseKbnESCluster; +} + +export interface DatabaseSearchParams extends DatabaseGenericParams { + analyzer?: string; + analyzeWildcard?: boolean; + defaultOperator?: DefaultOperator; + df?: string; + explain?: boolean; + storedFields?: DatabaseNameList; + docvalueFields?: DatabaseNameList; + fielddataFields?: DatabaseNameList; + from?: number; + ignoreUnavailable?: boolean; + allowNoIndices?: boolean; + expandWildcards?: ExpandWildcards; + lenient?: boolean; + lowercaseExpandedTerms?: boolean; + preference?: string; + q?: string; + routing?: DatabaseNameList; + scroll?: string; + searchType?: 'query_then_fetch' | 'dfs_query_then_fetch'; + size?: number; + sort?: DatabaseNameList; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + terminateAfter?: number; + stats?: DatabaseNameList; + suggestField?: string; + suggestMode?: 'missing' | 'popular' | 'always'; + suggestSize?: number; + suggestText?: string; + timeout?: string; + trackScores?: boolean; + version?: boolean; + requestCache?: boolean; + index?: DatabaseNameList; + type?: DatabaseNameList; +} + +export interface DatabaseSearchResponse { + took: number; + timed_out: boolean; + _scroll_id?: string; + _shards: DatabaseShardsResponse; + hits: { + total: number; + max_score: number; + hits: Array<{ + _index: string; + _id: string; + _score: number; + _source: T; + _seq_no?: number; + _primary_term?: number; + _explanation?: DatabaseExplanation; + fields?: any; + highlight?: any; + inner_hits?: any; + sort?: string[]; + }>; + }; + aggregations?: any; +} + +export interface DatabaseExplanation { + value: number; + description: string; + details: DatabaseExplanation[]; +} + +export interface DatabaseShardsResponse { + total: number; + successful: number; + failed: number; + skipped: number; +} + +export interface DatabaseGetDocumentResponse { + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + found: boolean; + _source: Source; +} + +export interface DatabaseBulkResponse { + took: number; + errors: boolean; + items: Array< + DatabaseDeleteDocumentResponse | DatabaseIndexDocumentResponse | DatabaseUpdateDocumentResponse + >; +} + +export interface DatabaseBulkIndexDocumentsParams extends DatabaseGenericParams { + waitForActiveShards?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + fields?: DatabaseNameList; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + pipeline?: string; + index?: string; +} + +export interface DatabaseMGetParams extends DatabaseGenericParams { + storedFields?: DatabaseNameList; + preference?: string; + realtime?: boolean; + refresh?: boolean; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + index: string; +} + +export interface DatabaseMGetResponse { + docs?: Array>; +} + +export interface DatabasePutTemplateParams extends DatabaseGenericParams { + name: string; + body: any; +} + +export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + index: string; + id: string; +} + +export interface DatabaseIndexDocumentResponse { + found: boolean; + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} + +export interface DatabaseUpdateDocumentResponse { + found: boolean; + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} + +export interface DatabaseDeleteDocumentResponse { + found: boolean; + _index: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} + +export interface DatabaseIndexDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + opType?: 'index' | 'create'; + parent?: string; + refresh?: string; + routing?: string; + timeout?: string; + timestamp?: Date | number; + ttl?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + pipeline?: string; + id?: string; + index: string; + body: T; +} + +export interface DatabaseGetResponse { + found: boolean; + _source: T; +} +export interface DatabaseCreateDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + timestamp?: Date | number; + ttl?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + pipeline?: string; + id?: string; + index: string; +} + +export interface DatabaseCreateDocumentResponse { + created: boolean; + result: string; +} + +export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + ifSeqNo?: number; + ifPrimaryTerm?: number; + index: string; + id: string; +} + +export interface DatabaseGetParams extends DatabaseGenericParams { + storedFields?: DatabaseNameList; + parent?: string; + preference?: string; + realtime?: boolean; + refresh?: boolean; + routing?: string; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _source_includes?: DatabaseNameList; + ifSeqNo?: number; + ifPrimaryTerm?: number; + id: string; + index: string; +} + +export type DatabaseNameList = string | string[] | boolean; +export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | ''; +export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all'; +export type DefaultOperator = 'AND' | 'OR'; +export type DatabaseConflicts = 'abort' | 'proceed'; + +export interface DatabaseGenericParams { + requestTimeout?: number; + maxRetries?: number; + method?: string; + body?: any; + ignore?: number | number[]; + filterPath?: string | string[]; +} + +export interface DatabaseDeleteDocumentResponse { + found: boolean; + _index: string; + _type: string; + _id: string; + _seq_no: number; + _primary_term: number; + result: string; +} diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts new file mode 100644 index 00000000000000..1ca3bcae8bfca6 --- /dev/null +++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkUser } from '../framework/adapter_types'; +import { internalAuthData } from './../framework/adapter_types'; +import { + DatabaseAdapter, + DatabaseBulkIndexDocumentsParams, + DatabaseCreateDocumentParams, + DatabaseCreateDocumentResponse, + DatabaseDeleteDocumentParams, + DatabaseDeleteDocumentResponse, + DatabaseGetDocumentResponse, + DatabaseGetParams, + DatabaseIndexDocumentParams, + DatabaseKbnESCluster, + DatabaseKbnESPlugin, + DatabaseMGetParams, + DatabaseMGetResponse, + DatabaseSearchParams, + DatabaseSearchResponse, +} from './adapter_types'; + +export class KibanaDatabaseAdapter implements DatabaseAdapter { + private es: DatabaseKbnESCluster; + + constructor(kbnElasticSearch: DatabaseKbnESPlugin) { + this.es = kbnElasticSearch.getCluster('admin'); + } + + public async get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise> { + const result = await this.callWithUser(user, 'get', params); + return result; + // todo + } + + public async mget( + user: FrameworkUser, + params: DatabaseMGetParams + ): Promise> { + const result = await this.callWithUser(user, 'mget', params); + return result; + // todo + } + + public async bulk(user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams): Promise { + const result = await this.callWithUser(user, 'bulk', params); + return result; + } + + public async create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise { + const result = await this.callWithUser(user, 'create', params); + return result; + } + public async index(user: FrameworkUser, params: DatabaseIndexDocumentParams): Promise { + const result = await this.callWithUser(user, 'index', params); + return result; + } + public async delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise { + const result = await this.callWithUser(user, 'delete', params); + return result; + } + + public async deleteByQuery( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise { + const result = await this.callWithUser(user, 'deleteByQuery', params); + return result; + } + + public async search( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise> { + const result = await this.callWithUser(user, 'search', params); + return result; + } + + public async searchAll( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise> { + const result = await this.callWithUser(user, 'search', { + scroll: '1m', + ...params, + body: { + size: 1000, + ...params.body, + }, + }); + return result; + } + + public async putTemplate(name: string, template: any): Promise { + const result = await this.callWithUser({ kind: 'internal' }, 'indices.putTemplate', { + name, + body: template, + }); + + return result; + } + + private callWithUser(user: FrameworkUser, esMethod: string, options: any = {}): any { + if (user.kind === 'authenticated') { + return this.es.callWithRequest( + { + headers: user[internalAuthData], + } as any, + esMethod, + options + ); + } else if (user.kind === 'internal') { + return this.es.callWithInternalUser(esMethod, options); + } else { + throw new Error('Invalid user type'); + } + } +} diff --git a/x-pack/legacy/plugins/ingest/common/constants/plugin.ts b/x-pack/legacy/plugins/ingest/common/constants/plugin.ts index 48a93fed18229b..523d8fbe3ab0ba 100644 --- a/x-pack/legacy/plugins/ingest/common/constants/plugin.ts +++ b/x-pack/legacy/plugins/ingest/common/constants/plugin.ts @@ -5,6 +5,6 @@ */ export const PLUGIN = { - ID: 'ingest-data', + ID: 'ingest', }; export const CONFIG_PREFIX = 'xpack.ingest-do-not-disable'; diff --git a/x-pack/legacy/plugins/ingest/index.ts b/x-pack/legacy/plugins/ingest/index.ts index 5f6196b8c1da42..78ab35ff453bad 100644 --- a/x-pack/legacy/plugins/ingest/index.ts +++ b/x-pack/legacy/plugins/ingest/index.ts @@ -8,6 +8,7 @@ import { resolve } from 'path'; import { PLUGIN } from './common/constants'; import { CONFIG_PREFIX } from './common/constants/plugin'; import { initServerWithKibana } from './server/kibana.index'; +import { mappings } from './server/mappings'; export const config = Joi.object({ enabled: Joi.boolean().default(true), @@ -20,6 +21,9 @@ export function ingest(kibana: any) { publicDir: resolve(__dirname, 'public'), config: () => config, configPrefix: CONFIG_PREFIX, + uiExports: { + mappings, + }, init(server: any) { initServerWithKibana(server); }, diff --git a/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap b/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap new file mode 100644 index 00000000000000..e9376255bc63a4 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap @@ -0,0 +1,77 @@ + +exports['Configurations Lib create should create a new configuration - create - {"name":"test","description":"test description","output":"defaut","monitoring_enabled":true,"agent_version":"8.0.0","data_sources":[]} (2)'] = { + "results": { + "id": "8a8874b0-bd51-11e9-a21b-dbec3e1a8be1" + } +} + +exports['Configurations Lib create should create a new configuration - create - {"name":"test","description":"test description","output":"defaut","monitoring_enabled":true,"shared_id":"994528b0-887f-4c71-923e-4ffe5dd302e2","version":0,"agent_version":"8.0.0","data_sources":[]} (2)'] = { + "results": { + "id": "715d5cb0-bd53-11e9-bb4e-fb77f27555ca", + "shared_id": "994528b0-887f-4c71-923e-4ffe5dd302e2", + "version": 0 + } +} + +exports['Configurations Lib create should create a new configuration - get info (1)'] = { + "results": { + "kibana": { + "version": "8.0.0" + }, + "license": { + "type": "trial", + "expired": false, + "expiry_date_in_millis": 1568242146524 + }, + "security": { + "enabled": true, + "available": true + }, + "watcher": { + "enabled": true, + "available": true + } + } +} + +exports['Configurations Lib create should create a new configuration - get info (2)'] = { + "results": { + "kibana": { + "version": "8.0.0" + }, + "license": { + "type": "trial", + "expired": false, + "expiry_date_in_millis": 1568240322629 + }, + "security": { + "enabled": true, + "available": true + }, + "watcher": { + "enabled": true, + "available": true + } + } +} + +exports['Configurations Lib create should create a new configuration - get info (3)'] = { + "results": { + "kibana": { + "version": "8.0.0" + }, + "license": { + "type": "trial", + "expired": false, + "expiry_date_in_millis": 1568240322629 + }, + "security": { + "enabled": true, + "available": true + }, + "watcher": { + "enabled": true, + "available": true + } + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts index 85d7f4ae697c80..f0f00c88744ce1 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/adapter_types.ts @@ -30,6 +30,8 @@ export const NewRuntimeConfigurationFile = t.interface( description: t.string, output: t.string, monitoring_enabled: t.boolean, + shared_id: t.string, + version: t.number, agent_version: t.string, data_sources: t.array(DataSource), }, diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts index a9c5563e7e1eb6..baa7bf72634900 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/default.ts @@ -6,7 +6,6 @@ import { SODatabaseAdapter } from '../so_database/default'; import { RuntimeConfigurationFile, NewConfigurationFile } from './adapter_types'; - import { ConfigurationFile, DatasourceInput, BackupConfigurationFile } from './adapter_types'; export class ConfigAdapter { diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts new file mode 100644 index 00000000000000..f6b303a8de09df --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { memorize } from '@mattapperson/slapshot/lib/memorize'; +import { NewConfigurationFile } from './adapter_types'; +import { ConfigurationFile, DatasourceInput, BackupConfigurationFile } from './adapter_types'; +import { ConfigAdapter } from './default'; + +export class MemorizedConfigAdapter { + constructor(private readonly adapter?: ConfigAdapter) {} + + public async create( + configuration: NewConfigurationFile + ): Promise<{ id: string; shared_id: string; version: number }> { + return await memorize( + `create - ${JSON.stringify(configuration)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.create(configuration); + }, + { + pure: false, + } + ); + } + + public async get(id: string): Promise { + return await memorize( + `get - ${JSON.stringify(id)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.get(id); + }, + { + pure: false, + } + ); + } + + public async list(page: number = 1, perPage: number = 25): Promise { + return await memorize( + `list - ${JSON.stringify({ page, perPage })}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.list(page, perPage); + }, + { + pure: false, + } + ); + } + + public async listVersions( + sharedID: string, + activeOnly = true, + page: number = 1, + perPage: number = 25 + ): Promise { + return await memorize( + `listVersions - ${JSON.stringify({ sharedID, activeOnly, page, perPage })}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.listVersions(sharedID, activeOnly, page, perPage); + }, + { + pure: false, + } + ); + } + + public async update( + id: string, + configuration: ConfigurationFile + ): Promise<{ id: string; version: number }> { + return await memorize( + `update - ${JSON.stringify({ id, configuration })}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.update(id, configuration); + }, + { + pure: false, + } + ); + } + + public async delete(id: string): Promise<{ success: boolean }> { + return await memorize( + `delete - ${JSON.stringify(id)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.delete(id); + }, + { + pure: false, + } + ); + } + + public async createBackup( + configuration: BackupConfigurationFile + ): Promise<{ success: boolean; id?: string; error?: string }> { + return await memorize( + `createBackup - ${JSON.stringify(configuration)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.createBackup(configuration); + }, + { + pure: false, + } + ); + } + + public async getBackup(id: string): Promise { + return await memorize( + `getBackup - ${JSON.stringify(id)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.getBackup(id); + }, + { + pure: false, + } + ); + } + + /** + * Inputs sub-domain type + */ + public async getInputsById( + ids: string[], + page: number = 1, + perPage: number = 25 + ): Promise { + return await memorize( + `getInputsById - ${JSON.stringify({ ids, page, perPage })}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.getInputsById(ids, page, perPage); + }, + { + pure: false, + } + ); + } + + public async listInputsforConfiguration( + configurationId: string, + page: number = 1, + perPage: number = 25 + ): Promise { + return await memorize( + `listInputsforConfiguration - ${JSON.stringify({ configurationId, page, perPage })}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.listInputsforConfiguration(configurationId, page, perPage); + }, + { + pure: false, + } + ); + } + + public async addInputs(inputs: DatasourceInput[]): Promise { + return await memorize( + `addInputs - ${JSON.stringify(inputs)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.addInputs(inputs); + }, + { + pure: false, + } + ); + } + + public async deleteInputs(inputIDs: string[]): Promise<{ success: boolean }> { + return await memorize( + `deleteInputs - ${JSON.stringify(inputIDs)}`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.deleteInputs(inputIDs); + }, + { + pure: false, + } + ); + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts index 30d61e9fa27da2..c48ddd27720d54 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts @@ -29,9 +29,13 @@ export class BackendFrameworkAdapter { private readonly CONFIG_PREFIX?: string ) { const xpackMainPlugin = this.server.plugins.xpack_main; - const thisPlugin = this.server.plugins.ingest; + const thisPlugin = (this.server.plugins as any)[this.PLUGIN_ID]; - mirrorPluginStatus(xpackMainPlugin, thisPlugin); + if (thisPlugin) { + mirrorPluginStatus(xpackMainPlugin, thisPlugin); + } else { + throw new Error('Plugin is not initalized in Kibana'); + } xpackMainPlugin.status.on('green', () => { this.xpackInfoWasUpdatedHandler(xpackMainPlugin.info); @@ -52,12 +56,24 @@ export class BackendFrameworkAdapter { } } + public async waitForStack() { + return new Promise(resolve => { + this.on('xpack.status.green', () => { + resolve(); + }); + }); + } + public getSetting(settingPath: string) { return this.server.config().get(settingPath); } public log(text: string) { - this.server.log(text); + if (this.server) { + this.server.log(text); + } else { + console.log(text); // eslint-disable-line + } } public exposeMethod(name: string, method: () => any) { @@ -128,6 +144,7 @@ export class BackendFrameworkAdapter { `Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}` ); } + this.info = xpackInfoUnpacked; return { diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/memorized.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/memorized.ts new file mode 100644 index 00000000000000..694add74c25d99 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/memorized.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Request } from 'src/legacy/server/kbn_server'; +import Slapshot from '@mattapperson/slapshot'; +// @ts-ignore +import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status'; +import { internalUser, KibanaUser } from './adapter_types'; +import { BackendFrameworkAdapter } from './default'; + +export class MemorizedBackendFrameworkAdapter { + public readonly internalUser = internalUser; + + public get info() { + return Slapshot.memorize( + `get info`, + async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return this.adapter.info; + }, + { + pure: false, + } + ); + } + + constructor(private readonly adapter?: BackendFrameworkAdapter) {} + + public on(event: 'xpack.status.green' | 'elasticsearch.status.green', cb: () => void) { + setTimeout(() => { + cb(); + }, 5); + } + + public getSetting(settingPath: string) { + return Slapshot.memorize(`getSetting - ${JSON.stringify(settingPath)}`, () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return this.adapter.getSetting(settingPath); + }); + } + + public log(text: string) {} + + public exposeMethod(name: string, method: () => any) {} + + public async getUser(request: Request): Promise { + return await Slapshot.memorize(`getUser - ${JSON.stringify(request)}`, async () => { + if (!this.adapter) { + throw new Error('An adapter must be provided when running tests online'); + } + return await this.adapter.getUser(request); + }); + } +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts index 3b3bf058092bc7..6eb4338b243b0b 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/compose/kibana.ts @@ -25,7 +25,7 @@ export function compose(server: KibanaLegacyServer): ServerLibs { const soDatabase = new SODatabaseAdapter(server.savedObjects, server.plugins.elasticsearch); const configAdapter = new ConfigAdapter(soDatabase); - const configuration = new ConfigurationLib(configAdapter); + const configuration = new ConfigurationLib(configAdapter, { framework }); const libs: ServerLibs = { configuration, diff --git a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts index 04ee15cf0a0384..2d48f3a6a014ee 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/configuration.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/configuration.ts @@ -5,6 +5,7 @@ */ import { merge, omit } from 'lodash'; import uuidv4 from 'uuid/v4'; +import uuid from 'uuid/v4'; import { ConfigAdapter } from './adapters/configurations/default'; import { BackendFrameworkLib } from './framework'; import { ConfigurationFile } from './adapters/configurations/adapter_types'; @@ -17,16 +18,19 @@ export class ConfigurationLib { } ) {} public async create(name: string, description?: string) { - if (!this.libs.framework.version) { + const info = await this.libs.framework.info; + if (!info) { throw new Error('Could not get version information about Kibana from xpack'); } - await this.adapter.create({ + return await this.adapter.create({ name, description: description || '', output: 'defaut', monitoring_enabled: true, - agent_version: this.libs.framework.version, + shared_id: uuid(), + version: 0, + agent_version: info.kibana.version, data_sources: [], }); } @@ -129,7 +133,13 @@ export class ConfigurationLib { // TODO fire events for fleet that update was made } - public async finishUpdateFrom(id: string) {} + public async finishUpdateFrom(configId: string) { + const oldConfig = await this.adapter.get(configId); + await this.adapter.update(configId, { + ...oldConfig, + status: 'inactive', + }); + } public async rollForward(id: string): Promise<{ id: string; version: number }> { return { diff --git a/x-pack/legacy/plugins/ingest/server/libs/configurations.contract.test.ts b/x-pack/legacy/plugins/ingest/server/libs/configurations.contract.test.ts new file mode 100644 index 00000000000000..14126dd755898a --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/libs/configurations.contract.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConfigurationLib } from './configuration'; +import { callWhenOnline } from '@mattapperson/slapshot/lib/call_when_online'; +import { MemorizedConfigAdapter } from './adapters/configurations/memorized'; +import { ConfigAdapter } from './adapters/configurations/default'; +import { SODatabaseAdapter } from './adapters/so_database/default'; +import { BackendFrameworkLib } from './framework'; +import { MemorizedBackendFrameworkAdapter } from './adapters/framework/memorized'; +import { BackendFrameworkAdapter } from './adapters/framework/default'; +import { camelCase } from 'lodash'; +import { PLUGIN } from '../../common/constants'; +import { CONFIG_PREFIX } from '../../common/constants/plugin'; +import { createKibanaServer } from '../../../../../test_utils/jest/contract_tests/servers'; + +describe('Configurations Lib', () => { + let realConfigAdapter: ConfigAdapter; + let servers: any; + let lib: ConfigurationLib; + let realFrameworkAdapter: BackendFrameworkAdapter; + + beforeAll(async () => { + await callWhenOnline(async () => { + servers = await createKibanaServer({ + security: { enabled: true }, + }); + const soAdapter = new SODatabaseAdapter( + servers.kbnServer.savedObjects, + servers.kbnServer.plugins.elasticsearch + ); + realConfigAdapter = new ConfigAdapter(soAdapter); + realFrameworkAdapter = new BackendFrameworkAdapter( + camelCase(PLUGIN.ID), + servers.kbnServer, + CONFIG_PREFIX + ); + await realFrameworkAdapter.waitForStack(); + }); + + const memorizedConfigAdapter = new MemorizedConfigAdapter(realConfigAdapter) as ConfigAdapter; + const memorizedFrameworkAdapter = new MemorizedBackendFrameworkAdapter( + realFrameworkAdapter + ) as BackendFrameworkAdapter; + + const framework = new BackendFrameworkLib(memorizedFrameworkAdapter); + lib = new ConfigurationLib(memorizedConfigAdapter, { framework }); + }); + + afterAll(async () => { + if (servers) { + await servers.shutdown(); + } + }); + + describe('create', () => { + it('should create a new configuration', async () => { + const newConfig = await lib.create('test', 'test description'); + + expect(typeof newConfig.id).toBe('string'); + expect(typeof newConfig.shared_id).toBe('string'); + expect(typeof newConfig.version).toBe('number'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts index 65552a04c6437e..8651b691437566 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/framework.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -5,20 +5,29 @@ */ import { Request } from 'src/legacy/server/kbn_server'; +import { get } from 'lodash'; import { BackendFrameworkAdapter } from './adapters/framework/default'; +import { LicenseType } from '../../../../../build/plugin/kibana/x-pack/plugins/beats_management/common/constants/security'; export class BackendFrameworkLib { /** * Expired `null` happens when we have no xpack info */ - public license = { - type: this.adapter.info ? this.adapter.info.license.type : 'unknown', - expired: this.adapter.info ? this.adapter.info.license.expired : null, - }; + public get license() { + return { + type: get(this.adapter, 'info.license.type', 'oss'), + expired: get(this.adapter, 'info.license.expired', null), + }; + } + public get info() { + return this.adapter.info; + } public get version() { - return this.adapter.info ? this.adapter.info.kibana.version : null; + return get(this.adapter, 'info.kibana.version', null) as string | null; + } + public get securityIsEnabled() { + return get(this.adapter, 'info.security.enabled', false); } - public securityIsEnabled = this.adapter.info ? this.adapter.info.security.enabled : false; public log = this.adapter.log; public on = this.adapter.on.bind(this.adapter); public internalUser = this.adapter.internalUser; @@ -34,4 +43,7 @@ export class BackendFrameworkLib { public exposeMethod(name: string, method: () => any) { return this.adapter.exposeMethod(name, method); } + public async waitForStack() { + return await this.adapter.waitForStack(); + } } diff --git a/x-pack/legacy/plugins/ingest/server/mappings.ts b/x-pack/legacy/plugins/ingest/server/mappings.ts new file mode 100644 index 00000000000000..e7a878bfe167c8 --- /dev/null +++ b/x-pack/legacy/plugins/ingest/server/mappings.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mappings = { + configurations: { + properties: { + name: { + type: 'text', + }, + description: { + type: 'text', + }, + output: { + type: 'keyword', + }, + monitoring_enabled: { + type: 'boolean', + }, + agent_version: { + type: 'keyword', + }, + data_sources: { + properties: { + id: { + type: 'keyword', + }, + meta: { + type: 'keyword', + }, + config_id: { + type: 'keyword', + }, + config: { + type: 'keyword', + }, + }, + }, + id: { + type: 'keyword', + }, + shared_id: { + type: 'keyword', + }, + version: { + type: 'integer', + }, + status: { + type: 'keyword', + }, + updated_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + updated_on: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + }, + }, +}; diff --git a/x-pack/test_utils/jest/contract_tests/servers.ts b/x-pack/test_utils/jest/contract_tests/servers.ts index 44bad3ea702cc1..8d85af5b4644f2 100644 --- a/x-pack/test_utils/jest/contract_tests/servers.ts +++ b/x-pack/test_utils/jest/contract_tests/servers.ts @@ -103,11 +103,15 @@ export async function createKibanaServer(xpackOption = {}) { // Allow kibana to start jest.setTimeout(120000); } - const root = kbnTestServer.createRootWithCorePlugins({ - elasticsearch: { ...getSharedESServer() }, - plugins: { paths: [PLUGIN_PATHS] }, - xpack: xpackOption, - }); + + const root = kbnTestServer.createRootWithCorePlugins( + { + elasticsearch: { ...getSharedESServer() }, + plugins: { paths: [PLUGIN_PATHS] }, + xpack: xpackOption, + }, + { oss: false } + ); await root.setup(); await root.start(); const { server } = (root as any).server.legacy.kbnServer; From 40deefa7875aa7bef7143339ece55cb385b1b3ca Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 16 Aug 2019 14:31:48 -0400 Subject: [PATCH 23/27] tweaks --- .../server/lib/compose/testing.ts | 51 +++++++++++++++++++ .../plugins/ingest/server/libs/framework.ts | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts diff --git a/x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts b/x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts new file mode 100644 index 00000000000000..b5fe6195fc7c70 --- /dev/null +++ b/x-pack/legacy/plugins/beats_management/server/lib/compose/testing.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; +import { MemoryConfigurationBlockAdapter } from '../adapters/configuration_blocks/memory_tags_adapter'; +import { HapiBackendFrameworkAdapter } from '../adapters/framework/hapi_framework_adapter'; +import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; +import { BeatEventsLib } from '../beat_events'; +import { CMBeatsDomain } from '../beats'; +import { ConfigurationBlocksLib } from '../configuration_blocks'; +import { BackendFrameworkLib } from '../framework'; +import { CMTagsDomain } from '../tags'; +import { CMTokensDomain } from '../tokens'; +import { CMServerLibs } from '../types'; + +export function compose(server: any): CMServerLibs { + const framework = new BackendFrameworkLib(new HapiBackendFrameworkAdapter(undefined, server)); + + const beatsAdapter = new MemoryBeatsAdapter(server.beatsDB || []); + const configAdapter = new MemoryConfigurationBlockAdapter(server.configsDB || []); + const tags = new CMTagsDomain( + new MemoryTagsAdapter(server.tagsDB || []), + configAdapter, + beatsAdapter + ); + const configurationBlocks = new ConfigurationBlocksLib(configAdapter, tags); + const tokens = new CMTokensDomain(new MemoryTokensAdapter(server.tokensDB || []), { + framework, + }); + const beats = new CMBeatsDomain(beatsAdapter, { + tags, + tokens, + framework, + }); + const beatEvents = new BeatEventsLib({} as any, beats); + + const libs: CMServerLibs = { + beatEvents, + framework, + beats, + tags, + tokens, + configurationBlocks, + }; + + return libs; +} diff --git a/x-pack/legacy/plugins/ingest/server/libs/framework.ts b/x-pack/legacy/plugins/ingest/server/libs/framework.ts index 8651b691437566..5b9abdd8f82582 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/framework.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/framework.ts @@ -7,7 +7,7 @@ import { Request } from 'src/legacy/server/kbn_server'; import { get } from 'lodash'; import { BackendFrameworkAdapter } from './adapters/framework/default'; -import { LicenseType } from '../../../../../build/plugin/kibana/x-pack/plugins/beats_management/common/constants/security'; +import { LicenseType } from '../../common/types/security'; export class BackendFrameworkLib { /** From b15f27a2ee16dcf542a96d039af3677d7595ac49 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 16 Aug 2019 16:57:25 -0400 Subject: [PATCH 24/27] fix test --- .../configurations.contract.test.ts.snap | 18 +++++++++++++++++- .../libs/adapters/configurations/memorized.ts | 3 ++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap b/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap index e9376255bc63a4..f997674c5bf1d3 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap +++ b/x-pack/legacy/plugins/ingest/server/libs/__memorize_snapshots__/configurations.contract.test.ts.snap @@ -13,6 +13,22 @@ exports['Configurations Lib create should create a new configuration - create - } } +exports['Configurations Lib create should create a new configuration - create - {"name":"test","description":"test description","output":"defaut","monitoring_enabled":true,"shared_id":"997e6674-d072-475b-89d3-9b9202e0dd99","version":0,"agent_version":"8.0.0","data_sources":[]} (2)'] = { + "results": { + "id": "9cd167f0-c065-11e9-9b54-89c2396bf183", + "shared_id": "997e6674-d072-475b-89d3-9b9202e0dd99", + "version": 0 + } +} + +exports['Configurations Lib create should create a new configuration - create - {"name":"test","description":"test description","output":"defaut","monitoring_enabled":true,"version":0,"agent_version":"8.0.0","data_sources":[],"shared_id":"string"} (2)'] = { + "results": { + "id": "385d2130-c068-11e9-a90f-d9a51a8c04f8", + "shared_id": "de5a13b9-4b80-4983-8e6a-1619e3f97b9a", + "version": 0 + } +} + exports['Configurations Lib create should create a new configuration - get info (1)'] = { "results": { "kibana": { @@ -21,7 +37,7 @@ exports['Configurations Lib create should create a new configuration - get info "license": { "type": "trial", "expired": false, - "expiry_date_in_millis": 1568242146524 + "expiry_date_in_millis": 1568580919209 }, "security": { "enabled": true, diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts index f6b303a8de09df..b4d52fc3ae7f5e 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/configurations/memorized.ts @@ -15,8 +15,9 @@ export class MemorizedConfigAdapter { public async create( configuration: NewConfigurationFile ): Promise<{ id: string; shared_id: string; version: number }> { + const { shared_id, ...config } = configuration; return await memorize( - `create - ${JSON.stringify(configuration)}`, + `create - ${JSON.stringify({ ...config, shared_id: 'string' })}`, async () => { if (!this.adapter) { throw new Error('An adapter must be provided when running tests online'); From 28aebfbcf4cbdda58d78902fe862f20c3a7c4fae Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 20 Aug 2019 11:05:04 -0400 Subject: [PATCH 25/27] remove whitespace --- .../plugins/ingest/server/libs/adapters/framework/default.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts index 92b491bd29d41f..c48ddd27720d54 100644 --- a/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts +++ b/x-pack/legacy/plugins/ingest/server/libs/adapters/framework/default.ts @@ -64,7 +64,6 @@ export class BackendFrameworkAdapter { }); } - public getSetting(settingPath: string) { return this.server.config().get(settingPath); } From 1e41913b3768c319f7233b20d01fa230561ca57c Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 20 Aug 2019 11:06:58 -0400 Subject: [PATCH 26/27] remove isVersionGreater --- .../plugins/fleet/public/lib/framework.ts | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/x-pack/legacy/plugins/fleet/public/lib/framework.ts b/x-pack/legacy/plugins/fleet/public/lib/framework.ts index e6ae33168384e9..ff07beaf558cca 100644 --- a/x-pack/legacy/plugins/fleet/public/lib/framework.ts +++ b/x-pack/legacy/plugins/fleet/public/lib/framework.ts @@ -31,30 +31,6 @@ export class FrameworkLib { ); } - public versionGreaterThen(version: string) { - const pa = this.adapter.version.split('.'); - const pb = version.split('.'); - for (let i = 0; i < 3; i++) { - const na = Number(pa[i]); - const nb = Number(pb[i]); - // version is greater - if (na > nb) { - return true; - } - // version is less then - if (nb > na) { - return false; - } - if (!isNaN(na) && isNaN(nb)) { - return true; - } - if (isNaN(na) && !isNaN(nb)) { - return false; - } - } - return true; - } - public currentUserHasOneOfRoles(roles: string[]) { // If the user has at least one of the roles requested, the returnd difference will be less // then the orig array size. difference only compares based on the left side arg From 56008d497516cfd10e9da0b449c5db0269ffafe5 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 20 Aug 2019 13:42:47 -0400 Subject: [PATCH 27/27] remove CRUFT from a bad merge --- .../fleet/public/lib/compose/kibana.ts | 2 +- .../fleet/common/constants/security.ts | 9 - .../plugins/fleet/common/types/domain_data.ts | 80 ------ x-pack/plugins/fleet/common/types/helpers.ts | 7 - x-pack/plugins/fleet/common/types/io_ts.ts | 33 --- x-pack/plugins/fleet/common/types/security.ts | 7 - .../fleet/common/types/std_return_format.ts | 116 -------- .../lib/adapters/agent/adapter_types.ts | 5 - .../adapters/agent/memory_agent_adapter.ts | 42 --- .../lib/adapters/agent/rest_agent_adapter.ts | 60 ---- .../adapters/elasticsearch/adapter_types.ts | 12 - .../lib/adapters/elasticsearch/memory.ts | 29 -- .../public/lib/adapters/elasticsearch/rest.ts | 76 ----- .../lib/adapters/framework/adapter_types.ts | 86 ------ .../framework/kibana_framework_adapter.ts | 263 ------------------ .../framework/testing_framework_adapter.ts | 69 ----- .../lib/adapters/rest_api/adapter_types.ts | 13 - .../rest_api/axios_rest_api_adapter.ts | 78 ------ .../rest_api/node_axios_api_adapter.ts | 92 ------ x-pack/plugins/fleet/public/lib/agent.ts | 50 ---- .../fleet/public/lib/compose/kibana.ts | 53 ---- .../fleet/public/lib/compose/memory.ts | 59 ---- .../fleet/public/lib/compose/scripts.ts | 55 ---- .../plugins/fleet/public/lib/elasticsearch.ts | 69 ----- x-pack/plugins/fleet/public/lib/framework.ts | 63 ----- x-pack/plugins/fleet/public/lib/types.ts | 52 ---- x-pack/plugins/fleet/tsconfig.json | 7 - 27 files changed, 1 insertion(+), 1486 deletions(-) delete mode 100644 x-pack/plugins/fleet/common/constants/security.ts delete mode 100644 x-pack/plugins/fleet/common/types/domain_data.ts delete mode 100644 x-pack/plugins/fleet/common/types/helpers.ts delete mode 100644 x-pack/plugins/fleet/common/types/io_ts.ts delete mode 100644 x-pack/plugins/fleet/common/types/security.ts delete mode 100644 x-pack/plugins/fleet/common/types/std_return_format.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts delete mode 100644 x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts delete mode 100644 x-pack/plugins/fleet/public/lib/agent.ts delete mode 100644 x-pack/plugins/fleet/public/lib/compose/kibana.ts delete mode 100644 x-pack/plugins/fleet/public/lib/compose/memory.ts delete mode 100644 x-pack/plugins/fleet/public/lib/compose/scripts.ts delete mode 100644 x-pack/plugins/fleet/public/lib/elasticsearch.ts delete mode 100644 x-pack/plugins/fleet/public/lib/framework.ts delete mode 100644 x-pack/plugins/fleet/public/lib/types.ts delete mode 100644 x-pack/plugins/fleet/tsconfig.json diff --git a/x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts b/x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts index 787d9469c1dd2d..6eb0087e2e5d18 100644 --- a/x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/fleet/public/lib/compose/kibana.ts @@ -12,7 +12,6 @@ import chrome from 'ui/chrome'; // @ts-ignore not typed yet import { management } from 'ui/management'; import routes from 'ui/routes'; -import { INDEX_NAMES } from '../../../common/constants/index_names'; import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; @@ -22,6 +21,7 @@ import { ElasticsearchLib } from '../elasticsearch'; import { FrontendLibs } from '../types'; import { PLUGIN } from '../../../common/constants/plugin'; import { FrameworkLib } from '../framework'; +import { INDEX_NAMES } from '../../../common/constants'; // A super early spot in kibana loading that we can use to hook before most other things const onKibanaReady = chrome.dangerouslyGetActiveInjector; diff --git a/x-pack/plugins/fleet/common/constants/security.ts b/x-pack/plugins/fleet/common/constants/security.ts deleted file mode 100644 index f27c45fc903b4e..00000000000000 --- a/x-pack/plugins/fleet/common/constants/security.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const REQUIRED_ROLES = ['fleet_admin']; -export const REQUIRED_LICENSES = ['standard', 'gold', 'trial', 'platinum']; -export const LICENSES = ['oss', 'basic', 'standard', 'gold', 'trial', 'platinum']; diff --git a/x-pack/plugins/fleet/common/types/domain_data.ts b/x-pack/plugins/fleet/common/types/domain_data.ts deleted file mode 100644 index 41ec4eae4c5d84..00000000000000 --- a/x-pack/plugins/fleet/common/types/domain_data.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import * as t from 'io-ts'; -import { DateFromString } from './io_ts'; - -// Here we create the runtime check for a generic, unknown beat config type. -// We can also pass in optional params to create spacific runtime checks that -// can be used to validate blocs on the API and UI -export const createConfigurationInterface = (beatConfigInterface: t.Mixed = t.Dictionary) => - t.interface( - { - id: t.union([t.undefined, t.string]), - name: t.string, - description: t.union([t.undefined, t.string]), - config: beatConfigInterface, - last_updated_by: t.union([t.undefined, t.string]), - last_updated: t.union([t.undefined, t.number]), - }, - 'Config' - ); -const BaseConfiguration = createConfigurationInterface(); -export interface ConfigurationBlock - extends Pick< - t.TypeOf, - Exclude, 'id'> - > { - id: string; -} - -export interface Agent { - id: string; - status?: AgentEvent; - enrollment_token: string; - active: boolean; - access_token: string; - verified_on?: string; - type: string; - version?: string; - host_ip: string; - host_name: string; - ephemeral_id?: string; - last_checkin?: Date; - event_rate?: string; - tags: string[]; - metadata?: {}; - name?: string; - last_updated: number; -} - -export const RuntimeAgentEvent = t.interface( - { - type: t.union([t.literal('STATE'), t.literal('ERROR')]), - beat: t.union([t.undefined, t.string]), - timestamp: DateFromString, - event: t.type({ - type: t.union([ - t.literal('RUNNING'), - t.literal('STARTING'), - t.literal('IN_PROGRESS'), - t.literal('CONFIG'), - t.literal('FAILED'), - t.literal('STOPPED'), - ]), - message: t.string, - uuid: t.union([t.undefined, t.string]), - }), - }, - 'AgentEvent' -); -export interface AgentEvent - extends Pick< - t.TypeOf, - Exclude, 'timestamp'> - > { - agent: string; - timestamp: Date; -} diff --git a/x-pack/plugins/fleet/common/types/helpers.ts b/x-pack/plugins/fleet/common/types/helpers.ts deleted file mode 100644 index 4258461310e164..00000000000000 --- a/x-pack/plugins/fleet/common/types/helpers.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export type FlatObject = { [Key in keyof T]: string }; diff --git a/x-pack/plugins/fleet/common/types/io_ts.ts b/x-pack/plugins/fleet/common/types/io_ts.ts deleted file mode 100644 index 51ab838ddd6c2b..00000000000000 --- a/x-pack/plugins/fleet/common/types/io_ts.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as t from 'io-ts'; - -export class DateFromStringType extends t.Type { - // eslint-disable-next-line - public readonly _tag: 'DateFromISOStringType' = 'DateFromISOStringType'; - constructor() { - super( - 'DateFromString', - (u): u is Date => u instanceof Date, - (u, c) => { - const validation = t.string.validate(u, c); - if (validation.isLeft()) { - return validation as any; - } else { - const s = validation.value; - const d = new Date(s); - return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d); - } - }, - a => a.toISOString() - ); - } -} -// eslint-disable-next-line -export interface DateFromString extends DateFromStringType {} - -export const DateFromString: DateFromString = new DateFromStringType(); diff --git a/x-pack/plugins/fleet/common/types/security.ts b/x-pack/plugins/fleet/common/types/security.ts deleted file mode 100644 index 691ea82b294d30..00000000000000 --- a/x-pack/plugins/fleet/common/types/security.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export type LicenseType = 'oss' | 'basic' | 'trial' | 'standard' | 'basic' | 'gold' | 'platinum'; diff --git a/x-pack/plugins/fleet/common/types/std_return_format.ts b/x-pack/plugins/fleet/common/types/std_return_format.ts deleted file mode 100644 index ded94bbff7f19e..00000000000000 --- a/x-pack/plugins/fleet/common/types/std_return_format.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export interface BaseReturnType { - error?: { - message: string; - code?: number; - }; - success: boolean; -} - -export interface ReturnTypeCreate extends BaseReturnType { - item: T; - action: 'created'; -} - -export interface ReturnTypeUpdate extends BaseReturnType { - item: T; - action: 'updated'; -} - -export interface ReturnTypeBulkCreate extends BaseReturnType { - results: Array<{ - item: T; - success: boolean; - action: 'created'; - error?: { - message: string; - code?: number; - }; - }>; -} - -// delete -export interface ReturnTypeDelete extends BaseReturnType { - action: 'deleted'; -} - -export interface ReturnTypeBulkDelete extends BaseReturnType { - results: Array<{ - success: boolean; - action: 'deleted'; - error?: { - message: string; - code?: number; - }; - }>; -} - -// upsert -export interface ReturnTypeUpsert extends BaseReturnType { - item: T; - action: 'created' | 'updated'; -} - -// upsert bulk -export interface ReturnTypeBulkUpsert extends BaseReturnType { - results: Array<{ - success: boolean; - action: 'created' | 'updated'; - error?: { - message: string; - code?: number; - }; - }>; -} - -// list -export interface ReturnTypeList extends BaseReturnType { - list: T[]; - page: number; - total: number; -} - -// get -export interface ReturnTypeGet extends BaseReturnType { - item: T; -} - -export interface ReturnTypeBulkGet extends BaseReturnType { - items: T[]; -} - -// action -- e.g. validate config block. Like ES simulate endpoint -export interface ReturnTypeAction extends BaseReturnType { - result: { - [key: string]: any; - }; -} -// e.g. -// { -// result: { -// username: { valid: true }, -// password: { valid: false, error: 'something' }, -// hosts: [ -// { valid: false }, { valid: true }, -// ] -// } -// } - -// bulk action -export interface ReturnTypeBulkAction extends BaseReturnType { - results?: Array<{ - success: boolean; - result?: { - [key: string]: any; - }; - error?: { - message: string; - code?: number; - }; - }>; -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts deleted file mode 100644 index 41bc2aa2588073..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/agent/adapter_types.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts deleted file mode 100644 index 3f6c4a3143750a..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/agent/memory_agent_adapter.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { omit } from 'lodash'; -import { Agent } from '../../../../common/types/domain_data'; - -export class AgentAdapter { - private memoryDB: Agent[]; - - constructor(db: Agent[]) { - this.memoryDB = db; - } - - public async get(id: string) { - return this.memoryDB.find(beat => beat.id === id) || null; - } - - public async update(id: string, beatData: Partial): Promise { - const index = this.memoryDB.findIndex(beat => beat.id === id); - - if (index === -1) { - return false; - } - - this.memoryDB[index] = { ...this.memoryDB[index], ...beatData }; - return true; - } - - public async getAll(ESQuery?: string) { - return this.memoryDB.map((beat: any) => omit(beat, ['access_token'])); - } - public async getOnConfig(tagId: string): Promise { - return this.memoryDB.map((beat: any) => omit(beat, ['access_token'])); - } - - public async getWithToken(enrollmentToken: string): Promise { - return this.memoryDB.map((beat: any) => omit(beat, ['access_token']))[0]; - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts deleted file mode 100644 index da04f615554a25..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/agent/rest_agent_adapter.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Agent } from '../../../../common/types/domain_data'; -import { - ReturnTypeGet, - ReturnTypeList, - ReturnTypeUpdate, -} from '../../../../common/types/std_return_format'; -import { RestAPIAdapter } from '../rest_api/adapter_types'; -import { AgentAdapter } from './memory_agent_adapter'; - -export class RestAgentAdapter extends AgentAdapter { - constructor(private readonly REST: RestAPIAdapter) { - super([]); - } - - public async get(id: string): Promise { - try { - return (await this.REST.get>(`/api/fleet/agent/${id}`)).item; - } catch (e) { - return null; - } - } - - public async getWithToken(enrollmentToken: string): Promise { - try { - return (await this.REST.get>( - `/api/fleet/agent/unknown/${enrollmentToken}` - )).item; - } catch (e) { - return null; - } - } - - public async getAll(ESQuery?: string): Promise { - try { - return (await this.REST.get>('/api/fleet/agents/all', { ESQuery })) - .list; - } catch (e) { - return []; - } - } - - public async getOnConfig(tagId: string): Promise { - try { - return (await this.REST.get>(`/api/fleet/agents/tag/${tagId}`)).list; - } catch (e) { - return []; - } - } - - public async update(id: string, beatData: Partial): Promise { - await this.REST.put>(`/api/fleet/agent/${id}`, beatData); - return true; - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts deleted file mode 100644 index 49408574932757..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/adapter_types.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; - -export interface ElasticsearchAdapter { - convertKueryToEsQuery: (kuery: string) => Promise; - getSuggestions: (kuery: string, selectionStart: any) => Promise; - isKueryValid(kuery: string): boolean; -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts deleted file mode 100644 index 1b918fb72c8091..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/memory.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; -import { ElasticsearchAdapter } from './adapter_types'; - -export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { - constructor( - private readonly mockIsKueryValid: (kuery: string) => boolean, - private readonly mockKueryToEsQuery: (kuery: string) => string, - private readonly suggestions: AutocompleteSuggestion[] - ) {} - - public isKueryValid(kuery: string): boolean { - return this.mockIsKueryValid(kuery); - } - public async convertKueryToEsQuery(kuery: string): Promise { - return this.mockKueryToEsQuery(kuery); - } - public async getSuggestions( - kuery: string, - selectionStart: any - ): Promise { - return this.suggestions; - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts b/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts deleted file mode 100644 index 8899ddd7976d55..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/elasticsearch/rest.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import { isEmpty } from 'lodash'; -import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers'; -import { RestAPIAdapter } from '../rest_api/adapter_types'; -import { ElasticsearchAdapter } from './adapter_types'; - -export class RestElasticsearchAdapter implements ElasticsearchAdapter { - private cachedIndexPattern: any = null; - constructor(private readonly api: RestAPIAdapter, private readonly indexPatternName: string) {} - - public isKueryValid(kuery: string): boolean { - try { - fromKueryExpression(kuery); - } catch (err) { - return false; - } - - return true; - } - public async convertKueryToEsQuery(kuery: string): Promise { - if (!this.isKueryValid(kuery)) { - return ''; - } - const ast = fromKueryExpression(kuery); - const indexPattern = await this.getIndexPattern(); - return JSON.stringify(toElasticsearchQuery(ast, indexPattern)); - } - public async getSuggestions( - kuery: string, - selectionStart: any - ): Promise { - const autocompleteProvider = getAutocompleteProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; - const indexPattern = await this.getIndexPattern(); - - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: null, - }); - const results = getAutocompleteSuggestions({ - query: kuery || '', - selectionStart, - selectionEnd: selectionStart, - }); - return results; - } - - private async getIndexPattern() { - if (this.cachedIndexPattern) { - return this.cachedIndexPattern; - } - const res = await this.api.get( - `/api/index_patterns/_fields_for_wildcard?pattern=${this.indexPatternName}` - ); - if (isEmpty(res.fields)) { - return; - } - this.cachedIndexPattern = { - fields: res.fields, - title: `${this.indexPatternName}`, - }; - return this.cachedIndexPattern; - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts deleted file mode 100644 index b8a75e284c2480..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/framework/adapter_types.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import * as t from 'io-ts'; -import { LICENSES } from './../../../../common/constants/security'; - -export interface FrameworkAdapter { - // Instance vars - info: FrameworkInfo; - version: string; - currentUser: FrameworkUser; - // Methods - waitUntilFrameworkReady(): Promise; - renderUIAtPath( - path: string, - component: React.ReactElement, - toController: 'management' | 'self' - ): void; - registerManagementSection(settings: { - id?: string; - name: string; - iconName: string; - order?: number; - }): void; - registerManagementUI(settings: { - sectionId?: string; - name: string; - basePath: string; - visable?: boolean; - order?: number; - }): void; -} - -export const RuntimeFrameworkInfo = t.type({ - basePath: t.string, - license: t.type({ - type: t.union(LICENSES.map(s => t.literal(s))), - expired: t.boolean, - expiry_date_in_millis: t.number, - }), - security: t.type({ - enabled: t.boolean, - available: t.boolean, - }), - settings: t.type({ - // encryptionKey: t.string, - // enrollmentTokensTtlInSeconds: t.number, - // defaultUserRoles: t.array(t.string), - }), -}); - -export interface FrameworkInfo extends t.TypeOf {} - -interface ManagementSection { - register( - sectionId: string, - options: { - visible: boolean; - display: string; - order: number; - url: string; - } - ): void; -} -export interface ManagementAPI { - getSection(sectionId: string): ManagementSection; - hasItem(sectionId: string): boolean; - register(sectionId: string, options: { display: string; icon: string; order: number }): void; -} - -export const RuntimeFrameworkUser = t.interface( - { - username: t.string, - roles: t.array(t.string), - full_name: t.union([t.null, t.string]), - email: t.union([t.null, t.string]), - enabled: t.boolean, - }, - 'FrameworkUser' -); -export interface FrameworkUser extends t.TypeOf {} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts deleted file mode 100644 index 0a8acd954c0a12..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/framework/kibana_framework_adapter.ts +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable max-classes-per-file */ -import { IScope } from 'angular'; -import { PathReporter } from 'io-ts/lib/PathReporter'; -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { UIRoutes } from 'ui/routes'; -import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; -import { - FrameworkAdapter, - FrameworkInfo, - FrameworkUser, - ManagementAPI, - RuntimeFrameworkInfo, - RuntimeFrameworkUser, -} from './adapter_types'; -interface IInjector { - get(injectable: string): any; -} - -export class KibanaFrameworkAdapter implements FrameworkAdapter { - public get info() { - if (this.xpackInfo) { - return this.xpackInfo; - } else { - throw new Error('framework adapter must have init called before anything else'); - } - } - - public get currentUser() { - return this.shieldUser!; - } - private xpackInfo: FrameworkInfo | null = null; - private adapterService: KibanaAdapterServiceProvider; - private shieldUser: FrameworkUser | null = null; - constructor( - private readonly PLUGIN_ID: string, - private readonly management: ManagementAPI, - private readonly routes: UIRoutes, - private readonly getBasePath: () => string, - private readonly onKibanaReady: () => Promise, - private readonly XPackInfoProvider: unknown, - public readonly version: string - ) { - this.adapterService = new KibanaAdapterServiceProvider(); - } - - public setUISettings = (key: string, value: any) => { - this.adapterService.callOrBuffer(({ config }) => { - config.set(key, value); - }); - }; - - public async waitUntilFrameworkReady(): Promise { - const $injector = await this.onKibanaReady(); - const Private: any = $injector.get('Private'); - - let xpackInfo: any; - try { - xpackInfo = Private(this.XPackInfoProvider); - } catch (e) { - xpackInfo = false; - } - - let xpackInfoUnpacked: FrameworkInfo; - try { - xpackInfoUnpacked = { - basePath: this.getBasePath(), - license: { - type: xpackInfo ? xpackInfo.getLicense().type : 'oss', - expired: xpackInfo ? !xpackInfo.getLicense().isActive : false, - expiry_date_in_millis: - xpackInfo.getLicense().expiryDateInMillis !== undefined - ? xpackInfo.getLicense().expiryDateInMillis - : -1, - }, - security: { - enabled: xpackInfo - ? xpackInfo.get(`features.${this.PLUGIN_ID}.security.enabled`, false) - : false, - available: xpackInfo - ? xpackInfo.get(`features.${this.PLUGIN_ID}.security.available`, false) - : false, - }, - settings: xpackInfo ? xpackInfo.get(`features.${this.PLUGIN_ID}.settings`) : {}, - }; - } catch (e) { - throw new Error(`Unexpected data structure from XPackInfoProvider, ${JSON.stringify(e)}`); - } - - const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked); - if (assertData.isLeft()) { - throw new Error( - `Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}` - ); - } - this.xpackInfo = xpackInfoUnpacked; - - try { - this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise; - const assertUser = RuntimeFrameworkUser.decode(this.shieldUser); - - if (assertUser.isLeft()) { - throw new Error( - `Error parsing user info in ${this.PLUGIN_ID}, ${PathReporter.report(assertUser)[0]}` - ); - } - } catch (e) { - this.shieldUser = null; - } - } - - public renderUIAtPath( - path: string, - component: React.ReactElement, - toController: 'management' | 'self' = 'self' - ) { - const adapter = this; - this.routes.when( - `${path}${[...Array(6)].map((e, n) => `/:arg${n}?`).join('')}`, // Hack because angular 1 does not support wildcards - { - template: - toController === 'self' - ? `<${this.PLUGIN_ID}>
` - : ` -
-
- `, - // eslint-disable-next-line max-classes-per-file - controller: ($scope: any, $route: any) => { - try { - $scope.$$postDigest(() => { - const elem = document.getElementById(`${this.PLUGIN_ID}ReactRoot`); - ReactDOM.render(component, elem); - adapter.manageAngularLifecycle($scope, $route, elem); - }); - $scope.$onInit = () => { - $scope.topNavMenu = []; - }; - } catch (e) { - throw new Error(`Error rendering Elastic Fleet to the dom, ${e.message}`); - } - }, - } - ); - } - - public registerManagementSection(settings: { - id?: string; - name: string; - iconName: string; - order?: number; - }) { - const sectionId = settings.id || this.PLUGIN_ID; - - if (!this.management.hasItem(sectionId)) { - this.management.register(sectionId, { - display: settings.name, - icon: settings.iconName, - order: settings.order || 30, - }); - } - } - - public registerManagementUI(settings: { - sectionId?: string; - name: string; - basePath: string; - visable?: boolean; - order?: number; - }) { - const sectionId = settings.sectionId || this.PLUGIN_ID; - - if (!this.management.hasItem(sectionId)) { - throw new Error( - `registerManagementUI was called with a sectionId of ${sectionId}, and that is is not yet regestered as a section` - ); - } - - const section = this.management.getSection(sectionId); - - section.register(sectionId, { - visible: settings.visable || true, - display: settings.name, - order: settings.order || 30, - url: `#${settings.basePath}`, - }); - } - - private manageAngularLifecycle($scope: any, $route: any, elem: any) { - const lastRoute = $route.current; - const deregister = $scope.$on('$locationChangeSuccess', () => { - const currentRoute = $route.current; - // if templates are the same we are on the same route - if (lastRoute.$$route.template === currentRoute.$$route.template) { - // this prevents angular from destroying scope - $route.current = lastRoute; - } else { - if (elem) { - ReactDOM.unmountComponentAtNode(elem); - elem.remove(); - } - } - }); - $scope.$on('$destroy', () => { - if (deregister) { - deregister(); - } - - // manually unmount component when scope is destroyed - if (elem) { - ReactDOM.unmountComponentAtNode(elem); - elem.remove(); - } - }); - } -} - -class KibanaAdapterServiceProvider { - public serviceRefs: KibanaAdapterServiceRefs | null = null; - public bufferedCalls: Array> = []; - - public $get($rootScope: IScope, config: KibanaUIConfig) { - this.serviceRefs = { - config, - rootScope: $rootScope, - }; - - this.applyBufferedCalls(this.bufferedCalls); - - return this; - } - - public callOrBuffer(serviceCall: (serviceRefs: KibanaAdapterServiceRefs) => void) { - if (this.serviceRefs !== null) { - this.applyBufferedCalls([serviceCall]); - } else { - this.bufferedCalls.push(serviceCall); - } - } - - public applyBufferedCalls( - bufferedCalls: Array> - ) { - if (!this.serviceRefs) { - return; - } - - this.serviceRefs.rootScope.$apply(() => { - bufferedCalls.forEach(serviceCall => { - if (!this.serviceRefs) { - return; - } - return serviceCall(this.serviceRefs); - }); - }); - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts deleted file mode 100644 index 9045c7ded2adaa..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/framework/testing_framework_adapter.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; -import { FrameworkAdapter, FrameworkInfo, FrameworkUser } from './adapter_types'; - -export class TestingFrameworkAdapter implements FrameworkAdapter { - public get info() { - if (this.xpackInfo) { - return this.xpackInfo; - } else { - throw new Error('framework adapter must have init called before anything else'); - } - } - - public get currentUser() { - return this.shieldUser!; - } - private settings: any; - constructor( - private readonly xpackInfo: FrameworkInfo | null, - private readonly shieldUser: FrameworkUser | null, - public readonly version: string - ) {} - - // We dont really want to have this, but it's needed to conditionaly render for k7 due to - // when that data is needed. - public getUISetting(key: 'k7design'): boolean { - return this.settings[key]; - } - - public setUISettings = (key: string, value: any) => { - this.settings[key] = value; - }; - - public async waitUntilFrameworkReady(): Promise { - return; - } - - public renderUIAtPath( - path: string, - component: React.ReactElement, - toController: 'management' | 'self' = 'self' - ) { - throw new Error('not yet implamented'); - } - - public registerManagementSection(settings: { - id?: string; - name: string; - iconName: string; - order?: number; - }) { - throw new Error('not yet implamented'); - } - - public registerManagementUI(settings: { - sectionId?: string; - name: string; - basePath: string; - visable?: boolean; - order?: number; - }) { - throw new Error('not yet implamented'); - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts deleted file mode 100644 index c40575eb6567fe..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/rest_api/adapter_types.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { FlatObject } from '../../../../common/types/helpers'; - -export interface RestAPIAdapter { - get(url: string, query?: FlatObject): Promise; - post(url: string, body?: { [key: string]: any }): Promise; - delete(url: string): Promise; - put(url: string, body?: any): Promise; -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts deleted file mode 100644 index 4d0a1728c28f89..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/rest_api/axios_rest_api_adapter.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios, { AxiosInstance } from 'axios'; -import { FlatObject } from '../../../../common/types/helpers'; -import { RestAPIAdapter } from './adapter_types'; -let globalAPI: AxiosInstance; - -export class AxiosRestAPIAdapter implements RestAPIAdapter { - constructor(private readonly xsrfToken: string, private readonly basePath: string) {} - - public async get(url: string, query?: FlatObject): Promise { - return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); - } - - public async post( - url: string, - body?: { [key: string]: any } - ): Promise { - return await this.REST.post(url, body).then(resp => resp.data); - } - - public async delete(url: string): Promise { - return await this.REST.delete(url).then(resp => resp.data); - } - - public async put(url: string, body?: any): Promise { - return await this.REST.put(url, body).then(resp => resp.data); - } - - private get REST() { - if (globalAPI) { - return globalAPI; - } - - globalAPI = axios.create({ - baseURL: this.basePath, - withCredentials: true, - responseType: 'json', - timeout: 30000, - headers: { - Accept: 'application/json', - credentials: 'same-origin', - 'Content-Type': 'application/json', - 'kbn-version': this.xsrfToken, - 'kbn-xsrf': this.xsrfToken, - }, - }); - // Add a request interceptor - globalAPI.interceptors.request.use( - config => { - // Do something before request is sent - return config; - }, - error => { - // Do something with request error - return Promise.reject(error); - } - ); - - // Add a response interceptor - globalAPI.interceptors.response.use( - response => { - // Do something with response data - return response; - }, - error => { - // Do something with response error - return Promise.reject(error); - } - ); - - return globalAPI; - } -} diff --git a/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts b/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts deleted file mode 100644 index 112d8b2210065b..00000000000000 --- a/x-pack/plugins/fleet/public/lib/adapters/rest_api/node_axios_api_adapter.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios, { AxiosInstance } from 'axios'; -import fs from 'fs'; -import { join, resolve } from 'path'; -import { FlatObject } from '../../../../common/types/helpers'; -import { RestAPIAdapter } from './adapter_types'; -const pkg = JSON.parse( - fs.readFileSync(resolve(join(__dirname, '../../../../../../../package.json'))).toString() -); - -let globalAPI: AxiosInstance; - -export class NodeAxiosAPIAdapter implements RestAPIAdapter { - constructor( - private readonly username: string, - private readonly password: string, - private readonly basePath: string - ) {} - - public async get(url: string, query?: FlatObject): Promise { - return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); - } - - public async post( - url: string, - body?: { [key: string]: any } - ): Promise { - return await this.REST.post(url, body).then(resp => resp.data); - } - - public async delete(url: string): Promise { - return await this.REST.delete(url).then(resp => resp.data); - } - - public async put(url: string, body?: any): Promise { - return await this.REST.put(url, body).then(resp => resp.data); - } - - private get REST() { - if (globalAPI) { - return globalAPI; - } - - globalAPI = axios.create({ - baseURL: this.basePath, - withCredentials: true, - responseType: 'json', - timeout: 60 * 10 * 1000, // 10min - auth: { - username: this.username, - password: this.password, - }, - headers: { - 'Access-Control-Allow-Origin': '*', - Accept: 'application/json', - 'Content-Type': 'application/json', - 'kbn-version': (pkg as any).version, - 'kbn-xsrf': 'xxx', - }, - }); - // Add a request interceptor - globalAPI.interceptors.request.use( - config => { - // Do something before request is sent - return config; - }, - error => { - // Do something with request error - return Promise.reject(error); - } - ); - - // Add a response interceptor - globalAPI.interceptors.response.use( - response => { - // Do something with response data - return response; - }, - error => { - // Do something with response error - return Promise.reject(JSON.stringify(error.response.data)); - } - ); - - return globalAPI; - } -} diff --git a/x-pack/plugins/fleet/public/lib/agent.ts b/x-pack/plugins/fleet/public/lib/agent.ts deleted file mode 100644 index 49710a2d5d36cf..00000000000000 --- a/x-pack/plugins/fleet/public/lib/agent.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Agent } from '../../common/types/domain_data'; -import { AgentAdapter } from './adapters/agent/memory_agent_adapter'; -import { ElasticsearchLib } from './elasticsearch'; - -export class AgentsLib { - constructor( - private readonly adapter: AgentAdapter, - private readonly elasticsearch: ElasticsearchLib - ) {} - - /** Get a single beat using it's ID for lookup */ - public async get(id: string): Promise { - const agent = await this.adapter.get(id); - return agent; - } - - /** Get a single agent using the token it was enrolled in for lookup */ - public getWithToken = async (enrollmentToken: string): Promise => { - const agent = await this.adapter.getWithToken(enrollmentToken); - return agent; - }; - - /** Get an array of agents that have a given tag id assigned to it */ - public getOnConfig = async (configId: string): Promise => { - const agents = await this.adapter.getOnConfig(configId); - return agents; - }; - - // FIXME: This needs to be paginated https://github.com/elastic/kibana/issues/26022 - /** Get an array of all enrolled agents. */ - public getAll = async (kuery?: string): Promise => { - let ESQuery; - if (kuery) { - ESQuery = await this.elasticsearch.convertKueryToEsQuery(kuery); - } - const agents = await this.adapter.getAll(ESQuery); - return agents; - }; - - /** Update a given agent via it's ID */ - public update = async (id: string, agentData: Partial): Promise => { - return await this.adapter.update(id, agentData); - }; -} diff --git a/x-pack/plugins/fleet/public/lib/compose/kibana.ts b/x-pack/plugins/fleet/public/lib/compose/kibana.ts deleted file mode 100644 index 17991b764cd716..00000000000000 --- a/x-pack/plugins/fleet/public/lib/compose/kibana.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { camelCase } from 'lodash'; -// @ts-ignore not typed yet -import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info'; -import 'ui/autoload/all'; -import chrome from 'ui/chrome'; -// @ts-ignore not typed yet -import { management } from 'ui/management'; -import routes from 'ui/routes'; -import { INDEX_NAMES } from '../../../common/constants/index_names'; -import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; -import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest'; -import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; -import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; -import { AgentsLib } from '../agent'; -import { ElasticsearchLib } from '../elasticsearch'; -import { FrontendLibs } from '../types'; -import { PLUGIN } from './../../../common/constants/plugin'; -import { FrameworkLib } from './../framework'; - -// A super early spot in kibana loading that we can use to hook before most other things -const onKibanaReady = chrome.dangerouslyGetActiveInjector; - -export function compose(): FrontendLibs { - const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); - const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.FLEET); - const elasticsearchLib = new ElasticsearchLib(esAdapter); - const agents = new AgentsLib(new RestAgentAdapter(api), elasticsearchLib); - - const framework = new FrameworkLib( - new KibanaFrameworkAdapter( - camelCase(PLUGIN.ID), - management, - routes, - chrome.getBasePath, - onKibanaReady, - XPackInfoProvider, - chrome.getKibanaVersion() - ) - ); - - const libs: FrontendLibs = { - framework, - elasticsearch: elasticsearchLib, - agents, - }; - return libs; -} diff --git a/x-pack/plugins/fleet/public/lib/compose/memory.ts b/x-pack/plugins/fleet/public/lib/compose/memory.ts deleted file mode 100644 index 5501c4066d89e0..00000000000000 --- a/x-pack/plugins/fleet/public/lib/compose/memory.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; -import 'ui/autoload/all'; -// @ts-ignore: path dynamic for kibana -import { management } from 'ui/management'; -// @ts-ignore: path dynamic for kibana -import { uiModules } from 'ui/modules'; -// @ts-ignore: path dynamic for kibana -import routes from 'ui/routes'; -// @ts-ignore: path dynamic for kibana -import { MemoryAgentAdapter } from '../adapters/agent/memory_agents_adapter'; -import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; -import { AgentsLib } from '../agent'; -import { FrameworkLib } from '../framework'; -import { FrontendLibs } from '../types'; -import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; -import { ElasticsearchLib } from './../elasticsearch'; - -const onKibanaReady = uiModules.get('kibana').run; - -export function compose( - mockIsKueryValid: (kuery: string) => boolean, - mockKueryToEsQuery: (kuery: string) => string, - suggestions: AutocompleteSuggestion[] -): FrontendLibs { - const esAdapter = new MemoryElasticsearchAdapter( - mockIsKueryValid, - mockKueryToEsQuery, - suggestions - ); - const elasticsearchLib = new ElasticsearchLib(esAdapter); - - const agents = new AgentsLib(new MemoryAgentAdapter([]), elasticsearchLib); - - const pluginUIModule = uiModules.get('app/fleet'); - - const framework = new FrameworkLib( - new KibanaFrameworkAdapter( - pluginUIModule, - management, - routes, - () => '', - onKibanaReady, - null, - '7.0.0' - ) - ); - const libs: FrontendLibs = { - framework, - elasticsearch: elasticsearchLib, - agents, - }; - return libs; -} diff --git a/x-pack/plugins/fleet/public/lib/compose/scripts.ts b/x-pack/plugins/fleet/public/lib/compose/scripts.ts deleted file mode 100644 index 174782150d6e02..00000000000000 --- a/x-pack/plugins/fleet/public/lib/compose/scripts.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { RestAgentAdapter } from '../adapters/agent/rest_agent_adapter'; -import { MemoryElasticsearchAdapter } from '../adapters/elasticsearch/memory'; -import { TestingFrameworkAdapter } from '../adapters/framework/testing_framework_adapter'; -import { NodeAxiosAPIAdapter } from '../adapters/rest_api/node_axios_api_adapter'; -import { AgentsLib } from '../agent'; -import { ElasticsearchLib } from '../elasticsearch'; -import { FrameworkLib } from '../framework'; -import { FrontendLibs } from '../types'; - -export function compose(basePath: string): FrontendLibs { - const api = new NodeAxiosAPIAdapter('elastic', 'changeme', basePath); - const esAdapter = new MemoryElasticsearchAdapter(() => true, () => '', []); - const elasticsearchLib = new ElasticsearchLib(esAdapter); - - const agents = new AgentsLib(new RestAgentAdapter(api), elasticsearchLib); - - const framework = new FrameworkLib( - new TestingFrameworkAdapter( - { - basePath, - license: { - type: 'gold', - expired: false, - expiry_date_in_millis: 34353453452345, - }, - security: { - enabled: true, - available: true, - }, - settings: {}, - }, - { - username: 'joeuser', - roles: ['fleet_admin'], - enabled: true, - full_name: null, - email: null, - }, - '6.7.0' - ) - ); - - const libs: FrontendLibs = { - framework, - elasticsearch: elasticsearchLib, - agents, - }; - return libs; -} diff --git a/x-pack/plugins/fleet/public/lib/elasticsearch.ts b/x-pack/plugins/fleet/public/lib/elasticsearch.ts deleted file mode 100644 index 7ea32a2eb9467e..00000000000000 --- a/x-pack/plugins/fleet/public/lib/elasticsearch.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; -import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; - -interface HiddenFields { - op: 'is' | 'startsWith' | 'withoutPrefix'; - value: string; -} - -export class ElasticsearchLib { - private readonly hiddenFields: HiddenFields[] = [ - { op: 'startsWith', value: 'enrollment_token' }, - { op: 'is', value: 'beat.active' }, - { op: 'is', value: 'beat.enrollment_token' }, - { op: 'is', value: 'beat.access_token' }, - { op: 'is', value: 'beat.ephemeral_id' }, - { op: 'is', value: 'beat.verified_on' }, - ]; - - constructor(private readonly adapter: ElasticsearchAdapter) {} - - public isKueryValid(kuery: string): boolean { - return this.adapter.isKueryValid(kuery); - } - public async convertKueryToEsQuery(kuery: string): Promise { - return await this.adapter.convertKueryToEsQuery(kuery); - } - - public async getSuggestions( - kuery: string, - selectionStart: any, - fieldPrefix?: string - ): Promise { - const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); - - const filteredSuggestions = suggestions.filter(suggestion => { - const hiddenFieldsCheck = this.hiddenFields; - - if (fieldPrefix) { - hiddenFieldsCheck.push({ - op: 'withoutPrefix', - value: `${fieldPrefix}.`, - }); - } - - return hiddenFieldsCheck.reduce((isvalid, field) => { - if (!isvalid) { - return false; - } - - switch (field.op) { - case 'startsWith': - return !suggestion.text.startsWith(field.value); - case 'is': - return suggestion.text.trim() !== field.value; - case 'withoutPrefix': - return suggestion.text.startsWith(field.value); - } - }, true); - }); - - return filteredSuggestions; - } -} diff --git a/x-pack/plugins/fleet/public/lib/framework.ts b/x-pack/plugins/fleet/public/lib/framework.ts deleted file mode 100644 index e6ae33168384e9..00000000000000 --- a/x-pack/plugins/fleet/public/lib/framework.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { difference, get } from 'lodash'; -import { LICENSES } from '../../common/constants/security'; -import { LicenseType } from '../../common/types/security'; -import { FrameworkAdapter } from './adapters/framework/adapter_types'; - -export class FrameworkLib { - public waitUntilFrameworkReady = this.adapter.waitUntilFrameworkReady.bind(this.adapter); - public renderUIAtPath = this.adapter.renderUIAtPath.bind(this.adapter); - public registerManagementSection = this.adapter.registerManagementSection.bind(this.adapter); - public registerManagementUI = this.adapter.registerManagementUI.bind(this.adapter); - - constructor(private readonly adapter: FrameworkAdapter) {} - - public get currentUser() { - return this.adapter.currentUser; - } - - public get info() { - return this.adapter.info; - } - - public licenseIsAtLeast(type: LicenseType) { - return ( - LICENSES.indexOf(get(this.adapter.info, 'license.type', 'oss')) >= LICENSES.indexOf(type) - ); - } - - public versionGreaterThen(version: string) { - const pa = this.adapter.version.split('.'); - const pb = version.split('.'); - for (let i = 0; i < 3; i++) { - const na = Number(pa[i]); - const nb = Number(pb[i]); - // version is greater - if (na > nb) { - return true; - } - // version is less then - if (nb > na) { - return false; - } - if (!isNaN(na) && isNaN(nb)) { - return true; - } - if (isNaN(na) && !isNaN(nb)) { - return false; - } - } - return true; - } - - public currentUserHasOneOfRoles(roles: string[]) { - // If the user has at least one of the roles requested, the returnd difference will be less - // then the orig array size. difference only compares based on the left side arg - return difference(roles, get(this.currentUser, 'roles', [])).length < roles.length; - } -} diff --git a/x-pack/plugins/fleet/public/lib/types.ts b/x-pack/plugins/fleet/public/lib/types.ts deleted file mode 100644 index c5a068a881be4d..00000000000000 --- a/x-pack/plugins/fleet/public/lib/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { IModule, IScope } from 'angular'; -import { AxiosRequestConfig } from 'axios'; -import { FrameworkAdapter } from './adapters/framework/adapter_types'; -import { AgentsLib } from './agent'; -import { ElasticsearchLib } from './elasticsearch'; -import { FrameworkLib } from './framework'; - -export interface FrontendLibs { - elasticsearch: ElasticsearchLib; - framework: FrameworkLib; - agents: AgentsLib; -} - -export type FramworkAdapterConstructable = new (uiModule: IModule) => FrameworkAdapter; - -// FIXME: replace AxiosRequestConfig with something more defined -export type RequestConfig = AxiosRequestConfig; - -export interface ApiAdapter { - kbnVersion: string; - - get(url: string, config?: RequestConfig | undefined): Promise; - post(url: string, data?: any, config?: AxiosRequestConfig | undefined): Promise; - delete(url: string, config?: RequestConfig | undefined): Promise; - put(url: string, data?: any, config?: RequestConfig | undefined): Promise; -} - -export interface UiKibanaAdapterScope extends IScope { - breadcrumbs: any[]; - topNavMenu: any[]; -} - -export interface KibanaUIConfig { - get(key: string): any; - set(key: string, value: any): Promise; -} - -export interface KibanaAdapterServiceRefs { - config: KibanaUIConfig; - rootScope: IScope; -} - -export type BufferedKibanaServiceCall = (serviceRefs: ServiceRefs) => void; - -export interface Chrome { - setRootTemplate(template: string): void; -} diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json deleted file mode 100644 index 67fefc7286ca4f..00000000000000 --- a/x-pack/plugins/fleet/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "exclude": ["**/node_modules/**"], - "paths": { - "react": ["../../../node_modules/@types/react"] - } -}